@h-rig/server 0.0.6-alpha.1 → 0.0.6-alpha.11

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,11 +1,12 @@
1
+ #!/usr/bin/env bun
1
2
  // @bun
2
3
  var __require = import.meta.require;
3
4
 
4
5
  // packages/server/src/server.ts
5
6
  import { spawn as spawn5 } from "child_process";
6
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync6 } from "fs";
7
+ import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
7
8
  import { open } from "fs/promises";
8
- import { dirname as dirname17, resolve as resolve22 } from "path";
9
+ import { dirname as dirname20, resolve as resolve24 } from "path";
9
10
  import {
10
11
  listAuthorityArtifactRoots,
11
12
  listAuthorityRuns as listAuthorityRuns7,
@@ -1786,7 +1787,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
1786
1787
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
1787
1788
  ...toTaskSummary(workspace.id, {
1788
1789
  ...task,
1789
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
1790
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
1790
1791
  }),
1791
1792
  graphId: graph.id
1792
1793
  }));
@@ -2312,7 +2313,6 @@ function createGitHubTaskReconciler(input) {
2312
2313
  }
2313
2314
 
2314
2315
  // packages/server/src/server-helpers/issue-analysis.ts
2315
- import { execFile } from "child_process";
2316
2316
  import { createHash } from "crypto";
