@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.
package/dist/src/index.js CHANGED
@@ -663,9 +663,9 @@ function createRemoteOrchestrationSummary(input) {
663
663
  }
664
664
  // packages/server/src/server.ts
665
665
  import { spawn as spawn5 } from "child_process";
666
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync6 } from "fs";
666
+ import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
667
667
  import { open } from "fs/promises";
668
- import { dirname as dirname17, resolve as resolve22 } from "path";
668
+ import { dirname as dirname20, resolve as resolve24 } from "path";
669
669
  import {
670
670
  listAuthorityArtifactRoots,
671
671
  listAuthorityRuns as listAuthorityRuns7,
@@ -2293,7 +2293,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
2293
2293
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
2294
2294
  ...toTaskSummary(workspace.id, {
2295
2295
  ...task,
2296
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
2296
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
2297
2297
  }),
2298
2298
  graphId: graph.id
2299
2299
  }));
@@ -2819,7 +2819,6 @@ function createGitHubTaskReconciler(input) {
2819
2819
  }
2820
2820
 
2821
2821
  // packages/server/src/server-helpers/issue-analysis.ts
2822
- import { execFile } from "child_process";
2823
2822
  import { createHash } from "crypto";
2824
2823
  function stableIssueHash(issue) {
2825
2824
  const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
@@ -2953,16 +2952,33 @@ function parseIssueAnalysisResult(raw) {
2953
2952
  return result;
2954
2953
  }
2955
2954
  function createDefaultPiIssueAnalysisCommandRunner() {
2956
- return (command, args, options) => new Promise((resolve11) => {
2957
- execFile(command, [...args], {
2958
- timeout: options.timeoutMs,
2959
- maxBuffer: 10 * 1024 * 1024,
2960
- env: options.env ? { ...process.env, ...options.env } : process.env
2961
- }, (error, stdout, stderr) => {
2962
- const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
2963
- resolve11({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
2955
+ return async (command, args, options) => {
2956
+ const env = options.env ? { ...process.env, ...options.env } : process.env;
2957
+ const proc = Bun.spawn([command, ...args], {
2958
+ stdout: "pipe",
2959
+ stderr: "pipe",
2960
+ env
2964
2961
  });
2965
- });
2962
+ let timedOut = false;
2963
+ const timer = setTimeout(() => {
2964
+ timedOut = true;
2965
+ proc.kill();
2966
+ }, options.timeoutMs);
2967
+ try {
2968
+ const [stdout, stderr, exitCode] = await Promise.all([
2969
+ new Response(proc.stdout).text(),
2970
+ new Response(proc.stderr).text(),
2971
+ proc.exited
2972
+ ]);
2973
+ return {
2974
+ exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
2975
+ stdout,
2976
+ stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
2977
+ };
2978
+ } finally {
2979
+ clearTimeout(timer);
2980
+ }
2981
+ };
2966
2982
  }
2967
2983
  function createPiIssueAnalyzer(input = {}) {
2968
2984
  const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
@@ -2970,7 +2986,10 @@ function createPiIssueAnalyzer(input = {}) {
2970
2986
  const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
2971
2987
  return async ({ prompt }) => {
2972
2988
  const args = ["--print", "--mode", "json", "--no-session"];
2973
- const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || "openai-codex/gpt-5.5";
2989
+ const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
2990
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
2991
+ if (provider)
2992
+ args.push("--provider", provider);
2974
2993
  if (model)
2975
2994
  args.push("--model", model);
2976
2995
  args.push(prompt);
@@ -3688,7 +3707,7 @@ function applyOrchestrationCommand2(state, command) {
3688
3707
  import { spawn as spawn3 } from "child_process";
3689
3708
  import { loadConfig } from "@rig/core/load-config";
3690
3709
  import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
3691
- import { dirname as dirname8, relative as relative2, resolve as resolve14 } from "path";
3710
+ import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
3692
3711
  import {
3693
3712
  listAuthorityRuns as listAuthorityRuns4,
3694
3713
  readAuthorityRun as readAuthorityRun4,
@@ -3811,8 +3830,9 @@ function summarizeRunValidationFailure(projectRoot, run) {
3811
3830
  }
3812
3831
 
3813
3832
  // packages/server/src/server-helpers/github-auth-store.ts
3814
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3815
- import { resolve as resolve13 } from "path";
3833
+ import { randomBytes } from "crypto";
3834
+ import { chmodSync, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3835
+ import { dirname as dirname8, resolve as resolve13 } from "path";
3816
3836
  function cleanString(value) {
3817
3837
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
3818
3838
  }
@@ -3824,6 +3844,44 @@ function cleanScopes(value) {
3824
3844
  return clean ? [clean] : [];
3825
3845
  });
3826
3846
  }
3847
+ function parseApiSessions(value) {
3848
+ if (!Array.isArray(value))
3849
+ return [];
3850
+ return value.flatMap((entry) => {
3851
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3852
+ return [];
3853
+ const record = entry;
3854
+ const token = cleanString(record.token);
3855
+ if (!token)
3856
+ return [];
3857
+ return [{
3858
+ token,
3859
+ login: cleanString(record.login),
3860
+ userId: cleanString(record.userId),
3861
+ createdAt: cleanString(record.createdAt) ?? undefined
3862
+ }];
3863
+ });
3864
+ }
3865
+ function parsePendingDevice(value) {
3866
+ if (!value || typeof value !== "object")
3867
+ return null;
3868
+ const record = value;
3869
+ const pollId = cleanString(record.pollId);
3870
+ const deviceCode = cleanString(record.deviceCode);
3871
+ const expiresAt = cleanString(record.expiresAt);
3872
+ const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
3873
+ if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
3874
+ return null;
3875
+ return { pollId, deviceCode, expiresAt, intervalSeconds };
3876
+ }
3877
+ function parsePendingDevices(value) {
3878
+ if (!Array.isArray(value))
3879
+ return [];
3880
+ return value.flatMap((entry) => {
3881
+ const pending = parsePendingDevice(entry);
3882
+ return pending ? [pending] : [];
3883
+ });
3884
+ }
3827
3885
  function readStoredAuth(stateFile) {
3828
3886
  if (!existsSync6(stateFile))
3829
3887
  return {};
@@ -3837,37 +3895,44 @@ function readStoredAuth(stateFile) {
3837
3895
  selectedRepo: cleanString(parsed.selectedRepo),
3838
3896
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
3839
3897
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
3898
+ pendingDevices: parsePendingDevices(parsed.pendingDevices),
3899
+ apiSessions: parseApiSessions(parsed.apiSessions),
3840
3900
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
3841
3901
  };
3842
3902
  } catch {
3843
3903
  return {};
3844
3904
  }
3845
3905
  }
3846
- function parsePendingDevice(value) {
3847
- if (!value || typeof value !== "object")
3848
- return null;
3849
- const record = value;
3850
- const pollId = cleanString(record.pollId);
3851
- const deviceCode = cleanString(record.deviceCode);
3852
- const expiresAt = cleanString(record.expiresAt);
3853
- const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
3854
- if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
3855
- return null;
3856
- return { pollId, deviceCode, expiresAt, intervalSeconds };
3906
+ function newApiSessionToken() {
3907
+ return `rig_${randomBytes(32).toString("base64url")}`;
3857
3908
  }
3858
3909
  function writeStoredAuth(stateFile, payload) {
3859
- mkdirSync6(resolve13(stateFile, ".."), { recursive: true });
3910
+ mkdirSync6(dirname8(stateFile), { recursive: true });
3860
3911
  writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
3861
3912
  `, { encoding: "utf8", mode: 384 });
3862
3913
  try {
3863
3914
  chmodSync(stateFile, 384);
3864
3915
  } catch {}
3865
3916
  }
3917
+ function localProjectAuthStateFile(projectRoot) {
3918
+ return resolve13(projectRoot, ".rig", "state", "github-auth.json");
3919
+ }
3866
3920
  function resolveGitHubAuthStateFile(projectRoot) {
3867
3921
  return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
3868
3922
  }
3869
- function createGitHubAuthStore(projectRoot) {
3870
- const stateFile = resolveGitHubAuthStateFile(projectRoot);
3923
+ function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
3924
+ const targetFile = localProjectAuthStateFile(projectRoot);
3925
+ mkdirSync6(dirname8(targetFile), { recursive: true });
3926
+ if (existsSync6(stateFile)) {
3927
+ copyFileSync(stateFile, targetFile);
3928
+ try {
3929
+ chmodSync(targetFile, 384);
3930
+ } catch {}
3931
+ return;
3932
+ }
3933
+ writeStoredAuth(targetFile, {});
3934
+ }
3935
+ function createGitHubAuthStoreFromStateFile(stateFile) {
3871
3936
  return {
3872
3937
  stateFile,
3873
3938
  status(options) {
@@ -3897,14 +3962,53 @@ function createGitHubAuthStore(projectRoot) {
3897
3962
  scopes: input.scopes ?? [],
3898
3963
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
3899
3964
  pendingDevice: null,
3965
+ pendingDevices: [],
3966
+ apiSessions: previous.apiSessions ?? [],
3967
+ updatedAt: new Date().toISOString()
3968
+ });
3969
+ },
3970
+ createApiSession() {
3971
+ const previous = readStoredAuth(stateFile);
3972
+ const token = newApiSessionToken();
3973
+ const session = {
3974
+ token,
3975
+ login: cleanString(previous.login),
3976
+ userId: cleanString(previous.userId),
3977
+ createdAt: new Date().toISOString()
3978
+ };
3979
+ writeStoredAuth(stateFile, {
3980
+ ...previous,
3981
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
3900
3982
  updatedAt: new Date().toISOString()
3901
3983
  });
3984
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
3985
+ },
3986
+ readApiSession(token) {
3987
+ const clean = cleanString(token);
3988
+ if (!clean)
3989
+ return null;
3990
+ const previous = readStoredAuth(stateFile);
3991
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
3992
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
3993
+ },
3994
+ copyToProjectRoot(projectRoot) {
3995
+ const targetFile = resolveGitHubAuthStateFile(projectRoot);
3996
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
3997
+ },
3998
+ copyToLocalProjectRoot(projectRoot) {
3999
+ copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
3902
4000
  },
3903
4001
  savePendingDevice(input) {
3904
4002
  const previous = readStoredAuth(stateFile);
4003
+ const pendingDevices = [
4004
+ ...previous.pendingDevice ? [previous.pendingDevice] : [],
4005
+ ...previous.pendingDevices ?? [],
4006
+ input
4007
+ ].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
3905
4008
  writeStoredAuth(stateFile, {
3906
4009
  ...previous,
3907
- pendingDevice: input,
4010
+ pendingDevice: null,
4011
+ pendingDevices,
3908
4012
  updatedAt: new Date().toISOString()
3909
4013
  });
3910
4014
  },
@@ -3917,23 +4021,32 @@ function createGitHubAuthStore(projectRoot) {
3917
4021
  });
3918
4022
  },
3919
4023
  readPendingDevice(pollId) {
3920
- const pending = readStoredAuth(stateFile).pendingDevice ?? null;
3921
- if (!pending || pending.pollId !== pollId)
4024
+ const previous = readStoredAuth(stateFile);
4025
+ const pending = [
4026
+ ...previous.pendingDevice ? [previous.pendingDevice] : [],
4027
+ ...previous.pendingDevices ?? []
4028
+ ].find((entry) => entry.pollId === pollId) ?? null;
4029
+ if (!pending)
3922
4030
  return null;
3923
4031
  if (Date.parse(pending.expiresAt) <= Date.now())
3924
4032
  return null;
3925
4033
  return pending;
3926
4034
  },
3927
- clearPendingDevice() {
4035
+ clearPendingDevice(pollId) {
3928
4036
  const previous = readStoredAuth(stateFile);
4037
+ const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
3929
4038
  writeStoredAuth(stateFile, {
3930
4039
  ...previous,
3931
4040
  pendingDevice: null,
4041
+ pendingDevices: remaining,
3932
4042
  updatedAt: new Date().toISOString()
3933
4043
  });
3934
4044
  }
3935
4045
  };
3936
4046
  }
4047
+ function createGitHubAuthStore(projectRoot) {
4048
+ return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
4049
+ }
3937
4050
 
3938
4051
  // packages/server/src/server-helpers/github-projects.ts
3939
4052
  function asRecord(value) {
@@ -4197,15 +4310,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
4197
4310
  if (!run.taskId)
4198
4311
  return;
4199
4312
  const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
4200
- await syncGitHubProjectStatusForTaskUpdate({
4201
- taskId: run.taskId,
4202
- status,
4203
- issueNodeId,
4204
- token: createGitHubAuthStore(projectRoot).readToken(),
4205
- config
4206
- }).catch(() => {
4207
- return;
4208
- });
4313
+ try {
4314
+ const result = await syncGitHubProjectStatusForTaskUpdate({
4315
+ taskId: run.taskId,
4316
+ status,
4317
+ issueNodeId,
4318
+ token: createGitHubAuthStore(projectRoot).readToken(),
4319
+ config
4320
+ });
4321
+ if (!result.synced && result.reason !== "project-sync-disabled") {
4322
+ appendRunLogEntry(projectRoot, run.runId, {
4323
+ id: `log:${run.runId}:github-project-sync:${status}`,
4324
+ title: "GitHub Project sync skipped",
4325
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
4326
+ tone: "warn",
4327
+ status: "running",
4328
+ createdAt: new Date().toISOString(),
4329
+ payload: { reason: result.reason, issueNodeId }
4330
+ });
4331
+ }
4332
+ } catch (error) {
4333
+ appendRunLogEntry(projectRoot, run.runId, {
4334
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
4335
+ title: "GitHub Project sync failed",
4336
+ detail: error instanceof Error ? error.message : String(error),
4337
+ tone: "error",
4338
+ status: "running",
4339
+ createdAt: new Date().toISOString(),
4340
+ payload: { issueNodeId }
4341
+ });
4342
+ }
4209
4343
  }
4210
4344
  async function autoAssignRunIssue(projectRoot, run) {
4211
4345
  if (!run.taskId)
@@ -4307,11 +4441,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
4307
4441
  return;
4308
4442
  throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
4309
4443
  }
4444
+ async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
4445
+ const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
4446
+ if (fromReader)
4447
+ return fromReader;
4448
+ const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
4449
+ if (projected)
4450
+ return projected;
4451
+ if (readTasks !== readWorkspaceTasks) {
4452
+ return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
4453
+ }
4454
+ return null;
4455
+ }
4310
4456
  async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
4311
4457
  if ("taskId" in input && input.taskId) {
4312
4458
  assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
4313
4459
  }
4314
- const sourceTask = "taskId" in input && input.taskId ? (await readTasks(projectRoot)).find((task) => task.id === input.taskId) ?? null : null;
4460
+ const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
4315
4461
  const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
4316
4462
  const runDir = resolveAuthorityRunDir3(projectRoot, input.runId);
4317
4463
  const runRecord = {
@@ -4383,6 +4529,7 @@ async function startLocalRun(state, runId, options) {
4383
4529
  throw new Error(`Run not found: ${runId}`);
4384
4530
  }
4385
4531
  const startedAt = new Date().toISOString();
4532
+ const resumeMode = options?.resume === true;
4386
4533
  state.runProcesses.set(runId, {
4387
4534
  runId,
4388
4535
  child: null,
@@ -4399,9 +4546,9 @@ async function startLocalRun(state, runId, options) {
4399
4546
  summary: run.title
4400
4547
  });
4401
4548
  appendRunLogEntry(state.projectRoot, runId, {
4402
- id: `log:${runId}:prepare`,
4403
- title: "Rig task run starting",
4404
- detail: run.taskId ?? run.title,
4549
+ id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
4550
+ title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
4551
+ detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
4405
4552
  tone: "info",
4406
4553
  status: "preparing",
4407
4554
  createdAt: startedAt
@@ -4479,7 +4626,15 @@ async function startLocalRun(state, runId, options) {
4479
4626
  RIG_SERVER_INTERNAL_EXEC: "1",
4480
4627
  ...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
4481
4628
  ...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
4482
- ...bridgeGitHubToken ? { RIG_GITHUB_TOKEN: bridgeGitHubToken } : {}
4629
+ ...bridgeGitHubToken ? {
4630
+ RIG_GITHUB_TOKEN: bridgeGitHubToken,
4631
+ GITHUB_TOKEN: bridgeGitHubToken,
4632
+ GH_TOKEN: bridgeGitHubToken
4633
+ } : {},
4634
+ ...resumeMode ? {
4635
+ RIG_RUN_RESUME: "1",
4636
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
4637
+ } : {}
4483
4638
  },
4484
4639
  stdio: ["ignore", "pipe", "pipe"]
4485
4640
  });
@@ -4630,7 +4785,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
4630
4785
  }
4631
4786
  try {
4632
4787
  const monorepoRoot = resolveMonorepoRoot3(projectRoot);
4633
- const outerProjectRoot = dirname8(dirname8(monorepoRoot));
4788
+ const outerProjectRoot = dirname9(dirname9(monorepoRoot));
4634
4789
  if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
4635
4790
  return outerProjectRoot;
4636
4791
  }
@@ -4651,7 +4806,7 @@ async function resumeRunRecord(state, input) {
4651
4806
  if (run.status === "completed") {
4652
4807
  throw new Error("Completed runs cannot be resumed.");
4653
4808
  }
4654
- await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
4809
+ await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
4655
4810
  }
4656
4811
  function appendRunMessage(projectRoot, input) {
4657
4812
  const run = readAuthorityRun4(projectRoot, input.runId);
@@ -4735,34 +4890,12 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
4735
4890
  writeQueueState(projectRoot, next);
4736
4891
  return next;
4737
4892
  }
4738
- var ORPHANABLE_LOCAL_RUN_STATUSES = new Set(["preparing", "running"]);
4739
- function reconcileOrphanedLocalRuns(state, runs, nowIso2) {
4740
- let changed = false;
4741
- for (const run of runs) {
4893
+ var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
4894
+ function collectResumableLocalRuns(state, runs) {
4895
+ return runs.filter((run) => {
4742
4896
  const status = normalizeString(run.status)?.toLowerCase() ?? "";
4743
- const serverPid = run.serverPid;
4744
- const wasStartedByRigServer = typeof serverPid === "number" || typeof serverPid === "string";
4745
- if (run.mode !== "local" || !wasStartedByRigServer || !ORPHANABLE_LOCAL_RUN_STATUSES.has(status) || state.runProcesses.has(run.runId)) {
4746
- continue;
4747
- }
4748
- const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
4749
- patchRunRecord(state.projectRoot, run.runId, {
4750
- status: "failed",
4751
- completedAt: run.completedAt ?? nowIso2,
4752
- updatedAt: nowIso2,
4753
- errorText: detail
4754
- });
4755
- appendRunLogEntry(state.projectRoot, run.runId, {
4756
- id: `log:${run.runId}:stale-local-run`,
4757
- title: "Run marked stale after server restart",
4758
- detail,
4759
- tone: "error",
4760
- status: "failed",
4761
- createdAt: nowIso2
4762
- });
4763
- changed = true;
4764
- }
4765
- return changed;
4897
+ return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
4898
+ });
4766
4899
  }
4767
4900
  async function reconcileScheduler(state, reason) {
4768
4901
  if (state.scheduler.reconciling) {
@@ -4777,7 +4910,20 @@ async function reconcileScheduler(state, reason) {
4777
4910
  const queue = readQueueState(state.projectRoot);
4778
4911
  const tasks = await state.snapshotService.getWorkspaceTasks();
4779
4912
  let runs = listAuthorityRuns4(state.projectRoot);
4780
- let changed = reconcileOrphanedLocalRuns(state, runs, new Date().toISOString());
4913
+ let changed = false;
4914
+ const resumableRuns = collectResumableLocalRuns(state, runs);
4915
+ for (const run of resumableRuns) {
4916
+ appendRunLogEntry(state.projectRoot, run.runId, {
4917
+ id: `log:${run.runId}:auto-resume:${Date.now()}`,
4918
+ title: "Run auto-resume scheduled",
4919
+ detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
4920
+ tone: "info",
4921
+ status: "preparing",
4922
+ createdAt: new Date().toISOString()
4923
+ });
4924
+ await startLocalRun(state, run.runId, { resume: true });
4925
+ changed = true;
4926
+ }
4781
4927
  if (changed) {
4782
4928
  runs = listAuthorityRuns4(state.projectRoot);
4783
4929
  }
@@ -4853,8 +4999,8 @@ async function reconcileScheduler(state, reason) {
4853
4999
  // packages/server/src/server-helpers/http-router.ts
4854
5000
  import { randomUUID } from "crypto";
4855
5001
  import { spawnSync as spawnSync3 } from "child_process";
4856
- import { basename, dirname as dirname12, isAbsolute as isAbsolute3, resolve as resolve18 } from "path";
4857
- import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync10 } from "fs";
5002
+ import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as resolve20 } from "path";
5003
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
4858
5004
  import {
4859
5005
  listAuthorityRuns as listAuthorityRuns5,
4860
5006
  readAuthorityRun as readAuthorityRun6,
@@ -4878,7 +5024,7 @@ import {
4878
5024
  } from "@rig/runtime/control-plane/remote";
4879
5025
 
4880
5026
  // packages/server/src/server-helpers/run-steering.ts
4881
- import { dirname as dirname9, resolve as resolve15 } from "path";
5027
+ import { dirname as dirname10, resolve as resolve15 } from "path";
4882
5028
  import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
4883
5029
  import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
4884
5030
  var steeringSequence = 0;
@@ -4965,7 +5111,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
4965
5111
  delivered: false
4966
5112
  };
4967
5113
  const path = runSteeringPath(projectRoot, runId);
4968
- mkdirSync8(dirname9(path), { recursive: true });
5114
+ mkdirSync8(dirname10(path), { recursive: true });
4969
5115
  appendJsonlRecord2(path, entry);
4970
5116
  appendRunTimelineEntry(projectRoot, runId, {
4971
5117
  id: entry.id,
@@ -5002,6 +5148,187 @@ import {
5002
5148
  updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
5003
5149
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
5004
5150
 
5151
+ // packages/server/src/server-helpers/github-api-session-index.ts
5152
+ import { chmodSync as chmodSync3, existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
5153
+ import { dirname as dirname12, resolve as resolve17 } from "path";
5154
+
5155
+ // packages/server/src/server-helpers/github-user-namespace.ts
5156
+ import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
5157
+ import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve16 } from "path";
5158
+ function cleanString3(value) {
5159
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
5160
+ }
5161
+ function sanitizePathSegment(value) {
5162
+ return value.trim().toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
5163
+ }
5164
+ function deriveGitHubUserNamespaceKey(identity) {
5165
+ const userId = cleanString3(identity.userId);
5166
+ if (userId) {
5167
+ const safeId = sanitizePathSegment(userId);
5168
+ if (safeId)
5169
+ return `ghu-${safeId}`;
5170
+ }
5171
+ const login = cleanString3(identity.login);
5172
+ if (login) {
5173
+ const safeLogin = sanitizePathSegment(login);
5174
+ if (safeLogin)
5175
+ return `ghu-login-${safeLogin}`;
5176
+ }
5177
+ throw new Error("GitHub user namespace requires a user id or login");
5178
+ }
5179
+ function resolveRemoteUserNamespacesRoot(projectRoot) {
5180
+ const explicitRoot = cleanString3(process.env.RIG_REMOTE_USER_NAMESPACE_ROOT);
5181
+ if (explicitRoot)
5182
+ return resolve16(explicitRoot);
5183
+ const stateDir2 = cleanString3(process.env.RIG_STATE_DIR);
5184
+ if (stateDir2)
5185
+ return resolve16(dirname11(resolve16(stateDir2)), "users");
5186
+ return resolve16(projectRoot, ".rig", "users");
5187
+ }
5188
+ function resolveRemoteUserNamespace(projectRoot, identity) {
5189
+ const key = deriveGitHubUserNamespaceKey(identity);
5190
+ const root = resolve16(resolveRemoteUserNamespacesRoot(projectRoot), key);
5191
+ const stateDir2 = resolve16(root, ".rig", "state");
5192
+ return {
5193
+ key,
5194
+ userId: cleanString3(identity.userId),
5195
+ login: cleanString3(identity.login),
5196
+ root,
5197
+ stateDir: stateDir2,
5198
+ authStateFile: resolve16(stateDir2, "github-auth.json"),
5199
+ metadataFile: resolve16(stateDir2, "user-namespace.json"),
5200
+ checkoutBaseDir: resolve16(root, "remote-checkouts"),
5201
+ snapshotBaseDir: resolve16(root, "remote-snapshots")
5202
+ };
5203
+ }
5204
+ function serializeRemoteUserNamespace(namespace) {
5205
+ return {
5206
+ key: namespace.key,
5207
+ userId: namespace.userId,
5208
+ login: namespace.login,
5209
+ root: namespace.root,
5210
+ checkoutBaseDir: namespace.checkoutBaseDir,
5211
+ snapshotBaseDir: namespace.snapshotBaseDir
5212
+ };
5213
+ }
5214
+ function isPathInsideNamespace(namespaceRoot, candidatePath) {
5215
+ const root = resolve16(namespaceRoot);
5216
+ const candidate = resolve16(candidatePath);
5217
+ const rel = relative3(root, candidate);
5218
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
5219
+ }
5220
+ function writeRemoteUserNamespaceMetadata(namespace) {
5221
+ mkdirSync9(namespace.stateDir, { recursive: true });
5222
+ const previous = (() => {
5223
+ if (!existsSync9(namespace.metadataFile))
5224
+ return null;
5225
+ try {
5226
+ const parsed = JSON.parse(readFileSync6(namespace.metadataFile, "utf8"));
5227
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
5228
+ } catch {
5229
+ return null;
5230
+ }
5231
+ })();
5232
+ const now = new Date().toISOString();
5233
+ writeFileSync8(namespace.metadataFile, `${JSON.stringify({
5234
+ key: namespace.key,
5235
+ userId: namespace.userId,
5236
+ login: namespace.login,
5237
+ root: namespace.root,
5238
+ checkoutBaseDir: namespace.checkoutBaseDir,
5239
+ snapshotBaseDir: namespace.snapshotBaseDir,
5240
+ createdAt: typeof previous?.createdAt === "string" ? previous.createdAt : now,
5241
+ updatedAt: now
5242
+ }, null, 2)}
5243
+ `, { encoding: "utf8", mode: 384 });
5244
+ try {
5245
+ chmodSync2(namespace.metadataFile, 384);
5246
+ } catch {}
5247
+ }
5248
+
5249
+ // packages/server/src/server-helpers/github-api-session-index.ts
5250
+ function cleanString4(value) {
5251
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
5252
+ }
5253
+ function resolveGitHubApiSessionIndexFile(projectRoot) {
5254
+ return resolve17(resolveRemoteUserNamespacesRoot(projectRoot), ".api-sessions.json");
5255
+ }
5256
+ function parseEntry(value) {
5257
+ if (!value || typeof value !== "object" || Array.isArray(value))
5258
+ return null;
5259
+ const record = value;
5260
+ const token = cleanString4(record.token);
5261
+ const namespaceKey = cleanString4(record.namespaceKey);
5262
+ const namespaceRoot = cleanString4(record.namespaceRoot);
5263
+ const authStateFile = cleanString4(record.authStateFile);
5264
+ const checkoutBaseDir = cleanString4(record.checkoutBaseDir);
5265
+ const snapshotBaseDir = cleanString4(record.snapshotBaseDir);
5266
+ const createdAt = cleanString4(record.createdAt);
5267
+ if (!token || !namespaceKey || !namespaceRoot || !authStateFile || !checkoutBaseDir || !snapshotBaseDir || !createdAt)
5268
+ return null;
5269
+ return {
5270
+ token,
5271
+ namespaceKey,
5272
+ namespaceRoot,
5273
+ authStateFile,
5274
+ checkoutBaseDir,
5275
+ snapshotBaseDir,
5276
+ createdAt,
5277
+ login: cleanString4(record.login),
5278
+ userId: cleanString4(record.userId),
5279
+ selectedRepo: cleanString4(record.selectedRepo)
5280
+ };
5281
+ }
5282
+ function readIndex(indexFile) {
5283
+ if (!existsSync10(indexFile))
5284
+ return [];
5285
+ try {
5286
+ const parsed = JSON.parse(readFileSync7(indexFile, "utf8"));
5287
+ return Array.isArray(parsed.sessions) ? parsed.sessions.flatMap((entry) => {
5288
+ const parsedEntry = parseEntry(entry);
5289
+ return parsedEntry ? [parsedEntry] : [];
5290
+ }) : [];
5291
+ } catch {
5292
+ return [];
5293
+ }
5294
+ }
5295
+ function writeIndex(indexFile, sessions) {
5296
+ mkdirSync10(dirname12(indexFile), { recursive: true });
5297
+ writeFileSync9(indexFile, `${JSON.stringify({ sessions }, null, 2)}
5298
+ `, { encoding: "utf8", mode: 384 });
5299
+ try {
5300
+ chmodSync3(indexFile, 384);
5301
+ } catch {}
5302
+ }
5303
+ function registerGitHubApiSession(input) {
5304
+ const cleanToken = cleanString4(input.token);
5305
+ if (!cleanToken)
5306
+ throw new Error("GitHub API session token is required");
5307
+ const indexFile = resolveGitHubApiSessionIndexFile(input.projectRoot);
5308
+ const createdAt = new Date().toISOString();
5309
+ const entry = {
5310
+ token: cleanToken,
5311
+ login: input.namespace.login,
5312
+ userId: input.namespace.userId,
5313
+ namespaceKey: input.namespace.key,
5314
+ namespaceRoot: input.namespace.root,
5315
+ authStateFile: input.namespace.authStateFile,
5316
+ checkoutBaseDir: input.namespace.checkoutBaseDir,
5317
+ snapshotBaseDir: input.namespace.snapshotBaseDir,
5318
+ selectedRepo: cleanString4(input.selectedRepo),
5319
+ createdAt
5320
+ };
5321
+ const previous = readIndex(indexFile).filter((session) => session.token !== cleanToken);
5322
+ writeIndex(indexFile, [...previous.slice(-199), entry]);
5323
+ return entry;
5324
+ }
5325
+ function readGitHubApiSession(input) {
5326
+ const cleanToken = cleanString4(input.token);
5327
+ if (!cleanToken)
5328
+ return null;
5329
+ return readIndex(resolveGitHubApiSessionIndexFile(input.projectRoot)).find((entry) => entry.token === cleanToken) ?? null;
5330
+ }
5331
+
5005
5332
  // packages/server/src/server-helpers/inspector-agent-lifecycle.ts
5006
5333
  function createInspectorAgentLifecycleController(options) {
5007
5334
  const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
@@ -5105,21 +5432,21 @@ function inspectorAgentLifecycleSnapshot(input) {
5105
5432
  // packages/server/src/server-helpers/project-registry.ts
5106
5433
  import { createHash as createHash2 } from "crypto";
5107
5434
  import { spawnSync as spawnSync2 } from "child_process";
5108
- import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, readdirSync as readdirSync3, writeFileSync as writeFileSync8 } from "fs";
5109
- import { dirname as dirname10, resolve as resolve16 } from "path";
5435
+ import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync8, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
5436
+ import { dirname as dirname13, resolve as resolve18 } from "path";
5110
5437
  function normalizeRepoSlug(value) {
5111
5438
  const trimmed = value.trim();
5112
5439
  return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
5113
5440
  }
5114
5441
  function registryPath(projectRoot) {
5115
- return resolve16(projectRoot, ".rig", "state", "projects.json");
5442
+ return resolve18(projectRoot, ".rig", "state", "projects.json");
5116
5443
  }
5117
5444
  function readRegistry(projectRoot) {
5118
5445
  const path = registryPath(projectRoot);
5119
- if (!existsSync9(path))
5446
+ if (!existsSync11(path))
5120
5447
  return {};
5121
5448
  try {
5122
- const payload = JSON.parse(readFileSync6(path, "utf8"));
5449
+ const payload = JSON.parse(readFileSync8(path, "utf8"));
5123
5450
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
5124
5451
  return {};
5125
5452
  const projects = payload.projects;
@@ -5130,14 +5457,14 @@ function readRegistry(projectRoot) {
5130
5457
  }
5131
5458
  function writeRegistry(projectRoot, projects) {
5132
5459
  const path = registryPath(projectRoot);
5133
- mkdirSync9(dirname10(path), { recursive: true });
5134
- writeFileSync8(path, `${JSON.stringify({ projects }, null, 2)}
5460
+ mkdirSync11(dirname13(path), { recursive: true });
5461
+ writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
5135
5462
  `, "utf8");
5136
5463
  }
5137
5464
  function resolveConfigPath(projectRoot) {
5138
5465
  for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
5139
- const path = resolve16(projectRoot, name);
5140
- if (existsSync9(path))
5466
+ const path = resolve18(projectRoot, name);
5467
+ if (existsSync11(path))
5141
5468
  return path;
5142
5469
  }
5143
5470
  return null;
@@ -5146,7 +5473,7 @@ function hashFile(path) {
5146
5473
  if (!path)
5147
5474
  return null;
5148
5475
  try {
5149
- return createHash2("sha256").update(readFileSync6(path)).digest("hex");
5476
+ return createHash2("sha256").update(readFileSync8(path)).digest("hex");
5150
5477
  } catch {
5151
5478
  return null;
5152
5479
  }
@@ -5162,11 +5489,11 @@ function readDefaultBranch(projectRoot) {
5162
5489
  return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
5163
5490
  }
5164
5491
  function buildRunSummary(projectRoot) {
5165
- const runsDir = resolve16(projectRoot, ".rig", "runs");
5492
+ const runsDir = resolve18(projectRoot, ".rig", "runs");
5166
5493
  try {
5167
5494
  const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
5168
5495
  try {
5169
- const run = JSON.parse(readFileSync6(resolve16(runsDir, entry.name, "run.json"), "utf8"));
5496
+ const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
5170
5497
  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 : "" }];
5171
5498
  } catch {
5172
5499
  return [];
@@ -5218,10 +5545,14 @@ function upsertProjectRecord(projectRoot, input) {
5218
5545
  function linkProjectCheckout(projectRoot, repoSlug, checkout) {
5219
5546
  return upsertProjectRecord(projectRoot, { repoSlug, checkout });
5220
5547
  }
5548
+ function projectRegistryContainsCheckout(projectRoot, checkoutPath) {
5549
+ const target = resolve18(checkoutPath);
5550
+ return Object.values(readRegistry(projectRoot)).some((project) => project.checkouts.some((checkout) => checkout.path ? resolve18(checkout.path) === target : false));
5551
+ }
5221
5552
 
5222
5553
  // packages/server/src/server-helpers/remote-checkout.ts
5223
- import { existsSync as existsSync10, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
5224
- import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve17 } from "path";
5554
+ import { existsSync as existsSync12, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
5555
+ import { dirname as dirname14, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve19 } from "path";
5225
5556
  function safeSlugSegments(repoSlug) {
5226
5557
  const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
5227
5558
  if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
@@ -5238,7 +5569,7 @@ function safeCheckoutKey(value) {
5238
5569
  }
5239
5570
  function repoSlugPath(baseDir, repoSlug, checkoutKey) {
5240
5571
  const key = safeCheckoutKey(checkoutKey);
5241
- return resolve17(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
5572
+ return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
5242
5573
  }
5243
5574
  function sanitizeSnapshotId(value, fallback) {
5244
5575
  const raw = (value ?? fallback).trim();
@@ -5246,7 +5577,7 @@ function sanitizeSnapshotId(value, fallback) {
5246
5577
  return safe || fallback;
5247
5578
  }
5248
5579
  function assertWithinRoot(root, relativePath) {
5249
- if (!relativePath || isAbsolute2(relativePath) || relativePath.includes("\x00")) {
5580
+ if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
5250
5581
  throw new Error(`Invalid snapshot file path: ${relativePath}`);
5251
5582
  }
5252
5583
  const normalizedRelative = relativePath.replace(/\\/g, "/");
@@ -5254,9 +5585,9 @@ function assertWithinRoot(root, relativePath) {
5254
5585
  if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
5255
5586
  throw new Error(`Unsafe snapshot file path: ${relativePath}`);
5256
5587
  }
5257
- const target = resolve17(root, ...segments);
5258
- const rel = relative3(root, target);
5259
- if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute2(rel)) {
5588
+ const target = resolve19(root, ...segments);
5589
+ const rel = relative4(root, target);
5590
+ if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute3(rel)) {
5260
5591
  throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
5261
5592
  }
5262
5593
  return target;
@@ -5274,17 +5605,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
5274
5605
  function extractUploadedSnapshotArchive(input) {
5275
5606
  const archive = decodeSnapshotArchive(input.archive);
5276
5607
  const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
5277
- const checkoutPath = resolve17(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
5278
- mkdirSync10(checkoutPath, { recursive: true });
5608
+ const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
5609
+ mkdirSync12(checkoutPath, { recursive: true });
5279
5610
  for (const file of archive.files) {
5280
5611
  if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
5281
5612
  throw new Error("Invalid snapshot archive file entry");
5282
5613
  }
5283
5614
  const target = assertWithinRoot(checkoutPath, file.path);
5284
- mkdirSync10(dirname11(target), { recursive: true });
5285
- writeFileSync9(target, Buffer.from(file.contentBase64, "base64"));
5615
+ mkdirSync12(dirname14(target), { recursive: true });
5616
+ writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
5286
5617
  }
5287
- writeFileSync9(resolve17(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
5618
+ writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
5288
5619
  repoSlug: input.repoSlug,
5289
5620
  snapshotId,
5290
5621
  fileCount: archive.files.length,
@@ -5319,7 +5650,7 @@ function gitCredentialConfig(token) {
5319
5650
  };
5320
5651
  }
5321
5652
  async function prepareRemoteCheckout(input) {
5322
- const exists = input.exists ?? existsSync10;
5653
+ const exists = input.exists ?? existsSync12;
5323
5654
  const strategy = input.strategy;
5324
5655
  if (strategy.kind === "uploaded-snapshot") {
5325
5656
  return extractUploadedSnapshotArchive({
@@ -5331,7 +5662,7 @@ async function prepareRemoteCheckout(input) {
5331
5662
  });
5332
5663
  }
5333
5664
  if (strategy.kind === "existing-path") {
5334
- const checkoutPath2 = resolve17(strategy.path);
5665
+ const checkoutPath2 = resolve19(strategy.path);
5335
5666
  if (!exists(checkoutPath2)) {
5336
5667
  throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
5337
5668
  }
@@ -5367,9 +5698,9 @@ function buildServerControlStatus() {
5367
5698
  };
5368
5699
  }
5369
5700
  function buildProjectConfigStatus(root) {
5370
- const hasConfigTs = existsSync11(resolve18(root, "rig.config.ts"));
5371
- const hasConfigJson = existsSync11(resolve18(root, "rig.config.json"));
5372
- const hasLegacyTaskConfig = existsSync11(resolve18(root, ".rig", "task-config.json"));
5701
+ const hasConfigTs = existsSync13(resolve20(root, "rig.config.ts"));
5702
+ const hasConfigJson = existsSync13(resolve20(root, "rig.config.json"));
5703
+ const hasLegacyTaskConfig = existsSync13(resolve20(root, ".rig", "task-config.json"));
5373
5704
  let kind = "missing";
5374
5705
  if (hasConfigTs)
5375
5706
  kind = "rig-config-ts";
@@ -5393,10 +5724,10 @@ function normalizeCommit(value) {
5393
5724
  function asPlainRecord(value) {
5394
5725
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
5395
5726
  }
5396
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
5727
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
5397
5728
  var RIG_CONFIG_DEV_DEPENDENCIES = {
5398
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
5399
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
5729
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
5730
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
5400
5731
  };
5401
5732
  function repoParts(repoSlug) {
5402
5733
  const [owner, repo] = repoSlug.split("/");
@@ -5405,24 +5736,24 @@ function repoParts(repoSlug) {
5405
5736
  return { owner, repo, slug: `${owner}/${repo}` };
5406
5737
  }
5407
5738
  function repairDir(checkoutPath) {
5408
- const dir = resolve18(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
5409
- mkdirSync11(dir, { recursive: true });
5739
+ const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
5740
+ mkdirSync13(dir, { recursive: true });
5410
5741
  return dir;
5411
5742
  }
5412
5743
  function backupCheckoutFile(checkoutPath, relativePath) {
5413
- const source = resolve18(checkoutPath, relativePath);
5414
- const backupPath = resolve18(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
5415
- mkdirSync11(dirname12(backupPath), { recursive: true });
5416
- copyFileSync(source, backupPath);
5744
+ const source = resolve20(checkoutPath, relativePath);
5745
+ const backupPath = resolve20(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
5746
+ mkdirSync13(dirname15(backupPath), { recursive: true });
5747
+ copyFileSync2(source, backupPath);
5417
5748
  return backupPath;
5418
5749
  }
5419
5750
  function parsePackageJsonLosslessly(checkoutPath) {
5420
- const packagePath = resolve18(checkoutPath, "package.json");
5421
- if (!existsSync11(packagePath)) {
5751
+ const packagePath = resolve20(checkoutPath, "package.json");
5752
+ if (!existsSync13(packagePath)) {
5422
5753
  return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
5423
5754
  }
5424
5755
  try {
5425
- const parsed = JSON.parse(readFileSync7(packagePath, "utf8"));
5756
+ const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
5426
5757
  return {
5427
5758
  existed: true,
5428
5759
  packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
@@ -5436,9 +5767,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
5436
5767
  }
5437
5768
  }
5438
5769
  function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
5439
- const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync11(resolve18(checkoutPath, name)));
5440
- const packagePath = resolve18(checkoutPath, "package.json");
5441
- if (!hasConfig && !existsSync11(packagePath)) {
5770
+ const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
5771
+ const packagePath = resolve20(checkoutPath, "package.json");
5772
+ if (!hasConfig && !existsSync13(packagePath)) {
5442
5773
  return { skipped: true, reason: "package.json and rig.config missing" };
5443
5774
  }
5444
5775
  const parsed = parsePackageJsonLosslessly(checkoutPath);
@@ -5457,7 +5788,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
5457
5788
  }
5458
5789
  const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
5459
5790
  if (changed) {
5460
- writeFileSync10(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
5791
+ writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
5461
5792
  `, "utf8");
5462
5793
  }
5463
5794
  return {
@@ -5483,11 +5814,11 @@ function configLooksStructurallyUsable(source) {
5483
5814
  return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
5484
5815
  }
5485
5816
  function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
5486
- const configPath = resolve18(checkoutPath, "rig.config.ts");
5487
- const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync11(resolve18(checkoutPath, name)));
5817
+ const configPath = resolve20(checkoutPath, "rig.config.ts");
5818
+ const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
5488
5819
  if (existingConfigName) {
5489
- const existingPath = resolve18(checkoutPath, existingConfigName);
5490
- const source = readFileSync7(existingPath, "utf8");
5820
+ const existingPath = resolve20(checkoutPath, existingConfigName);
5821
+ const source = readFileSync9(existingPath, "utf8");
5491
5822
  if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
5492
5823
  return { path: existingPath, changed: false, reason: "config structurally complete" };
5493
5824
  }
@@ -5501,7 +5832,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
5501
5832
  }
5502
5833
  }
5503
5834
  const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
5504
- writeFileSync10(configPath, generatedRigConfigSource(repoSlug), "utf8");
5835
+ writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
5505
5836
  return {
5506
5837
  path: configPath,
5507
5838
  changed: true,
@@ -5510,7 +5841,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
5510
5841
  };
5511
5842
  }
5512
5843
  function validateRemoteCheckoutRigConfig(checkoutPath) {
5513
- const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync11(resolve18(checkoutPath, name)));
5844
+ const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
5514
5845
  if (!configFile)
5515
5846
  return { ok: false, error: "missing rig config" };
5516
5847
  if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
@@ -5532,7 +5863,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
5532
5863
  return { ok: true, configFile };
5533
5864
  }
5534
5865
  function installRemoteCheckoutPackages(checkoutPath) {
5535
- if (!existsSync11(resolve18(checkoutPath, "package.json"))) {
5866
+ if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
5536
5867
  return { skipped: true, reason: "package.json missing" };
5537
5868
  }
5538
5869
  if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
@@ -5545,8 +5876,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
5545
5876
  return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
5546
5877
  }
5547
5878
  function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
5548
- const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync11(resolve18(checkoutPath, name)));
5549
- const hasPackage = existsSync11(resolve18(checkoutPath, "package.json"));
5879
+ const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
5880
+ const hasPackage = existsSync13(resolve20(checkoutPath, "package.json"));
5550
5881
  if (!hasConfig && !hasPackage) {
5551
5882
  return {
5552
5883
  packageJson: { skipped: true, reason: "package.json and rig.config missing" },
@@ -5644,26 +5975,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
5644
5975
  }
5645
5976
  function readGitHeadCommit(projectRoot) {
5646
5977
  try {
5647
- let gitDir = resolve18(projectRoot, ".git");
5978
+ let gitDir = resolve20(projectRoot, ".git");
5648
5979
  try {
5649
- const dotGit = readFileSync7(gitDir, "utf8").trim();
5980
+ const dotGit = readFileSync9(gitDir, "utf8").trim();
5650
5981
  const gitDirPrefix = "gitdir:";
5651
5982
  if (dotGit.startsWith(gitDirPrefix)) {
5652
- gitDir = resolve18(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
5983
+ gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
5653
5984
  }
5654
5985
  } catch {}
5655
- const head = readFileSync7(resolve18(gitDir, "HEAD"), "utf8").trim();
5986
+ const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
5656
5987
  const refPrefix = "ref:";
5657
5988
  if (!head.startsWith(refPrefix)) {
5658
5989
  return normalizeCommit(head);
5659
5990
  }
5660
5991
  const ref = head.slice(refPrefix.length).trim();
5661
- const refPath = resolve18(gitDir, ref);
5662
- if (existsSync11(refPath)) {
5663
- return normalizeCommit(readFileSync7(refPath, "utf8").trim());
5992
+ const refPath = resolve20(gitDir, ref);
5993
+ if (existsSync13(refPath)) {
5994
+ return normalizeCommit(readFileSync9(refPath, "utf8").trim());
5664
5995
  }
5665
- const commonDir = normalizeString(readFileSync7(resolve18(gitDir, "commondir"), "utf8"));
5666
- return commonDir ? normalizeCommit(readFileSync7(resolve18(gitDir, commonDir, ref), "utf8").trim()) : null;
5996
+ const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
5997
+ return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
5667
5998
  } catch {
5668
5999
  return null;
5669
6000
  }
@@ -5701,9 +6032,9 @@ function configuredRepoFromTaskSource(taskSource) {
5701
6032
  const repo = normalizeString(taskSource?.repo);
5702
6033
  return owner && repo ? `${owner}/${repo}` : null;
5703
6034
  }
5704
- async function buildTaskSourceStatus(state, config) {
6035
+ async function buildTaskSourceStatus(state, config, requestAuth) {
5705
6036
  const diagnostics = state.snapshotService.getTaskSourceErrors();
5706
- const selectedRepo = createGitHubAuthStore(state.projectRoot).status({
6037
+ const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
5707
6038
  oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
5708
6039
  }).selectedRepo;
5709
6040
  try {
@@ -5750,36 +6081,134 @@ function bearerTokenFromRequest(req) {
5750
6081
  function isLoopbackRequest(req) {
5751
6082
  try {
5752
6083
  const hostname = new URL(req.url).hostname.toLowerCase();
5753
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
6084
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5754
6085
  } catch {
5755
6086
  return false;
5756
6087
  }
5757
6088
  }
5758
6089
  function isPublicRigAuthBootstrapRoute(pathname) {
5759
- 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";
6090
+ 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";
6091
+ }
6092
+ function buildRigInstallScript() {
6093
+ return `#!/usr/bin/env bash
6094
+ set -euo pipefail
6095
+
6096
+ say() {
6097
+ printf 'rig-install: %s
6098
+ ' "$*"
6099
+ }
6100
+
6101
+ if ! command -v bun >/dev/null 2>&1; then
6102
+ say "Bun not found; installing Bun first"
6103
+ curl -fsSL https://bun.sh/install | bash
6104
+ export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
6105
+ export PATH="$BUN_INSTALL/bin:$PATH"
6106
+ fi
6107
+
6108
+ if ! command -v bun >/dev/null 2>&1; then
6109
+ printf 'rig-install: bun install completed, but bun is still not on PATH. Add ~/.bun/bin to PATH and retry.
6110
+ ' >&2
6111
+ exit 1
6112
+ fi
6113
+
6114
+ say "Installing @h-rig/cli@latest"
6115
+ bun add -g @h-rig/cli@latest
6116
+
6117
+ export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
6118
+ export PATH="$BUN_INSTALL/bin:$PATH"
6119
+
6120
+ if ! command -v rig >/dev/null 2>&1; then
6121
+ printf 'rig-install: rig installed, but rig is not on PATH. Add %s/bin to PATH and retry.
6122
+ ' "$BUN_INSTALL" >&2
6123
+ exit 1
6124
+ fi
6125
+
6126
+ say "Verifying rig"
6127
+ rig --help >/dev/null
6128
+ say "Done. Run: rig --help"
6129
+ `;
5760
6130
  }
5761
6131
  function normalizePrMode(value) {
5762
6132
  const mode = normalizeString(value);
5763
6133
  return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
5764
6134
  }
6135
+ function requestAuthResult(input) {
6136
+ return {
6137
+ authorized: input.authorized,
6138
+ actor: input.actor ?? null,
6139
+ reason: input.reason,
6140
+ login: input.login ?? null,
6141
+ userId: input.userId ?? null,
6142
+ userNamespace: input.userNamespace ?? null,
6143
+ authStateFile: input.authStateFile ?? null
6144
+ };
6145
+ }
6146
+ function namespaceFromSessionIndex(entry) {
6147
+ const stateDir2 = dirname15(entry.authStateFile);
6148
+ return {
6149
+ key: entry.namespaceKey,
6150
+ userId: entry.userId,
6151
+ login: entry.login,
6152
+ root: entry.namespaceRoot,
6153
+ stateDir: stateDir2,
6154
+ authStateFile: entry.authStateFile,
6155
+ metadataFile: resolve20(stateDir2, "user-namespace.json"),
6156
+ checkoutBaseDir: entry.checkoutBaseDir,
6157
+ snapshotBaseDir: entry.snapshotBaseDir
6158
+ };
6159
+ }
5765
6160
  function authorizeRigHttpRequest(input) {
5766
6161
  if (input.legacyAuthorized) {
5767
- return { authorized: true, actor: "rig-local-server", reason: "server-token" };
6162
+ return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
5768
6163
  }
5769
6164
  const bearer = bearerTokenFromRequest(input.req);
5770
6165
  const store = createGitHubAuthStore(input.projectRoot);
5771
6166
  const storedToken = store.readToken();
6167
+ const session = bearer ? store.readApiSession(bearer) : null;
6168
+ if (session) {
6169
+ return requestAuthResult({
6170
+ authorized: true,
6171
+ actor: session.login ?? "github-operator",
6172
+ reason: "github-session",
6173
+ login: session.login,
6174
+ userId: session.userId,
6175
+ authStateFile: store.stateFile
6176
+ });
6177
+ }
5772
6178
  if (bearer && storedToken && bearer === storedToken) {
5773
6179
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
5774
- return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
6180
+ return requestAuthResult({
6181
+ authorized: true,
6182
+ actor: status.login ?? "github-operator",
6183
+ reason: "github-token",
6184
+ login: status.login,
6185
+ userId: status.userId,
6186
+ authStateFile: store.stateFile
6187
+ });
6188
+ }
6189
+ const indexedSession = readGitHubApiSession({ projectRoot: input.projectRoot, token: bearer });
6190
+ if (indexedSession) {
6191
+ const userNamespace = namespaceFromSessionIndex(indexedSession);
6192
+ return requestAuthResult({
6193
+ authorized: true,
6194
+ actor: indexedSession.login ?? "github-operator",
6195
+ reason: "github-user-session",
6196
+ login: indexedSession.login,
6197
+ userId: indexedSession.userId,
6198
+ userNamespace,
6199
+ authStateFile: indexedSession.authStateFile
6200
+ });
5775
6201
  }
5776
6202
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
5777
- return { authorized: true, actor: null, reason: "public-bootstrap" };
6203
+ return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
5778
6204
  }
5779
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
5780
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
6205
+ if (!input.serverAuthToken && !storedToken) {
6206
+ if (isLoopbackRequest(input.req)) {
6207
+ return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
6208
+ }
6209
+ return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
5781
6210
  }
5782
- return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
6211
+ return requestAuthResult({ authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" });
5783
6212
  }
5784
6213
  async function fetchGitHubUserInfo(token) {
5785
6214
  const response = await fetch("https://api.github.com/user", {
@@ -5799,6 +6228,67 @@ async function fetchGitHubUserInfo(token) {
5799
6228
  scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
5800
6229
  };
5801
6230
  }
6231
+ function shouldWriteRootAuthCompat(projectRoot) {
6232
+ if (process.env.RIG_REMOTE_USER_NAMESPACE_ROOT?.trim())
6233
+ return false;
6234
+ const stateDir2 = normalizeString(process.env.RIG_STATE_DIR);
6235
+ if (!stateDir2)
6236
+ return true;
6237
+ return resolve20(stateDir2) === resolve20(projectRoot, ".rig", "state");
6238
+ }
6239
+ function requestScopedRegistryRoot(stateProjectRoot, requestAuth) {
6240
+ return requestAuth.userNamespace?.root ?? stateProjectRoot;
6241
+ }
6242
+ function requestScopedAuthStore(stateProjectRoot, requestAuth) {
6243
+ return requestAuth.authStateFile ? createGitHubAuthStoreFromStateFile(requestAuth.authStateFile) : createGitHubAuthStore(stateProjectRoot);
6244
+ }
6245
+ function userNamespaceResponse(namespace) {
6246
+ return namespace ? serializeRemoteUserNamespace(namespace) : undefined;
6247
+ }
6248
+ function resolveNamespacedBaseDir(input) {
6249
+ if (input.explicitBaseDir)
6250
+ return input.explicitBaseDir;
6251
+ const envBase = normalizeString(process.env[input.envName]);
6252
+ if (input.userNamespace) {
6253
+ return envBase ? resolve20(envBase, input.userNamespace.key) : input.userNamespace[input.legacySubdir === "remote-checkouts" ? "checkoutBaseDir" : "snapshotBaseDir"];
6254
+ }
6255
+ return envBase ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve20(normalizeString(process.env.RIG_STATE_DIR), input.legacySubdir) : resolve20(input.legacyProjectRoot, ".rig", input.legacySubdir));
6256
+ }
6257
+ function explicitCheckoutKey(body, checkoutInput, requestAuth) {
6258
+ return normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? (requestAuth.userNamespace ? undefined : "default");
6259
+ }
6260
+ function saveGitHubTokenForRemoteUser(input) {
6261
+ const namespace = resolveRemoteUserNamespace(input.projectRoot, { userId: input.user.userId, login: input.user.login });
6262
+ writeRemoteUserNamespaceMetadata(namespace);
6263
+ const store = createGitHubAuthStoreFromStateFile(namespace.authStateFile);
6264
+ store.saveToken({
6265
+ token: input.token,
6266
+ tokenSource: input.tokenSource,
6267
+ login: input.user.login,
6268
+ userId: input.user.userId,
6269
+ scopes: input.user.scopes,
6270
+ selectedRepo: input.selectedRepo
6271
+ });
6272
+ const apiSession = store.createApiSession();
6273
+ registerGitHubApiSession({ projectRoot: input.projectRoot, token: apiSession.token, namespace, selectedRepo: input.selectedRepo });
6274
+ const requestedRoot = normalizeString(input.requestedProjectRoot);
6275
+ if (requestedRoot && isAbsolute4(requestedRoot) && existsSync13(resolve20(requestedRoot))) {
6276
+ copyGitHubAuthStateToLocalProjectRoot(namespace.authStateFile, resolve20(requestedRoot));
6277
+ }
6278
+ if (shouldWriteRootAuthCompat(input.projectRoot)) {
6279
+ const rootStore = createGitHubAuthStore(input.projectRoot);
6280
+ rootStore.saveToken({
6281
+ token: input.token,
6282
+ tokenSource: input.tokenSource,
6283
+ login: input.user.login,
6284
+ userId: input.user.userId,
6285
+ scopes: input.user.scopes,
6286
+ selectedRepo: input.selectedRepo
6287
+ });
6288
+ rootStore.createApiSession();
6289
+ }
6290
+ return { store, namespace, apiSessionToken: apiSession.token };
6291
+ }
5802
6292
  async function postGitHubForm(endpoint, body) {
5803
6293
  const response = await fetch(endpoint, {
5804
6294
  method: "POST",
@@ -5816,11 +6306,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
5816
6306
  const requestedRoot = normalizeString(rawRoot);
5817
6307
  if (!requestedRoot)
5818
6308
  return currentRoot;
5819
- if (!isAbsolute3(requestedRoot)) {
6309
+ if (!isAbsolute4(requestedRoot)) {
5820
6310
  throw new Error("projectRoot must be an absolute path on the Rig server host");
5821
6311
  }
5822
- const normalizedRoot = resolve18(requestedRoot);
5823
- if (!existsSync11(normalizedRoot)) {
6312
+ const normalizedRoot = resolve20(requestedRoot);
6313
+ if (!existsSync13(normalizedRoot)) {
5824
6314
  throw new Error("projectRoot does not exist on the Rig server host");
5825
6315
  }
5826
6316
  return normalizedRoot;
@@ -6008,7 +6498,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
6008
6498
  if (runnable.length === 0)
6009
6499
  return null;
6010
6500
  const queue = readQueueState(projectRoot);
6011
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
6501
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
6012
6502
  return runnable.toSorted((left, right) => {
6013
6503
  const leftId = taskIdOf(left) ?? "";
6014
6504
  const rightId = taskIdOf(right) ?? "";
@@ -6048,6 +6538,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
6048
6538
  }
6049
6539
  return filtered;
6050
6540
  }
6541
+ function issueAnalysisTargetFor(source) {
6542
+ if (!source)
6543
+ return null;
6544
+ const candidate = source;
6545
+ if (typeof candidate.updateTask !== "function")
6546
+ return null;
6547
+ return {
6548
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
6549
+ updateTask: candidate.updateTask.bind(candidate),
6550
+ ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
6551
+ ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
6552
+ ...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
6553
+ };
6554
+ }
6555
+ function uniqueStringList(value) {
6556
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
6557
+ return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
6558
+ }
6559
+ function taskRecordId(task) {
6560
+ return String(task.id ?? "");
6561
+ }
6051
6562
  function redactRemoteEndpoint(endpoint) {
6052
6563
  const { token, ...rest } = endpoint;
6053
6564
  return {
@@ -6132,9 +6643,16 @@ function createRigServerFetch(state, deps) {
6132
6643
  notifications: state.targets.length
6133
6644
  });
6134
6645
  }
6646
+ if (url.pathname === "/install" && req.method === "GET") {
6647
+ return new Response(buildRigInstallScript(), {
6648
+ headers: {
6649
+ "Content-Type": "text/x-shellscript; charset=utf-8"
6650
+ }
6651
+ });
6652
+ }
6135
6653
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
6136
6654
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
6137
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
6655
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
6138
6656
  const requestAuth = authorizeRigHttpRequest({
6139
6657
  req,
6140
6658
  pathname: url.pathname,
@@ -6390,9 +6908,70 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6390
6908
  note: "GitHub issue lifecycle labels are created on demand by the configured task source when supported."
6391
6909
  });
6392
6910
  }
6911
+ if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
6912
+ const body = await deps.readJsonBody(req);
6913
+ const ids = uniqueStringList(body.ids ?? body.id);
6914
+ const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
6915
+ if (ids.length === 0 && !analyzeAll) {
6916
+ return deps.badRequest("ids is required unless all=true");
6917
+ }
6918
+ const ctx = await getCachedPluginHostContext(state.projectRoot);
6919
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
6920
+ const target = issueAnalysisTargetFor(source);
6921
+ if (!source || !target) {
6922
+ return deps.badRequest("Configured task source does not support issue-analysis writeback");
6923
+ }
6924
+ const allTasks = [...await source.list()];
6925
+ const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
6926
+ const cached = allTasks.find((task) => taskRecordId(task) === id);
6927
+ if (cached)
6928
+ return cached;
6929
+ return typeof source.get === "function" ? await source.get(id) : undefined;
6930
+ }))).filter((task) => Boolean(task));
6931
+ if (issues.length === 0) {
6932
+ return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
6933
+ }
6934
+ const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
6935
+ const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
6936
+ const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
6937
+ const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
6938
+ const service = createIssueAnalysisService({
6939
+ analyzer: createPiIssueAnalyzer({
6940
+ ...model ? { model } : {},
6941
+ env: { RIG_PROJECT_ROOT: state.projectRoot }
6942
+ }),
6943
+ writeBack: createIssueAnalysisWriteBack({ target })
6944
+ });
6945
+ const reason = normalizeString(body.reason) ?? "http-issue-analysis";
6946
+ let results;
6947
+ try {
6948
+ results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
6949
+ } catch (error) {
6950
+ return deps.jsonResponse({
6951
+ ok: false,
6952
+ error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
6953
+ reason,
6954
+ ids: issues.map((issue) => issue.id)
6955
+ }, 502);
6956
+ }
6957
+ deps.snapshotService.invalidate("issue-analysis-http-run");
6958
+ await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
6959
+ return;
6960
+ });
6961
+ deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
6962
+ return deps.jsonResponse({
6963
+ ok: true,
6964
+ reason,
6965
+ analyzed: results.map((entry) => ({
6966
+ id: entry.issue.id,
6967
+ title: entry.issue.title ?? null,
6968
+ result: entry.result
6969
+ }))
6970
+ });
6971
+ }
6393
6972
  if (url.pathname === "/api/server/status") {
6394
6973
  const config = buildProjectConfigStatus(state.projectRoot);
6395
- const taskSource = await buildTaskSourceStatus(state, config);
6974
+ const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
6396
6975
  return deps.jsonResponse({
6397
6976
  ok: true,
6398
6977
  projectRoot: state.projectRoot,
@@ -6416,8 +6995,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6416
6995
  path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
6417
6996
  ref: normalizeString(rawCheckout?.ref) ?? undefined
6418
6997
  } : undefined;
6419
- const record = upsertProjectRecord(state.projectRoot, { repoSlug, checkout });
6420
- return deps.jsonResponse({ ok: true, project: record });
6998
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
6999
+ const record = upsertProjectRecord(registryRoot, { repoSlug, checkout });
7000
+ return deps.jsonResponse({ ok: true, project: record, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6421
7001
  }
6422
7002
  const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
6423
7003
  if (snapshotUploadMatch && req.method === "POST") {
@@ -6430,8 +7010,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6430
7010
  if (!archiveContentBase64) {
6431
7011
  return deps.badRequest("archiveContentBase64 is required");
6432
7012
  }
6433
- 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"));
6434
- const checkoutKey = normalizeString(body.checkoutKey) ?? "default";
7013
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
7014
+ const baseDir = resolveNamespacedBaseDir({
7015
+ explicitBaseDir: normalizeString(body.baseDir),
7016
+ envName: "RIG_REMOTE_SNAPSHOT_BASE_DIR",
7017
+ userNamespace: requestAuth.userNamespace,
7018
+ legacyProjectRoot: state.projectRoot,
7019
+ legacySubdir: "remote-snapshots"
7020
+ });
7021
+ const checkoutKey = explicitCheckoutKey(body, body, requestAuth);
6435
7022
  try {
6436
7023
  const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
6437
7024
  const checkout = extractUploadedSnapshotArchive({
@@ -6444,14 +7031,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6444
7031
  const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
6445
7032
  const packageInstall = installRemoteCheckoutPackages(checkout.path);
6446
7033
  const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
6447
- const project = linkProjectCheckout(state.projectRoot, repoSlug, {
7034
+ const project = linkProjectCheckout(registryRoot, repoSlug, {
6448
7035
  kind: "uploaded-snapshot",
6449
7036
  path: checkout.path,
6450
7037
  ref: checkout.snapshotId
6451
7038
  });
6452
7039
  deps.snapshotService.invalidate("uploaded-snapshot-checkout");
6453
7040
  deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
6454
- return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
7041
+ return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6455
7042
  } catch (error) {
6456
7043
  return deps.jsonResponse({
6457
7044
  ok: false,
@@ -6471,10 +7058,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6471
7058
  if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
6472
7059
  return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
6473
7060
  }
6474
- 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"));
6475
- const checkoutKey = normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? "default";
7061
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
7062
+ const baseDir = resolveNamespacedBaseDir({
7063
+ explicitBaseDir: normalizeString(body.baseDir) ?? normalizeString(checkoutInput.baseDir),
7064
+ envName: "RIG_REMOTE_CHECKOUT_BASE_DIR",
7065
+ userNamespace: requestAuth.userNamespace,
7066
+ legacyProjectRoot: state.projectRoot,
7067
+ legacySubdir: "remote-checkouts"
7068
+ });
7069
+ const checkoutKey = explicitCheckoutKey(body, checkoutInput, requestAuth);
6476
7070
  const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
6477
- const credentialToken = createGitHubAuthStore(state.projectRoot).readToken();
7071
+ const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
6478
7072
  try {
6479
7073
  const checkout = await prepareRemoteCheckout({
6480
7074
  command: runRemoteCheckoutCommand,
@@ -6483,14 +7077,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6483
7077
  const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
6484
7078
  const packageInstall = installRemoteCheckoutPackages(checkout.path);
6485
7079
  const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
6486
- const project = linkProjectCheckout(state.projectRoot, repoSlug, {
7080
+ const project = linkProjectCheckout(registryRoot, repoSlug, {
6487
7081
  kind: checkout.kind,
6488
7082
  path: checkout.path,
6489
7083
  ref: checkout.ref ?? checkout.snapshotId ?? undefined
6490
7084
  });
6491
7085
  deps.snapshotService.invalidate("remote-checkout-prepared");
6492
7086
  deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
6493
- return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
7087
+ return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6494
7088
  } catch (error) {
6495
7089
  return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
6496
7090
  }
@@ -6507,16 +7101,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6507
7101
  if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
6508
7102
  return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
6509
7103
  }
6510
- const project = linkProjectCheckout(state.projectRoot, repoSlug, {
7104
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
7105
+ const project = linkProjectCheckout(registryRoot, repoSlug, {
6511
7106
  kind,
6512
7107
  path: normalizeString(body.path) ?? state.projectRoot,
6513
7108
  ref: normalizeString(body.ref) ?? undefined
6514
7109
  });
6515
- return deps.jsonResponse({ ok: true, project });
7110
+ return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6516
7111
  }
6517
7112
  if (req.method === "GET") {
6518
- const project = getProjectRecord(state.projectRoot, repoSlug);
6519
- return project ? deps.jsonResponse({ ok: true, project }) : deps.notFound();
7113
+ const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
7114
+ const project = getProjectRecord(registryRoot, repoSlug);
7115
+ return project ? deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) }) : deps.notFound();
6520
7116
  }
6521
7117
  }
6522
7118
  if (url.pathname === "/api/server/project-root" && req.method === "POST") {
@@ -6525,11 +7121,27 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6525
7121
  if (!requestedRoot) {
6526
7122
  return deps.badRequest("projectRoot is required");
6527
7123
  }
6528
- if (!isAbsolute3(requestedRoot)) {
7124
+ if (!isAbsolute4(requestedRoot)) {
6529
7125
  return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
6530
7126
  }
6531
- const normalizedRoot = resolve18(requestedRoot);
6532
- const exists = existsSync11(normalizedRoot);
7127
+ const normalizedRoot = resolve20(requestedRoot);
7128
+ const exists = existsSync13(normalizedRoot);
7129
+ if (exists && requestAuth.userNamespace) {
7130
+ const allowedByNamespace = isPathInsideNamespace(requestAuth.userNamespace.root, normalizedRoot);
7131
+ const allowedByRegistry = projectRegistryContainsCheckout(requestAuth.userNamespace.root, normalizedRoot);
7132
+ if (!allowedByNamespace && !allowedByRegistry) {
7133
+ return deps.jsonResponse({
7134
+ ok: false,
7135
+ error: "Requested project root is outside the authenticated GitHub user namespace.",
7136
+ projectRoot: state.projectRoot,
7137
+ requestedProjectRoot: normalizedRoot,
7138
+ userNamespace: userNamespaceResponse(requestAuth.userNamespace)
7139
+ }, 403);
7140
+ }
7141
+ copyGitHubAuthStateToLocalProjectRoot(requestAuth.userNamespace.authStateFile, normalizedRoot);
7142
+ } else if (exists) {
7143
+ createGitHubAuthStore(state.projectRoot).copyToLocalProjectRoot(normalizedRoot);
7144
+ }
6533
7145
  const control = buildServerControlStatus();
6534
7146
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
6535
7147
  if (!exists) {
@@ -6543,7 +7155,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6543
7155
  message: "Requested project root does not exist on the Rig server host."
6544
7156
  }, 404);
6545
7157
  }
6546
- if (!existsSync11(resolve18(normalizedRoot, "rig.config.ts")) && !existsSync11(resolve18(normalizedRoot, "rig.config.json"))) {
7158
+ if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
6547
7159
  return deps.jsonResponse({
6548
7160
  ok: false,
6549
7161
  projectRoot: state.projectRoot,
@@ -6578,6 +7190,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6578
7190
  exists,
6579
7191
  control,
6580
7192
  requiresRestart: false,
7193
+ userNamespace: userNamespaceResponse(requestAuth.userNamespace),
6581
7194
  message: "Project-root switch accepted. Rig server restart has been scheduled."
6582
7195
  }, 202);
6583
7196
  }
@@ -6592,11 +7205,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6592
7205
  }, 409);
6593
7206
  }
6594
7207
  if (url.pathname === "/api/github/auth/status") {
6595
- const store = createGitHubAuthStore(state.projectRoot);
6596
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
7208
+ const store = requestScopedAuthStore(state.projectRoot, requestAuth);
7209
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
6597
7210
  }
6598
7211
  if (url.pathname === "/api/github/repo/permissions") {
6599
- const store = createGitHubAuthStore(state.projectRoot);
7212
+ const store = requestScopedAuthStore(state.projectRoot, requestAuth);
6600
7213
  const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
6601
7214
  if (!auth.signedIn) {
6602
7215
  return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
@@ -6618,21 +7231,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6618
7231
  const body = await deps.readJsonBody(req);
6619
7232
  const token = normalizeString(body.token);
6620
7233
  const selectedRepo = normalizeString(body.selectedRepo);
7234
+ const requestedProjectRoot = normalizeString(body.projectRoot);
6621
7235
  if (!token) {
6622
7236
  return deps.badRequest("token is required");
6623
7237
  }
6624
7238
  try {
6625
7239
  const user = await fetchGitHubUserInfo(token);
6626
- const store = createGitHubAuthStore(state.projectRoot);
6627
- store.saveToken({
7240
+ const saved = saveGitHubTokenForRemoteUser({
7241
+ projectRoot: state.projectRoot,
6628
7242
  token,
6629
7243
  tokenSource: "manual-token",
6630
- login: user.login,
6631
- userId: user.userId,
6632
- scopes: user.scopes,
6633
- selectedRepo
7244
+ user,
7245
+ selectedRepo,
7246
+ requestedProjectRoot
7247
+ });
7248
+ return deps.jsonResponse({
7249
+ ok: true,
7250
+ ...saved.store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }),
7251
+ apiSessionToken: saved.apiSessionToken,
7252
+ userNamespace: userNamespaceResponse(saved.namespace)
6634
7253
  });
6635
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
6636
7254
  } catch (error) {
6637
7255
  const message = error instanceof Error ? error.message : String(error);
6638
7256
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -6699,8 +7317,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6699
7317
  }
6700
7318
  const token = result.payload.access_token;
6701
7319
  const user = await fetchGitHubUserInfo(token);
6702
- store.saveToken({ token, tokenSource: "oauth-device", login: user.login, userId: user.userId, scopes: user.scopes });
6703
- return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }) });
7320
+ const saved = saveGitHubTokenForRemoteUser({
7321
+ projectRoot: state.projectRoot,
7322
+ token,
7323
+ tokenSource: "oauth-device",
7324
+ user,
7325
+ selectedRepo: null
7326
+ });
7327
+ store.clearPendingDevice(pollId);
7328
+ return deps.jsonResponse({
7329
+ ok: true,
7330
+ status: "signed-in",
7331
+ ...saved.store.status({ oauthConfigured: true }),
7332
+ apiSessionToken: saved.apiSessionToken,
7333
+ userNamespace: userNamespaceResponse(saved.namespace)
7334
+ });
6704
7335
  }
6705
7336
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
6706
7337
  const body = await deps.readJsonBody(req);
@@ -6709,7 +7340,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6709
7340
  if (!owner || !repo) {
6710
7341
  return deps.badRequest("owner and repo are required");
6711
7342
  }
6712
- const store = createGitHubAuthStore(state.projectRoot);
7343
+ const store = requestScopedAuthStore(state.projectRoot, requestAuth);
6713
7344
  const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
6714
7345
  const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
6715
7346
  return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
@@ -6730,7 +7361,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6730
7361
  return deps.badRequest(error instanceof Error ? error.message : String(error));
6731
7362
  }
6732
7363
  const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
6733
- const configPath = resolve18(targetRoot, "rig.config.ts");
7364
+ const configPath = resolve20(targetRoot, "rig.config.ts");
6734
7365
  const source = buildGitHubProjectConfigSource({
6735
7366
  projectName: rawProjectName,
6736
7367
  owner,
@@ -6742,8 +7373,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6742
7373
  ok: true,
6743
7374
  projectRoot: targetRoot,
6744
7375
  configPath,
6745
- exists: existsSync11(configPath),
6746
- requiresOverwrite: existsSync11(configPath),
7376
+ exists: existsSync13(configPath),
7377
+ requiresOverwrite: existsSync13(configPath),
6747
7378
  source,
6748
7379
  owner,
6749
7380
  repo,
@@ -6779,8 +7410,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6779
7410
  assignee,
6780
7411
  githubUserId: authStatus.userId ?? authStatus.login
6781
7412
  });
6782
- const configPath = resolve18(targetRoot, "rig.config.ts");
6783
- if (existsSync11(configPath) && !overwrite) {
7413
+ const configPath = resolve20(targetRoot, "rig.config.ts");
7414
+ if (existsSync13(configPath) && !overwrite) {
6784
7415
  return deps.jsonResponse({
6785
7416
  ok: false,
6786
7417
  error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
@@ -6796,11 +7427,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6796
7427
  return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
6797
7428
  }
6798
7429
  let backupPath = null;
6799
- if (existsSync11(configPath)) {
7430
+ if (existsSync13(configPath)) {
6800
7431
  backupPath = backupConfigPath(configPath);
6801
- copyFileSync(configPath, backupPath);
7432
+ copyFileSync2(configPath, backupPath);
6802
7433
  }
6803
- writeFileSync10(configPath, source, "utf8");
7434
+ writeFileSync12(configPath, source, "utf8");
6804
7435
  const selectedRepo = `${owner}/${repo}`;
6805
7436
  store.saveSelectedRepo(selectedRepo);
6806
7437
  const targetStore = createGitHubAuthStore(targetRoot);
@@ -7072,11 +7703,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
7072
7703
  const runId = normalizeString(body.runId);
7073
7704
  const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
7074
7705
  const promptOverride = normalizeString(body.promptOverride);
7706
+ const restart = body.restart === true;
7075
7707
  if (!runId) {
7076
7708
  return deps.badRequest("runId is required");
7077
7709
  }
7078
7710
  try {
7079
- await deps.resumeRunRecord(state, { runId, createdAt, promptOverride });
7711
+ await deps.resumeRunRecord(state, { runId, createdAt, promptOverride, restart });
7080
7712
  deps.broadcastSnapshotInvalidation(state);
7081
7713
  return deps.jsonResponse({ ok: true, runId, createdAt });
7082
7714
  } catch (error) {
@@ -7085,7 +7717,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
7085
7717
  }
7086
7718
  if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
7087
7719
  const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
7088
- 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";
7720
+ 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";
7089
7721
  if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
7090
7722
  return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
7091
7723
  }
@@ -7401,9 +8033,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
7401
8033
  } catch {
7402
8034
  return deps.badRequest("Invalid artifact path");
7403
8035
  }
7404
- mkdirSync11(dirname12(artifactPath), { recursive: true });
8036
+ mkdirSync13(dirname15(artifactPath), { recursive: true });
7405
8037
  const bytes = Buffer.from(contentBase64, "base64");
7406
- writeFileSync10(artifactPath, bytes);
8038
+ writeFileSync12(artifactPath, bytes);
7407
8039
  writeJsonFile4(`${artifactPath}.json`, {
7408
8040
  workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
7409
8041
  runId,
@@ -7565,12 +8197,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
7565
8197
  try {
7566
8198
  const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
7567
8199
  const runRoot = deps.normalizeRelativePath(runsRoot, runId);
7568
- const artifactsRoot = resolve18(runRoot, "remote-artifacts");
8200
+ const artifactsRoot = resolve20(runRoot, "remote-artifacts");
7569
8201
  artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
7570
8202
  } catch {
7571
8203
  return deps.badRequest("Invalid artifact path");
7572
8204
  }
7573
- if (!existsSync11(artifactPath)) {
8205
+ if (!existsSync13(artifactPath)) {
7574
8206
  return deps.notFound();
7575
8207
  }
7576
8208
  return new Response(Bun.file(artifactPath));
@@ -8463,8 +9095,8 @@ async function routeWebSocketRequest(state, deps, request) {
8463
9095
  }
8464
9096
 
8465
9097
  // packages/server/src/server-helpers/inspector-jobs.ts
8466
- import { existsSync as existsSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync10, writeFileSync as writeFileSync13 } from "fs";
8467
- import { dirname as dirname15, resolve as resolve21 } from "path";
9098
+ import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync15 } from "fs";
9099
+ import { dirname as dirname18, resolve as resolve23 } from "path";
8468
9100
  import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
8469
9101
  import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
8470
9102
  import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
@@ -8572,8 +9204,8 @@ import { randomUUID as randomUUID3 } from "crypto";
8572
9204
 
8573
9205
  // packages/server/src/inspector/mission.ts
8574
9206
  import { randomUUID as randomUUID2 } from "crypto";
8575
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync8, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync11 } from "fs";
8576
- import { dirname as dirname13, join, resolve as resolve19 } from "path";
9207
+ import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync13 } from "fs";
9208
+ import { dirname as dirname16, join, resolve as resolve21 } from "path";
8577
9209
  function isJsonValue(value) {
8578
9210
  if (value === null)
8579
9211
  return true;
@@ -8613,7 +9245,7 @@ function isRecord2(value) {
8613
9245
  }
8614
9246
  function readJsonRecord(path) {
8615
9247
  try {
8616
- const parsed = JSON.parse(readFileSync8(path, "utf8"));
9248
+ const parsed = JSON.parse(readFileSync10(path, "utf8"));
8617
9249
  if (!isRecord2(parsed)) {
8618
9250
  return { ok: false, error: `Mission file ${path} does not contain an object` };
8619
9251
  }
@@ -8693,14 +9325,14 @@ function missionActionDetails(mission) {
8693
9325
  };
8694
9326
  }
8695
9327
  function writeJsonFile5(path, value) {
8696
- mkdirSync12(dirname13(path), { recursive: true });
9328
+ mkdirSync14(dirname16(path), { recursive: true });
8697
9329
  const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
8698
- writeFileSync11(tempPath, `${JSON.stringify(value, null, 2)}
9330
+ writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
8699
9331
  `, "utf8");
8700
9332
  renameSync(tempPath, path);
8701
9333
  }
8702
9334
  function resolveInspectorMissionPaths(projectRoot) {
8703
- const inspectorDir = resolve19(resolveRigServerPaths(projectRoot).stateDir, "inspector");
9335
+ const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
8704
9336
  return {
8705
9337
  inspectorDir,
8706
9338
  missionsDir: join(inspectorDir, "missions"),
@@ -8709,8 +9341,8 @@ function resolveInspectorMissionPaths(projectRoot) {
8709
9341
  }
8710
9342
  function createInspectorMissionController(options) {
8711
9343
  const paths = resolveInspectorMissionPaths(options.projectRoot);
8712
- mkdirSync12(paths.missionsDir, { recursive: true });
8713
- mkdirSync12(paths.journalsDir, { recursive: true });
9344
+ mkdirSync14(paths.missionsDir, { recursive: true });
9345
+ mkdirSync14(paths.journalsDir, { recursive: true });
8714
9346
  const now = options.now ?? (() => new Date().toISOString());
8715
9347
  const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
8716
9348
  function missionPath(missionId) {
@@ -8720,15 +9352,15 @@ function createInspectorMissionController(options) {
8720
9352
  return join(paths.journalsDir, `${missionId}.jsonl`);
8721
9353
  }
8722
9354
  function appendMissionJournal(entry) {
8723
- mkdirSync12(paths.journalsDir, { recursive: true });
9355
+ mkdirSync14(paths.journalsDir, { recursive: true });
8724
9356
  appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
8725
9357
  `, "utf8");
8726
9358
  }
8727
9359
  function listMissionJournal(missionId) {
8728
9360
  const path = journalPath(missionId);
8729
- if (!existsSync12(path))
9361
+ if (!existsSync14(path))
8730
9362
  return [];
8731
- return readFileSync8(path, "utf8").split(`
9363
+ return readFileSync10(path, "utf8").split(`
8732
9364
  `).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
8733
9365
  id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
8734
9366
  missionId,
@@ -8744,7 +9376,7 @@ function createInspectorMissionController(options) {
8744
9376
  }
8745
9377
  function readMissionOnly(missionId) {
8746
9378
  const path = missionPath(missionId);
8747
- if (!existsSync12(path)) {
9379
+ if (!existsSync14(path)) {
8748
9380
  return { ok: false, error: `Mission ${missionId} was not found` };
8749
9381
  }
8750
9382
  const read = readJsonRecord(path);
@@ -8795,7 +9427,7 @@ function createInspectorMissionController(options) {
8795
9427
  const source = cloneJsonRecord(input.sourceTask);
8796
9428
  const missionId = nextId();
8797
9429
  const path = missionPath(missionId);
8798
- if (existsSync12(path)) {
9430
+ if (existsSync14(path)) {
8799
9431
  const existing = readMissionOnly(missionId);
8800
9432
  if (!existing.ok)
8801
9433
  return existing;
@@ -10395,8 +11027,8 @@ function createCodexInspectorTransport(options) {
10395
11027
  const sendRequest = async (method, params) => {
10396
11028
  const id = nextRequestId;
10397
11029
  nextRequestId += 1;
10398
- const response = new Promise((resolve20, reject) => {
10399
- pendingResponses.set(id, { resolve: resolve20, reject });
11030
+ const response = new Promise((resolve22, reject) => {
11031
+ pendingResponses.set(id, { resolve: resolve22, reject });
10400
11032
  });
10401
11033
  response.catch(() => {});
10402
11034
  try {
@@ -10706,9 +11338,9 @@ function createCodexInspectorTransport(options) {
10706
11338
  }
10707
11339
  lastAssistantMessage = null;
10708
11340
  lastError = null;
10709
- const turnResult = new Promise((resolve20, reject) => {
11341
+ const turnResult = new Promise((resolve22, reject) => {
10710
11342
  currentTurn = {
10711
- resolve: resolve20,
11343
+ resolve: resolve22,
10712
11344
  reject,
10713
11345
  events: []
10714
11346
  };
@@ -10768,13 +11400,13 @@ function createCodexInspectorTransport(options) {
10768
11400
  };
10769
11401
  }
10770
11402
  function writeChildLine(child, line) {
10771
- return new Promise((resolve20, reject) => {
11403
+ return new Promise((resolve22, reject) => {
10772
11404
  child.stdin.write(line, (error) => {
10773
11405
  if (error) {
10774
11406
  reject(error);
10775
11407
  return;
10776
11408
  }
10777
- resolve20();
11409
+ resolve22();
10778
11410
  });
10779
11411
  });
10780
11412
  }
@@ -10787,10 +11419,10 @@ function terminateChild(child) {
10787
11419
  } catch {}
10788
11420
  }
10789
11421
  async function waitForChildSpawn(child) {
10790
- await new Promise((resolve20, reject) => {
11422
+ await new Promise((resolve22, reject) => {
10791
11423
  const onSpawn = () => {
10792
11424
  cleanup();
10793
- resolve20();
11425
+ resolve22();
10794
11426
  };
10795
11427
  const onError = (error) => {
10796
11428
  cleanup();
@@ -11302,8 +11934,8 @@ function createGlobalInspectorService(options) {
11302
11934
 
11303
11935
  // packages/server/src/inspector/upstream-sync.ts
11304
11936
  import { spawnSync as spawnSync4 } from "child_process";
11305
- import { existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
11306
- import { dirname as dirname14, resolve as resolve20 } from "path";
11937
+ import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync14 } from "fs";
11938
+ import { dirname as dirname17, resolve as resolve22 } from "path";
11307
11939
  import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
11308
11940
  var UPSTREAM_VALIDATION_DESCRIPTIONS = {
11309
11941
  "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.",
@@ -11441,34 +12073,34 @@ function defaultGitRunner(repoRoot, args) {
11441
12073
  }
11442
12074
  function upstreamStatePath(projectRoot, override) {
11443
12075
  if (override) {
11444
- return resolve20(override);
12076
+ return resolve22(override);
11445
12077
  }
11446
- return resolve20(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
12078
+ return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
11447
12079
  }
11448
12080
  function readUpstreamState(projectRoot, statePath) {
11449
12081
  const path = upstreamStatePath(projectRoot, statePath);
11450
- if (!existsSync13(path)) {
12082
+ if (!existsSync15(path)) {
11451
12083
  return null;
11452
12084
  }
11453
12085
  try {
11454
- return JSON.parse(readFileSync9(path, "utf-8"));
12086
+ return JSON.parse(readFileSync11(path, "utf-8"));
11455
12087
  } catch {
11456
12088
  return null;
11457
12089
  }
11458
12090
  }
11459
12091
  function writeUpstreamState(projectRoot, state, statePath) {
11460
12092
  const path = upstreamStatePath(projectRoot, statePath);
11461
- mkdirSync13(dirname14(path), { recursive: true });
11462
- writeFileSync12(path, `${JSON.stringify(state, null, 2)}
12093
+ mkdirSync15(dirname17(path), { recursive: true });
12094
+ writeFileSync14(path, `${JSON.stringify(state, null, 2)}
11463
12095
  `, "utf8");
11464
12096
  }
11465
12097
  function readImportedRevision(projectRoot, upstreamsDocPath) {
11466
12098
  const monorepoRoot = resolveMonorepoRoot4(projectRoot);
11467
- const docPath = upstreamsDocPath ? resolve20(upstreamsDocPath) : resolve20(monorepoRoot, "docs", "UPSTREAMS.md");
11468
- if (!existsSync13(docPath)) {
12099
+ const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
12100
+ if (!existsSync15(docPath)) {
11469
12101
  throw new Error(`UPSTREAMS.md not found at ${docPath}`);
11470
12102
  }
11471
- const docContent = readFileSync9(docPath, "utf-8");
12103
+ const docContent = readFileSync11(docPath, "utf-8");
11472
12104
  const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
11473
12105
  if (!revision) {
11474
12106
  throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
@@ -11490,7 +12122,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
11490
12122
  return null;
11491
12123
  }
11492
12124
  function isGitCheckout(path, gitRunner) {
11493
- if (!existsSync13(resolve20(path, ".git"))) {
12125
+ if (!existsSync15(resolve22(path, ".git"))) {
11494
12126
  return false;
11495
12127
  }
11496
12128
  const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
@@ -11499,12 +12131,12 @@ function isGitCheckout(path, gitRunner) {
11499
12131
  function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
11500
12132
  const monorepoRoot = resolveMonorepoRoot4(projectRoot);
11501
12133
  const candidates = [
11502
- explicitCheckout ? resolve20(explicitCheckout) : "",
11503
- process.env.UPSTREAM_CHECKOUT?.trim() ? resolve20(process.env.UPSTREAM_CHECKOUT.trim()) : "",
11504
- process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve20(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
11505
- resolve20(projectRoot, "..", "humoongate"),
11506
- resolve20(monorepoRoot, "..", "humoongate"),
11507
- resolve20(monorepoRoot, "humoongate")
12134
+ explicitCheckout ? resolve22(explicitCheckout) : "",
12135
+ process.env.UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.UPSTREAM_CHECKOUT.trim()) : "",
12136
+ process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
12137
+ resolve22(projectRoot, "..", "humoongate"),
12138
+ resolve22(monorepoRoot, "..", "humoongate"),
12139
+ resolve22(monorepoRoot, "humoongate")
11508
12140
  ].filter(Boolean);
11509
12141
  for (const candidate of candidates) {
11510
12142
  if (isGitCheckout(candidate, gitRunner)) {
@@ -11740,10 +12372,10 @@ async function runUpstreamSyncScan(options) {
11740
12372
  }
11741
12373
 
11742
12374
  // packages/server/src/server-helpers/task-config.ts
11743
- import { existsSync as existsSync14 } from "fs";
12375
+ import { existsSync as existsSync16 } from "fs";
11744
12376
  async function readTaskConfig(projectRoot) {
11745
12377
  const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
11746
- if (!existsSync14(taskConfigPath)) {
12378
+ if (!existsSync16(taskConfigPath)) {
11747
12379
  return {};
11748
12380
  }
11749
12381
  try {
@@ -11779,11 +12411,11 @@ function resolveFollowupSourceCommit(input) {
11779
12411
  }
11780
12412
  async function createInspectorFollowupTask(projectRoot, input) {
11781
12413
  const monorepoRoot = resolveMonorepoRoot5(projectRoot);
11782
- const issuesPath = resolve21(monorepoRoot, ".beads", "issues.jsonl");
11783
- const taskStatePath = resolve21(monorepoRoot, ".beads", "task-state.json");
11784
- const taskConfigPath = resolve21(monorepoRoot, ".rig", "task-config.json");
11785
- mkdirSync14(dirname15(issuesPath), { recursive: true });
11786
- mkdirSync14(dirname15(taskConfigPath), { recursive: true });
12414
+ const issuesPath = resolve23(monorepoRoot, ".beads", "issues.jsonl");
12415
+ const taskStatePath = resolve23(monorepoRoot, ".beads", "task-state.json");
12416
+ const taskConfigPath = resolve23(monorepoRoot, ".rig", "task-config.json");
12417
+ mkdirSync16(dirname18(issuesPath), { recursive: true });
12418
+ mkdirSync16(dirname18(taskConfigPath), { recursive: true });
11787
12419
  const summary = normalizeString(input.summary) ?? "Inspector follow-up";
11788
12420
  const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
11789
12421
  const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
@@ -11802,7 +12434,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
11802
12434
  const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
11803
12435
  const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
11804
12436
  const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
11805
- const existingIssueLines = existsSync15(issuesPath) ? readFileSync10(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
12437
+ const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
11806
12438
  const existingIssues = existingIssueLines.map((line) => {
11807
12439
  try {
11808
12440
  return JSON.parse(line);
@@ -11811,7 +12443,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
11811
12443
  }
11812
12444
  }).filter((value) => value !== null);
11813
12445
  const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
11814
- const rawTaskState = existsSync15(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
12446
+ const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
11815
12447
  const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
11816
12448
  const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
11817
12449
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
@@ -11837,7 +12469,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
11837
12469
  updated_at: createdAt,
11838
12470
  labels: mergedLabels
11839
12471
  };
11840
- writeFileSync13(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
12472
+ writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
11841
12473
  `)}
11842
12474
  ${JSON.stringify(issueRecord)}
11843
12475
  ` : `${JSON.stringify(issueRecord)}
@@ -11855,7 +12487,7 @@ ${JSON.stringify(issueRecord)}
11855
12487
  labels: mergedLabels
11856
12488
  };
11857
12489
  });
11858
- writeFileSync13(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
12490
+ writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
11859
12491
  `)}
11860
12492
  `, "utf8");
11861
12493
  }
@@ -11878,14 +12510,14 @@ ${JSON.stringify(issueRecord)}
11878
12510
  }
11879
12511
  };
11880
12512
  }
11881
- writeFileSync13(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
12513
+ writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
11882
12514
  `, "utf8");
11883
12515
  tasks[taskId] = {
11884
12516
  status,
11885
12517
  sourceCommit: resolveFollowupSourceCommit(input),
11886
12518
  ...sourceKey ? { sourceKey } : {}
11887
12519
  };
11888
- writeFileSync13(taskStatePath, `${JSON.stringify({
12520
+ writeFileSync15(taskStatePath, `${JSON.stringify({
11889
12521
  schemaVersion: 1,
11890
12522
  baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
11891
12523
  tasks
@@ -12193,12 +12825,12 @@ function isAuthorizedLinearWebhookRequest(req) {
12193
12825
  }
12194
12826
 
12195
12827
  // packages/server/src/server-helpers/notifications.ts
12196
- import { existsSync as existsSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync11 } from "fs";
12197
- import { dirname as dirname16 } from "path";
12828
+ import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
12829
+ import { dirname as dirname19 } from "path";
12198
12830
  async function loadNotificationConfig(path) {
12199
- if (!existsSync16(path)) {
12831
+ if (!existsSync18(path)) {
12200
12832
  const defaultConfig = { targets: [] };
12201
- mkdirSync15(dirname16(path), { recursive: true });
12833
+ mkdirSync17(dirname19(path), { recursive: true });
12202
12834
  await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
12203
12835
  `);
12204
12836
  return defaultConfig;
@@ -12213,10 +12845,10 @@ async function loadNotificationConfig(path) {
12213
12845
  }
12214
12846
  }
12215
12847
  function readRecentEvents(file, limit) {
12216
- if (!existsSync16(file)) {
12848
+ if (!existsSync18(file)) {
12217
12849
  return [];
12218
12850
  }
12219
- const lines = readFileSync11(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
12851
+ const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
12220
12852
  const events = [];
12221
12853
  for (const line of lines) {
12222
12854
  try {
@@ -12311,11 +12943,11 @@ function extractObjectLiteralBlock(source, property) {
12311
12943
  }
12312
12944
  function readFallbackIssueAnalysisConfig(projectRoot) {
12313
12945
  for (const fileName of ["rig.config.ts", "rig.config.json"]) {
12314
- const path = resolve22(projectRoot, fileName);
12315
- if (!existsSync17(path))
12946
+ const path = resolve24(projectRoot, fileName);
12947
+ if (!existsSync19(path))
12316
12948
  continue;
12317
12949
  try {
12318
- const source = readFileSync12(path, "utf8");
12950
+ const source = readFileSync14(path, "utf8");
12319
12951
  if (fileName.endsWith(".json"))
12320
12952
  return JSON.parse(source);
12321
12953
  const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
@@ -12448,8 +13080,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
12448
13080
  async function withServerPathEnv(projectRoot, fn) {
12449
13081
  const waitForTurn = serverPathEnvQueue;
12450
13082
  let releaseTurn;
12451
- serverPathEnvQueue = new Promise((resolve23) => {
12452
- releaseTurn = resolve23;
13083
+ serverPathEnvQueue = new Promise((resolve25) => {
13084
+ releaseTurn = resolve25;
12453
13085
  });
12454
13086
  await waitForTurn;
12455
13087
  const paths = resolveServerAuthorityPaths(projectRoot);
@@ -12485,9 +13117,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
12485
13117
  return withServerPathEnv(projectRoot, fn);
12486
13118
  }
12487
13119
  async function readWorkspaceTasks(projectRoot) {
12488
- const issuesPath = resolve22(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
13120
+ const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
12489
13121
  const taskConfig = await readTaskConfig(projectRoot);
12490
- if (!existsSync17(issuesPath)) {
13122
+ if (!existsSync19(issuesPath)) {
12491
13123
  return [];
12492
13124
  }
12493
13125
  const latestById = new Map;
@@ -12506,7 +13138,7 @@ async function readWorkspaceTasks(projectRoot) {
12506
13138
  description: normalizeString(entry.description),
12507
13139
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
12508
13140
  status: normalizedStatus ?? "unknown",
12509
- sourceStatus: normalizedStatus ? null : rawStatus,
13141
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
12510
13142
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
12511
13143
  issueType: normalizeString(entry.issue_type),
12512
13144
  role: normalizeString(config.role) ?? null,
@@ -12561,11 +13193,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
12561
13193
  continue;
12562
13194
  add(run.artifactRoot);
12563
13195
  if (run.worktreePath) {
12564
- add(resolve22(run.worktreePath, "artifacts", taskId));
13196
+ add(resolve24(run.worktreePath, "artifacts", taskId));
12565
13197
  }
12566
13198
  }
12567
13199
  for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
12568
- add(resolve22(artifactsRoot, taskId));
13200
+ add(resolve24(artifactsRoot, taskId));
12569
13201
  }
12570
13202
  return candidates;
12571
13203
  }
@@ -12579,7 +13211,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
12579
13211
  }
12580
13212
  }
12581
13213
  return taskIds.flatMap((currentTaskId) => {
12582
- const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync17(path));
13214
+ const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
12583
13215
  if (!currentRoot) {
12584
13216
  return [];
12585
13217
  }
@@ -12591,7 +13223,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
12591
13223
  taskId: currentTaskId,
12592
13224
  kind: "file",
12593
13225
  label: fileName,
12594
- path: resolve22(currentRoot, fileName),
13226
+ path: resolve24(currentRoot, fileName),
12595
13227
  url: null,
12596
13228
  metadata: {
12597
13229
  fileName
@@ -12634,11 +13266,11 @@ function buildInspectorStreamPayload(state, sequence) {
12634
13266
  }
12635
13267
  function listRemoteRunArtifacts(projectRoot, runId) {
12636
13268
  const root = remoteArtifactsRoot(projectRoot, runId);
12637
- if (!existsSync17(root)) {
13269
+ if (!existsSync19(root)) {
12638
13270
  return [];
12639
13271
  }
12640
13272
  return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
12641
- const artifactPath = resolve22(root, entry.name);
13273
+ const artifactPath = resolve24(root, entry.name);
12642
13274
  const stat = statSync6(artifactPath);
12643
13275
  const meta = readJsonFile4(`${artifactPath}.json`, null);
12644
13276
  return {
@@ -12880,8 +13512,8 @@ function fileStats(path) {
12880
13512
  }
12881
13513
  }
12882
13514
  function runFileCursor(projectRoot, run) {
12883
- const runDir = dirname17(runLogsPath(projectRoot, run.runId));
12884
- const runJson = fileStats(resolve22(runDir, "run.json"));
13515
+ const runDir = dirname20(runLogsPath(projectRoot, run.runId));
13516
+ const runJson = fileStats(resolve24(runDir, "run.json"));
12885
13517
  const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
12886
13518
  const logs = fileStats(runLogsPath(projectRoot, run.runId));
12887
13519
  return {
@@ -12931,10 +13563,10 @@ function startRunFileWatcher(state, pollMs) {
12931
13563
  }, Math.max(250, Math.min(pollMs, 1000)));
12932
13564
  }
12933
13565
  function startPoller(state, pollMs) {
12934
- let offset = existsSync17(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
13566
+ let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
12935
13567
  return setInterval(async () => {
12936
13568
  try {
12937
- if (!existsSync17(state.eventsFile)) {
13569
+ if (!existsSync19(state.eventsFile)) {
12938
13570
  return;
12939
13571
  }
12940
13572
  const file = await open(state.eventsFile, "r");
@@ -12986,6 +13618,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
12986
13618
  const server = Bun.serve({
12987
13619
  hostname: options.host,
12988
13620
  port: options.port,
13621
+ idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
12989
13622
  fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
12990
13623
  websocket: {
12991
13624
  open(ws) {
@@ -13061,7 +13694,7 @@ function resolveProjectRoot() {
13061
13694
  return resolveRigProjectRoot({
13062
13695
  envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
13063
13696
  cwd: process.cwd(),
13064
- fallbackRoot: resolve22(import.meta.dir, "../..")
13697
+ fallbackRoot: resolve24(import.meta.dir, "../..")
13065
13698
  });
13066
13699
  }
13067
13700
  var __testOnly = {