@h-rig/server 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.
@@ -1,6 +1,5 @@
1
1
  // @bun
2
2
  // packages/server/src/server-helpers/issue-analysis.ts
3
- import { execFile } from "child_process";
4
3
  import { createHash } from "crypto";
5
4
  function stableIssueHash(issue) {
6
5
  const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
@@ -134,16 +133,33 @@ function parseIssueAnalysisResult(raw) {
134
133
  return result;
135
134
  }
136
135
  function createDefaultPiIssueAnalysisCommandRunner() {
137
- return (command, args, options) => new Promise((resolve) => {
138
- execFile(command, [...args], {
139
- timeout: options.timeoutMs,
140
- maxBuffer: 10 * 1024 * 1024,
141
- env: options.env ? { ...process.env, ...options.env } : process.env
142
- }, (error, stdout, stderr) => {
143
- const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
144
- resolve({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
136
+ return async (command, args, options) => {
137
+ const env = options.env ? { ...process.env, ...options.env } : process.env;
138
+ const proc = Bun.spawn([command, ...args], {
139
+ stdout: "pipe",
140
+ stderr: "pipe",
141
+ env
145
142
  });
146
- });
143
+ let timedOut = false;
144
+ const timer = setTimeout(() => {
145
+ timedOut = true;
146
+ proc.kill();
147
+ }, options.timeoutMs);
148
+ try {
149
+ const [stdout, stderr, exitCode] = await Promise.all([
150
+ new Response(proc.stdout).text(),
151
+ new Response(proc.stderr).text(),
152
+ proc.exited
153
+ ]);
154
+ return {
155
+ exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
156
+ stdout,
157
+ stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
158
+ };
159
+ } finally {
160
+ clearTimeout(timer);
161
+ }
162
+ };
147
163
  }
148
164
  function createPiIssueAnalyzer(input = {}) {
149
165
  const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
@@ -151,7 +167,10 @@ function createPiIssueAnalyzer(input = {}) {
151
167
  const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
152
168
  return async ({ prompt }) => {
153
169
  const args = ["--print", "--mode", "json", "--no-session"];
154
- const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || "openai-codex/gpt-5.5";
170
+ const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
171
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
172
+ if (provider)
173
+ args.push("--provider", provider);
155
174
  if (model)
156
175
  args.push("--model", model);
157
176
  args.push(prompt);
@@ -2,8 +2,8 @@
2
2
  // packages/server/src/server-helpers/run-mutations.ts
3
3
  import { spawn } from "child_process";
4
4
  import { loadConfig } from "@rig/core/load-config";
5
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync3, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
6
- import { dirname as dirname5, relative as relative2, resolve as resolve9 } from "path";
5
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
6
+ import { dirname as dirname5, relative as relative2, resolve as resolve10 } from "path";
7
7
  import {
8
8
  listAuthorityRuns as listAuthorityRuns7,
9
9
  readAuthorityRun as readAuthorityRun8,
@@ -98,8 +98,8 @@ function normalizeStatus(value) {
98
98
  }
99
99
 
100
100
  // packages/server/src/server.ts
101
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
102
- import { dirname as dirname4, resolve as resolve7 } from "path";
101
+ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
102
+ import { dirname as dirname4, resolve as resolve8 } from "path";
103
103
  import {
104
104
  listAuthorityArtifactRoots,
105
105
  listAuthorityRuns as listAuthorityRuns6,
@@ -295,6 +295,24 @@ var snapshotCache = new Map;
295
295
  var contextCache = new Map;
296
296
  var taskListCache = new Map;
297
297
 
298
+ // packages/server/src/server-helpers/task-projection.ts
299
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
300
+ import { resolve as resolve5 } from "path";
301
+ function projectionPath(projectRoot) {
302
+ return resolve5(projectRoot, ".rig", "state", "task-projection.json");
303
+ }
304
+ function readTaskProjection(projectRoot) {
305
+ const file = projectionPath(projectRoot);
306
+ if (!existsSync3(file))
307
+ return null;
308
+ try {
309
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
310
+ return parsed && parsed.version === 1 && Array.isArray(parsed.tasks) ? parsed : null;
311
+ } catch {
312
+ return null;
313
+ }
314
+ }
315
+
298
316
  // packages/server/src/server-helpers/terminal-runtime.ts
299
317
  import { WS_CHANNELS as WS_CHANNELS2 } from "@rig/contracts";
300
318
 
@@ -302,7 +320,7 @@ import { WS_CHANNELS as WS_CHANNELS2 } from "@rig/contracts";
302
320
  import { RIG_WS_CHANNELS } from "@rig/contracts";
303
321
 
304
322
  // packages/server/src/server-helpers/run-writers.ts
305
- import { resolve as resolve5 } from "path";
323
+ import { resolve as resolve6 } from "path";
306
324
  import {
307
325
  appendJsonlRecord,
308
326
  readAuthorityRun as readAuthorityRun3,
@@ -327,7 +345,7 @@ function patchRunRecord(projectRoot, runId, patch) {
327
345
  ...patch,
328
346
  updatedAt: normalizeString(patch.updatedAt) ?? new Date().toISOString()
329
347
  };
330
- writeJsonFile2(resolve5(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
348
+ writeJsonFile2(resolve6(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
331
349
  return next;
332
350
  }
333
351
  function buildRunStartPatch(startedAt) {
@@ -459,8 +477,9 @@ import {
459
477
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
460
478
 
461
479
  // packages/server/src/server-helpers/github-auth-store.ts
462
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
463
- import { resolve as resolve6 } from "path";
480
+ import { randomBytes } from "crypto";
481
+ import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "fs";
482
+ import { resolve as resolve7 } from "path";
464
483
  function cleanString(value) {
465
484
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
466
485
  }
@@ -472,11 +491,29 @@ function cleanScopes(value) {
472
491
  return clean ? [clean] : [];
473
492
  });
474
493
  }
494
+ function parseApiSessions(value) {
495
+ if (!Array.isArray(value))
496
+ return [];
497
+ return value.flatMap((entry) => {
498
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
499
+ return [];
500
+ const record = entry;
501
+ const token = cleanString(record.token);
502
+ if (!token)
503
+ return [];
504
+ return [{
505
+ token,
506
+ login: cleanString(record.login),
507
+ userId: cleanString(record.userId),
508
+ createdAt: cleanString(record.createdAt) ?? undefined
509
+ }];
510
+ });
511
+ }
475
512
  function readStoredAuth(stateFile) {
476
- if (!existsSync3(stateFile))
513
+ if (!existsSync4(stateFile))
477
514
  return {};
478
515
  try {
479
- const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
516
+ const parsed = JSON.parse(readFileSync2(stateFile, "utf8"));
480
517
  return {
481
518
  ...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
482
519
  login: cleanString(parsed.login),
@@ -485,6 +522,7 @@ function readStoredAuth(stateFile) {
485
522
  selectedRepo: cleanString(parsed.selectedRepo),
486
523
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
487
524
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
525
+ apiSessions: parseApiSessions(parsed.apiSessions),
488
526
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
489
527
  };
490
528
  } catch {
@@ -503,16 +541,19 @@ function parsePendingDevice(value) {
503
541
  return null;
504
542
  return { pollId, deviceCode, expiresAt, intervalSeconds };
505
543
  }
544
+ function newApiSessionToken() {
545
+ return `rig_${randomBytes(32).toString("base64url")}`;
546
+ }
506
547
  function writeStoredAuth(stateFile, payload) {
507
- mkdirSync3(resolve6(stateFile, ".."), { recursive: true });
508
- writeFileSync3(stateFile, `${JSON.stringify(payload, null, 2)}
548
+ mkdirSync4(resolve7(stateFile, ".."), { recursive: true });
549
+ writeFileSync4(stateFile, `${JSON.stringify(payload, null, 2)}
509
550
  `, { encoding: "utf8", mode: 384 });
510
551
  try {
511
552
  chmodSync(stateFile, 384);
512
553
  } catch {}
513
554
  }
514
555
  function resolveGitHubAuthStateFile(projectRoot) {
515
- return resolve6(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
556
+ return resolve7(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
516
557
  }
517
558
  function createGitHubAuthStore(projectRoot) {
518
559
  const stateFile = resolveGitHubAuthStateFile(projectRoot);
@@ -545,9 +586,38 @@ function createGitHubAuthStore(projectRoot) {
545
586
  scopes: input.scopes ?? [],
546
587
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
547
588
  pendingDevice: null,
589
+ apiSessions: previous.apiSessions ?? [],
548
590
  updatedAt: new Date().toISOString()
549
591
  });
550
592
  },
593
+ createApiSession() {
594
+ const previous = readStoredAuth(stateFile);
595
+ const token = newApiSessionToken();
596
+ const session = {
597
+ token,
598
+ login: cleanString(previous.login),
599
+ userId: cleanString(previous.userId),
600
+ createdAt: new Date().toISOString()
601
+ };
602
+ writeStoredAuth(stateFile, {
603
+ ...previous,
604
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
605
+ updatedAt: new Date().toISOString()
606
+ });
607
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
608
+ },
609
+ readApiSession(token) {
610
+ const clean = cleanString(token);
611
+ if (!clean)
612
+ return null;
613
+ const previous = readStoredAuth(stateFile);
614
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
615
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
616
+ },
617
+ copyToProjectRoot(projectRoot2) {
618
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
619
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
620
+ },
551
621
  savePendingDevice(input) {
552
622
  const previous = readStoredAuth(stateFile);
553
623
  writeStoredAuth(stateFile, {
@@ -775,10 +845,10 @@ function extractGitHubIssueNodeId(task) {
775
845
  }
776
846
 
777
847
  // packages/server/src/server-helpers/http-router.ts
778
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.0";
848
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
779
849
  var RIG_CONFIG_DEV_DEPENDENCIES = {
780
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
781
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
850
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
851
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
782
852
  };
783
853
 
784
854
  // packages/server/src/server-helpers/ws-router.ts
@@ -917,10 +987,10 @@ var CLUSTERS = {
917
987
  };
918
988
 
919
989
  // packages/server/src/server-helpers/task-config.ts
920
- import { existsSync as existsSync4 } from "fs";
990
+ import { existsSync as existsSync5 } from "fs";
921
991
  async function readTaskConfig(projectRoot) {
922
992
  const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
923
- if (!existsSync4(taskConfigPath)) {
993
+ if (!existsSync5(taskConfigPath)) {
924
994
  return {};
925
995
  }
926
996
  try {
@@ -937,8 +1007,8 @@ var serverPathEnvQueue = Promise.resolve();
937
1007
  async function withServerPathEnv(projectRoot, fn) {
938
1008
  const waitForTurn = serverPathEnvQueue;
939
1009
  let releaseTurn;
940
- serverPathEnvQueue = new Promise((resolve8) => {
941
- releaseTurn = resolve8;
1010
+ serverPathEnvQueue = new Promise((resolve9) => {
1011
+ releaseTurn = resolve9;
942
1012
  });
943
1013
  await waitForTurn;
944
1014
  const paths = resolveServerAuthorityPaths(projectRoot);
@@ -974,9 +1044,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
974
1044
  return withServerPathEnv(projectRoot, fn);
975
1045
  }
976
1046
  async function readWorkspaceTasks(projectRoot) {
977
- const issuesPath = resolve7(resolveMonorepoRoot5(projectRoot), ".beads", "issues.jsonl");
1047
+ const issuesPath = resolve8(resolveMonorepoRoot5(projectRoot), ".beads", "issues.jsonl");
978
1048
  const taskConfig = await readTaskConfig(projectRoot);
979
- if (!existsSync5(issuesPath)) {
1049
+ if (!existsSync6(issuesPath)) {
980
1050
  return [];
981
1051
  }
982
1052
  const latestById = new Map;
@@ -995,7 +1065,7 @@ async function readWorkspaceTasks(projectRoot) {
995
1065
  description: normalizeString(entry.description),
996
1066
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
997
1067
  status: normalizedStatus ?? "unknown",
998
- sourceStatus: normalizedStatus ? null : rawStatus,
1068
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
999
1069
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
1000
1070
  issueType: normalizeString(entry.issue_type),
1001
1071
  role: normalizeString(config.role) ?? null,
@@ -1022,7 +1092,7 @@ async function readWorkspaceTasks(projectRoot) {
1022
1092
  if (false) {}
1023
1093
 
1024
1094
  // packages/server/src/server-helpers/validation-failure.ts
1025
- import { resolve as resolve8 } from "path";
1095
+ import { resolve as resolve9 } from "path";
1026
1096
  import {
1027
1097
  readJsonFile as readJsonFile4,
1028
1098
  resolveTaskArtifactDirs
@@ -1036,7 +1106,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
1036
1106
  continue;
1037
1107
  }
1038
1108
  seen.add(artifactRoot);
1039
- const summary = readJsonFile4(resolve8(artifactRoot, "validation-summary.json"), null);
1109
+ const summary = readJsonFile4(resolve9(artifactRoot, "validation-summary.json"), null);
1040
1110
  if (!summary || summary.status !== "fail") {
1041
1111
  continue;
1042
1112
  }
@@ -1120,15 +1190,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
1120
1190
  if (!run.taskId)
1121
1191
  return;
1122
1192
  const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
1123
- await syncGitHubProjectStatusForTaskUpdate({
1124
- taskId: run.taskId,
1125
- status,
1126
- issueNodeId,
1127
- token: createGitHubAuthStore(projectRoot).readToken(),
1128
- config
1129
- }).catch(() => {
1130
- return;
1131
- });
1193
+ try {
1194
+ const result = await syncGitHubProjectStatusForTaskUpdate({
1195
+ taskId: run.taskId,
1196
+ status,
1197
+ issueNodeId,
1198
+ token: createGitHubAuthStore(projectRoot).readToken(),
1199
+ config
1200
+ });
1201
+ if (!result.synced && result.reason !== "project-sync-disabled") {
1202
+ appendRunLogEntry(projectRoot, run.runId, {
1203
+ id: `log:${run.runId}:github-project-sync:${status}`,
1204
+ title: "GitHub Project sync skipped",
1205
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
1206
+ tone: "warn",
1207
+ status: "running",
1208
+ createdAt: new Date().toISOString(),
1209
+ payload: { reason: result.reason, issueNodeId }
1210
+ });
1211
+ }
1212
+ } catch (error) {
1213
+ appendRunLogEntry(projectRoot, run.runId, {
1214
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
1215
+ title: "GitHub Project sync failed",
1216
+ detail: error instanceof Error ? error.message : String(error),
1217
+ tone: "error",
1218
+ status: "running",
1219
+ createdAt: new Date().toISOString(),
1220
+ payload: { issueNodeId }
1221
+ });
1222
+ }
1132
1223
  }
1133
1224
  async function autoAssignRunIssue(projectRoot, run) {
1134
1225
  if (!run.taskId)
@@ -1230,11 +1321,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
1230
1321
  return;
1231
1322
  throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
1232
1323
  }
1324
+ async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
1325
+ const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
1326
+ if (fromReader)
1327
+ return fromReader;
1328
+ const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
1329
+ if (projected)
1330
+ return projected;
1331
+ if (readTasks !== readWorkspaceTasks) {
1332
+ return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
1333
+ }
1334
+ return null;
1335
+ }
1233
1336
  async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
1234
1337
  if ("taskId" in input && input.taskId) {
1235
1338
  assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
1236
1339
  }
1237
- const sourceTask = "taskId" in input && input.taskId ? (await readTasks(projectRoot)).find((task) => task.id === input.taskId) ?? null : null;
1340
+ const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
1238
1341
  const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
1239
1342
  const runDir = resolveAuthorityRunDir4(projectRoot, input.runId);
1240
1343
  const runRecord = {
@@ -1268,11 +1371,11 @@ async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTask
1268
1371
  initiatedBy: input.initiatedBy ?? null,
1269
1372
  ...sourceTask ? { sourceTask: sourceTaskContract(sourceTask) } : {}
1270
1373
  };
1271
- mkdirSync5(runDir, { recursive: true });
1272
- writeFileSync5(resolve9(runDir, "run.json"), `${JSON.stringify(runRecord, null, 2)}
1374
+ mkdirSync6(runDir, { recursive: true });
1375
+ writeFileSync6(resolve10(runDir, "run.json"), `${JSON.stringify(runRecord, null, 2)}
1273
1376
  `, "utf8");
1274
1377
  if ("initialPrompt" in input && input.initialPrompt && input.initialPrompt.trim().length > 0) {
1275
- writeFileSync5(resolve9(runDir, "timeline.jsonl"), `${JSON.stringify({
1378
+ writeFileSync6(resolve10(runDir, "timeline.jsonl"), `${JSON.stringify({
1276
1379
  id: `message-${Date.now()}`,
1277
1380
  type: "user_message",
1278
1381
  text: input.initialPrompt,
@@ -1306,6 +1409,7 @@ async function startLocalRun(state, runId, options) {
1306
1409
  throw new Error(`Run not found: ${runId}`);
1307
1410
  }
1308
1411
  const startedAt = new Date().toISOString();
1412
+ const resumeMode = options?.resume === true;
1309
1413
  state.runProcesses.set(runId, {
1310
1414
  runId,
1311
1415
  child: null,
@@ -1322,9 +1426,9 @@ async function startLocalRun(state, runId, options) {
1322
1426
  summary: run.title
1323
1427
  });
1324
1428
  appendRunLogEntry(state.projectRoot, runId, {
1325
- id: `log:${runId}:prepare`,
1326
- title: "Rig task run starting",
1327
- detail: run.taskId ?? run.title,
1429
+ id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
1430
+ title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
1431
+ detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
1328
1432
  tone: "info",
1329
1433
  status: "preparing",
1330
1434
  createdAt: startedAt
@@ -1332,8 +1436,8 @@ async function startLocalRun(state, runId, options) {
1332
1436
  broadcastRunLogAppended(state, runId, readLatestRawRunLog(state.projectRoot, runId));
1333
1437
  broadcastSnapshotInvalidation(state);
1334
1438
  const cliProjectRoot = resolveLocalRunCliProjectRoot(state.projectRoot);
1335
- const cliEntryPoint = resolve9(cliProjectRoot, "packages/cli/bin/rig.ts");
1336
- if (!existsSync6(cliEntryPoint)) {
1439
+ const cliEntryPoint = resolve10(cliProjectRoot, "packages/cli/bin/rig.ts");
1440
+ if (!existsSync7(cliEntryPoint)) {
1337
1441
  const completedAt = new Date().toISOString();
1338
1442
  const failureSummary = `Rig task-run entrypoint missing at ${relative2(state.projectRoot, cliEntryPoint)}`;
1339
1443
  patchRunRecord(state.projectRoot, runId, {
@@ -1402,7 +1506,15 @@ async function startLocalRun(state, runId, options) {
1402
1506
  RIG_SERVER_INTERNAL_EXEC: "1",
1403
1507
  ...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
1404
1508
  ...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
1405
- ...bridgeGitHubToken ? { RIG_GITHUB_TOKEN: bridgeGitHubToken } : {}
1509
+ ...bridgeGitHubToken ? {
1510
+ RIG_GITHUB_TOKEN: bridgeGitHubToken,
1511
+ GITHUB_TOKEN: bridgeGitHubToken,
1512
+ GH_TOKEN: bridgeGitHubToken
1513
+ } : {},
1514
+ ...resumeMode ? {
1515
+ RIG_RUN_RESUME: "1",
1516
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
1517
+ } : {}
1406
1518
  },
1407
1519
  stdio: ["ignore", "pipe", "pipe"]
1408
1520
  });
@@ -1442,9 +1554,9 @@ async function startLocalRun(state, runId, options) {
1442
1554
  handleRunProcessOutput(Buffer.isBuffer(data) ? data.toString("utf8") : String(data), "error", "Rig task run stderr");
1443
1555
  });
1444
1556
  try {
1445
- const exit = await new Promise((resolve10) => {
1446
- child.once("error", (error) => resolve10({ code: 1, signal: null, error }));
1447
- child.once("close", (code, signal) => resolve10({ code, signal }));
1557
+ const exit = await new Promise((resolve11) => {
1558
+ child.once("error", (error) => resolve11({ code: 1, signal: null, error }));
1559
+ child.once("close", (code, signal) => resolve11({ code, signal }));
1448
1560
  });
1449
1561
  if (exit.error) {
1450
1562
  throw new Error(`Failed to start task run: ${exit.error.message}`);
@@ -1544,17 +1656,17 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
1544
1656
  process.env.PROJECT_RIG_ROOT?.trim()
1545
1657
  ].filter((value) => !!value);
1546
1658
  for (const candidate of envCandidates) {
1547
- if (existsSync6(resolve9(candidate, "packages/cli/bin/rig.ts"))) {
1548
- return resolve9(candidate);
1659
+ if (existsSync7(resolve10(candidate, "packages/cli/bin/rig.ts"))) {
1660
+ return resolve10(candidate);
1549
1661
  }
1550
1662
  }
1551
- if (existsSync6(resolve9(projectRoot, "packages/cli/bin/rig.ts"))) {
1663
+ if (existsSync7(resolve10(projectRoot, "packages/cli/bin/rig.ts"))) {
1552
1664
  return projectRoot;
1553
1665
  }
1554
1666
  try {
1555
1667
  const monorepoRoot = resolveMonorepoRoot6(projectRoot);
1556
1668
  const outerProjectRoot = dirname5(dirname5(monorepoRoot));
1557
- if (existsSync6(resolve9(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
1669
+ if (existsSync7(resolve10(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
1558
1670
  return outerProjectRoot;
1559
1671
  }
1560
1672
  } catch {}
@@ -1574,15 +1686,15 @@ async function resumeRunRecord(state, input) {
1574
1686
  if (run.status === "completed") {
1575
1687
  throw new Error("Completed runs cannot be resumed.");
1576
1688
  }
1577
- await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
1689
+ await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
1578
1690
  }
1579
1691
  function appendRunMessage(projectRoot, input) {
1580
1692
  const run = readAuthorityRun8(projectRoot, input.runId);
1581
1693
  if (!run) {
1582
1694
  throw new Error(`Run not found: ${input.runId}`);
1583
1695
  }
1584
- const timelinePath = resolve9(resolveAuthorityRunDir4(projectRoot, input.runId), "timeline.jsonl");
1585
- const existingLines = fileExists(timelinePath) ? readFileSync3(timelinePath, "utf8").trim() : "";
1696
+ const timelinePath = resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "timeline.jsonl");
1697
+ const existingLines = fileExists(timelinePath) ? readFileSync4(timelinePath, "utf8").trim() : "";
1586
1698
  const nextLine = JSON.stringify({
1587
1699
  id: input.messageId,
1588
1700
  type: "user_message",
@@ -1590,11 +1702,11 @@ function appendRunMessage(projectRoot, input) {
1590
1702
  attachments: input.attachments ?? [],
1591
1703
  createdAt: input.createdAt
1592
1704
  });
1593
- writeFileSync5(timelinePath, existingLines.length > 0 ? `${existingLines}
1705
+ writeFileSync6(timelinePath, existingLines.length > 0 ? `${existingLines}
1594
1706
  ${nextLine}
1595
1707
  ` : `${nextLine}
1596
1708
  `, "utf8");
1597
- writeJsonFile4(resolve9(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), {
1709
+ writeJsonFile4(resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), {
1598
1710
  ...run,
1599
1711
  updatedAt: input.createdAt
1600
1712
  });
@@ -1620,7 +1732,7 @@ async function stopRunRecord(stateOrProjectRoot, input) {
1620
1732
  completedAt: run.completedAt ?? input.createdAt,
1621
1733
  updatedAt: input.createdAt
1622
1734
  };
1623
- writeJsonFile4(resolve9(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), nextRun);
1735
+ writeJsonFile4(resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), nextRun);
1624
1736
  if (run.status !== "completed" && run.taskId) {
1625
1737
  const taskId = run.taskId;
1626
1738
  (async () => {
@@ -1658,34 +1770,12 @@ function removeTaskIdsFromQueueState2(projectRoot, taskIds) {
1658
1770
  writeQueueState(projectRoot, next);
1659
1771
  return next;
1660
1772
  }
1661
- var ORPHANABLE_LOCAL_RUN_STATUSES = new Set(["preparing", "running"]);
1662
- function reconcileOrphanedLocalRuns(state, runs, nowIso) {
1663
- let changed = false;
1664
- for (const run of runs) {
1773
+ var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
1774
+ function collectResumableLocalRuns(state, runs) {
1775
+ return runs.filter((run) => {
1665
1776
  const status = normalizeString(run.status)?.toLowerCase() ?? "";
1666
- const serverPid = run.serverPid;
1667
- const wasStartedByRigServer = typeof serverPid === "number" || typeof serverPid === "string";
1668
- if (run.mode !== "local" || !wasStartedByRigServer || !ORPHANABLE_LOCAL_RUN_STATUSES.has(status) || state.runProcesses.has(run.runId)) {
1669
- continue;
1670
- }
1671
- const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
1672
- patchRunRecord(state.projectRoot, run.runId, {
1673
- status: "failed",
1674
- completedAt: run.completedAt ?? nowIso,
1675
- updatedAt: nowIso,
1676
- errorText: detail
1677
- });
1678
- appendRunLogEntry(state.projectRoot, run.runId, {
1679
- id: `log:${run.runId}:stale-local-run`,
1680
- title: "Run marked stale after server restart",
1681
- detail,
1682
- tone: "error",
1683
- status: "failed",
1684
- createdAt: nowIso
1685
- });
1686
- changed = true;
1687
- }
1688
- return changed;
1777
+ return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
1778
+ });
1689
1779
  }
1690
1780
  async function reconcileScheduler(state, reason) {
1691
1781
  if (state.scheduler.reconciling) {
@@ -1700,7 +1790,20 @@ async function reconcileScheduler(state, reason) {
1700
1790
  const queue = readQueueState(state.projectRoot);
1701
1791
  const tasks = await state.snapshotService.getWorkspaceTasks();
1702
1792
  let runs = listAuthorityRuns7(state.projectRoot);
1703
- let changed = reconcileOrphanedLocalRuns(state, runs, new Date().toISOString());
1793
+ let changed = false;
1794
+ const resumableRuns = collectResumableLocalRuns(state, runs);
1795
+ for (const run of resumableRuns) {
1796
+ appendRunLogEntry(state.projectRoot, run.runId, {
1797
+ id: `log:${run.runId}:auto-resume:${Date.now()}`,
1798
+ title: "Run auto-resume scheduled",
1799
+ detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
1800
+ tone: "info",
1801
+ status: "preparing",
1802
+ createdAt: new Date().toISOString()
1803
+ });
1804
+ await startLocalRun(state, run.runId, { resume: true });
1805
+ changed = true;
1806
+ }
1704
1807
  if (changed) {
1705
1808
  runs = listAuthorityRuns7(state.projectRoot);
1706
1809
  }
@@ -699,7 +699,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
699
699
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
700
700
  ...toTaskSummary(workspace.id, {
701
701
  ...task,
702
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
702
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
703
703
  }),
704
704
  graphId: graph.id
705
705
  }));
@@ -711,7 +711,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
711
711
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
712
712
  ...toTaskSummary(workspace.id, {
713
713
  ...task,
714
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
714
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
715
715
  }),
716
716
  graphId: graph.id
717
717
  }));
@@ -403,7 +403,7 @@ var TERMINAL_RUN_STATUSES2 = new Set([
403
403
  "needs-attention",
404
404
  "stopped"
405
405
  ]);
406
- var ORPHANABLE_LOCAL_RUN_STATUSES = new Set(["preparing", "running"]);
406
+ var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
407
407
 
408
408
  // packages/server/src/server-helpers/http-router.ts
409
409
  import {
@@ -437,10 +437,10 @@ import {
437
437
  buildTaskRunLifecycleComment as buildTaskRunLifecycleComment2,
438
438
  updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
439
439
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
440
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.0";
440
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
441
441
  var RIG_CONFIG_DEV_DEPENDENCIES = {
442
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
443
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
442
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
443
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
444
444
  };
445
445
 
446
446
  // packages/server/src/server-helpers/inspector-jobs.ts