@h-rig/cli 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.
@@ -2,8 +2,8 @@
2
2
  var __require = import.meta.require;
3
3
 
4
4
  // packages/cli/src/commands/setup.ts
5
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, writeFileSync as writeFileSync2 } from "fs";
6
- import { resolve as resolve5 } from "path";
5
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync, writeFileSync as writeFileSync2 } from "fs";
6
+ import { resolve as resolve6 } from "path";
7
7
 
8
8
  // packages/cli/src/runner.ts
9
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
@@ -171,8 +171,8 @@ async function buildPiSetupChecks(input = {}) {
171
171
  }
172
172
 
173
173
  // packages/cli/src/commands/_doctor-checks.ts
174
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
175
- import { resolve as resolve4 } from "path";
174
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
175
+ import { resolve as resolve5 } from "path";
176
176
  import { isSupportedBunVersion, MIN_SUPPORTED_BUN_VERSION } from "@rig/runtime/control-plane/setup-version";
177
177
 
178
178
  // packages/cli/src/commands/_connection-state.ts
@@ -259,15 +259,33 @@ function resolveSelectedConnection(projectRoot, options = {}) {
259
259
 
260
260
  // packages/cli/src/commands/_server-client.ts
261
261
  import { spawnSync } from "child_process";
262
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
263
+ import { resolve as resolve4 } from "path";
262
264
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
263
265
  var cachedGitHubBearerToken;
264
266
  function cleanToken(value) {
265
267
  const trimmed = value?.trim();
266
268
  return trimmed ? trimmed : null;
267
269
  }
268
- function readGitHubBearerTokenForRemote() {
270
+ function readPrivateRemoteSessionToken(projectRoot) {
271
+ const path = resolve4(projectRoot, ".rig", "state", "github-auth.json");
272
+ if (!existsSync3(path))
273
+ return null;
274
+ try {
275
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
276
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ function readGitHubBearerTokenForRemote(projectRoot) {
269
282
  if (cachedGitHubBearerToken !== undefined)
270
283
  return cachedGitHubBearerToken;
284
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
285
+ if (privateSession) {
286
+ cachedGitHubBearerToken = privateSession;
287
+ return cachedGitHubBearerToken;
288
+ }
271
289
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
272
290
  if (envToken) {
273
291
  cachedGitHubBearerToken = envToken;
@@ -287,7 +305,7 @@ async function ensureServerForCli(projectRoot) {
287
305
  if (selected?.connection.kind === "remote") {
288
306
  return {
289
307
  baseUrl: selected.connection.baseUrl,
290
- authToken: readGitHubBearerTokenForRemote(),
308
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
291
309
  connectionKind: "remote"
292
310
  };
293
311
  }
@@ -387,11 +405,11 @@ function repoSlugFromConfig(config) {
387
405
  function loadFallbackConfig(projectRoot) {
388
406
  const candidates = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
389
407
  for (const name of candidates) {
390
- const path = resolve4(projectRoot, name);
391
- if (!existsSync3(path))
408
+ const path = resolve5(projectRoot, name);
409
+ if (!existsSync4(path))
392
410
  continue;
393
411
  try {
394
- const source = readFileSync3(path, "utf8");
412
+ const source = readFileSync4(path, "utf8");
395
413
  if (name.endsWith(".json"))
396
414
  return JSON.parse(source);
397
415
  const owner = source.match(/owner\s*:\s*["']([^"']+)["']/)?.[1];
@@ -470,7 +488,7 @@ async function runRigDoctorChecks(options) {
470
488
  checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
471
489
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
472
490
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
473
- const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync3(resolve4(projectRoot, name)));
491
+ const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
474
492
  checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "Run `rig init` or fix the config error."));
475
493
  const taskSourceKind = config?.taskSource?.kind;
476
494
  checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
@@ -638,8 +656,8 @@ function runSetupInit(projectRoot) {
638
656
  mkdirSync2(stateDir, { recursive: true });
639
657
  mkdirSync2(logsDir, { recursive: true });
640
658
  mkdirSync2(artifactsDir, { recursive: true });
641
- const failuresPath = resolve5(stateDir, "failed_approaches.md");
642
- if (!existsSync4(failuresPath)) {
659
+ const failuresPath = resolve6(stateDir, "failed_approaches.md");
660
+ if (!existsSync5(failuresPath)) {
643
661
  writeFileSync2(failuresPath, `# Failed Approaches
644
662
 
645
663
  `, "utf-8");
@@ -657,18 +675,18 @@ async function runSetupCheck(projectRoot) {
657
675
  }
658
676
  async function runSetupPreflight(projectRoot) {
659
677
  await runSetupCheck(projectRoot);
660
- const validationRoot = resolve5(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
661
- if (existsSync4(validationRoot)) {
678
+ const validationRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
679
+ if (existsSync5(validationRoot)) {
662
680
  const validators = readdirSync(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
663
681
  for (const validator of validators) {
664
- const script = resolve5(validationRoot, validator.name, "validate.sh");
665
- if (existsSync4(script)) {
682
+ const script = resolve6(validationRoot, validator.name, "validate.sh");
683
+ if (existsSync5(script)) {
666
684
  console.log(`OK: validator script ${script}`);
667
685
  }
668
686
  }
669
687
  }
670
- const hooksRoot = resolve5(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
671
- if (existsSync4(hooksRoot)) {
688
+ const hooksRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
689
+ if (existsSync5(hooksRoot)) {
672
690
  const hooks = readdirSync(hooksRoot).filter((name) => name.endsWith(".sh"));
673
691
  for (const hook of hooks) {
674
692
  console.log(`OK: hook ${hook}`);
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task-run-driver.ts
3
- import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync } from "fs";
3
+ import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
4
4
  import { resolve as resolve4 } from "path";
5
5
  import { spawn, spawnSync } from "child_process";
6
6
  import { createInterface as createLineInterface } from "readline";
@@ -135,6 +135,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
135
135
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
136
136
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
137
137
  const next = {
138
+ ...existing ?? {},
138
139
  runId: input.runId,
139
140
  projectRoot,
140
141
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -405,7 +406,24 @@ var PI_CANONICAL_RUN_STAGES = [
405
406
  function canonicalPiRunStages() {
406
407
  return [...PI_CANONICAL_RUN_STAGES];
407
408
  }
409
+ function looksLikeGitHubToken(value) {
410
+ const token = value?.trim();
411
+ if (!token)
412
+ return false;
413
+ return /^(gh[opusr]_|github_pat_)/.test(token);
414
+ }
415
+ function githubBridgeEnv(token) {
416
+ const clean = token?.trim();
417
+ if (!clean)
418
+ return {};
419
+ return {
420
+ RIG_GITHUB_TOKEN: clean,
421
+ GITHUB_TOKEN: clean,
422
+ GH_TOKEN: clean
423
+ };
424
+ }
408
425
  function buildPiRigBridgeEnv(input) {
426
+ const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
409
427
  return {
410
428
  RIG_PROJECT_ROOT: input.projectRoot,
411
429
  PROJECT_RIG_ROOT: input.projectRoot,
@@ -415,7 +433,7 @@ function buildPiRigBridgeEnv(input) {
415
433
  RIG_RUNTIME_ADAPTER: "pi",
416
434
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
417
435
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
418
- ...input.githubToken ? { RIG_GITHUB_TOKEN: input.githubToken } : {}
436
+ ...githubBridgeEnv(githubToken)
419
437
  };
420
438
  }
421
439
  function runGitSync(cwd, args, input) {
@@ -476,6 +494,14 @@ function buildTaskRunReviewEnv(config) {
476
494
  ...review?.provider ? { AI_REVIEW_PROVIDER: review.provider } : {}
477
495
  };
478
496
  }
497
+ function buildDirtyBaselineHandshakeEnv(input) {
498
+ if (input.baselineMode !== "dirty-snapshot")
499
+ return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
500
+ return {
501
+ RIG_BASELINE_MODE: "dirty-snapshot",
502
+ RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
503
+ };
504
+ }
479
505
  function positiveInt(value, fallback) {
480
506
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
481
507
  }
@@ -575,6 +601,12 @@ function appendPiStageLog(input) {
575
601
  });
576
602
  emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
577
603
  }
604
+ async function runCheckedCommand(command, args, cwd, label = "git") {
605
+ const result = await command(args, { cwd });
606
+ if (result.exitCode !== 0) {
607
+ throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
608
+ }
609
+ }
578
610
  function createCommandRunner(binary) {
579
611
  return async (args, options) => {
580
612
  const child = spawn(binary, [...args], {
@@ -650,6 +682,7 @@ async function runTaskRunPostValidationLifecycle(input) {
650
682
  command: gitCommand
651
683
  });
652
684
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
685
+ await runCheckedCommand(gitCommand, ["push", "--set-upstream", "origin", branch], workspace, "git");
653
686
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
654
687
  const pr = await prAutomation({
655
688
  projectRoot: workspace,
@@ -660,7 +693,6 @@ async function runTaskRunPostValidationLifecycle(input) {
660
693
  sourceTask: input.sourceTask,
661
694
  uploadedSnapshot: input.uploadedSnapshot,
662
695
  command: ghCommand,
663
- gitCommand,
664
696
  steerPi,
665
697
  lifecycle: {
666
698
  onPrOpened: async ({ prUrl }) => {
@@ -1050,8 +1082,11 @@ function stringArrayField(record, key) {
1050
1082
  }
1051
1083
  async function executeRigOwnedTaskRun(context, input) {
1052
1084
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1085
+ const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1086
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
1087
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1053
1088
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1054
- const prompt = buildRunPrompt({
1089
+ let prompt = buildRunPrompt({
1055
1090
  projectRoot: context.projectRoot,
1056
1091
  taskId: input.taskId,
1057
1092
  fallbackTitle: input.title,
@@ -1105,14 +1140,14 @@ async function executeRigOwnedTaskRun(context, input) {
1105
1140
  taskId: runtimeTaskId,
1106
1141
  createdAt: startedAt,
1107
1142
  runtimeAdapter: input.runtimeAdapter,
1108
- status: "created"
1143
+ status: resumeMode ? "preparing" : "created"
1109
1144
  });
1110
1145
  patchAuthorityRun(context.projectRoot, input.runId, {
1111
1146
  status: "preparing",
1112
1147
  startedAt,
1113
1148
  completedAt: null,
1114
1149
  errorText: null,
1115
- artifactRoot: null,
1150
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
1116
1151
  runtimeAdapter: input.runtimeAdapter,
1117
1152
  runtimeMode: input.runtimeMode,
1118
1153
  interactionMode: input.interactionMode,
@@ -1128,9 +1163,9 @@ async function executeRigOwnedTaskRun(context, input) {
1128
1163
  detail: input.taskId ?? input.title ?? runtimeTaskId
1129
1164
  });
1130
1165
  appendRunLog(context.projectRoot, input.runId, {
1131
- id: `log:${input.runId}:start`,
1132
- title: "Rig task run started",
1133
- detail: input.taskId ?? input.title ?? runtimeTaskId,
1166
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
1167
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
1168
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
1134
1169
  tone: "info",
1135
1170
  status: "preparing",
1136
1171
  createdAt: startedAt
@@ -1151,7 +1186,22 @@ async function executeRigOwnedTaskRun(context, input) {
1151
1186
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1152
1187
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1153
1188
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1154
- patchAuthorityRun(context.projectRoot, input.runId, { planning: planningClassification });
1189
+ const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1190
+ const persistedPlanning = {
1191
+ ...planningClassification,
1192
+ classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1193
+ artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1194
+ classifiedAt: new Date().toISOString()
1195
+ };
1196
+ mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1197
+ writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1198
+ `, "utf8");
1199
+ patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1200
+ prompt = `${prompt}
1201
+
1202
+ Rig planning classification:
1203
+ ${JSON.stringify(persistedPlanning, null, 2)}
1204
+ ${planningClassification.planningRequired ? `Before implementing, write a concise implementation plan to ${planningArtifactPath}. Treat that plan artifact as required acceptance evidence for the Plan stage.` : "Planning is not required for this run; briefly state why before implementation."}`;
1155
1205
  if (input.runtimeAdapter === "pi") {
1156
1206
  for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
1157
1207
  appendPiStageLog({
@@ -1193,11 +1243,11 @@ async function executeRigOwnedTaskRun(context, input) {
1193
1243
  let reviewAction;
1194
1244
  let verificationStarted = false;
1195
1245
  let reviewStarted = false;
1196
- let latestRuntimeWorkspace = null;
1197
- let latestSessionDir = null;
1198
- let latestLogsDir = null;
1246
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1247
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
1248
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1199
1249
  let latestProviderCommand = null;
1200
- let latestRuntimeBranch = null;
1250
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1201
1251
  let snapshotSidecarPromise = null;
1202
1252
  let dirtyBaselineApplied = false;
1203
1253
  const childEnv = {
@@ -1215,9 +1265,14 @@ async function executeRigOwnedTaskRun(context, input) {
1215
1265
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
1216
1266
  RIG_SERVER_RUN_ID: input.runId
1217
1267
  },
1218
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
1268
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
1269
+ ...resumeMode ? {
1270
+ RIG_RUN_RESUME: "1",
1271
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
1272
+ } : {}
1219
1273
  };
1220
1274
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1275
+ Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
1221
1276
  const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
1222
1277
  const maxAttempts = automationLimits.maxValidationAttempts;
1223
1278
  const promoteToValidating = (detail) => {
@@ -1281,13 +1336,20 @@ async function executeRigOwnedTaskRun(context, input) {
1281
1336
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
1282
1337
  dirtyBaselineApplied = true;
1283
1338
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1339
+ const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1340
+ if (readyFile) {
1341
+ mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1342
+ writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1343
+ `, "utf8");
1344
+ }
1284
1345
  appendRunLog(context.projectRoot, input.runId, {
1285
1346
  id: `log:${input.runId}:dirty-baseline`,
1286
1347
  title: "Dirty baseline snapshot",
1287
1348
  detail: dirty.detail,
1288
1349
  tone: dirty.applied ? "tool" : "info",
1289
1350
  status: dirty.applied ? "completed" : "skipped",
1290
- createdAt: new Date().toISOString()
1351
+ createdAt: new Date().toISOString(),
1352
+ payload: readyFile ? { readyFile } : undefined
1291
1353
  });
1292
1354
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1293
1355
  }
@@ -1513,7 +1575,36 @@ async function executeRigOwnedTaskRun(context, input) {
1513
1575
  let reviewFailureDetail = null;
1514
1576
  const stderrLines = [];
1515
1577
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
1516
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
1578
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
1579
+ appendRunLog(context.projectRoot, input.runId, {
1580
+ id: `log:${input.runId}:resume-closeout-phase`,
1581
+ title: "Resume continuing from closeout phase",
1582
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
1583
+ tone: "info",
1584
+ status: resumePreviousStatus,
1585
+ createdAt: new Date().toISOString()
1586
+ });
1587
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
1588
+ exit = { code: 0, signal: null };
1589
+ } else if (resumeMode && latestRuntimeWorkspace) {
1590
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
1591
+ taskId: input.taskId ?? runtimeTaskId,
1592
+ workspaceDir: latestRuntimeWorkspace
1593
+ });
1594
+ if (acceptedArtifactState.accepted) {
1595
+ appendRunLog(context.projectRoot, input.runId, {
1596
+ id: `log:${input.runId}:resume-accepted-artifacts`,
1597
+ title: "Resume found accepted artifacts; continuing closeout",
1598
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
1599
+ tone: "info",
1600
+ status: "validating",
1601
+ createdAt: new Date().toISOString()
1602
+ });
1603
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
1604
+ exit = { code: 0, signal: null };
1605
+ }
1606
+ }
1607
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
1517
1608
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
1518
1609
  const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
1519
1610
  cwd: context.projectRoot,
@@ -1759,6 +1850,29 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1759
1850
  });
1760
1851
  throw new CliError2(terminalFailureDetail, exit.code ?? 1);
1761
1852
  }
1853
+ if (planningClassification.planningRequired) {
1854
+ const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1855
+ const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1856
+ if (!existsSync2(expectedPlanPath)) {
1857
+ const failedAt = new Date().toISOString();
1858
+ const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1859
+ patchAuthorityRun(context.projectRoot, input.runId, {
1860
+ status: "needs_attention",
1861
+ completedAt: failedAt,
1862
+ errorText: failureDetail
1863
+ });
1864
+ appendRunLog(context.projectRoot, input.runId, {
1865
+ id: `log:${input.runId}:plan-artifact-missing`,
1866
+ title: "Required plan artifact missing",
1867
+ detail: failureDetail,
1868
+ tone: "error",
1869
+ status: "needs_attention",
1870
+ createdAt: failedAt
1871
+ });
1872
+ emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
1873
+ throw new CliError2(failureDetail, 1);
1874
+ }
1875
+ }
1762
1876
  const runPiPrFeedbackFix = async (message) => {
1763
1877
  appendPiStageLog({
1764
1878
  projectRoot: context.projectRoot,
@@ -1929,5 +2043,6 @@ export {
1929
2043
  buildTaskRunReviewEnv,
1930
2044
  buildPiValidationRetrySteeringPrompt,
1931
2045
  buildPiRigBridgeEnv,
2046
+ buildDirtyBaselineHandshakeEnv,
1932
2047
  applyDirtyBaselineSnapshot
1933
2048
  };
@@ -1,9 +1,9 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task.ts
3
- import { readFileSync as readFileSync3 } from "fs";
3
+ import { readFileSync as readFileSync4 } from "fs";
4
4
  import { spawnSync as spawnSync2 } from "child_process";
5
5
  import { createInterface as createInterface3 } from "readline/promises";
6
- import { resolve as resolve3 } from "path";
6
+ import { resolve as resolve4 } from "path";
7
7
 
8
8
  // packages/cli/src/runner.ts
9
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
@@ -189,15 +189,33 @@ function resolveSelectedConnection(projectRoot, options = {}) {
189
189
 
190
190
  // packages/cli/src/commands/_server-client.ts
191
191
  import { spawnSync } from "child_process";
192
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
193
+ import { resolve as resolve2 } from "path";
192
194
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
193
195
  var cachedGitHubBearerToken;
194
196
  function cleanToken(value) {
195
197
  const trimmed = value?.trim();
196
198
  return trimmed ? trimmed : null;
197
199
  }
198
- function readGitHubBearerTokenForRemote() {
200
+ function readPrivateRemoteSessionToken(projectRoot) {
201
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
202
+ if (!existsSync2(path))
203
+ return null;
204
+ try {
205
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
206
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ function readGitHubBearerTokenForRemote(projectRoot) {
199
212
  if (cachedGitHubBearerToken !== undefined)
200
213
  return cachedGitHubBearerToken;
214
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
215
+ if (privateSession) {
216
+ cachedGitHubBearerToken = privateSession;
217
+ return cachedGitHubBearerToken;
218
+ }
201
219
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
202
220
  if (envToken) {
203
221
  cachedGitHubBearerToken = envToken;
@@ -217,7 +235,7 @@ async function ensureServerForCli(projectRoot) {
217
235
  if (selected?.connection.kind === "remote") {
218
236
  return {
219
237
  baseUrl: selected.connection.baseUrl,
220
- authToken: readGitHubBearerTokenForRemote(),
238
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
221
239
  connectionKind: "remote"
222
240
  };
223
241
  }
@@ -378,9 +396,9 @@ async function submitTaskRunViaServer(context, input) {
378
396
  }
379
397
 
380
398
  // packages/cli/src/commands/_pi-install.ts
381
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
399
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
382
400
  import { homedir as homedir2 } from "os";
383
- import { resolve as resolve2 } from "path";
401
+ import { resolve as resolve3 } from "path";
384
402
  var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
385
403
  async function defaultCommandRunner(command, options = {}) {
386
404
  const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
@@ -392,7 +410,7 @@ async function defaultCommandRunner(command, options = {}) {
392
410
  return { exitCode, stdout, stderr };
393
411
  }
394
412
  function resolvePiRigExtensionPath(homeDir) {
395
- return resolve2(homeDir, ".pi", "agent", "extensions", "pi-rig");
413
+ return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
396
414
  }
397
415
  function resolvePiHomeDir(inputHomeDir) {
398
416
  return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
@@ -420,13 +438,13 @@ async function checkPiRigInstall(input = {}) {
420
438
  piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
421
439
  };
422
440
  }
423
- const exists = input.exists ?? existsSync2;
441
+ const exists = input.exists ?? existsSync3;
424
442
  const runner = input.commandRunner ?? defaultCommandRunner;
425
443
  const piResult = await safeRun(runner, ["pi", "--version"]);
426
444
  const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
427
445
  const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
428
446
  ${piListResult.stderr}`);
429
- const legacyBridge = exists(resolve2(extensionPath, "index.ts"));
447
+ const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
430
448
  const hasPiRig = listedPiRig;
431
449
  return {
432
450
  extensionPath,
@@ -1101,7 +1119,7 @@ async function executeTask(context, args, options) {
1101
1119
  const fileFlag = takeOption(rest.slice(1), "--file");
1102
1120
  let content;
1103
1121
  if (fileFlag.value) {
1104
- content = readFileSync3(resolve3(context.projectRoot, fileFlag.value), "utf-8");
1122
+ content = readFileSync4(resolve4(context.projectRoot, fileFlag.value), "utf-8");
1105
1123
  } else {
1106
1124
  content = await readStdin();
1107
1125
  }