@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
@@ -1198,8 +1198,8 @@ function githubStatusFor(issue) {
1198
1198
  return "open";
1199
1199
  }
1200
1200
  function selectedGitHubEnv() {
1201
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1202
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1201
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1202
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1203
1203
  }
1204
1204
  function ghSpawnOptions() {
1205
1205
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -5983,12 +5983,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
5983
5983
  "task-result.json",
5984
5984
  "validation-summary.json"
5985
5985
  ]);
5986
- function resolveHostRigBinDir(root) {
5987
- return resolve24(root, ".rig", "bin");
5988
- }
5989
5986
  function isRuntimeGatewayGitPath(candidate) {
5990
5987
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
5991
5988
  }
5989
+ function isRuntimeGatewayGhPath(candidate) {
5990
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
5991
+ }
5992
5992
  function resolveOptionalMonorepoRoot(projectRoot) {
5993
5993
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
5994
5994
  if (runtimeWorkspace && existsSync21(resolve24(runtimeWorkspace, ".git"))) {
@@ -6023,6 +6023,9 @@ function resolveGitBinary(projectRoot) {
6023
6023
  }
6024
6024
  return "git";
6025
6025
  }
6026
+ function escapeRegExp2(value) {
6027
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6028
+ }
6026
6029
  function safeCurrentTaskId(projectRoot) {
6027
6030
  try {
6028
6031
  const taskId = currentTaskId(projectRoot);
@@ -6181,17 +6184,15 @@ function gitOpenPr(options) {
6181
6184
  const target = options.target || (taskId ? "monorepo" : "project");
6182
6185
  let repoRoot = options.projectRoot;
6183
6186
  let repoLabel = "project-rig";
6184
- let defaultBase = process.env.RIG_PR_BASE_PROJECT || "main";
6187
+ const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
6185
6188
  if (target === "monorepo") {
6186
6189
  repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
6187
6190
  repoLabel = "monorepo";
6188
- defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
6189
6191
  if (taskId) {
6190
6192
  gitSyncBranch(options.projectRoot, taskId, "monorepo");
6191
6193
  }
6192
6194
  } else if (taskId) {
6193
6195
  gitSyncBranch(options.projectRoot, taskId, "project");
6194
- defaultBase = inferProjectBase(options.projectRoot, defaultBase);
6195
6196
  }
6196
6197
  if (!existsSync21(resolve24(repoRoot, ".git"))) {
6197
6198
  throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
@@ -6200,9 +6201,9 @@ function gitOpenPr(options) {
6200
6201
  if (!branch || branch === "HEAD") {
6201
6202
  throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
6202
6203
  }
6203
- const base = options.base || defaultBase;
6204
6204
  const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
6205
6205
  const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
6206
+ const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
6206
6207
  refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
6207
6208
  let reviewer = (options.reviewer || "").trim();
6208
6209
  let reviewerSource = reviewer ? "flag" : undefined;
@@ -6238,6 +6239,7 @@ function gitOpenPr(options) {
6238
6239
  "",
6239
6240
  "## Task",
6240
6241
  `- beads: ${taskId || "n/a"}`,
6242
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
6241
6243
  "",
6242
6244
  "## Review",
6243
6245
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -6329,6 +6331,29 @@ function gitOpenPr(options) {
6329
6331
  }
6330
6332
  return result;
6331
6333
  }
6334
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
6335
+ const lines = [];
6336
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
6337
+ if (runId) {
6338
+ lines.push(`- Run: ${runId}`);
6339
+ }
6340
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
6341
+ if (closeout) {
6342
+ lines.push(`- ${closeout}`);
6343
+ }
6344
+ return lines;
6345
+ }
6346
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
6347
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
6348
+ if (sourceIssueId) {
6349
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
6350
+ if (match) {
6351
+ const [, sourceRepo, issueNumber] = match;
6352
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
6353
+ }
6354
+ }
6355
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
6356
+ }
6332
6357
  function readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl) {
6333
6358
  const view = runCapture2(withGhRepo([
6334
6359
  gh,
@@ -6479,32 +6504,19 @@ function resolveGithubCliBinary(projectRoot) {
6479
6504
  if (explicit) {
6480
6505
  candidates.add(explicit);
6481
6506
  }
6507
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
6508
+ candidates.add(candidate);
6509
+ }
6482
6510
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
6483
6511
  for (const entry of explicitPathEntries) {
6484
6512
  candidates.add(resolve24(entry, "gh"));
6485
6513
  }
6486
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
6487
- if (hostProjectRoot) {
6488
- candidates.add(resolve24(resolveHostRigBinDir(hostProjectRoot), "gh"));
6489
- }
6490
- candidates.add(resolve24(resolveHostRigBinDir(projectRoot), "gh"));
6491
- const runtimeContext = loadRuntimeContextFromEnv();
6492
- if (runtimeContext?.binDir) {
6493
- candidates.add(resolve24(runtimeContext.binDir, "gh"));
6494
- }
6495
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
6496
- if (runtimeHome) {
6497
- candidates.add(resolve24(runtimeHome, "bin", "gh"));
6498
- }
6499
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
6500
- candidates.add(candidate);
6501
- }
6502
6514
  const bunResolved = Bun.which("gh");
6503
6515
  if (bunResolved) {
6504
6516
  candidates.add(bunResolved);
6505
6517
  }
6506
6518
  for (const candidate of candidates) {
6507
- if (candidate && existsSync21(candidate)) {
6519
+ if (candidate && existsSync21(candidate) && !isRuntimeGatewayGhPath(candidate)) {
6508
6520
  return candidate;
6509
6521
  }
6510
6522
  }
@@ -6653,6 +6665,32 @@ function withGhRepo(command, repoNameWithOwner) {
6653
6665
  }
6654
6666
  return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
6655
6667
  }
6668
+ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
6669
+ const remote = remoteName || "origin";
6670
+ const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
6671
+ if (symbolic.exitCode === 0) {
6672
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp2(remote)}/`), "");
6673
+ if (ref && ref !== "HEAD") {
6674
+ return ref;
6675
+ }
6676
+ }
6677
+ const lsRemote = runCapture2(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
6678
+ if (lsRemote.exitCode === 0) {
6679
+ const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
6680
+ if (match?.[1]) {
6681
+ return match[1];
6682
+ }
6683
+ }
6684
+ const gh = resolveGithubCliBinary(projectRoot);
6685
+ if (gh && repoNameWithOwner) {
6686
+ const api = runCapture2(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
6687
+ const branch = api.exitCode === 0 ? api.stdout.trim() : "";
6688
+ if (branch) {
6689
+ return branch;
6690
+ }
6691
+ }
6692
+ return fallback;
6693
+ }
6656
6694
  function inferProjectBase(projectRoot, fallback) {
6657
6695
  const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
6658
6696
  if (containing.exitCode !== 0) {
@@ -7034,6 +7072,10 @@ function runtimeGitEnv(projectRoot) {
7034
7072
  }
7035
7073
  env[key] = value;
7036
7074
  }
7075
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
7076
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
7077
+ env.GITHUB_TOKEN = rigGithubToken;
7078
+ }
7037
7079
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
7038
7080
  env.GITHUB_TOKEN = env.GH_TOKEN;
7039
7081
  }
@@ -7057,6 +7099,13 @@ function runtimeGitEnv(projectRoot) {
7057
7099
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
7058
7100
  env.GH_TOKEN = env.GITHUB_TOKEN;
7059
7101
  }
7102
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
7103
+ if (gitHubToken) {
7104
+ env.RIG_GITHUB_TOKEN = gitHubToken;
7105
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
7106
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
7107
+ applyGitHubCredentialHelperEnv(env);
7108
+ }
7060
7109
  if (runtimeKnownHosts && existsSync21(runtimeKnownHosts)) {
7061
7110
  const sshParts = [
7062
7111
  "ssh",
@@ -7073,6 +7122,14 @@ function runtimeGitEnv(projectRoot) {
7073
7122
  }
7074
7123
  return Object.keys(env).length > 0 ? env : undefined;
7075
7124
  }
7125
+ function applyGitHubCredentialHelperEnv(env) {
7126
+ env.GIT_TERMINAL_PROMPT = "0";
7127
+ env.GIT_CONFIG_COUNT = "2";
7128
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
7129
+ env.GIT_CONFIG_VALUE_0 = "";
7130
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
7131
+ 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';
7132
+ }
7076
7133
  function loadPersistedRuntimeSecrets(runtimeRoot) {
7077
7134
  if (!runtimeRoot) {
7078
7135
  return {};
@@ -104,6 +104,44 @@ function parsePrChecks(value) {
104
104
  }];
105
105
  });
106
106
  }
107
+ function parsePrViewStatusCheckRollup(value) {
108
+ if (!value?.trim())
109
+ return [];
110
+ try {
111
+ const parsed = JSON.parse(value);
112
+ const rollup = Array.isArray(parsed.statusCheckRollup) ? parsed.statusCheckRollup : [];
113
+ return rollup.flatMap((entry) => {
114
+ if (!entry || typeof entry !== "object")
115
+ return [];
116
+ const record = entry;
117
+ const name = typeof record.name === "string" ? record.name : "";
118
+ if (!name.trim())
119
+ return [];
120
+ return [{
121
+ name,
122
+ status: typeof record.status === "string" ? record.status : null,
123
+ state: typeof record.state === "string" ? record.state : null,
124
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
125
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.link === "string" ? record.link : null
126
+ }];
127
+ });
128
+ } catch {
129
+ return [];
130
+ }
131
+ }
132
+ async function readPrChecks(input) {
133
+ const checks = await input.command(["pr", "checks", input.prUrl, "--json", "name,state,link"], input.cwd ? { cwd: input.cwd } : undefined);
134
+ if (checks.exitCode === 0) {
135
+ return parsePrChecks(checks.stdout);
136
+ }
137
+ const combined = `${checks.stderr ?? ""}
138
+ ${checks.stdout ?? ""}`;
139
+ if (!/unknown flag.*--json|unknown flag: --json|unknown shorthand flag/i.test(combined)) {
140
+ throw new Error(`gh pr checks ${input.prUrl} --json name,state,link failed (${checks.exitCode}): ${checks.stderr ?? checks.stdout ?? ""}`.trim());
141
+ }
142
+ const view = await runChecked(input.command, ["pr", "view", input.prUrl, "--json", "statusCheckRollup"], input.cwd, "gh");
143
+ return parsePrViewStatusCheckRollup(view.stdout);
144
+ }
107
145
  function parsePrViewReviewThreads(value) {
108
146
  if (!value?.trim())
109
147
  return [];
@@ -164,6 +202,30 @@ function normalizePrUrl(stdout) {
164
202
  throw new Error("gh pr create did not return a PR URL");
165
203
  return url;
166
204
  }
205
+ async function ensureExistingPrBodyHasRigMarkers(input) {
206
+ const view = await input.command(["pr", "view", input.prUrl, "--json", "body"], input.cwd ? { cwd: input.cwd } : undefined);
207
+ if (view.exitCode !== 0) {
208
+ throw new Error(`gh pr view ${input.prUrl} --json body failed (${view.exitCode}): ${view.stderr ?? view.stdout ?? ""}`.trim());
209
+ }
210
+ let currentBody = "";
211
+ try {
212
+ const parsed = JSON.parse(view.stdout ?? "{}");
213
+ currentBody = typeof parsed.body === "string" ? parsed.body : "";
214
+ } catch {
215
+ currentBody = "";
216
+ }
217
+ const requiredBlocks = input.body.split(/\n{2,}/).map((block) => block.trim()).filter((block) => /^Run: /i.test(block) || /^Closes #\d+/i.test(block));
218
+ const missing = requiredBlocks.filter((block) => !currentBody.includes(block));
219
+ if (missing.length === 0)
220
+ return;
221
+ const nextBody = [currentBody.trim(), ...missing].filter(Boolean).join(`
222
+
223
+ `);
224
+ const edit = await input.command(["pr", "edit", input.prUrl, "--body", nextBody], input.cwd ? { cwd: input.cwd } : undefined);
225
+ if (edit.exitCode !== 0) {
226
+ throw new Error(`gh pr edit ${input.prUrl} --body failed (${edit.exitCode}): ${edit.stderr ?? edit.stdout ?? ""}`.trim());
227
+ }
228
+ }
167
229
  async function runChecked(command, args, cwd, label = "gh") {
168
230
  const result = await command(args, cwd ? { cwd } : undefined);
169
231
  if (result.exitCode !== 0) {
@@ -188,27 +250,33 @@ function statusPathFromShortLine(line) {
188
250
  }
189
251
  return renamedPath;
190
252
  }
253
+ function isRuntimeCommitExcludedPath(path) {
254
+ const normalized = path.replace(/^\.\/+/, "").replace(/\/+$/, "");
255
+ return RIG_RUNTIME_COMMIT_EXCLUDES.some((excluded) => normalized === excluded || normalized.startsWith(`${excluded}/`));
256
+ }
257
+ function committableRunChangePaths(statusText) {
258
+ const seen = new Set;
259
+ const paths = [];
260
+ for (const line of statusText.split(/\r?\n/)) {
261
+ const path = statusPathFromShortLine(line);
262
+ if (!path || isRuntimeCommitExcludedPath(path) || seen.has(path))
263
+ continue;
264
+ seen.add(path);
265
+ paths.push(path);
266
+ }
267
+ return paths;
268
+ }
191
269
  function hasCommittableRunChanges(statusText) {
192
- return statusText.split(/\r?\n/).map((line) => statusPathFromShortLine(line)).filter(Boolean).some((path) => !RIG_RUNTIME_COMMIT_EXCLUDES.some((excluded) => path === excluded || path.startsWith(`${excluded}/`)));
270
+ return committableRunChangePaths(statusText).length > 0;
193
271
  }
194
272
  async function commitRunChanges(input) {
195
- const status = await runChecked(input.command, ["status", "--short"], input.cwd, "git");
273
+ const status = await runChecked(input.command, ["status", "--short", "--untracked-files=all"], input.cwd, "git");
196
274
  const statusText = status.stdout ?? "";
197
- if (!statusText.trim() || !hasCommittableRunChanges(statusText)) {
275
+ const committablePaths = committableRunChangePaths(statusText);
276
+ if (!statusText.trim() || committablePaths.length === 0) {
198
277
  return { committed: false, status: statusText };
199
278
  }
200
- await runChecked(input.command, [
201
- "add",
202
- "-A",
203
- "--",
204
- ".",
205
- ":(exclude).rig",
206
- ":(exclude).rig/**",
207
- ":(exclude)artifacts",
208
- ":(exclude)artifacts/**",
209
- ":(exclude)node_modules",
210
- ":(exclude)node_modules/**"
211
- ], input.cwd, "git");
279
+ await runChecked(input.command, ["add", "-A", "--", ...committablePaths], input.cwd, "git");
212
280
  const staged = await input.command(["diff", "--cached", "--quiet"], { cwd: input.cwd });
213
281
  if (staged.exitCode === 0) {
214
282
  return { committed: false, status: statusText };
@@ -284,7 +352,7 @@ async function runRepoDefaultMerge(input) {
284
352
  }
285
353
  async function runPrAutomation(input) {
286
354
  const prConfig = input.config?.pr ?? {};
287
- if (prConfig.mode === "off") {
355
+ if (prConfig.mode === "off" || prConfig.mode === "ask") {
288
356
  return { status: "skipped", iterations: 0, actionableFeedback: [] };
289
357
  }
290
358
  const body = buildPrAutomationBody({
@@ -314,12 +382,15 @@ ${createResult.stdout ?? ""}`) : null;
314
382
  throw new Error(`gh ${createArgs.join(" ")} failed (${createResult.exitCode}): ${createResult.stderr ?? createResult.stdout ?? ""}`.trim());
315
383
  }
316
384
  const prUrl = existingPrUrl ?? normalizePrUrl(createResult.stdout);
385
+ if (existingPrUrl) {
386
+ await ensureExistingPrBodyHasRigMarkers({ prUrl, body, command: input.command, cwd: input.projectRoot });
387
+ }
317
388
  await input.lifecycle?.onPrOpened?.({ prUrl });
318
389
  const { maxPrFixIterations } = resolvePrAutomationLimits(input.config);
319
390
  let latestFeedback = [];
320
391
  for (let iteration = 1;iteration <= maxPrFixIterations; iteration += 1) {
321
392
  await input.lifecycle?.onReviewCiStarted?.({ prUrl, iteration });
322
- const checks = prConfig.watchChecks === false ? [] : parsePrChecks((await runChecked(input.command, ["pr", "checks", prUrl, "--json", "name,state,link"], input.projectRoot)).stdout);
393
+ const checks = prConfig.watchChecks === false ? [] : await readPrChecks({ prUrl, command: input.command, cwd: input.projectRoot });
323
394
  const reviewThreads = prConfig.autoFixReview === false ? [] : parsePrViewReviewThreads((await runChecked(input.command, ["pr", "view", prUrl, "--json", "reviewDecision,reviews"], input.projectRoot)).stdout);
324
395
  latestFeedback = collectActionablePrFeedback({
325
396
  checks,
@@ -2713,11 +2713,13 @@ function runStatus(projectRoot, runtimeContext) {
2713
2713
  recentRuns: runs.slice(0, 10)
2714
2714
  };
2715
2715
  }
2716
- async function runResume(projectRoot, runtimeContext) {
2717
- const resumable = listAuthorityRuns(projectRoot).map((entry) => readAuthorityRun(projectRoot, entry.runId)).filter((run2) => Boolean(run2)).filter((run2) => run2.mode === "local" && (run2.status === "stopped" || run2.status === "failed" || run2.status === "created")).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
2718
- const run = resumable[0];
2716
+ function latestLocalRunForResume(projectRoot) {
2717
+ return listAuthorityRuns(projectRoot).map((entry) => readAuthorityRun(projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.mode === "local" && ["created", "preparing", "running", "validating", "reviewing", "stopped", "failed", "needs_attention"].includes(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0] ?? null;
2718
+ }
2719
+ async function submitRunResumeRequest(projectRoot, input) {
2720
+ const run = latestLocalRunForResume(projectRoot);
2719
2721
  if (!run) {
2720
- throw new RemoteCliError("RIG_RUN_NOTHING_TO_RESUME", "No stopped local run is available to resume.", 2);
2722
+ throw new RemoteCliError(input.failureCode, input.nothingMessage, 2);
2721
2723
  }
2722
2724
  const server = await ensureServerForRuns(projectRoot);
2723
2725
  const response = await fetch(`${server.baseUrl}/api/runs/resume`, {
@@ -2726,14 +2728,28 @@ async function runResume(projectRoot, runtimeContext) {
2726
2728
  "content-type": "application/json",
2727
2729
  ...server.authToken ? { authorization: `Bearer ${server.authToken}` } : {}
2728
2730
  },
2729
- body: JSON.stringify({ runId: run.runId, createdAt: new Date().toISOString() })
2731
+ body: JSON.stringify({ runId: run.runId, createdAt: new Date().toISOString(), restart: input.restart })
2730
2732
  });
2731
2733
  if (!response.ok) {
2732
2734
  const text = await response.text();
2733
- throw new RemoteCliError("RIG_RUN_RESUME_FAILED", text || response.statusText, 1, { runId: run.runId });
2735
+ throw new RemoteCliError(input.restart ? "RIG_RUN_RESTART_FAILED" : "RIG_RUN_RESUME_FAILED", text || response.statusText, 1, { runId: run.runId });
2734
2736
  }
2735
2737
  return { runId: run.runId };
2736
2738
  }
2739
+ async function runResume(projectRoot, runtimeContext) {
2740
+ return submitRunResumeRequest(projectRoot, {
2741
+ restart: false,
2742
+ failureCode: "RIG_RUN_NOTHING_TO_RESUME",
2743
+ nothingMessage: "No resumable local run is available."
2744
+ });
2745
+ }
2746
+ async function runRestart(projectRoot, runtimeContext) {
2747
+ return submitRunResumeRequest(projectRoot, {
2748
+ restart: true,
2749
+ failureCode: "RIG_RUN_NOTHING_TO_RESTART",
2750
+ nothingMessage: "No local run is available to restart."
2751
+ });
2752
+ }
2737
2753
  async function runStop(projectRoot) {
2738
2754
  const activeRuns = listAuthorityRuns(projectRoot).map((entry) => readAuthorityRun(projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.mode === "local" && ACTIVE_RUN_STATUSES.has(String(run.status ?? "")));
2739
2755
  if (activeRuns.length === 0) {
@@ -3267,6 +3283,7 @@ export {
3267
3283
  runStop,
3268
3284
  runStatus,
3269
3285
  runResume,
3286
+ runRestart,
3270
3287
  resolvePreferredShellBinary,
3271
3288
  resolveLocalControlBinarySourceRoot,
3272
3289
  resolveDefaultEpic,
@@ -1089,8 +1089,8 @@ function githubStatusFor(issue) {
1089
1089
  return "open";
1090
1090
  }
1091
1091
  function selectedGitHubEnv() {
1092
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1093
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1092
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1093
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1094
1094
  }
1095
1095
  function ghSpawnOptions() {
1096
1096
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -1354,8 +1354,8 @@ function githubStatusFor(issue) {
1354
1354
  return "open";
1355
1355
  }
1356
1356
  function selectedGitHubEnv() {
1357
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1358
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1357
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1358
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1359
1359
  }
1360
1360
  function ghSpawnOptions() {
1361
1361
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -977,8 +977,8 @@ function githubStatusFor(issue) {
977
977
  return "open";
978
978
  }
979
979
  function selectedGitHubEnv() {
980
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
981
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
980
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
981
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
982
982
  }
983
983
  function ghSpawnOptions() {
984
984
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -3848,8 +3848,8 @@ function githubStatusFor(issue) {
3848
3848
  return "open";
3849
3849
  }
3850
3850
  function selectedGitHubEnv() {
3851
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
3852
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
3851
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
3852
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
3853
3853
  }
3854
3854
  function ghSpawnOptions() {
3855
3855
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -6154,20 +6154,23 @@ function hashProjectPath(workspaceDir) {
6154
6154
  }
6155
6155
  function resolveGithubCliBinaryPath() {
6156
6156
  const explicit = process.env.RIG_GH_BIN?.trim();
6157
- if (explicit && existsSync24(explicit)) {
6157
+ if (explicit && existsSync24(explicit) && !isRuntimeGatewayGhPath(explicit)) {
6158
6158
  return explicit;
6159
6159
  }
6160
- const bunResolved = Bun.which("gh");
6161
- if (bunResolved && existsSync24(bunResolved)) {
6162
- return bunResolved;
6163
- }
6164
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
6160
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
6165
6161
  if (existsSync24(candidate)) {
6166
6162
  return candidate;
6167
6163
  }
6168
6164
  }
6165
+ const bunResolved = Bun.which("gh");
6166
+ if (bunResolved && existsSync24(bunResolved) && !isRuntimeGatewayGhPath(bunResolved)) {
6167
+ return bunResolved;
6168
+ }
6169
6169
  return "";
6170
6170
  }
6171
+ function isRuntimeGatewayGhPath(candidate) {
6172
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
6173
+ }
6171
6174
  async function resolveGithubCliAuthToken(ghBinary = "") {
6172
6175
  const gh = ghBinary || resolveGithubCliBinaryPath();
6173
6176
  if (!gh) {
@@ -6268,6 +6271,8 @@ async function runtimeEnv(projectRoot, runtime) {
6268
6271
  XDG_CACHE_HOME: runtime.cacheDir,
6269
6272
  XDG_STATE_HOME: runtime.stateDir,
6270
6273
  RIG_AGENT_ID: runtime.id,
6274
+ ...process.env.RIG_RUN_ID?.trim() ? { RIG_RUN_ID: process.env.RIG_RUN_ID.trim() } : {},
6275
+ ...process.env.RIG_SERVER_RUN_ID?.trim() ? { RIG_SERVER_RUN_ID: process.env.RIG_SERVER_RUN_ID.trim() } : {},
6271
6276
  RIG_TASK_ID: runtime.taskId,
6272
6277
  RIG_TASK_RUNTIME_ID: runtime.id,
6273
6278
  RIG_TASK_WORKSPACE: runtime.workspaceDir,
@@ -6331,6 +6336,10 @@ async function runtimeEnv(projectRoot, runtime) {
6331
6336
  env[key] = value;
6332
6337
  }
6333
6338
  }
6339
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
6340
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
6341
+ env.GITHUB_TOKEN = rigGithubToken;
6342
+ }
6334
6343
  const fallbackGithubToken = !env.GITHUB_TOKEN && !env.GH_TOKEN ? await resolveGithubCliAuthToken(hostGhBinary) : "";
6335
6344
  if (fallbackGithubToken) {
6336
6345
  env.GITHUB_TOKEN = fallbackGithubToken;
@@ -6341,6 +6350,13 @@ async function runtimeEnv(projectRoot, runtime) {
6341
6350
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
6342
6351
  env.GH_TOKEN = env.GITHUB_TOKEN;
6343
6352
  }
6353
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || rigGithubToken;
6354
+ if (gitHubToken) {
6355
+ env.RIG_GITHUB_TOKEN = gitHubToken;
6356
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
6357
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
6358
+ applyGitHubCredentialHelperEnv(env);
6359
+ }
6344
6360
  if (!env.GREPTILE_GITHUB_TOKEN && env.GITHUB_TOKEN) {
6345
6361
  env.GREPTILE_GITHUB_TOKEN = env.GITHUB_TOKEN;
6346
6362
  }
@@ -6436,12 +6452,21 @@ async function materializeRuntimeCertBundle(runtime) {
6436
6452
  }
6437
6453
  return targetPath;
6438
6454
  }
6455
+ function applyGitHubCredentialHelperEnv(env) {
6456
+ env.GIT_TERMINAL_PROMPT = "0";
6457
+ env.GIT_CONFIG_COUNT = "2";
6458
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
6459
+ env.GIT_CONFIG_VALUE_0 = "";
6460
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
6461
+ 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';
6462
+ }
6439
6463
  function persistRuntimeSecrets(runtimeRoot, env) {
6440
6464
  const secretsPath = resolve27(runtimeRoot, "runtime-secrets.json");
6441
6465
  const persisted = {};
6442
6466
  for (const key of [
6443
6467
  "GITHUB_TOKEN",
6444
6468
  "GH_TOKEN",
6469
+ "RIG_GITHUB_TOKEN",
6445
6470
  "GREPTILE_GITHUB_TOKEN",
6446
6471
  "GREPTILE_API_KEY",
6447
6472
  "AI_REVIEW_MODE",
@@ -8091,7 +8116,11 @@ async function ensureAgentRuntime(options) {
8091
8116
  mkdirSync18(runtime.binDir, { recursive: true });
8092
8117
  mkdirSync18(workspaceLayout.distDir, { recursive: true });
8093
8118
  prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
8094
- await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
8119
+ if (options.preserveTaskArtifacts) {
8120
+ console.log(`[rig-agent] Preserving runtime task artifacts for resume of ${options.taskId}.`);
8121
+ } else {
8122
+ await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
8123
+ }
8095
8124
  const ctx = {
8096
8125
  runtimeId: options.id,
8097
8126
  taskId: options.taskId,
@@ -686,20 +686,23 @@ function hashProjectPath(workspaceDir) {
686
686
  }
687
687
  function resolveGithubCliBinaryPath() {
688
688
  const explicit = process.env.RIG_GH_BIN?.trim();
689
- if (explicit && existsSync5(explicit)) {
689
+ if (explicit && existsSync5(explicit) && !isRuntimeGatewayGhPath(explicit)) {
690
690
  return explicit;
691
691
  }
692
- const bunResolved = Bun.which("gh");
693
- if (bunResolved && existsSync5(bunResolved)) {
694
- return bunResolved;
695
- }
696
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
692
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
697
693
  if (existsSync5(candidate)) {
698
694
  return candidate;
699
695
  }
700
696
  }
697
+ const bunResolved = Bun.which("gh");
698
+ if (bunResolved && existsSync5(bunResolved) && !isRuntimeGatewayGhPath(bunResolved)) {
699
+ return bunResolved;
700
+ }
701
701
  return "";
702
702
  }
703
+ function isRuntimeGatewayGhPath(candidate) {
704
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
705
+ }
703
706
  async function resolveGithubCliAuthToken(ghBinary = "") {
704
707
  const gh = ghBinary || resolveGithubCliBinaryPath();
705
708
  if (!gh) {
@@ -800,6 +803,8 @@ async function runtimeEnv(projectRoot, runtime) {
800
803
  XDG_CACHE_HOME: runtime.cacheDir,
801
804
  XDG_STATE_HOME: runtime.stateDir,
802
805
  RIG_AGENT_ID: runtime.id,
806
+ ...process.env.RIG_RUN_ID?.trim() ? { RIG_RUN_ID: process.env.RIG_RUN_ID.trim() } : {},
807
+ ...process.env.RIG_SERVER_RUN_ID?.trim() ? { RIG_SERVER_RUN_ID: process.env.RIG_SERVER_RUN_ID.trim() } : {},
803
808
  RIG_TASK_ID: runtime.taskId,
804
809
  RIG_TASK_RUNTIME_ID: runtime.id,
805
810
  RIG_TASK_WORKSPACE: runtime.workspaceDir,
@@ -863,6 +868,10 @@ async function runtimeEnv(projectRoot, runtime) {
863
868
  env[key] = value;
864
869
  }
865
870
  }
871
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
872
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
873
+ env.GITHUB_TOKEN = rigGithubToken;
874
+ }
866
875
  const fallbackGithubToken = !env.GITHUB_TOKEN && !env.GH_TOKEN ? await resolveGithubCliAuthToken(hostGhBinary) : "";
867
876
  if (fallbackGithubToken) {
868
877
  env.GITHUB_TOKEN = fallbackGithubToken;
@@ -873,6 +882,13 @@ async function runtimeEnv(projectRoot, runtime) {
873
882
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
874
883
  env.GH_TOKEN = env.GITHUB_TOKEN;
875
884
  }
885
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || rigGithubToken;
886
+ if (gitHubToken) {
887
+ env.RIG_GITHUB_TOKEN = gitHubToken;
888
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
889
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
890
+ applyGitHubCredentialHelperEnv(env);
891
+ }
876
892
  if (!env.GREPTILE_GITHUB_TOKEN && env.GITHUB_TOKEN) {
877
893
  env.GREPTILE_GITHUB_TOKEN = env.GITHUB_TOKEN;
878
894
  }
@@ -968,12 +984,21 @@ async function materializeRuntimeCertBundle(runtime) {
968
984
  }
969
985
  return targetPath;
970
986
  }
987
+ function applyGitHubCredentialHelperEnv(env) {
988
+ env.GIT_TERMINAL_PROMPT = "0";
989
+ env.GIT_CONFIG_COUNT = "2";
990
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
991
+ env.GIT_CONFIG_VALUE_0 = "";
992
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
993
+ 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';
994
+ }
971
995
  function persistRuntimeSecrets(runtimeRoot, env) {
972
996
  const secretsPath = resolve7(runtimeRoot, "runtime-secrets.json");
973
997
  const persisted = {};
974
998
  for (const key of [
975
999
  "GITHUB_TOKEN",
976
1000
  "GH_TOKEN",
1001
+ "RIG_GITHUB_TOKEN",
977
1002
  "GREPTILE_GITHUB_TOKEN",
978
1003
  "GREPTILE_API_KEY",
979
1004
  "AI_REVIEW_MODE",