2317
2317
  function stableIssueHash(issue) {
2318
2318
  const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
@@ -2446,16 +2446,33 @@ function parseIssueAnalysisResult(raw) {
2446
2446
  return result;
2447
2447
  }
2448
2448
  function createDefaultPiIssueAnalysisCommandRunner() {
2449
- return (command, args, options) => new Promise((resolve11) => {
2450
- execFile(command, [...args], {
2451
- timeout: options.timeoutMs,
2452
- maxBuffer: 10 * 1024 * 1024,
2453
- env: options.env ? { ...process.env, ...options.env } : process.env
2454
- }, (error, stdout, stderr) => {
2455
- const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
2456
- resolve11({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
2449
+ return async (command, args, options) => {
2450
+ const env = options.env ? { ...process.env, ...options.env } : process.env;
2451
+ const proc = Bun.spawn([command, ...args], {
2452
+ stdout: "pipe",
2453
+ stderr: "pipe",
2454
+ env
2457
2455
  });
2458
- });
2456
+ let timedOut = false;
2457
+ const timer = setTimeout(() => {
2458
+ timedOut = true;
2459
+ proc.kill();
2460
+ }, options.timeoutMs);
2461
+ try {
2462
+ const [stdout, stderr, exitCode] = await Promise.all([
2463
+ new Response(proc.stdout).text(),
2464
+ new Response(proc.stderr).text(),
2465
+ proc.exited
2466
+ ]);
2467
+ return {
2468
+ exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
2469
+ stdout,
2470
+ stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
2471
+ };
2472
+ } finally {
2473
+ clearTimeout(timer);
2474
+ }
2475
+ };
2459
2476
  }
2460
2477
  function createPiIssueAnalyzer(input = {}) {
2461
2478
  const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
@@ -2463,7 +2480,10 @@ function createPiIssueAnalyzer(input = {}) {
2463
2480
  const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
2464
2481
  return async ({ prompt }) => {
2465
2482
  const args = ["--print", "--mode", "json", "--no-session"];
2466
- const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || "openai-codex/gpt-5.5";
2483
+ const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
2484
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
2485
+ if (provider)
2486
+ args.push("--provider", provider);
2467
2487
  if (model)
2468
2488
  args.push("--model", model);
2469
2489
  args.push(prompt);
@@ -3181,7 +3201,7 @@ function applyOrchestrationCommand(state, command) {
3181
3201
  import { spawn as spawn3 } from "child_process";
3182
3202
  import { loadConfig } from "@rig/core/load-config";
3183
3203
  import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
3184
- import { dirname as dirname8, relative as relative2, resolve as resolve14 } from "path";
3204
+ import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
3185
3205
  import {
3186
3206
  listAuthorityRuns as listAuthorityRuns4,
3187
3207
  readAuthorityRun as readAuthorityRun4,
@@ -3304,8 +3324,9 @@ function summarizeRunValidationFailure(projectRoot, run) {
3304
3324
  }
3305
3325
 
3306
3326
  // packages/server/src/server-helpers/github-auth-store.ts
3307
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3308
- import { resolve as resolve13 } from "path";
3327
+ import { randomBytes } from "crypto";
3328
+ import { chmodSync, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3329
+ import { dirname as dirname8, resolve as resolve13 } from "path";
3309
3330
  function cleanString(value) {
3310
3331
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
3311
3332
  }
@@ -3317,6 +3338,44 @@ function cleanScopes(value) {
3317
3338
  return clean ? [clean] : [];
3318
3339
  });
3319
3340
  }
3341
+ function parseApiSessions(value) {
3342
+ if (!Array.isArray(value))
3343
+ return [];
3344
+ return value.flatMap((entry) => {
3345
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3346
+ return [];
3347
+ const record = entry;
3348
+ const token = cleanString(record.token);
3349
+ if (!token)
3350
+ return [];
3351
+ return [{
3352
+ token,
3353
+ login: cleanString(record.login),
3354
+ userId: cleanString(record.userId),
3355
+ createdAt: cleanString(record.createdAt) ?? undefined
3356
+ }];
3357
+ });
3358
+ }
3359
+ function parsePendingDevice(value) {
3360
+ if (!value || typeof value !== "object")
3361
+ return null;
3362
+ const record = value;
3363
+ const pollId = cleanString(record.pollId);
3364
+ const deviceCode = cleanString(record.deviceCode);
3365
+ const expiresAt = cleanString(record.expiresAt);
3366
+ const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
3367
+ if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
3368
+ return null;
3369
+ return { pollId, deviceCode, expiresAt, intervalSeconds };
3370
+ }
3371
+ function parsePendingDevices(value) {
3372
+ if (!Array.isArray(value))
3373
+ return [];
3374
+ return value.flatMap((entry) => {
3375
+ const pending = parsePendingDevice(entry);
3376
+ return pending ? [pending] : [];
3377
+ });
3378
+ }
3320
3379
  function readStoredAuth(stateFile) {
3321
3380
  if (!existsSync6(stateFile))
3322
3381
  return {};
@@ -3330,37 +3389,44 @@ function readStoredAuth(stateFile) {
3330
3389
  selectedRepo: cleanString(parsed.selectedRepo),
3331
3390
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
3332
3391
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
3392
+ pendingDevices: parsePendingDevices(parsed.pendingDevices),
3393
+ apiSessions: parseApiSessions(parsed.apiSessions),
3333
3394
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
3334
3395
  };
3335
3396
  } catch {
3336
3397
  return {};
3337
3398
  }
3338
3399
  }
3339
- function parsePendingDevice(value) {
3340
- if (!value || typeof value !== "object")
3341
- return null;
3342
- const record = value;
3343
- const pollId = cleanString(record.pollId);
3344
- const deviceCode = cleanString(record.deviceCode);
3345
- const expiresAt = cleanString(record.expiresAt);
3346
- const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
3347
- if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
3348
- return null;
3349
- return { pollId, deviceCode, expiresAt, intervalSeconds };
3400
+ function newApiSessionToken() {
3401
+ return `rig_${randomBytes(32).toString("base64url")}`;
3350
3402
  }
3351
3403
  function writeStoredAuth(stateFile, payload) {
3352
- mkdirSync6(resolve13(stateFile, ".."), { recursive: true });
3404
+ mkdirSync6(dirname8(stateFile), { recursive: true });
3353
3405
  writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
3354
3406
  `, { encoding: "utf8", mode: 384 });
3355
3407
  try {
3356
3408
  chmodSync(stateFile, 384);
3357
3409
  } catch {}
3358
3410
  }
3411
+ function localProjectAuthStateFile(projectRoot) {
3412
+ return resolve13(projectRoot, ".rig", "state", "github-auth.json");
3413
+ }
3359
3414
  function resolveGitHubAuthStateFile(projectRoot) {
3360
3415
  return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
3361
3416
  }
3362
- function createGitHubAuthStore(projectRoot) {
3363
- const stateFile = resolveGitHubAuthStateFile(projectRoot);
3417
+ function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
3418
+ const targetFile = localProjectAuthStateFile(projectRoot);
3419
+ mkdirSync6(dirname8(targetFile), { recursive: true });
3420
+ if (existsSync6(stateFile)) {
3421
+ copyFileSync(stateFile, targetFile);
3422
+ try {
3423
+ chmodSync(targetFile, 384);
3424
+ } catch {}
3425
+ return;
3426
+ }
3427
+ writeStoredAuth(targetFile, {});
3428
+ }
3429
+ function createGitHubAuthStoreFromStateFile(stateFile) {
3364
3430
  return {
3365
3431
  stateFile,
3366
3432
  status(options) {
@@ -3390,14 +3456,53 @@ function createGitHubAuthStore(projectRoot) {
3390
3456
  scopes: input.scopes ?? [],
3391
3457
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
3392
3458
  pendingDevice: null,
3459
+ pendingDevices: [],
3460
+ apiSessions: previous.apiSessions ?? [],
3461
+ updatedAt: new Date().toISOString()
3462
+ });
3463
+ },
3464
+ createApiSession() {
3465
+ const previous = readStoredAuth(stateFile);
3466
+ const token = newApiSessionToken();
3467
+ const session = {
3468
+ token,
3469
+ login: cleanString(previous.login),
3470
+ userId: cleanString(previous.userId),
3471
+ createdAt: new Date().toISOString()
3472
+ };
3473
+ writeStoredAuth(stateFile, {
3474
+ ...previous,
3475
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
3393
3476
  updatedAt: new Date().toISOString()
3394
3477
  });
3478
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
3479
+ },
3480
+ readApiSession(token) {
3481
+ const clean = cleanString(token);
3482
+ if (!clean)
3483
+ return null;
3484
+ const previous = readStoredAuth(stateFile);
3485
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
3486
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
3487
+ },
3488
+ copyToProjectRoot(projectRoot) {
3489
+ const targetFile = resolveGitHubAuthStateFile(projectRoot);
3490
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
3491
+ },
3492
+ copyToLocalProjectRoot(projectRoot) {
3493
+ copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
3395
3494
  },
3396
3495
  savePendingDevice(input) {
3397
3496
  const previous = readStoredAuth(stateFile);
3497
+ const pendingDevices = [
3498
+ ...previous.pendingDevice ? [previous.pendingDevice] : [],
3499
+ ...previous.pendingDevices ?? [],
3500
+ input
3501
+ ].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
3398
3502
  writeStoredAuth(stateFile, {
3399
3503
  ...previous,
3400
- pendingDevice: input,
3504
+ pendingDevice: null,
3505
+ pendingDevices,
3401
3506
  updatedAt: new Date().toISOString()
3402
3507
  });
3403
3508
  },
@@ -3410,23 +3515,32 @@ function createGitHubAuthStore(projectRoot) {
3410
3515
  });
3411
3516
  },
3412
3517
  readPendingDevice(pollId) {
3413
- const pending = readStoredAuth(stateFile).pendingDevice ?? null;
3414
- if (!pending || pending.pollId !== pollId)
3518
+ const previous = readStoredAuth(stateFile);
3519
+ const pending = [
3520
+ ...previous.pendingDevice ? [previous.pendingDevice] : [],
3521
+ ...previous.pendingDevices ?? []
3522
+ ].find((entry) => entry.pollId === pollId) ?? null;
3523
+ if (!pending)
3415
3524
  return null;
3416
3525
  if (Date.parse(pending.expiresAt) <= Date.now())
3417
3526
  return null;
3418
3527
  return pending;
3419
3528
  },
3420
- clearPendingDevice() {
3529
+ clearPendingDevice(pollId) {
3421
3530
  const previous = readStoredAuth(stateFile);
3531
+ const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
3422
3532
  writeStoredAuth(stateFile, {
3423
3533
  ...previous,
3424
3534
  pendingDevice: null,
3535
+ pendingDevices: remaining,
3425
3536
  updatedAt: new Date().toISOString()
3426
3537
  });
3427
3538
  }
3428
3539
  };
3429
3540
  }
3541
+ function createGitHubAuthStore(projectRoot) {
3542
+ return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
3543
+ }
3430
3544
 
3431
3545
  // packages/server/src/server-helpers/github-projects.ts
3432
3546
  function asRecord(value) {
@@ -3690,15 +3804,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
3690
3804
  if (!run.taskId)
3691
3805
  return;
3692
3806
  const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
3693
- await syncGitHubProjectStatusForTaskUpdate({
3694
- taskId: run.taskId,
3695
- status,
3696
- issueNodeId,
3697
- token: createGitHubAuthStore(projectRoot).readToken(),
3698
- config
3699
- }).catch(() => {
3700
- return;
3701
- });
3807
+ try {
3808
+ const result = await syncGitHubProjectStatusForTaskUpdate({
3809
+ taskId: run.taskId,
3810
+ status,
3811
+ issueNodeId,
3812
+ token: createGitHubAuthStore(projectRoot).readToken(),
3813
+ config
3814
+ });
3815
+ if (!result.synced && result.reason !== "project-sync-disabled") {
3816
+ appendRunLogEntry(projectRoot, run.runId, {
3817
+ id: `log:${run.runId}:github-project-sync:${status}`,
3818
+ title: "GitHub Project sync skipped",
3819
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
3820
+ tone: "warn",
3821
+ status: "running",
3822
+ createdAt: new Date().toISOString(),
3823
+ payload: { reason: result.reason, issueNodeId }
3824
+ });
3825
+ }
3826
+ } catch (error) {
3827
+ appendRunLogEntry(projectRoot, run.runId, {
3828
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
3829
+ title: "GitHub Project sync failed",
3830
+ detail: error instanceof Error ? error.message : String(error),
3831
+ tone: "error",
3832
+ status: "running",
3833
+ createdAt: new Date().toISOString(),
3834
+ payload: { issueNodeId }
3835
+ });
3836
+ }
3702
3837
  }
3703
3838
  async function autoAssignRunIssue(projectRoot, run) {
3704
3839
  if (!run.taskId)
@@ -3800,11 +3935,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
3800
3935
  return;
3801
3936
  throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
3802
3937
  }
3938
+ async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
3939
+ const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
3940
+ if (fromReader)
3941
+ return fromReader;
3942
+ const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
3943
+ if (projected)
3944
+ return projected;
3945
+ if (readTasks !== readWorkspaceTasks) {
3946
+ return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
3947
+ }
3948
+ return null;
3949
+ }
3803
3950
  async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
3804
3951
  if ("taskId" in input && input.taskId) {
3805
3952
  assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
3806
3953
  }
3807
- const sourceTask = "taskId" in input && input.taskId ? (await readTasks(projectRoot)).find((task) => task.id === input.taskId) ?? null : null;
3954
+ const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
3808
3955
  const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
3809
3956
  const runDir = resolveAuthorityRunDir3(projectRoot, input.runId);
3810
3957
  const runRecord = {
@@ -3876,6 +4023,7 @@ async function startLocalRun(state, runId, options) {
3876
4023
  throw new Error(`Run not found: ${runId}`);
3877
4024
  }
3878
4025
  const startedAt = new Date().toISOString();
4026
+ const resumeMode = options?.resume === true;
3879
4027
  state.runProcesses.set(runId, {
3880
4028
  runId,
3881
4029
  child: null,
@@ -3892,9 +4040,9 @@ async function startLocalRun(state, runId, options) {
3892
4040
  summary: run.title
3893
4041
  });
3894
4042
  appendRunLogEntry(state.projectRoot, runId, {
3895
- id: `log:${runId}:prepare`,
3896
- title: "Rig task run starting",
3897
- detail: run.taskId ?? run.title,
4043
+ id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
4044
+ title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
4045
+ detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
3898
4046
  tone: "info",
3899
4047
  status: "preparing",
3900
4048
  createdAt: startedAt
@@ -3972,7 +4120,15 @@ async function startLocalRun(state, runId, options) {
3972
4120
  RIG_SERVER_INTERNAL_EXEC: "1",
3973
4121
  ...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
3974
4122
  ...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
3975
- ...bridgeGitHubToken ? { RIG_GITHUB_TOKEN: bridgeGitHubToken } : {}
4123
+ ...bridgeGitHubToken ? {
4124
+ RIG_GITHUB_TOKEN: bridgeGitHubToken,
4125
+ GITHUB_TOKEN: bridgeGitHubToken,
4126
+ GH_TOKEN: bridgeGitHubToken
4127
+ } : {},
4128
+ ...resumeMode ? {
4129
+ RIG_RUN_RESUME: "1",
4130
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
4131
+ } : {}
3976
4132
  },
3977
4133
  stdio: ["ignore", "pipe", "pipe"]
3978
4134
  });
@@ -4123,7 +4279,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
4123
4279
  }
4124
4280
  try {
4125
4281
  const monorepoRoot = resolveMonorepoRoot3(projectRoot);
4126
- const outerProjectRoot = dirname8(dirname8(monorepoRoot));
4282
+ const outerProjectRoot = dirname9(dirname9(monorepoRoot));
4127
4283
  if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
4128
4284
  return outerProjectRoot;
4129
4285
  }
@@ -4144,7 +4300,7 @@ async function resumeRunRecord(state, input) {
4144
4300
  if (run.status === "completed") {
4145
4301
  throw new Error("Completed runs cannot be resumed.");
4146
4302
  }
4147
- await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
4303
+ await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
4148
4304
  }
4149
4305
  function appendRunMessage(projectRoot, input) {
4150
4306
  const run = readAuthorityRun4(projectRoot, input.runId);
@@ -4228,34 +4384,12 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
4228
4384
  writeQueueState(projectRoot, next);
4229
4385
  return next;
4230
4386
  }
4231
- var ORPHANABLE_LOCAL_RUN_STATUSES = new Set(["preparing", "running"]);
4232
- function reconcileOrphanedLocalRuns(state, runs, nowIso) {
4233
- let changed = false;
4234
- for (const run of runs) {
4387
+ var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
4388
+ function collectResumableLocalRuns(state, runs) {
4389
+ return runs.filter((run) => {
4235
4390
  const status = normalizeString(run.status)?.toLowerCase() ?? "";
4236
- const serverPid = run.serverPid;
4237
- const wasStartedByRigServer = typeof serverPid === "number" || typeof serverPid === "string";
4238
- if (run.mode !== "local" || !wasStartedByRigServer || !ORPHANABLE_LOCAL_RUN_STATUSES.has(status) || state.runProcesses.has(run.runId)) {
4239
- continue;
4240
- }
4241
- const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
4242
- patchRunRecord(state.projectRoot, run.runId, {
4243
- status: "failed",
4244
- completedAt: run.completedAt ?? nowIso,
4245
- updatedAt: nowIso,
4246
- errorText: detail
4247
- });
4248
- appendRunLogEntry(state.projectRoot, run.runId, {
4249
- id: `log:${run.runId}:stale-local-run`,
4250
- title: "Run marked stale after server restart",
4251
- detail,
4252
- tone: "error",
4253
- status: "failed",
4254
- createdAt: nowIso
4255
- });
4256
- changed = true;
4257
- }
4258
- return changed;
4391
+ return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
4392
+ });
4259
4393
  }
4260
4394
  async function reconcileScheduler(state, reason) {
4261
4395
  if (state.scheduler.reconciling) {
@@ -4270,7 +4404,20 @@ async function reconcileScheduler(state, reason) {
4270
4404
  const queue = readQueueState(state.projectRoot);
4271
4405
  const tasks = await state.snapshotService.getWorkspaceTasks();
4272
4406
  let runs = listAuthorityRuns4(state.projectRoot);
4273
- let changed = reconcileOrphanedLocalRuns(state, runs, new Date().toISOString());
4407
+ let changed = false;
4408
+ const resumableRuns = collectResumableLocalRuns(state, runs);
4409
+ for (const run of resumableRuns) {
4410
+ appendRunLogEntry(state.projectRoot, run.runId, {
4411
+ id: `log:${run.runId}:auto-resume:${Date.now()}`,
4412
+ title: "Run auto-resume scheduled",
4413
+ detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
4414
+ tone: "info",
4415
+ status: "preparing",
4416
+ createdAt: new Date().toISOString()
4417
+ });
4418
+ await startLocalRun(state, run.runId, { resume: true });
4419
+ changed = true;
4420
+ }
4274
4421
  if (changed) {
4275
4422
  runs = listAuthorityRuns4(state.projectRoot);
4276
4423
  }
@@ -4346,8 +4493,8 @@ async function reconcileScheduler(state, reason) {
4346
4493
  // packages/server/src/server-helpers/http-router.ts
4347
4494
  import { randomUUID } from "crypto";
4348
4495
  import { spawnSync as spawnSync3 } from "child_process";
4349
- import { basename, dirname as dirname12, isAbsolute as isAbsolute3, resolve as resolve18 } from "path";
4350
- import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync10 } from "fs";
4496
+ import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as resolve20 } from "path";
4497
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
4351
4498
  import {
4352
4499
  listAuthorityRuns as listAuthorityRuns5,
4353
4500
  readAuthorityRun as readAuthorityRun6,
@@ -4371,7 +4518,7 @@ import {
4371
4518
  } from "@rig/runtime/control-plane/remote";
4372
4519
 
4373
4520
  // packages/server/src/server-helpers/run-steering.ts
4374
- import { dirname as dirname9, resolve as resolve15 } from "path";
4521
+ import { dirname as dirname10, resolve as resolve15 } from "path";
4375
4522
  import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
4376
4523
  import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
4377
4524
  var steeringSequence = 0;
@@ -4458,7 +4605,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
4458
4605
  delivered: false
4459
4606
  };
4460
4607
  const path = runSteeringPath(projectRoot, runId);
4461
- mkdirSync8(dirname9(path), { recursive: true });
4608
+ mkdirSync8(dirname10(path), { recursive: true });
4462
4609
  appendJsonlRecord2(path, entry);
4463
4610
  appendRunTimelineEntry(projectRoot, runId, {
4464
4611
  id: entry.id,
@@ -4495,6 +4642,187 @@ import {
4495
4642
  updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
4496
4643
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
4497
4644
 
4645
+ // packages/server/src/server-helpers/github-api-session-index.ts
4646
+ import { chmodSync as chmodSync3, existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
4647
+ import { dirname as dirname12, resolve as resolve17 } from "path";
4648
+
4649
+ // packages/server/src/server-helpers/github-user-namespace.ts
4650
+ import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
4651
+ import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve16 } from "path";
4652
+ function cleanString3(value) {
4653
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
4654
+ }
4655
+ function sanitizePathSegment(value) {
4656
+ return value.trim().toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
4657
+ }
4658
+ function deriveGitHubUserNamespaceKey(identity) {
4659
+ const userId = cleanString3(identity.userId);
4660
+ if (userId) {
4661
+ const safeId = sanitizePathSegment(userId);
4662
+ if (safeId)
4663
+ return `ghu-${safeId}`;
4664
+ }
4665
+ const login = cleanString3(identity.login);
4666
+ if (login) {
4667
+ const safeLogin = sanitizePathSegment(login);
4668
+ if (safeLogin)
4669
+ return `ghu-login-${safeLogin}`;
4670
+ }
4671
+ throw new Error("GitHub user namespace requires a user id or login");
4672
+ }
4673
+ function resolveRemoteUserNamespacesRoot(projectRoot) {
4674
+ const explicitRoot = cleanString3(process.env.RIG_REMOTE_USER_NAMESPACE_ROOT);
4675
+ if (explicitRoot)
4676
+ return resolve16(explicitRoot);
4677
+ const stateDir2 = cleanString3(process.env.RIG_STATE_DIR);
4678
+ if (stateDir2)
4679
+ return resolve16(dirname11(resolve16(stateDir2)), "users");
4680
+ return resolve16(projectRoot, ".rig", "users");
4681
+ }
4682
+ function resolveRemoteUserNamespace(projectRoot, identity) {
4683
+ const key = deriveGitHubUserNamespaceKey(identity);
4684
+ const root = resolve16(resolveRemoteUserNamespacesRoot(projectRoot), key);
4685
+ const stateDir2 = resolve16(root, ".rig", "state");
4686
+ return {
4687
+ key,
4688
+ userId: cleanString3(identity.userId),
4689
+ login: cleanString3(identity.login),
4690
+ root,
4691
+ stateDir: stateDir2,
4692
+ authStateFile: resolve16(stateDir2, "github-auth.json"),
4693
+ metadataFile: resolve16(stateDir2, "user-namespace.json"),
4694
+ checkoutBaseDir: resolve16(root, "remote-checkouts"),
4695
+ snapshotBaseDir: resolve16(root, "remote-snapshots")
4696
+ };
4697
+ }
4698
+ function serializeRemoteUserNamespace(namespace) {
4699
+ return {
4700
+ key: namespace.key,
4701
+ userId: namespace.userId,
4702
+ login: namespace.login,
4703
+ root: namespace.root,
4704
+ checkoutBaseDir: namespace.checkoutBaseDir,
4705
+ snapshotBaseDir: namespace.snapshotBaseDir
4706
+ };
4707
+ }
4708
+ function isPathInsideNamespace(namespaceRoot, candidatePath) {
4709
+ const root = resolve16(namespaceRoot);
4710
+ const candidate = resolve16(candidatePath);
4711
+ const rel = relative3(root, candidate);
4712
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
4713
+ }
4714
+ function writeRemoteUserNamespaceMetadata(namespace) {
4715
+ mkdirSync9(namespace.stateDir, { recursive: true });
4716
+ const previous = (() => {
4717
+ if (!existsSync9(namespace.metadataFile))
4718
+ return null;
4719
+ try {
4720
+ const parsed = JSON.parse(readFileSync6(namespace.metadataFile, "utf8"));
4721
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
4722
+ } catch {
4723
+ return null;
4724
+ }
4725
+ })();
4726
+ const now = new Date().toISOString();
4727
+ writeFileSync8(namespace.metadataFile, `${JSON.stringify({
4728
+ key: namespace.key,
4729
+ userId: namespace.userId,
4730
+ login: namespace.login,
4731
+ root: namespace.root,
4732
+ checkoutBaseDir: namespace.checkoutBaseDir,
4733
+ snapshotBaseDir: namespace.snapshotBaseDir,
4734
+ createdAt: typeof previous?.createdAt === "string" ? previous.createdAt : now,
4735
+ updatedAt: now
4736
+ }, null, 2)}
4737
+ `, { encoding: "utf8", mode: 384 });
4738
+ try {
4739
+ chmodSync2(namespace.metadataFile, 384);
4740
+ } catch {}
4741
+ }
4742
+
4743
+ // packages/server/src/server-helpers/github-api-session-index.ts
4744
+ function cleanString4(value) {
4745
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
4746
+ }
4747
+ function resolveGitHubApiSessionIndexFile(projectRoot) {
4748
+ return resolve17(resolveRemoteUserNamespacesRoot(projectRoot), ".api-sessions.json");
4749
+ }
4750
+ function parseEntry(value) {
4751
+ if (!value || typeof value !== "object" || Array.isArray(value))
4752
+ return null;
4753
+ const record = value;
4754
+ const token = cleanString4(record.token);
4755
+ const namespaceKey = cleanString4(record.namespaceKey);
4756
+ const namespaceRoot = cleanString4(record.namespaceRoot);
4757
+ const authStateFile = cleanString4(record.authStateFile);
4758
+ const checkoutBaseDir = cleanString4(record.checkoutBaseDir);
4759
+ const snapshotBaseDir = cleanString4(record.snapshotBaseDir);
4760
+ const createdAt = cleanString4(record.createdAt);
4761
+ if (!token || !namespaceKey || !namespaceRoot || !authStateFile || !checkoutBaseDir || !snapshotBaseDir || !createdAt)
4762
+ return null;
4763
+ return {
4764
+ token,
4765
+ namespaceKey,
4766
+ namespaceRoot,
4767
+ authStateFile,
4768
+ checkoutBaseDir,
4769
+ snapshotBaseDir,
4770
+ createdAt,
4771
+ login: cleanString4(record.login),
4772
+ userId: cleanString4(record.userId),
4773
+ selectedRepo: cleanString4(record.selectedRepo)
4774
+ };
4775
+ }
4776
+ function readIndex(indexFile) {
4777
+ if (!existsSync10(indexFile))
4778
+ return [];
4779
+ try {
4780
+ const parsed = JSON.parse(readFileSync7(indexFile, "utf8"));
4781
+ return Array.isArray(parsed.sessions) ? parsed.sessions.flatMap((entry) => {
4782
+ const parsedEntry = parseEntry(entry);
4783
+ return parsedEntry ? [parsedEntry] : [];
4784
+ }) : [];
4785
+ } catch {
4786
+ return [];
4787
+ }
4788
+ }
4789
+ function writeIndex(indexFile, sessions) {
4790
+ mkdirSync10(dirname12(indexFile), { recursive: true });
4791
+ writeFileSync9(indexFile, `${JSON.stringify({ sessions }, null, 2)}
4792
+ `, { encoding: "utf8", mode: 384 });
4793
+ try {
4794
+ chmodSync3(indexFile, 384);
4795
+ } catch {}
4796
+ }
4797
+ function registerGitHubApiSession(input) {
4798
+ const cleanToken = cleanString4(input.token);
4799
+ if (!cleanToken)
4800
+ throw new Error("GitHub API session token is required");
4801
+ const indexFile = resolveGitHubApiSessionIndexFile(input.projectRoot);
4802
+ const createdAt = new Date().toISOString();
4803
+ const entry = {
4804
+ token: cleanToken,
4805
+ login: input.namespace.login,
4806
+ userId: input.namespace.userId,
4807
+ namespaceKey: input.namespace.key,
4808
+ namespaceRoot: input.namespace.root,
4809
+ authStateFile: input.namespace.authStateFile,
4810
+ checkoutBaseDir: input.namespace.checkoutBaseDir,
4811
+ snapshotBaseDir: input.namespace.snapshotBaseDir,
4812
+ selectedRepo: cleanString4(input.selectedRepo),
4813
+ createdAt
4814
+ };
4815
+ const previous = readIndex(indexFile).filter((session) => session.token !== cleanToken);
4816
+ writeIndex(indexFile, [...previous.slice(-199), entry]);
4817
+ return entry;
4818
+ }
4819
+ function readGitHubApiSession(input) {
4820
+ const cleanToken = cleanString4(input.token);
4821
+ if (!cleanToken)
4822
+ return null;
4823
+ return readIndex(resolveGitHubApiSessionIndexFile(input.projectRoot)).find((entry) => entry.token === cleanToken) ?? null;
4824
+ }
4825
+
4498
4826
  // packages/server/src/server-helpers/inspector-agent-lifecycle.ts
4499
4827
  function createInspectorAgentLifecycleController(options) {
4500
4828
  const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
@@ -4598,21 +4926,21 @@ function inspectorAgentLifecycleSnapshot(input) {
4598
4926
  // packages/server/src/server-helpers/project-registry.ts
4599
4927
  import { createHash as createHash2 } from "crypto";
4600
4928
  import { spawnSync as spawnSync2 } from "child_process";
4601
- import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, readdirSync as readdirSync3, writeFileSync as writeFileSync8 } from "fs";
4602
- import { dirname as dirname10, resolve as resolve16 } from "path";
4929
+ import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync8, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
4930
+ import { dirname as dirname13, resolve as resolve18 } from "path";
4603
4931
  function normalizeRepoSlug(value) {
4604
4932
  const trimmed = value.trim();
4605
4933
  return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
4606
4934
  }
4607
4935
  function registryPath(projectRoot) {
4608
- return resolve16(projectRoot, ".rig", "state", "projects.json");
4936
+ return resolve18(projectRoot, ".rig", "state", "projects.json");
4609
4937
  }
4610
4938
  function readRegistry(projectRoot) {
4611
4939
  const path = registryPath(projectRoot);
4612
- if (!existsSync9(path))
4940
+ if (!existsSync11(path))
4613
4941
  return {};
4614
4942
  try {
4615
- const payload = JSON.parse(readFileSync6(path, "utf8"));
4943
+ const payload = JSON.parse(readFileSync8(path, "utf8"));
4616
4944
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
4617
4945
  return {};
4618
4946
  const projects = payload.projects;
@@ -4623,14 +4951,14 @@ function readRegistry(projectRoot) {
4623
4951
  }
4624
4952
  function writeRegistry(projectRoot, projects) {
4625
4953
  const path = registryPath(projectRoot);
4626
- mkdirSync9(dirname10(path), { recursive: true });
4627
- writeFileSync8(path, `${JSON.stringify({ projects }, null, 2)}
4954
+ mkdirSync11(dirname13(path), { recursive: true });
4955
+ writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
4628
4956
  `, "utf8");
4629
4957
  }
4630
4958
  function resolveConfigPath(projectRoot) {
4631
4959
  for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
4632
- const path = resolve16(projectRoot, name);
4633
- if (existsSync9(path))
4960
+ const path = resolve18(projectRoot, name);
4961
+ if (existsSync11(path))
4634
4962
  return path;
4635
4963
  }
4636
4964
  return null;
@@ -4639,7 +4967,7 @@ function hashFile(path) {
4639
4967
  if (!path)
4640
4968
  return null;
4641
4969
  try {
4642
- return createHash2("sha256").update(readFileSync6(path)).digest("hex");
4970
+ return createHash2("sha256").update(readFileSync8(path)).digest("hex");
4643
4971
  } catch {
4644
4972
  return null;
4645
4973
  }
@@ -4655,11 +4983,11 @@ function readDefaultBranch(projectRoot) {
4655
4983
  return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
4656
4984
  }
4657
4985
  function buildRunSummary(projectRoot) {
4658
- const runsDir = resolve16(projectRoot, ".rig", "runs");
4986
+ const runsDir = resolve18(projectRoot, ".rig", "runs");
4659
4987
  try {
4660
4988
  const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
4661
4989
  try {
4662
- const run = JSON.parse(readFileSync6(resolve16(runsDir, entry.name, "run.json"), "utf8"));
4990
+ const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
4663
4991
  return [{ runId: typeof run.runId === "string" ? run.runId : entry.name, status: typeof run.status === "string" ? run.status : "unknown", updatedAt: typeof run.updatedAt === "string" ? run.updatedAt : "" }];
4664
4992
  } catch {
4665
4993
  return [];
@@ -4711,10 +5039,14 @@ function upsertProjectRecord(projectRoot, input) {
4711
5039
  function linkProjectCheckout(projectRoot, repoSlug, checkout) {
4712
5040
  return upsertProjectRecord(projectRoot, { repoSlug, checkout });
4713
5041
  }
5042
+ function projectRegistryContainsCheckout(projectRoot, checkoutPath) {
5043
+ const target = resolve18(checkoutPath);
5044
+ return Object.values(readRegistry(projectRoot)).some((project) => project.checkouts.some((checkout) => checkout.path ? resolve18(checkout.path) === target : false));
5045
+ }
4714
5046
 
4715
5047
  // packages/server/src/server-helpers/remote-checkout.ts
4716
- import { existsSync as existsSync10, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
4717
- import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve17 } from "path";
5048
+ import { existsSync as existsSync12, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
5049
+ import { dirname as dirname14, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve19 } from "path";
4718
5050
  function safeSlugSegments(repoSlug) {
4719
5051
  const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
4720
5052
  if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
@@ -4731,7 +5063,7 @@ function safeCheckoutKey(value) {
4731
5063
  }
4732
5064
  function repoSlugPath(baseDir, repoSlug, checkoutKey) {
4733
5065
  const key = safeCheckoutKey(checkoutKey);
4734
- return resolve17(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
5066
+ return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
4735
5067
  }
4736
5068
  function sanitizeSnapshotId(value, fallback) {
4737
5069
  const raw = (value ?? fallback).trim();
@@ -4739,7 +5071,7 @@ function sanitizeSnapshotId(value, fallback) {
4739
5071
  return safe || fallback;
4740
5072
  }
4741
5073
  function assertWithinRoot(root, relativePath) {
4742
- if (!relativePath || isAbsolute2(relativePath) || relativePath.includes("\x00")) {
5074
+ if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
4743
5075
  throw new Error(`Invalid snapshot file path: ${relativePath}`);
4744
5076
  }
4745
5077
  const normalizedRelative = relativePath.replace(/\\/g, "/");
@@ -4747,9 +5079,9 @@ function assertWithinRoot(root, relativePath) {
4747
5079
  if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
4748
5080
  throw new Error(`Unsafe snapshot file path: ${relativePath}`);
4749
5081
  }
4750
- const target = resolve17(root, ...segments);
4751
- const rel = relative3(root, target);
4752
- if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute2(rel)) {
5082
+ const target = resolve19(root, ...segments);
5083
+ const rel = relative4(root, target);
5084
+ if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute3(rel)) {
4753
5085
  throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
4754
5086
  }
4755
5087
  return target;
@@ -4767,17 +5099,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
4767
5099
  function extractUploadedSnapshotArchive(input) {
4768
5100
  const archive = decodeSnapshotArchive(input.archive);
4769
5101
  const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
4770
- const checkoutPath = resolve17(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
4771
- mkdirSync10(checkoutPath, { recursive: true });
5102
+ const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
5103
+ mkdirSync12(checkoutPath, { recursive: true });
4772
5104
  for (const file of archive.files) {
4773
5105
  if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
4774
5106
  throw new Error("Invalid snapshot archive file entry");
4775
5107
  }
4776
5108
  const target = assertWithinRoot(checkoutPath, file.path);
4777
- mkdirSync10(dirname11(target), { recursive: true });
4778
- writeFileSync9(target, Buffer.from(file.contentBase64, "base64"));
5109
+ mkdirSync12(dirname14(target), { recursive: true });
5110
+ writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
4779
5111
  }
4780
- writeFileSync9(resolve17(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
5112
+ writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
4781
5113
  repoSlug: input.repoSlug,
4782
5114
  snapshotId,
4783
5115
  fileCount: archive.files.length,
@@ -4812,7 +5144,7 @@ function gitCredentialConfig(token) {
4812
5144
  };
4813
5145
  }
4814
5146
  async function prepareRemoteCheckout(input) {
4815
- const exists = input.exists ?? existsSync10;
5147
+ const exists = input.exists ?? existsSync12;
4816
5148
  const strategy = input.strategy;
4817
5149
  if (strategy.kind === "uploaded-snapshot") {
4818
5150
  return extractUploadedSnapshotArchive({
@@ -4824,7 +5156,7 @@ async function prepareRemoteCheckout(input) {
4824
5156
  });
4825
5157
  }
4826
5158
  if (strategy.kind === "existing-path") {
4827
- const checkoutPath2 = resolve17(strategy.path);
5159
+ const checkoutPath2 = resolve19(strategy.path);
4828
5160
  if (!exists(checkoutPath2)) {
4829
5161
  throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
4830
5162
  }
@@ -4860,9 +5192,9 @@ function buildServerControlStatus() {
4860
5192
  };
4861
5193
  }
4862
5194
  function buildProjectConfigStatus(root) {
4863
- const hasConfigTs = existsSync11(resolve18(root, "rig.config.ts"));
4864
- const hasConfigJson = existsSync11(resolve18(root, "rig.config.json"));
4865
- const hasLegacyTaskConfig = existsSync11(resolve18(root, ".rig", "task-config.json"));
5195
+ const hasConfigTs = existsSync13(resolve20(root, "rig.config.ts"));
5196
+ const hasConfigJson = existsSync13(resolve20(root, "rig.config.json"));
5197
+ const hasLegacyTaskConfig = existsSync13(resolve20(root, ".rig", "task-config.json"));
4866
5198
  let kind = "missing";
4867
5199
  if (hasConfigTs)
4868
5200
  kind = "rig-config-ts";
@@ -4886,10 +5218,10 @@ function normalizeCommit(value) {
4886
5218
  function asPlainRecord(value) {
4887
5219
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4888
5220
  }
4889
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
5221
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
4890
5222
  var RIG_CONFIG_DEV_DEPENDENCIES = {
4891
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
4892
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
5223
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
5224
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
4893
5225
  };
4894
5226
  function repoParts(repoSlug) {
4895
5227
  const [owner, repo] = repoSlug.split("/");
@@ -4898,24 +5230,24 @@ function repoParts(repoSlug) {
4898
5230
  return { owner, repo, slug: `${owner}/${repo}` };
4899
5231
  }
4900
5232
  function repairDir(checkoutPath) {
4901
- const dir = resolve18(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
4902
- mkdirSync11(dir, { recursive: true });
5233
+ const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
5234
+ mkdirSync13(dir, { recursive: true });
4903
5235
  return dir;
4904
5236
  }
4905
5237
  function backupCheckoutFile(checkoutPath, relativePath) {
4906
- const source = resolve18(checkoutPath, relativePath);
4907
- const backupPath = resolve18(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
4908
- mkdirSync11(dirname12(backupPath), { recursive: true });
4909
- copyFileSync(source, backupPath);
5238
+ const source = resolve20(checkoutPath, relativePath);
5239
+ const backupPath = resolve20(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
5240
+ mkdirSync13(dirname15(backupPath), { recursive: true });
5241
+ copyFileSync2(source, backupPath);
4910
5242
  return backupPath;
4911
5243
  }
4912
5244
  function parsePackageJsonLosslessly(checkoutPath) {
4913
- const packagePath = resolve18(checkoutPath, "package.json");
4914
- if (!existsSync11(packagePath)) {
5245
+ const packagePath = resolve20(checkoutPath, "package.json");
5246
+ if (!existsSync13(packagePath)) {
4915
5247
  return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
4916
5248
  }
4917
5249
  try {
4918
- const parsed = JSON.parse(readFileSync7(packagePath, "utf8"));
5250
+ const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
4919
5251
  return {
4920
5252
  existed: true,
4921
5253
  packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
@@ -4929,9 +5261,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
4929
5261
  }
4930
5262
  }
4931
5263
  function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
4932
- const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync11(resolve18(checkoutPath, name)));
4933
- const packagePath = resolve18(checkoutPath, "package.json");
4934
- if (!hasConfig && !existsSync11(packagePath)) {
5264
+ const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
5265
+ const packagePath = resolve20(checkoutPath, "package.json");
5266
+ if (!hasConfig && !existsSync13(packagePath)) {
4935
5267
  return { skipped: true, reason: "package.json and rig.config missing" };
4936
5268
  }
4937
5269
  const parsed = parsePackageJsonLosslessly(checkoutPath);
@@ -4950,7 +5282,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
4950
5282
  }
4951
5283
  const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
4952
5284
  if (changed) {
4953
- writeFileSync10(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
5285
+ writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
4954
5286
  `, "utf8");
4955
5287
  }
4956
5288
  return {
@@ -4976,11 +5308,11 @@ function configLooksStructurallyUsable(source) {
4976
5308
  return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
4977
5309
  }
4978
5310
  function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
4979
- const configPath = resolve18(checkoutPath, "rig.config.ts");
4980
- const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync11(resolve18(checkoutPath, name)));
5311
+ const configPath = resolve20(checkoutPath, "rig.config.ts");
5312
+ const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
4981
5313
  if (existingConfigName) {
4982
- const existingPath = resolve18(checkoutPath, existingConfigName);
4983
- const source = readFileSync7(existingPath, "utf8");
5314
+ const existingPath = resolve20(checkoutPath, existingConfigName);
5315
+ const source = readFileSync9(existingPath, "utf8");
4984
5316
  if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
4985
5317
  return { path: existingPath, changed: false, reason: "config structurally complete" };
4986
5318
  }
@@ -4994,7 +5326,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
4994
5326
  }
4995
5327
  }
4996
5328
  const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
4997
- writeFileSync10(configPath, generatedRigConfigSource(repoSlug), "utf8");
5329
+ writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
4998
5330
  return {
4999
5331
  path: configPath,
5000
5332
  changed: true,
@@ -5003,7 +5335,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
5003
5335
  };
5004
5336
  }
5005
5337
  function validateRemoteCheckoutRigConfig(checkoutPath) {
5006
- const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync11(resolve18(checkoutPath, name)));
5338
+ const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
5007
5339
  if (!configFile)
5008
5340
  return { ok: false, error: "missing rig config" };
5009
5341
  if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
@@ -5025,7 +5357,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
5025
5357
  return { ok: true, configFile };
5026
5358
  }
5027
5359
  function installRemoteCheckoutPackages(checkoutPath) {
5028
- if (!existsSync11(resolve18(checkoutPath, "package.json"))) {
5360
+ if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
5029
5361
  return { skipped: true, reason: "package.json missing" };
5030
5362
  }
5031
5363
  if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
@@ -5038,8 +5370,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
5038
5370
  return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
5039
5371
  }
5040
5372
  function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
5041
- const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync11(resolve18(checkoutPath, name)));
5042
- const hasPackage = existsSync11(resolve18(checkoutPath, "package.json"));
5373
+ const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
5374
+ const hasPackage = existsSync13(resolve20(checkoutPath, "package.json"));
5043
5375
  if (!hasConfig && !hasPackage) {
5044
5376
  return {
5045
5377
  packageJson: { skipped: true, reason: "package.json and rig.config missing" },
@@ -5137,26 +5469,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
5137
5469
  }
5138
5470
  function readGitHeadCommit(projectRoot) {
5139
5471
  try {
5140
- let gitDir = resolve18(projectRoot, ".git");
5472
+ let gitDir = resolve20(projectRoot, ".git");
5141
5473
  try {
5142
- const dotGit = readFileSync7(gitDir, "utf8").trim();
5474
+ const dotGit = readFileSync9(gitDir, "utf8").trim();
5143
5475
  const gitDirPrefix = "gitdir:";
5144
5476
  if (dotGit.startsWith(gitDirPrefix)) {
5145
- gitDir = resolve18(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
5477
+ gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
5146
5478
  }
5147
5479
  } catch {}
5148
- const head = readFileSync7(resolve18(gitDir, "HEAD"), "utf8").trim();
5480
+ const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
5149
5481
  const refPrefix = "ref:";
5150
5482
  if (!head.startsWith(refPrefix)) {
5151
5483
  return normalizeCommit(head);
5152
5484
  }
5153
5485
  const ref = head.slice(refPrefix.length).trim();
5154
- const refPath = resolve18(gitDir, ref);
5155
- if (existsSync11(refPath)) {
5156
- return normalizeCommit(readFileSync7(refPath, "utf8").trim());
5486
+ const refPath = resolve20(gitDir, ref);
5487
+ if (existsSync13(refPath)) {
5488
+ return normalizeCommit(readFileSync9(refPath, "utf8").trim());
5157
5489
  }
5158
- const commonDir = normalizeString(readFileSync7(resolve18(gitDir, "commondir"), "utf8"));
5159
- return commonDir ? normalizeCommit(readFileSync7(resolve18(gitDir, commonDir, ref), "utf8").trim()) : null;
5490
+ const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
5491
+ return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
5160
5492
  } catch {
5161
5493
  return null;
5162
5494
  }
@@ -5194,9 +5526,9 @@ function configuredRepoFromTaskSource(taskSource) {
5194
5526
  const repo = normalizeString(taskSource?.repo);
5195
5527
  return owner && repo ? `${owner}/${repo}` : null;
5196
5528
  }
5197
- async function buildTaskSourceStatus(state, config) {
5529
+ async function buildTaskSourceStatus(state, config, requestAuth) {
5198
5530
  const diagnostics = state.snapshotService.getTaskSourceErrors();
5199
- const selectedRepo = createGitHubAuthStore(state.projectRoot).status({
5531
+ const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
5200
5532
  oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
5201
5533
  }).selectedRepo;
5202
5534
  try {
@@ -5243,36 +5575,134 @@ function bearerTokenFromRequest(req) {
5243
5575
  function isLoopbackRequest(req) {
5244
5576
  try {
5245
5577
  const hostname = new URL(req.url).hostname.toLowerCase();
5246
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5578
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5247
5579
  } catch {
5248
5580
  return false;
5249
5581
  }
5250
5582
  }
5251
5583
  function isPublicRigAuthBootstrapRoute(pathname) {
5252
- return pathname === "/" || pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/github/auth/status" || pathname === "/api/github/auth/token" || pathname === "/api/github/auth/device/start" || pathname === "/api/github/auth/device/poll";
5584
+ return pathname === "/" || pathname === "/install" || pathname === "/health" || pathname === "/api/health" || pathname === "/api/github/auth/status" || pathname === "/api/github/auth/token" || pathname === "/api/github/auth/device/start" || pathname === "/api/github/auth/device/poll";
5585
+ }
5586
+ function buildRigInstallScript() {
5587
+ return `#!/usr/bin/env bash
5588
+ set -euo pipefail
5589
+
5590
+ say() {
5591
+ printf 'rig-install: %s
5592
+ ' "$*"
5593
+ }
5594
+
5595
+ if ! command -v bun >/dev/null 2>&1; then
5596
+ say "Bun not found; installing Bun first"
5597
+ curl -fsSL https://bun.sh/install | bash
5598
+ export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
5599
+ export PATH="$BUN_INSTALL/bin:$PATH"
5600
+ fi
5601
+
5602
+ if ! command -v bun >/dev/null 2>&1; then
5603
+ printf 'rig-install: bun install completed, but bun is still not on PATH. Add ~/.bun/bin to PATH and retry.
5604
+ ' >&2
5605
+ exit 1
5606
+ fi
5607
+
5608
+ say "Installing @h-rig/cli@latest"
5609
+ bun add -g @h-rig/cli@latest
5610
+
5611
+ export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
5612
+ export PATH="$BUN_INSTALL/bin:$PATH"
5613
+
5614
+ if ! command -v rig >/dev/null 2>&1; then
5615
+ printf 'rig-install: rig installed, but rig is not on PATH. Add %s/bin to PATH and retry.
5616
+ ' "$BUN_INSTALL" >&2
5617
+ exit 1
5618
+ fi
5619
+
5620
+ say "Verifying rig"
5621
+ rig --help >/dev/null
5622
+ say "Done. Run: rig --help"
5623
+ `;
5253
5624
  }
5254
5625
  function normalizePrMode(value) {
5255
5626
  const mode = normalizeString(value);
5256
5627
  return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
5257
5628
  }
5629
+ function requestAuthResult(input) {
5630
+ return {
5631
+ authorized: input.authorized,
5632
+ actor: input.actor ?? null,
5633
+ reason: input.reason,
5634
+ login: input.login ?? null,
5635
+ userId: input.userId ?? null,
5636
+ userNamespace: input.userNamespace ?? null,
5637
+ authStateFile: input.authStateFile ?? null
5638
+ };
5639
+ }
5640
+ function namespaceFromSessionIndex(entry) {
5641
+ const stateDir2 = dirname15(entry.authStateFile);
5642
+ return {
5643
+ key: entry.namespaceKey,
5644
+ userId: entry.userId,
5645
+ login: entry.login,
5646
+ root: entry.namespaceRoot,
5647
+ stateDir: stateDir2,
5648
+ authStateFile: entry.authStateFile,
5649
+ metadataFile: resolve20(stateDir2, "user-namespace.json"),
5650
+ checkoutBaseDir: entry.checkoutBaseDir,
5651
+ snapshotBaseDir: entry.snapshotBaseDir
5652
+ };
5653
+ }
5258
5654
  function authorizeRigHttpRequest(input) {
5259
5655
  if (input.legacyAuthorized) {
5260
- return { authorized: true, actor: "rig-local-server", reason: "server-token" };
5656
+ return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
5261
5657
  }
5262
5658
  const bearer = bearerTokenFromRequest(input.req);
5263
5659
  const store = createGitHubAuthStore(input.projectRoot);
5264
5660
  const storedToken = store.readToken();
5661
+ const session = bearer ? store.readApiSession(bearer) : null;
5662
+ if (session) {
5663
+ return requestAuthResult({
5664
+ authorized: true,
5665
+ actor: session.login ?? "github-operator",
5666
+ reason: "github-session",
5667
+ login: session.login,
5668
+ userId: session.userId,
5669
+ authStateFile: store.stateFile
5670
+ });
5671
+ }
5265
5672
  if (bearer && storedToken && bearer === storedToken) {
5266
5673
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
5267
- return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
5674
+ return requestAuthResult({
5675
+ authorized: true,
5676
+ actor: status.login ?? "github-operator",
5677
+ reason: "github-token",
5678
+ login: status.login,
5679
+ userId: status.userId,
5680
+ authStateFile: store.stateFile
5681
+ });
5682
+ }
5683
+ const indexedSession = readGitHubApiSession({ projectRoot: input.projectRoot, token: bearer });
5684
+ if (indexedSession) {
5685
+ const userNamespace = namespaceFromSessionIndex(indexedSession);
5686
+ return requestAuthResult({
5687
+ authorized: true,
5688
+ actor: indexedSession.login ?? "github-operator",
5689
+ reason: "github-user-session",
5690
+ login: indexedSession.login,
5691
+ userId: indexedSession.userId,
5692
+ userNamespace,
5693
+ authStateFile: indexedSession.authStateFile
5694
+ });
5268
5695
  }
5269
5696
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
5270
- return { authorized: true, actor: null, reason: "public-bootstrap" };
5697
+ return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
5271
5698
  }
5272
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
5273
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5699
+ if (!input.serverAuthToken && !storedToken) {
5700
+ if (isLoopbackRequest(input.req)) {
5701
+ return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
5702
+ }
5703
+ return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
5274
5704
  }
5275
- return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
5705
+ return requestAuthResult({ authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" });
5276
5706
  }
5277
5707
  async function fetchGitHubUserInfo(token) {
5278
5708
  const response = await fetch("https://api.github.com/user", {
@@ -5292,6 +5722,67 @@ async function fetchGitHubUserInfo(token) {
5292
5722
  scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
5293
5723
  };
5294
5724
  }
5725
+ function shouldWriteRootAuthCompat(projectRoot) {
5726
+ if (process.env.RIG_REMOTE_USER_NAMESPACE_ROOT?.trim())
5727
+ return false;
5728
+ const stateDir2 = normalizeString(process.env.RIG_STATE_DIR);
5729
+ if (!stateDir2)
5730
+ return true;
5731
+ return resolve20(stateDir2) === resolve20(projectRoot, ".rig", "state");
5732
+ }
5733
+ function requestScopedRegistryRoot(stateProjectRoot, requestAuth) {
5734
+ return requestAuth.userNamespace?.root ?? stateProjectRoot;
5735
+ }
5736
+ function requestScopedAuthStore(stateProjectRoot, requestAuth) {
5737
+ return requestAuth.authStateFile ? createGitHubAuthStoreFromStateFile(requestAuth.authStateFile) : createGitHubAuthStore(stateProjectRoot);
5738
+ }
5739
+ function userNamespaceResponse(namespace) {
5740
+ return namespace ? serializeRemoteUserNamespace(namespace) : undefined;
5741
+ }
5742
+ function resolveNamespacedBaseDir(input) {
5743
+ if (input.explicitBaseDir)
5744
+ return input.explicitBaseDir;
5745
+ const envBase = normalizeString(process.env[input.envName]);
5746
+ if (input.userNamespace) {
5747
+ return envBase ? resolve20(envBase, input.userNamespace.key) : input.userNamespace[input.legacySubdir === "remote-checkouts" ? "checkoutBaseDir" : "snapshotBaseDir"];
5748
+ }
5749
+ return envBase ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve20(normalizeString(process.env.RIG_STATE_DIR), input.legacySubdir) : resolve20(input.legacyProjectRoot, ".rig", input.legacySubdir));
5750
+ }
5751
+ function explicitCheckoutKey(body, checkoutInput, requestAuth) {
5752
+ return normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? (requestAuth.userNamespace ? undefined : "default");
5753
+ }
5754
+ function saveGitHubTokenForRemoteUser(input) {
5755
+ const namespace = resolveRemoteUserNamespace(input.projectRoot, { userId: input.user.userId, login: input.user.login });
5756
+ writeRemoteUserNamespaceMetadata(namespace);
5757
+ const store = createGitHubAuthStoreFromStateFile(namespace.authStateFile);
5758
+ store.saveToken({
5759
+ token: input.token,
5760
+ tokenSource: input.tokenSource,
5761
+ login: input.user.login,
5762
+ userId: input.user.userId,
5763
+ scopes: input.user.scopes,
5764
+ selectedRepo: input.selectedRepo
5765
+ });
5766
+ const apiSession = store.createApiSession();
5767
+ registerGitHubApiSession({ projectRoot: input.projectRoot, token: apiSession.token, namespace, selectedRepo: input.selectedRepo });
5768
+ const requestedRoot = normalizeString(input.requestedProjectRoot);
5769
+ if (requestedRoot && isAbsolute4(requestedRoot) && existsSync13(resolve20(requestedRoot))) {
5770
+ copyGitHubAuthStateToLocalProjectRoot(namespace.authStateFile, resolve20(requestedRoot));
5771
+ }
5772
+ if (shouldWriteRootAuthCompat(input.projectRoot)) {
5773
+ const rootStore = createGitHubAuthStore(input.projectRoot);
5774
+ rootStore.saveToken({
5775
+ token: input.token,
5776
+ tokenSource: input.tokenSource,
5777
+ login: input.user.login,
5778
+ userId: input.user.userId,
5779
+ scopes: input.user.scopes,
5780
+ selectedRepo: input.selectedRepo
5781
+ });
5782
+ rootStore.createApiSession();
5783
+ }
5784
+ return { store, namespace, apiSessionToken: apiSession.token };
5785
+ }
5295
5786
  async function postGitHubForm(endpoint, body) {
5296
5787
  const response = await fetch(endpoint, {
5297
5788
  method: "POST",
@@ -5309,11 +5800,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
5309
5800
  const requestedRoot = normalizeString(rawRoot);
5310
5801
  if (!requestedRoot)
5311
5802
  return currentRoot;
5312
- if (!isAbsolute3(requestedRoot)) {
5803
+ if (!isAbsolute4(requestedRoot)) {
5313
5804
  throw new Error("projectRoot must be an absolute path on the Rig server host");
5314
5805
  }
5315
- const normalizedRoot = resolve18(requestedRoot);
5316
- if (!existsSync11(normalizedRoot)) {
5806
+ const normalizedRoot = resolve20(requestedRoot);
5807
+ if (!existsSync13(normalizedRoot)) {
5317
5808
  throw new Error("projectRoot does not exist on the Rig server host");
5318
5809
  }
5319
5810
  return normalizedRoot;
@@ -5501,7 +5992,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
5501
5992
  if (runnable.length === 0)
5502
5993
  return null;
5503
5994
  const queue = readQueueState(projectRoot);
5504
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
5995
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
5505
5996
  return runnable.toSorted((left, right) => {
5506
5997
  const leftId = taskIdOf(left) ?? "";
5507
5998
  const rightId = taskIdOf(right) ?? "";
@@ -5541,6 +6032,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
5541
6032
  }
5542
6033
  return filtered;
5543
6034
  }
6035
+ function issueAnalysisTargetFor(source) {
6036
+ if (!source)
6037
+ return null;
6038
+ const candidate = source;
6039
+ if (typeof candidate.updateTask !== "function")
6040
+ return null;
6041
+ return {
6042
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
6043
+ updateTask: candidate.updateTask.bind(candidate),
6044
+ ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
6045
+ ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
6046
+ ...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
6047
+ };
6048
+ }
6049
+ function uniqueStringList(value) {
6050
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
6051
+ return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
6052
+ }
6053
+ function taskRecordId(task) {
6054
+ return String(task.id ?? "");
6055
+ }
5544
6056
  function redactRemoteEndpoint(endpoint) {
5545
6057
  const { token, ...rest } = endpoint;
5546
6058
  return {
@@ -5625,9 +6137,16 @@ function createRigServerFetch(state, deps) {
5625
6137
  notifications: state.targets.length
5626
6138
  });
5627
6139
  }
6140
+ if (url.pathname === "/install" && req.method === "GET") {
6141
+ return new Response(buildRigInstallScript(), {
6142
+ headers: {
6143
+ "Content-Type": "text/x-shellscript; charset=utf-8"
6144
+ }
6145
+ });
6146
+ }
5628
6147
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
5629
6148
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
5630
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
6149
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
5631
6150
  const requestAuth = authorizeRigHttpRequest({
5632
6151
  req,
5633
6152
  pathname: url.pathname,
@@ -5883,9 +6402,70 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5883
6402
  note: "GitHub issue lifecycle labels are created on demand by the configured task source when supported."
5884
6403
  });
5885
6404
  }
6405
+ if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
6406
+ const body = await deps.readJsonBody(req);
6407
+ const ids = uniqueStringList(body.ids ?? body.id);
6408
+ const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
6409
+ if (ids.length === 0 && !analyzeAll) {
6410
+ return deps.badRequest("ids is required unless all=true");
6411
+ }
6412
+ const ctx = await getCachedPluginHostContext(state.projectRoot);
6413
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
6414
+ const target = issueAnalysisTargetFor(source);
6415
+ if (!source || !target) {
6416
+ return deps.badRequest("Configured task source does not support issue-analysis writeback");
6417
+ }
6418
+ const allTasks = [...await source.list()];
6419
+ const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
6420
+ const cached = allTasks.find((task) => taskRecordId(task) === id);
6421
+ if (cached)
6422
+ return cached;
6423
+ return typeof source.get === "function" ? await source.get(id) : undefined;
6424
+ }))).filter((task) => Boolean(task));
6425
+ if (issues.length === 0) {
6426
+ return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
6427
+ }
6428
+ const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
6429
+ const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
6430
+ const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
6431
+ const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
6432
+ const service = createIssueAnalysisService({
6433
+ analyzer: createPiIssueAnalyzer({
6434
+ ...model ? { model } : {},
6435
+ env: { RIG_PROJECT_ROOT: state.projectRoot }
6436
+ }),
6437
+ writeBack: createIssueAnalysisWriteBack({ target })
6438
+ });
6439
+ const reason = normalizeString(body.reason) ?? "http-issue-analysis";
6440
+ let results;
6441
+ try {
6442
+ results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
6443
+ } catch (error) {
6444
+ return deps.jsonResponse({
6445
+ ok: false,
6446
+ error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
6447
+ reason,
6448
+ ids: issues.map((issue) => issue.id)
6449
+ }, 502);
6450
+ }
6451
+ deps.snapshotService.invalidate("issue-analysis-http-run");
6452
+ await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
6453
+ return;
6454
+ });
6455
+ deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
6456
+ return deps.jsonResponse({
6457
+ ok: true,
6458
+ reason,
6459
+ analyzed: results.map((entry) => ({
6460
+ id: entry.issue.id,
6461
+ title: entry.issue.title ?? null,
6462
+ result: entry.result
6463
+ }))
6464
+ });
6465
+ }
5886
6466
  if (url.pathname === "/api/server/status") {
5887
6467
  const config = buildProjectConfigStatus(state.projectRoot);
5888
- const taskSource = await buildTaskSourceStatus(state, config);
6468
+ const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
5889
6469
  return deps.jsonResponse({
5890
6470
  ok: true,
5891
6471
  projectRoot: state.projectRoot,
@@ -5909,8 +6489,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5909
6489
  path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
5910
6490
  ref: normalizeString(rawCheckout?.ref) ?? undefined
5911
6491
  } : undefined;
5912
- const record = upsertProjectRecord(state.projectRoot, { repoSlug, checkout });
5913
- return deps.jsonResponse({ ok: true, project: record });
6492
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
6493
+ const record = upsertProjectRecord(registryRoot, { repoSlug, checkout });
6494
+ return deps.jsonResponse({ ok: true, project: record, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
5914
6495
  }
5915
6496
  const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
5916
6497
  if (snapshotUploadMatch && req.method === "POST") {
@@ -5923,8 +6504,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5923
6504
  if (!archiveContentBase64) {
5924
6505
  return deps.badRequest("archiveContentBase64 is required");
5925
6506
  }
5926
- const baseDir = normalizeString(body.baseDir) ?? normalizeString(process.env.RIG_REMOTE_SNAPSHOT_BASE_DIR) ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve18(normalizeString(process.env.RIG_STATE_DIR), "remote-snapshots") : resolve18(state.projectRoot, ".rig", "remote-snapshots"));
5927
- const checkoutKey = normalizeString(body.checkoutKey) ?? "default";
6507
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
6508
+ const baseDir = resolveNamespacedBaseDir({
6509
+ explicitBaseDir: normalizeString(body.baseDir),
6510
+ envName: "RIG_REMOTE_SNAPSHOT_BASE_DIR",
6511
+ userNamespace: requestAuth.userNamespace,
6512
+ legacyProjectRoot: state.projectRoot,
6513
+ legacySubdir: "remote-snapshots"
6514
+ });
6515
+ const checkoutKey = explicitCheckoutKey(body, body, requestAuth);
5928
6516
  try {
5929
6517
  const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
5930
6518
  const checkout = extractUploadedSnapshotArchive({
@@ -5937,14 +6525,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5937
6525
  const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
5938
6526
  const packageInstall = installRemoteCheckoutPackages(checkout.path);
5939
6527
  const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
5940
- const project = linkProjectCheckout(state.projectRoot, repoSlug, {
6528
+ const project = linkProjectCheckout(registryRoot, repoSlug, {
5941
6529
  kind: "uploaded-snapshot",
5942
6530
  path: checkout.path,
5943
6531
  ref: checkout.snapshotId
5944
6532
  });
5945
6533
  deps.snapshotService.invalidate("uploaded-snapshot-checkout");
5946
6534
  deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
5947
- return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
6535
+ return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
5948
6536
  } catch (error) {
5949
6537
  return deps.jsonResponse({
5950
6538
  ok: false,
@@ -5964,10 +6552,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5964
6552
  if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
5965
6553
  return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
5966
6554
  }
5967
- const baseDir = normalizeString(body.baseDir) ?? normalizeString(checkoutInput.baseDir) ?? normalizeString(process.env.RIG_REMOTE_CHECKOUT_BASE_DIR) ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve18(normalizeString(process.env.RIG_STATE_DIR), "remote-checkouts") : resolve18(state.projectRoot, ".rig", "remote-checkouts"));
5968
- const checkoutKey = normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? "default";
6555
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
6556
+ const baseDir = resolveNamespacedBaseDir({
6557
+ explicitBaseDir: normalizeString(body.baseDir) ?? normalizeString(checkoutInput.baseDir),
6558
+ envName: "RIG_REMOTE_CHECKOUT_BASE_DIR",
6559
+ userNamespace: requestAuth.userNamespace,
6560
+ legacyProjectRoot: state.projectRoot,
6561
+ legacySubdir: "remote-checkouts"
6562
+ });
6563
+ const checkoutKey = explicitCheckoutKey(body, checkoutInput, requestAuth);
5969
6564
  const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
5970
- const credentialToken = createGitHubAuthStore(state.projectRoot).readToken();
6565
+ const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
5971
6566
  try {
5972
6567
  const checkout = await prepareRemoteCheckout({
5973
6568
  command: runRemoteCheckoutCommand,
@@ -5976,14 +6571,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5976
6571
  const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
5977
6572
  const packageInstall = installRemoteCheckoutPackages(checkout.path);
5978
6573
  const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
5979
- const project = linkProjectCheckout(state.projectRoot, repoSlug, {
6574
+ const project = linkProjectCheckout(registryRoot, repoSlug, {
5980
6575
  kind: checkout.kind,
5981
6576
  path: checkout.path,
5982
6577
  ref: checkout.ref ?? checkout.snapshotId ?? undefined
5983
6578
  });
5984
6579
  deps.snapshotService.invalidate("remote-checkout-prepared");
5985
6580
  deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
5986
- return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
6581
+ return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
5987
6582
  } catch (error) {
5988
6583
  return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
5989
6584
  }
@@ -6000,16 +6595,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6000
6595
  if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
6001
6596
  return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
6002
6597
  }
6003
- const project = linkProjectCheckout(state.projectRoot, repoSlug, {
6598
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
6599
+ const project = linkProjectCheckout(registryRoot, repoSlug, {
6004
6600
  kind,
6005
6601
  path: normalizeString(body.path) ?? state.projectRoot,
6006
6602
  ref: normalizeString(body.ref) ?? undefined
6007
6603
  });
6008
- return deps.jsonResponse({ ok: true, project });
6604
+ return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6009
6605
  }
6010
6606
  if (req.method === "GET") {
6011
- const project = getProjectRecord(state.projectRoot, repoSlug);
6012
- return project ? deps.jsonResponse({ ok: true, project }) : deps.notFound();
6607
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
6608
+ const project = getProjectRecord(registryRoot, repoSlug);
6609
+ return project ? deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) }) : deps.notFound();
6013
6610
  }
6014
6611
  }
6015
6612
  if (url.pathname === "/api/server/project-root" && req.method === "POST") {
@@ -6018,11 +6615,27 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6018
6615
  if (!requestedRoot) {
6019
6616
  return deps.badRequest("projectRoot is required");
6020
6617
  }
6021
- if (!isAbsolute3(requestedRoot)) {
6618
+ if (!isAbsolute4(requestedRoot)) {
6022
6619
  return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
6023
6620
  }
6024
- const normalizedRoot = resolve18(requestedRoot);
6025
- const exists = existsSync11(normalizedRoot);
6621
+ const normalizedRoot = resolve20(requestedRoot);
6622
+ const exists = existsSync13(normalizedRoot);
6623
+ if (exists && requestAuth.userNamespace) {
6624
+ const allowedByNamespace = isPathInsideNamespace(requestAuth.userNamespace.root, normalizedRoot);
6625
+ const allowedByRegistry = projectRegistryContainsCheckout(requestAuth.userNamespace.root, normalizedRoot);
6626
+ if (!allowedByNamespace && !allowedByRegistry) {
6627
+ return deps.jsonResponse({
6628
+ ok: false,
6629
+ error: "Requested project root is outside the authenticated GitHub user namespace.",
6630
+ projectRoot: state.projectRoot,
6631
+ requestedProjectRoot: normalizedRoot,
6632
+ userNamespace: userNamespaceResponse(requestAuth.userNamespace)
6633
+ }, 403);
6634
+ }
6635
+ copyGitHubAuthStateToLocalProjectRoot(requestAuth.userNamespace.authStateFile, normalizedRoot);
6636
+ } else if (exists) {
6637
+ createGitHubAuthStore(state.projectRoot).copyToLocalProjectRoot(normalizedRoot);
6638
+ }
6026
6639
  const control = buildServerControlStatus();
6027
6640
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
6028
6641
  if (!exists) {
@@ -6036,7 +6649,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6036
6649
  message: "Requested project root does not exist on the Rig server host."
6037
6650
  }, 404);
6038
6651
  }
6039
- if (!existsSync11(resolve18(normalizedRoot, "rig.config.ts")) && !existsSync11(resolve18(normalizedRoot, "rig.config.json"))) {
6652
+ if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
6040
6653
  return deps.jsonResponse({
6041
6654
  ok: false,
6042
6655
  projectRoot: state.projectRoot,
@@ -6071,6 +6684,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6071
6684
  exists,
6072
6685
  control,
6073
6686
  requiresRestart: false,
6687
+ userNamespace: userNamespaceResponse(requestAuth.userNamespace),
6074
6688
  message: "Project-root switch accepted. Rig server restart has been scheduled."
6075
6689
  }, 202);
6076
6690
  }
@@ -6085,11 +6699,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6085
6699
  }, 409);
6086
6700
  }
6087
6701
  if (url.pathname === "/api/github/auth/status") {
6088
- const store = createGitHubAuthStore(state.projectRoot);
6089
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
6702
+ const store = requestScopedAuthStore(state.projectRoot, requestAuth);
6703
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6090
6704
  }
6091
6705
  if (url.pathname === "/api/github/repo/permissions") {
6092
- const store = createGitHubAuthStore(state.projectRoot);
6706
+ const store = requestScopedAuthStore(state.projectRoot, requestAuth);
6093
6707
  const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
6094
6708
  if (!auth.signedIn) {
6095
6709
  return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
@@ -6111,21 +6725,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6111
6725
  const body = await deps.readJsonBody(req);
6112
6726
  const token = normalizeString(body.token);
6113
6727
  const selectedRepo = normalizeString(body.selectedRepo);
6728
+ const requestedProjectRoot = normalizeString(body.projectRoot);
6114
6729
  if (!token) {
6115
6730
  return deps.badRequest("token is required");
6116
6731
  }
6117
6732
  try {
6118
6733
  const user = await fetchGitHubUserInfo(token);
6119
- const store = createGitHubAuthStore(state.projectRoot);
6120
- store.saveToken({
6734
+ const saved = saveGitHubTokenForRemoteUser({
6735
+ projectRoot: state.projectRoot,
6121
6736
  token,
6122
6737
  tokenSource: "manual-token",
6123
- login: user.login,
6124
- userId: user.userId,
6125
- scopes: user.scopes,
6126
- selectedRepo
6738
+ user,
6739
+ selectedRepo,
6740
+ requestedProjectRoot
6741
+ });
6742
+ return deps.jsonResponse({
6743
+ ok: true,
6744
+ ...saved.store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }),
6745
+ apiSessionToken: saved.apiSessionToken,
6746
+ userNamespace: userNamespaceResponse(saved.namespace)
6127
6747
  });
6128
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
6129
6748
  } catch (error) {
6130
6749
  const message = error instanceof Error ? error.message : String(error);
6131
6750
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -6192,8 +6811,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6192
6811
  }
6193
6812
  const token = result.payload.access_token;
6194
6813
  const user = await fetchGitHubUserInfo(token);
6195
- store.saveToken({ token, tokenSource: "oauth-device", login: user.login, userId: user.userId, scopes: user.scopes });
6196
- return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }) });
6814
+ const saved = saveGitHubTokenForRemoteUser({
6815
+ projectRoot: state.projectRoot,
6816
+ token,
6817
+ tokenSource: "oauth-device",
6818
+ user,
6819
+ selectedRepo: null
6820
+ });
6821
+ store.clearPendingDevice(pollId);
6822
+ return deps.jsonResponse({
6823
+ ok: true,
6824
+ status: "signed-in",
6825
+ ...saved.store.status({ oauthConfigured: true }),
6826
+ apiSessionToken: saved.apiSessionToken,
6827
+ userNamespace: userNamespaceResponse(saved.namespace)
6828
+ });
6197
6829
  }
6198
6830
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
6199
6831
  const body = await deps.readJsonBody(req);
@@ -6202,7 +6834,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6202
6834
  if (!owner || !repo) {
6203
6835
  return deps.badRequest("owner and repo are required");
6204
6836
  }
6205
- const store = createGitHubAuthStore(state.projectRoot);
6837
+ const store = requestScopedAuthStore(state.projectRoot, requestAuth);
6206
6838
  const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
6207
6839
  const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
6208
6840
  return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
@@ -6223,7 +6855,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6223
6855
  return deps.badRequest(error instanceof Error ? error.message : String(error));
6224
6856
  }
6225
6857
  const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
6226
- const configPath = resolve18(targetRoot, "rig.config.ts");
6858
+ const configPath = resolve20(targetRoot, "rig.config.ts");
6227
6859
  const source = buildGitHubProjectConfigSource({
6228
6860
  projectName: rawProjectName,
6229
6861
  owner,
@@ -6235,8 +6867,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6235
6867
  ok: true,
6236
6868
  projectRoot: targetRoot,
6237
6869
  configPath,
6238
- exists: existsSync11(configPath),
6239
- requiresOverwrite: existsSync11(configPath),
6870
+ exists: existsSync13(configPath),
6871
+ requiresOverwrite: existsSync13(configPath),
6240
6872
  source,
6241
6873
  owner,
6242
6874
  repo,
@@ -6272,8 +6904,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6272
6904
  assignee,
6273
6905
  githubUserId: authStatus.userId ?? authStatus.login
6274
6906
  });
6275
- const configPath = resolve18(targetRoot, "rig.config.ts");
6276
- if (existsSync11(configPath) && !overwrite) {
6907
+ const configPath = resolve20(targetRoot, "rig.config.ts");
6908
+ if (existsSync13(configPath) && !overwrite) {
6277
6909
  return deps.jsonResponse({
6278
6910
  ok: false,
6279
6911
  error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
@@ -6289,11 +6921,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6289
6921
  return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
6290
6922
  }
6291
6923
  let backupPath = null;
6292
- if (existsSync11(configPath)) {
6924
+ if (existsSync13(configPath)) {
6293
6925
  backupPath = backupConfigPath(configPath);
6294
- copyFileSync(configPath, backupPath);
6926
+ copyFileSync2(configPath, backupPath);
6295
6927
  }
6296
- writeFileSync10(configPath, source, "utf8");
6928
+ writeFileSync12(configPath, source, "utf8");
6297
6929
  const selectedRepo = `${owner}/${repo}`;
6298
6930
  store.saveSelectedRepo(selectedRepo);
6299
6931
  const targetStore = createGitHubAuthStore(targetRoot);
@@ -6565,11 +7197,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6565
7197
  const runId = normalizeString(body.runId);
6566
7198
  const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
6567
7199
  const promptOverride = normalizeString(body.promptOverride);
7200
+ const restart = body.restart === true;
6568
7201
  if (!runId) {
6569
7202
  return deps.badRequest("runId is required");
6570
7203
  }
6571
7204
  try {
6572
- await deps.resumeRunRecord(state, { runId, createdAt, promptOverride });
7205
+ await deps.resumeRunRecord(state, { runId, createdAt, promptOverride, restart });
6573
7206
  deps.broadcastSnapshotInvalidation(state);
6574
7207
  return deps.jsonResponse({ ok: true, runId, createdAt });
6575
7208
  } catch (error) {
@@ -6578,7 +7211,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6578
7211
  }
6579
7212
  if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
6580
7213
  const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
6581
- const packageSource = configuredPackageSource ?? [process.env.RIG_HOST_PROJECT_ROOT, process.cwd(), state.projectRoot].map((root) => normalizeString(root)).filter((root) => Boolean(root)).map((root) => resolve18(root, "packages", "pi-rig")).find((candidate) => existsSync11(resolve18(candidate, "package.json"))) ?? "npm:@rig/pi-rig";
7214
+ const packageSource = configuredPackageSource ?? [process.env.RIG_HOST_PROJECT_ROOT, process.cwd(), state.projectRoot].map((root) => normalizeString(root)).filter((root) => Boolean(root)).map((root) => resolve20(root, "packages", "pi-rig")).find((candidate) => existsSync13(resolve20(candidate, "package.json"))) ?? "npm:@rig/pi-rig";
6582
7215
  if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
6583
7216
  return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
6584
7217
  }
@@ -6894,9 +7527,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6894
7527
  } catch {
6895
7528
  return deps.badRequest("Invalid artifact path");
6896
7529
  }
6897
- mkdirSync11(dirname12(artifactPath), { recursive: true });
7530
+ mkdirSync13(dirname15(artifactPath), { recursive: true });
6898
7531
  const bytes = Buffer.from(contentBase64, "base64");
6899
- writeFileSync10(artifactPath, bytes);
7532
+ writeFileSync12(artifactPath, bytes);
6900
7533
  writeJsonFile4(`${artifactPath}.json`, {
6901
7534
  workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
6902
7535
  runId,
@@ -7058,12 +7691,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
7058
7691
  try {
7059
7692
  const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
7060
7693
  const runRoot = deps.normalizeRelativePath(runsRoot, runId);
7061
- const artifactsRoot = resolve18(runRoot, "remote-artifacts");
7694
+ const artifactsRoot = resolve20(runRoot, "remote-artifacts");
7062
7695
  artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
7063
7696
  } catch {
7064
7697
  return deps.badRequest("Invalid artifact path");
7065
7698
  }
7066
- if (!existsSync11(artifactPath)) {
7699
+ if (!existsSync13(artifactPath)) {
7067
7700
  return deps.notFound();
7068
7701
  }
7069
7702
  return new Response(Bun.file(artifactPath));
@@ -7956,8 +8589,8 @@ async function routeWebSocketRequest(state, deps, request) {
7956
8589
  }
7957
8590
 
7958
8591
  // packages/server/src/server-helpers/inspector-jobs.ts
7959
- import { existsSync as existsSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync10, writeFileSync as writeFileSync13 } from "fs";
7960
- import { dirname as dirname15, resolve as resolve21 } from "path";
8592
+ import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync15 } from "fs";
8593
+ import { dirname as dirname18, resolve as resolve23 } from "path";
7961
8594
  import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
7962
8595
  import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
7963
8596
  import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
@@ -8065,8 +8698,8 @@ import { randomUUID as randomUUID3 } from "crypto";
8065
8698
 
8066
8699
  // packages/server/src/inspector/mission.ts
8067
8700
  import { randomUUID as randomUUID2 } from "crypto";
8068
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync8, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync11 } from "fs";
8069
- import { dirname as dirname13, join, resolve as resolve19 } from "path";
8701
+ import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync13 } from "fs";
8702
+ import { dirname as dirname16, join, resolve as resolve21 } from "path";
8070
8703
  function isJsonValue(value) {
8071
8704
  if (value === null)
8072
8705
  return true;
@@ -8106,7 +8739,7 @@ function isRecord2(value) {
8106
8739
  }
8107
8740
  function readJsonRecord(path) {
8108
8741
  try {
8109
- const parsed = JSON.parse(readFileSync8(path, "utf8"));
8742
+ const parsed = JSON.parse(readFileSync10(path, "utf8"));
8110
8743
  if (!isRecord2(parsed)) {
8111
8744
  return { ok: false, error: `Mission file ${path} does not contain an object` };
8112
8745
  }
@@ -8186,14 +8819,14 @@ function missionActionDetails(mission) {
8186
8819
  };
8187
8820
  }
8188
8821
  function writeJsonFile5(path, value) {
8189
- mkdirSync12(dirname13(path), { recursive: true });
8822
+ mkdirSync14(dirname16(path), { recursive: true });
8190
8823
  const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
8191
- writeFileSync11(tempPath, `${JSON.stringify(value, null, 2)}
8824
+ writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
8192
8825
  `, "utf8");
8193
8826
  renameSync(tempPath, path);
8194
8827
  }
8195
8828
  function resolveInspectorMissionPaths(projectRoot) {
8196
- const inspectorDir = resolve19(resolveRigServerPaths(projectRoot).stateDir, "inspector");
8829
+ const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
8197
8830
  return {
8198
8831
  inspectorDir,
8199
8832
  missionsDir: join(inspectorDir, "missions"),
@@ -8202,8 +8835,8 @@ function resolveInspectorMissionPaths(projectRoot) {
8202
8835
  }
8203
8836
  function createInspectorMissionController(options) {
8204
8837
  const paths = resolveInspectorMissionPaths(options.projectRoot);
8205
- mkdirSync12(paths.missionsDir, { recursive: true });
8206
- mkdirSync12(paths.journalsDir, { recursive: true });
8838
+ mkdirSync14(paths.missionsDir, { recursive: true });
8839
+ mkdirSync14(paths.journalsDir, { recursive: true });
8207
8840
  const now = options.now ?? (() => new Date().toISOString());
8208
8841
  const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
8209
8842
  function missionPath(missionId) {
@@ -8213,15 +8846,15 @@ function createInspectorMissionController(options) {
8213
8846
  return join(paths.journalsDir, `${missionId}.jsonl`);
8214
8847
  }
8215
8848
  function appendMissionJournal(entry) {
8216
- mkdirSync12(paths.journalsDir, { recursive: true });
8849
+ mkdirSync14(paths.journalsDir, { recursive: true });
8217
8850
  appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
8218
8851
  `, "utf8");
8219
8852
  }
8220
8853
  function listMissionJournal(missionId) {
8221
8854
  const path = journalPath(missionId);
8222
- if (!existsSync12(path))
8855
+ if (!existsSync14(path))
8223
8856
  return [];
8224
- return readFileSync8(path, "utf8").split(`
8857
+ return readFileSync10(path, "utf8").split(`
8225
8858
  `).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
8226
8859
  id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
8227
8860
  missionId,
@@ -8237,7 +8870,7 @@ function createInspectorMissionController(options) {
8237
8870
  }
8238
8871
  function readMissionOnly(missionId) {
8239
8872
  const path = missionPath(missionId);
8240
- if (!existsSync12(path)) {
8873
+ if (!existsSync14(path)) {
8241
8874
  return { ok: false, error: `Mission ${missionId} was not found` };
8242
8875
  }
8243
8876
  const read = readJsonRecord(path);
@@ -8288,7 +8921,7 @@ function createInspectorMissionController(options) {
8288
8921
  const source = cloneJsonRecord(input.sourceTask);
8289
8922
  const missionId = nextId();
8290
8923
  const path = missionPath(missionId);
8291
- if (existsSync12(path)) {
8924
+ if (existsSync14(path)) {
8292
8925
  const existing = readMissionOnly(missionId);
8293
8926
  if (!existing.ok)
8294
8927
  return existing;
@@ -9888,8 +10521,8 @@ function createCodexInspectorTransport(options) {
9888
10521
  const sendRequest = async (method, params) => {
9889
10522
  const id = nextRequestId;
9890
10523
  nextRequestId += 1;
9891
- const response = new Promise((resolve20, reject) => {
9892
- pendingResponses.set(id, { resolve: resolve20, reject });
10524
+ const response = new Promise((resolve22, reject) => {
10525
+ pendingResponses.set(id, { resolve: resolve22, reject });
9893
10526
  });
9894
10527
  response.catch(() => {});
9895
10528
  try {
@@ -10199,9 +10832,9 @@ function createCodexInspectorTransport(options) {
10199
10832
  }
10200
10833
  lastAssistantMessage = null;
10201
10834
  lastError = null;
10202
- const turnResult = new Promise((resolve20, reject) => {
10835
+ const turnResult = new Promise((resolve22, reject) => {
10203
10836
  currentTurn = {
10204
- resolve: resolve20,
10837
+ resolve: resolve22,
10205
10838
  reject,
10206
10839
  events: []
10207
10840
  };
@@ -10261,13 +10894,13 @@ function createCodexInspectorTransport(options) {
10261
10894
  };
10262
10895
  }
10263
10896
  function writeChildLine(child, line) {
10264
- return new Promise((resolve20, reject) => {
10897
+ return new Promise((resolve22, reject) => {
10265
10898
  child.stdin.write(line, (error) => {
10266
10899
  if (error) {
10267
10900
  reject(error);
10268
10901
  return;
10269
10902
  }
10270
- resolve20();
10903
+ resolve22();
10271
10904
  });
10272
10905
  });
10273
10906
  }
@@ -10280,10 +10913,10 @@ function terminateChild(child) {
10280
10913
  } catch {}
10281
10914
  }
10282
10915
  async function waitForChildSpawn(child) {
10283
- await new Promise((resolve20, reject) => {
10916
+ await new Promise((resolve22, reject) => {
10284
10917
  const onSpawn = () => {
10285
10918
  cleanup();
10286
- resolve20();
10919
+ resolve22();
10287
10920
  };
10288
10921
  const onError = (error) => {
10289
10922
  cleanup();
@@ -10795,8 +11428,8 @@ function createGlobalInspectorService(options) {
10795
11428
 
10796
11429
  // packages/server/src/inspector/upstream-sync.ts
10797
11430
  import { spawnSync as spawnSync4 } from "child_process";
10798
- import { existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
10799
- import { dirname as dirname14, resolve as resolve20 } from "path";
11431
+ import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync14 } from "fs";
11432
+ import { dirname as dirname17, resolve as resolve22 } from "path";
10800
11433
  import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
10801
11434
  var UPSTREAM_VALIDATION_DESCRIPTIONS = {
10802
11435
  "integration:hg-auth-backport": "Preserves the upstream auth hardening cluster: nonce-backed node-client login, signature-aware JWT verification, shared-token onboarding semantics, and regression coverage.",
@@ -10934,34 +11567,34 @@ function defaultGitRunner(repoRoot, args) {
10934
11567
  }
10935
11568
  function upstreamStatePath(projectRoot, override) {
10936
11569
  if (override) {
10937
- return resolve20(override);
11570
+ return resolve22(override);
10938
11571
  }
10939
- return resolve20(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
11572
+ return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
10940
11573
  }
10941
11574
  function readUpstreamState(projectRoot, statePath) {
10942
11575
  const path = upstreamStatePath(projectRoot, statePath);
10943
- if (!existsSync13(path)) {
11576
+ if (!existsSync15(path)) {
10944
11577
  return null;
10945
11578
  }
10946
11579
  try {
10947
- return JSON.parse(readFileSync9(path, "utf-8"));
11580
+ return JSON.parse(readFileSync11(path, "utf-8"));
10948
11581
  } catch {
10949
11582
  return null;
10950
11583
  }
10951
11584
  }
10952
11585
  function writeUpstreamState(projectRoot, state, statePath) {
10953
11586
  const path = upstreamStatePath(projectRoot, statePath);
10954
- mkdirSync13(dirname14(path), { recursive: true });
10955
- writeFileSync12(path, `${JSON.stringify(state, null, 2)}
11587
+ mkdirSync15(dirname17(path), { recursive: true });
11588
+ writeFileSync14(path, `${JSON.stringify(state, null, 2)}
10956
11589
  `, "utf8");
10957
11590
  }
10958
11591
  function readImportedRevision(projectRoot, upstreamsDocPath) {
10959
11592
  const monorepoRoot = resolveMonorepoRoot4(projectRoot);
10960
- const docPath = upstreamsDocPath ? resolve20(upstreamsDocPath) : resolve20(monorepoRoot, "docs", "UPSTREAMS.md");
10961
- if (!existsSync13(docPath)) {
11593
+ const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
11594
+ if (!existsSync15(docPath)) {
10962
11595
  throw new Error(`UPSTREAMS.md not found at ${docPath}`);
10963
11596
  }
10964
- const docContent = readFileSync9(docPath, "utf-8");
11597
+ const docContent = readFileSync11(docPath, "utf-8");
10965
11598
  const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
10966
11599
  if (!revision) {
10967
11600
  throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
@@ -10983,7 +11616,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
10983
11616
  return null;
10984
11617
  }
10985
11618
  function isGitCheckout(path, gitRunner) {
10986
- if (!existsSync13(resolve20(path, ".git"))) {
11619
+ if (!existsSync15(resolve22(path, ".git"))) {
10987
11620
  return false;
10988
11621
  }
10989
11622
  const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
@@ -10992,12 +11625,12 @@ function isGitCheckout(path, gitRunner) {
10992
11625
  function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
10993
11626
  const monorepoRoot = resolveMonorepoRoot4(projectRoot);
10994
11627
  const candidates = [
10995
- explicitCheckout ? resolve20(explicitCheckout) : "",
10996
- process.env.UPSTREAM_CHECKOUT?.trim() ? resolve20(process.env.UPSTREAM_CHECKOUT.trim()) : "",
10997
- process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve20(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
10998
- resolve20(projectRoot, "..", "humoongate"),
10999
- resolve20(monorepoRoot, "..", "humoongate"),
11000
- resolve20(monorepoRoot, "humoongate")
11628
+ explicitCheckout ? resolve22(explicitCheckout) : "",
11629
+ process.env.UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.UPSTREAM_CHECKOUT.trim()) : "",
11630
+ process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
11631
+ resolve22(projectRoot, "..", "humoongate"),
11632
+ resolve22(monorepoRoot, "..", "humoongate"),
11633
+ resolve22(monorepoRoot, "humoongate")
11001
11634
  ].filter(Boolean);
11002
11635
  for (const candidate of candidates) {
11003
11636
  if (isGitCheckout(candidate, gitRunner)) {
@@ -11233,10 +11866,10 @@ async function runUpstreamSyncScan(options) {
11233
11866
  }
11234
11867
 
11235
11868
  // packages/server/src/server-helpers/task-config.ts
11236
- import { existsSync as existsSync14 } from "fs";
11869
+ import { existsSync as existsSync16 } from "fs";
11237
11870
  async function readTaskConfig(projectRoot) {
11238
11871
  const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
11239
- if (!existsSync14(taskConfigPath)) {
11872
+ if (!existsSync16(taskConfigPath)) {
11240
11873
  return {};
11241
11874
  }
11242
11875
  try {
@@ -11272,11 +11905,11 @@ function resolveFollowupSourceCommit(input) {
11272
11905
  }
11273
11906
  async function createInspectorFollowupTask(projectRoot, input) {
11274
11907
  const monorepoRoot = resolveMonorepoRoot5(projectRoot);
11275
- const issuesPath = resolve21(monorepoRoot, ".beads", "issues.jsonl");
11276
- const taskStatePath = resolve21(monorepoRoot, ".beads", "task-state.json");
11277
- const taskConfigPath = resolve21(monorepoRoot, ".rig", "task-config.json");
11278
- mkdirSync14(dirname15(issuesPath), { recursive: true });
11279
- mkdirSync14(dirname15(taskConfigPath), { recursive: true });
11908
+ const issuesPath = resolve23(monorepoRoot, ".beads", "issues.jsonl");
11909
+ const taskStatePath = resolve23(monorepoRoot, ".beads", "task-state.json");
11910
+ const taskConfigPath = resolve23(monorepoRoot, ".rig", "task-config.json");
11911
+ mkdirSync16(dirname18(issuesPath), { recursive: true });
11912
+ mkdirSync16(dirname18(taskConfigPath), { recursive: true });
11280
11913
  const summary = normalizeString(input.summary) ?? "Inspector follow-up";
11281
11914
  const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
11282
11915
  const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
@@ -11295,7 +11928,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
11295
11928
  const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
11296
11929
  const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
11297
11930
  const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
11298
- const existingIssueLines = existsSync15(issuesPath) ? readFileSync10(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
11931
+ const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
11299
11932
  const existingIssues = existingIssueLines.map((line) => {
11300
11933
  try {
11301
11934
  return JSON.parse(line);
@@ -11304,7 +11937,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
11304
11937
  }
11305
11938
  }).filter((value) => value !== null);
11306
11939
  const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
11307
- const rawTaskState = existsSync15(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
11940
+ const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
11308
11941
  const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
11309
11942
  const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
11310
11943
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
@@ -11330,7 +11963,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
11330
11963
  updated_at: createdAt,
11331
11964
  labels: mergedLabels
11332
11965
  };
11333
- writeFileSync13(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
11966
+ writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
11334
11967
  `)}
11335
11968
  ${JSON.stringify(issueRecord)}
11336
11969
  ` : `${JSON.stringify(issueRecord)}
@@ -11348,7 +11981,7 @@ ${JSON.stringify(issueRecord)}
11348
11981
  labels: mergedLabels
11349
11982
  };
11350
11983
  });
11351
- writeFileSync13(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
11984
+ writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
11352
11985
  `)}
11353
11986
  `, "utf8");
11354
11987
  }
@@ -11371,14 +12004,14 @@ ${JSON.stringify(issueRecord)}
11371
12004
  }
11372
12005
  };
11373
12006
  }
11374
- writeFileSync13(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
12007
+ writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
11375
12008
  `, "utf8");
11376
12009
  tasks[taskId] = {
11377
12010
  status,
11378
12011
  sourceCommit: resolveFollowupSourceCommit(input),
11379
12012
  ...sourceKey ? { sourceKey } : {}
11380
12013
  };
11381
- writeFileSync13(taskStatePath, `${JSON.stringify({
12014
+ writeFileSync15(taskStatePath, `${JSON.stringify({
11382
12015
  schemaVersion: 1,
11383
12016
  baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
11384
12017
  tasks
@@ -11686,12 +12319,12 @@ function isAuthorizedLinearWebhookRequest(req) {
11686
12319
  }
11687
12320
 
11688
12321
  // packages/server/src/server-helpers/notifications.ts
11689
- import { existsSync as existsSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync11 } from "fs";
11690
- import { dirname as dirname16 } from "path";
12322
+ import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
12323
+ import { dirname as dirname19 } from "path";
11691
12324
  async function loadNotificationConfig(path) {
11692
- if (!existsSync16(path)) {
12325
+ if (!existsSync18(path)) {
11693
12326
  const defaultConfig = { targets: [] };
11694
- mkdirSync15(dirname16(path), { recursive: true });
12327
+ mkdirSync17(dirname19(path), { recursive: true });
11695
12328
  await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
11696
12329
  `);
11697
12330
  return defaultConfig;
@@ -11706,10 +12339,10 @@ async function loadNotificationConfig(path) {
11706
12339
  }
11707
12340
  }
11708
12341
  function readRecentEvents(file, limit) {
11709
- if (!existsSync16(file)) {
12342
+ if (!existsSync18(file)) {
11710
12343
  return [];
11711
12344
  }
11712
- const lines = readFileSync11(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
12345
+ const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
11713
12346
  const events = [];
11714
12347
  for (const line of lines) {
11715
12348
  try {
@@ -11804,11 +12437,11 @@ function extractObjectLiteralBlock(source, property) {
11804
12437
  }
11805
12438
  function readFallbackIssueAnalysisConfig(projectRoot) {
11806
12439
  for (const fileName of ["rig.config.ts", "rig.config.json"]) {
11807
- const path = resolve22(projectRoot, fileName);
11808
- if (!existsSync17(path))
12440
+ const path = resolve24(projectRoot, fileName);
12441
+ if (!existsSync19(path))
11809
12442
  continue;
11810
12443
  try {
11811
- const source = readFileSync12(path, "utf8");
12444
+ const source = readFileSync14(path, "utf8");
11812
12445
  if (fileName.endsWith(".json"))
11813
12446
  return JSON.parse(source);
11814
12447
  const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
@@ -11941,8 +12574,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
11941
12574
  async function withServerPathEnv(projectRoot, fn) {
11942
12575
  const waitForTurn = serverPathEnvQueue;
11943
12576
  let releaseTurn;
11944
- serverPathEnvQueue = new Promise((resolve23) => {
11945
- releaseTurn = resolve23;
12577
+ serverPathEnvQueue = new Promise((resolve25) => {
12578
+ releaseTurn = resolve25;
11946
12579
  });
11947
12580
  await waitForTurn;
11948
12581
  const paths = resolveServerAuthorityPaths(projectRoot);
@@ -11978,9 +12611,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
11978
12611
  return withServerPathEnv(projectRoot, fn);
11979
12612
  }
11980
12613
  async function readWorkspaceTasks(projectRoot) {
11981
- const issuesPath = resolve22(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
12614
+ const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
11982
12615
  const taskConfig = await readTaskConfig(projectRoot);
11983
- if (!existsSync17(issuesPath)) {
12616
+ if (!existsSync19(issuesPath)) {
11984
12617
  return [];
11985
12618
  }
11986
12619
  const latestById = new Map;
@@ -11999,7 +12632,7 @@ async function readWorkspaceTasks(projectRoot) {
11999
12632
  description: normalizeString(entry.description),
12000
12633
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
12001
12634
  status: normalizedStatus ?? "unknown",
12002
- sourceStatus: normalizedStatus ? null : rawStatus,
12635
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
12003
12636
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
12004
12637
  issueType: normalizeString(entry.issue_type),
12005
12638
  role: normalizeString(config.role) ?? null,
@@ -12054,11 +12687,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
12054
12687
  continue;
12055
12688
  add(run.artifactRoot);
12056
12689
  if (run.worktreePath) {
12057
- add(resolve22(run.worktreePath, "artifacts", taskId));
12690
+ add(resolve24(run.worktreePath, "artifacts", taskId));
12058
12691
  }
12059
12692
  }
12060
12693
  for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
12061
- add(resolve22(artifactsRoot, taskId));
12694
+ add(resolve24(artifactsRoot, taskId));
12062
12695
  }
12063
12696
  return candidates;
12064
12697
  }
@@ -12072,7 +12705,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
12072
12705
  }
12073
12706
  }
12074
12707
  return taskIds.flatMap((currentTaskId) => {
12075
- const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync17(path));
12708
+ const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
12076
12709
  if (!currentRoot) {
12077
12710
  return [];
12078
12711
  }
@@ -12084,7 +12717,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
12084
12717
  taskId: currentTaskId,
12085
12718
  kind: "file",
12086
12719
  label: fileName,
12087
- path: resolve22(currentRoot, fileName),
12720
+ path: resolve24(currentRoot, fileName),
12088
12721
  url: null,
12089
12722
  metadata: {
12090
12723
  fileName
@@ -12127,11 +12760,11 @@ function buildInspectorStreamPayload(state, sequence) {
12127
12760
  }
12128
12761
  function listRemoteRunArtifacts(projectRoot, runId) {
12129
12762
  const root = remoteArtifactsRoot(projectRoot, runId);
12130
- if (!existsSync17(root)) {
12763
+ if (!existsSync19(root)) {
12131
12764
  return [];
12132
12765
  }
12133
12766
  return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
12134
- const artifactPath = resolve22(root, entry.name);
12767
+ const artifactPath = resolve24(root, entry.name);
12135
12768
  const stat = statSync6(artifactPath);
12136
12769
  const meta = readJsonFile4(`${artifactPath}.json`, null);
12137
12770
  return {
@@ -12373,8 +13006,8 @@ function fileStats(path) {
12373
13006
  }
12374
13007
  }
12375
13008
  function runFileCursor(projectRoot, run) {
12376
- const runDir = dirname17(runLogsPath(projectRoot, run.runId));
12377
- const runJson = fileStats(resolve22(runDir, "run.json"));
13009
+ const runDir = dirname20(runLogsPath(projectRoot, run.runId));
13010
+ const runJson = fileStats(resolve24(runDir, "run.json"));
12378
13011
  const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
12379
13012
  const logs = fileStats(runLogsPath(projectRoot, run.runId));
12380
13013
  return {
@@ -12424,10 +13057,10 @@ function startRunFileWatcher(state, pollMs) {
12424
13057
  }, Math.max(250, Math.min(pollMs, 1000)));
12425
13058
  }
12426
13059
  function startPoller(state, pollMs) {
12427
- let offset = existsSync17(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
13060
+ let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
12428
13061
  return setInterval(async () => {
12429
13062
  try {
12430
- if (!existsSync17(state.eventsFile)) {
13063
+ if (!existsSync19(state.eventsFile)) {
12431
13064
  return;
12432
13065
  }
12433
13066
  const file = await open(state.eventsFile, "r");
@@ -12479,6 +13112,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
12479
13112
  const server = Bun.serve({
12480
13113
  hostname: options.host,
12481
13114
  port: options.port,
13115
+ idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
12482
13116
  fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
12483
13117
  websocket: {
12484
13118
  open(ws) {
@@ -12554,7 +13188,7 @@ function resolveProjectRoot() {
12554
13188
  return resolveRigProjectRoot({
12555
13189
  envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
12556
13190
  cwd: process.cwd(),
12557
- fallbackRoot: resolve22(import.meta.dir, "../..")
13191
+ fallbackRoot: resolve24(import.meta.dir, "../..")
12558
13192
  });
12559
13193
  }
12560
13194
  var __testOnly = {