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

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
@@ -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
  }));
@@ -2970,7 +2970,10 @@ function createPiIssueAnalyzer(input = {}) {
2970
2970
  const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
2971
2971
  return async ({ prompt }) => {
2972
2972
  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";
2973
+ const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
2974
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
2975
+ if (provider)
2976
+ args.push("--provider", provider);
2974
2977
  if (model)
2975
2978
  args.push("--model", model);
2976
2979
  args.push(prompt);
@@ -3811,6 +3814,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
3811
3814
  }
3812
3815
 
3813
3816
  // packages/server/src/server-helpers/github-auth-store.ts
3817
+ import { randomBytes } from "crypto";
3814
3818
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3815
3819
  import { resolve as resolve13 } from "path";
3816
3820
  function cleanString(value) {
@@ -3824,6 +3828,24 @@ function cleanScopes(value) {
3824
3828
  return clean ? [clean] : [];
3825
3829
  });
3826
3830
  }
3831
+ function parseApiSessions(value) {
3832
+ if (!Array.isArray(value))
3833
+ return [];
3834
+ return value.flatMap((entry) => {
3835
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3836
+ return [];
3837
+ const record = entry;
3838
+ const token = cleanString(record.token);
3839
+ if (!token)
3840
+ return [];
3841
+ return [{
3842
+ token,
3843
+ login: cleanString(record.login),
3844
+ userId: cleanString(record.userId),
3845
+ createdAt: cleanString(record.createdAt) ?? undefined
3846
+ }];
3847
+ });
3848
+ }
3827
3849
  function readStoredAuth(stateFile) {
3828
3850
  if (!existsSync6(stateFile))
3829
3851
  return {};
@@ -3837,6 +3859,7 @@ function readStoredAuth(stateFile) {
3837
3859
  selectedRepo: cleanString(parsed.selectedRepo),
3838
3860
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
3839
3861
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
3862
+ apiSessions: parseApiSessions(parsed.apiSessions),
3840
3863
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
3841
3864
  };
3842
3865
  } catch {
@@ -3855,6 +3878,9 @@ function parsePendingDevice(value) {
3855
3878
  return null;
3856
3879
  return { pollId, deviceCode, expiresAt, intervalSeconds };
3857
3880
  }
3881
+ function newApiSessionToken() {
3882
+ return `rig_${randomBytes(32).toString("base64url")}`;
3883
+ }
3858
3884
  function writeStoredAuth(stateFile, payload) {
3859
3885
  mkdirSync6(resolve13(stateFile, ".."), { recursive: true });
3860
3886
  writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -3897,9 +3923,38 @@ function createGitHubAuthStore(projectRoot) {
3897
3923
  scopes: input.scopes ?? [],
3898
3924
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
3899
3925
  pendingDevice: null,
3926
+ apiSessions: previous.apiSessions ?? [],
3900
3927
  updatedAt: new Date().toISOString()
3901
3928
  });
3902
3929
  },
3930
+ createApiSession() {
3931
+ const previous = readStoredAuth(stateFile);
3932
+ const token = newApiSessionToken();
3933
+ const session = {
3934
+ token,
3935
+ login: cleanString(previous.login),
3936
+ userId: cleanString(previous.userId),
3937
+ createdAt: new Date().toISOString()
3938
+ };
3939
+ writeStoredAuth(stateFile, {
3940
+ ...previous,
3941
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
3942
+ updatedAt: new Date().toISOString()
3943
+ });
3944
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
3945
+ },
3946
+ readApiSession(token) {
3947
+ const clean = cleanString(token);
3948
+ if (!clean)
3949
+ return null;
3950
+ const previous = readStoredAuth(stateFile);
3951
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
3952
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
3953
+ },
3954
+ copyToProjectRoot(projectRoot2) {
3955
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
3956
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
3957
+ },
3903
3958
  savePendingDevice(input) {
3904
3959
  const previous = readStoredAuth(stateFile);
3905
3960
  writeStoredAuth(stateFile, {
@@ -4197,15 +4252,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
4197
4252
  if (!run.taskId)
4198
4253
  return;
4199
4254
  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
- });
4255
+ try {
4256
+ const result = await syncGitHubProjectStatusForTaskUpdate({
4257
+ taskId: run.taskId,
4258
+ status,
4259
+ issueNodeId,
4260
+ token: createGitHubAuthStore(projectRoot).readToken(),
4261
+ config
4262
+ });
4263
+ if (!result.synced && result.reason !== "project-sync-disabled") {
4264
+ appendRunLogEntry(projectRoot, run.runId, {
4265
+ id: `log:${run.runId}:github-project-sync:${status}`,
4266
+ title: "GitHub Project sync skipped",
4267
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
4268
+ tone: "warn",
4269
+ status: "running",
4270
+ createdAt: new Date().toISOString(),
4271
+ payload: { reason: result.reason, issueNodeId }
4272
+ });
4273
+ }
4274
+ } catch (error) {
4275
+ appendRunLogEntry(projectRoot, run.runId, {
4276
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
4277
+ title: "GitHub Project sync failed",
4278
+ detail: error instanceof Error ? error.message : String(error),
4279
+ tone: "error",
4280
+ status: "running",
4281
+ createdAt: new Date().toISOString(),
4282
+ payload: { issueNodeId }
4283
+ });
4284
+ }
4209
4285
  }
4210
4286
  async function autoAssignRunIssue(projectRoot, run) {
4211
4287
  if (!run.taskId)
@@ -4479,7 +4555,11 @@ async function startLocalRun(state, runId, options) {
4479
4555
  RIG_SERVER_INTERNAL_EXEC: "1",
4480
4556
  ...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
4481
4557
  ...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
4482
- ...bridgeGitHubToken ? { RIG_GITHUB_TOKEN: bridgeGitHubToken } : {}
4558
+ ...bridgeGitHubToken ? {
4559
+ RIG_GITHUB_TOKEN: bridgeGitHubToken,
4560
+ GITHUB_TOKEN: bridgeGitHubToken,
4561
+ GH_TOKEN: bridgeGitHubToken
4562
+ } : {}
4483
4563
  },
4484
4564
  stdio: ["ignore", "pipe", "pipe"]
4485
4565
  });
@@ -5393,10 +5473,10 @@ function normalizeCommit(value) {
5393
5473
  function asPlainRecord(value) {
5394
5474
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
5395
5475
  }
5396
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
5476
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
5397
5477
  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}`
5478
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
5479
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
5400
5480
  };
5401
5481
  function repoParts(repoSlug) {
5402
5482
  const [owner, repo] = repoSlug.split("/");
@@ -5750,13 +5830,13 @@ function bearerTokenFromRequest(req) {
5750
5830
  function isLoopbackRequest(req) {
5751
5831
  try {
5752
5832
  const hostname = new URL(req.url).hostname.toLowerCase();
5753
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5833
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5754
5834
  } catch {
5755
5835
  return false;
5756
5836
  }
5757
5837
  }
5758
5838
  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";
5839
+ return pathname === "/" || 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";
5760
5840
  }
5761
5841
  function normalizePrMode(value) {
5762
5842
  const mode = normalizeString(value);
@@ -5769,6 +5849,10 @@ function authorizeRigHttpRequest(input) {
5769
5849
  const bearer = bearerTokenFromRequest(input.req);
5770
5850
  const store = createGitHubAuthStore(input.projectRoot);
5771
5851
  const storedToken = store.readToken();
5852
+ const session = bearer ? store.readApiSession(bearer) : null;
5853
+ if (session) {
5854
+ return { authorized: true, actor: session.login ?? "github-operator", reason: "github-session" };
5855
+ }
5772
5856
  if (bearer && storedToken && bearer === storedToken) {
5773
5857
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
5774
5858
  return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
@@ -5776,8 +5860,11 @@ function authorizeRigHttpRequest(input) {
5776
5860
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
5777
5861
  return { authorized: true, actor: null, reason: "public-bootstrap" };
5778
5862
  }
5779
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
5780
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5863
+ if (!input.serverAuthToken && !storedToken) {
5864
+ if (isLoopbackRequest(input.req)) {
5865
+ return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5866
+ }
5867
+ return { authorized: false, actor: null, reason: "auth-required" };
5781
5868
  }
5782
5869
  return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
5783
5870
  }
@@ -6008,7 +6095,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
6008
6095
  if (runnable.length === 0)
6009
6096
  return null;
6010
6097
  const queue = readQueueState(projectRoot);
6011
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
6098
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
6012
6099
  return runnable.toSorted((left, right) => {
6013
6100
  const leftId = taskIdOf(left) ?? "";
6014
6101
  const rightId = taskIdOf(right) ?? "";
@@ -6048,6 +6135,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
6048
6135
  }
6049
6136
  return filtered;
6050
6137
  }
6138
+ function issueAnalysisTargetFor(source) {
6139
+ if (!source)
6140
+ return null;
6141
+ const candidate = source;
6142
+ if (typeof candidate.updateTask !== "function")
6143
+ return null;
6144
+ return {
6145
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
6146
+ updateTask: candidate.updateTask.bind(candidate),
6147
+ ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
6148
+ ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
6149
+ ...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
6150
+ };
6151
+ }
6152
+ function uniqueStringList(value) {
6153
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
6154
+ return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
6155
+ }
6156
+ function taskRecordId(task) {
6157
+ return String(task.id ?? "");
6158
+ }
6051
6159
  function redactRemoteEndpoint(endpoint) {
6052
6160
  const { token, ...rest } = endpoint;
6053
6161
  return {
@@ -6134,7 +6242,7 @@ function createRigServerFetch(state, deps) {
6134
6242
  }
6135
6243
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
6136
6244
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
6137
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
6245
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
6138
6246
  const requestAuth = authorizeRigHttpRequest({
6139
6247
  req,
6140
6248
  pathname: url.pathname,
@@ -6390,6 +6498,67 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6390
6498
  note: "GitHub issue lifecycle labels are created on demand by the configured task source when supported."
6391
6499
  });
6392
6500
  }
6501
+ if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
6502
+ const body = await deps.readJsonBody(req);
6503
+ const ids = uniqueStringList(body.ids ?? body.id);
6504
+ const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
6505
+ if (ids.length === 0 && !analyzeAll) {
6506
+ return deps.badRequest("ids is required unless all=true");
6507
+ }
6508
+ const ctx = await getCachedPluginHostContext(state.projectRoot);
6509
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
6510
+ const target = issueAnalysisTargetFor(source);
6511
+ if (!source || !target) {
6512
+ return deps.badRequest("Configured task source does not support issue-analysis writeback");
6513
+ }
6514
+ const allTasks = [...await source.list()];
6515
+ const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
6516
+ const cached = allTasks.find((task) => taskRecordId(task) === id);
6517
+ if (cached)
6518
+ return cached;
6519
+ return typeof source.get === "function" ? await source.get(id) : undefined;
6520
+ }))).filter((task) => Boolean(task));
6521
+ if (issues.length === 0) {
6522
+ return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
6523
+ }
6524
+ const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
6525
+ const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
6526
+ const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
6527
+ const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
6528
+ const service = createIssueAnalysisService({
6529
+ analyzer: createPiIssueAnalyzer({
6530
+ ...model ? { model } : {},
6531
+ env: { RIG_PROJECT_ROOT: state.projectRoot }
6532
+ }),
6533
+ writeBack: createIssueAnalysisWriteBack({ target })
6534
+ });
6535
+ const reason = normalizeString(body.reason) ?? "http-issue-analysis";
6536
+ let results;
6537
+ try {
6538
+ results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
6539
+ } catch (error) {
6540
+ return deps.jsonResponse({
6541
+ ok: false,
6542
+ error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
6543
+ reason,
6544
+ ids: issues.map((issue) => issue.id)
6545
+ }, 502);
6546
+ }
6547
+ deps.snapshotService.invalidate("issue-analysis-http-run");
6548
+ await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
6549
+ return;
6550
+ });
6551
+ deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
6552
+ return deps.jsonResponse({
6553
+ ok: true,
6554
+ reason,
6555
+ analyzed: results.map((entry) => ({
6556
+ id: entry.issue.id,
6557
+ title: entry.issue.title ?? null,
6558
+ result: entry.result
6559
+ }))
6560
+ });
6561
+ }
6393
6562
  if (url.pathname === "/api/server/status") {
6394
6563
  const config = buildProjectConfigStatus(state.projectRoot);
6395
6564
  const taskSource = await buildTaskSourceStatus(state, config);
@@ -6530,6 +6699,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6530
6699
  }
6531
6700
  const normalizedRoot = resolve18(requestedRoot);
6532
6701
  const exists = existsSync11(normalizedRoot);
6702
+ if (exists) {
6703
+ createGitHubAuthStore(state.projectRoot).copyToProjectRoot(normalizedRoot);
6704
+ }
6533
6705
  const control = buildServerControlStatus();
6534
6706
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
6535
6707
  if (!exists) {
@@ -6618,21 +6790,30 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6618
6790
  const body = await deps.readJsonBody(req);
6619
6791
  const token = normalizeString(body.token);
6620
6792
  const selectedRepo = normalizeString(body.selectedRepo);
6793
+ const requestedProjectRoot = normalizeString(body.projectRoot);
6621
6794
  if (!token) {
6622
6795
  return deps.badRequest("token is required");
6623
6796
  }
6624
6797
  try {
6625
6798
  const user = await fetchGitHubUserInfo(token);
6626
- const store = createGitHubAuthStore(state.projectRoot);
6627
- store.saveToken({
6628
- token,
6629
- tokenSource: "manual-token",
6630
- login: user.login,
6631
- userId: user.userId,
6632
- scopes: user.scopes,
6633
- selectedRepo
6634
- });
6635
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
6799
+ const storeRoots = [
6800
+ state.projectRoot,
6801
+ ...requestedProjectRoot && isAbsolute3(requestedProjectRoot) && existsSync11(resolve18(requestedProjectRoot)) ? [resolve18(requestedProjectRoot)] : []
6802
+ ].filter((root, index, roots) => roots.indexOf(root) === index);
6803
+ const stores = storeRoots.map((root) => createGitHubAuthStore(root));
6804
+ for (const store2 of stores) {
6805
+ store2.saveToken({
6806
+ token,
6807
+ tokenSource: "manual-token",
6808
+ login: user.login,
6809
+ userId: user.userId,
6810
+ scopes: user.scopes,
6811
+ selectedRepo
6812
+ });
6813
+ }
6814
+ const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
6815
+ const apiSession = store.createApiSession();
6816
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
6636
6817
  } catch (error) {
6637
6818
  const message = error instanceof Error ? error.message : String(error);
6638
6819
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -6700,7 +6881,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6700
6881
  const token = result.payload.access_token;
6701
6882
  const user = await fetchGitHubUserInfo(token);
6702
6883
  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 }) });
6884
+ const apiSession = store.createApiSession();
6885
+ return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }), apiSessionToken: apiSession.token });
6704
6886
  }
6705
6887
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
6706
6888
  const body = await deps.readJsonBody(req);
@@ -12506,7 +12688,7 @@ async function readWorkspaceTasks(projectRoot) {
12506
12688
  description: normalizeString(entry.description),
12507
12689
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
12508
12690
  status: normalizedStatus ?? "unknown",
12509
- sourceStatus: normalizedStatus ? null : rawStatus,
12691
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
12510
12692
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
12511
12693
  issueType: normalizeString(entry.issue_type),
12512
12694
  role: normalizeString(config.role) ?? null,
@@ -12986,6 +13168,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
12986
13168
  const server = Bun.serve({
12987
13169
  hostname: options.host,
12988
13170
  port: options.port,
13171
+ idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
12989
13172
  fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
12990
13173
  websocket: {
12991
13174
  open(ws) {
@@ -1,5 +1,6 @@
1
1
  // @bun
2
2
  // packages/server/src/server-helpers/github-auth-store.ts
3
+ import { randomBytes } from "crypto";
3
4
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
5
  import { resolve as resolve2 } from "path";
5
6
 
@@ -39,6 +40,24 @@ function cleanScopes(value) {
39
40
  return clean ? [clean] : [];
40
41
  });
41
42
  }
43
+ function parseApiSessions(value) {
44
+ if (!Array.isArray(value))
45
+ return [];
46
+ return value.flatMap((entry) => {
47
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
48
+ return [];
49
+ const record = entry;
50
+ const token = cleanString(record.token);
51
+ if (!token)
52
+ return [];
53
+ return [{
54
+ token,
55
+ login: cleanString(record.login),
56
+ userId: cleanString(record.userId),
57
+ createdAt: cleanString(record.createdAt) ?? undefined
58
+ }];
59
+ });
60
+ }
42
61
  function readStoredAuth(stateFile) {
43
62
  if (!existsSync(stateFile))
44
63
  return {};
@@ -52,6 +71,7 @@ function readStoredAuth(stateFile) {
52
71
  selectedRepo: cleanString(parsed.selectedRepo),
53
72
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
54
73
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
74
+ apiSessions: parseApiSessions(parsed.apiSessions),
55
75
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
56
76
  };
57
77
  } catch {
@@ -70,6 +90,9 @@ function parsePendingDevice(value) {
70
90
  return null;
71
91
  return { pollId, deviceCode, expiresAt, intervalSeconds };
72
92
  }
93
+ function newApiSessionToken() {
94
+ return `rig_${randomBytes(32).toString("base64url")}`;
95
+ }
73
96
  function writeStoredAuth(stateFile, payload) {
74
97
  mkdirSync(resolve2(stateFile, ".."), { recursive: true });
75
98
  writeFileSync(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -112,9 +135,38 @@ function createGitHubAuthStore(projectRoot) {
112
135
  scopes: input.scopes ?? [],
113
136
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
114
137
  pendingDevice: null,
138
+ apiSessions: previous.apiSessions ?? [],
115
139
  updatedAt: new Date().toISOString()
116
140
  });
117
141
  },
142
+ createApiSession() {
143
+ const previous = readStoredAuth(stateFile);
144
+ const token = newApiSessionToken();
145
+ const session = {
146
+ token,
147
+ login: cleanString(previous.login),
148
+ userId: cleanString(previous.userId),
149
+ createdAt: new Date().toISOString()
150
+ };
151
+ writeStoredAuth(stateFile, {
152
+ ...previous,
153
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
154
+ updatedAt: new Date().toISOString()
155
+ });
156
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
157
+ },
158
+ readApiSession(token) {
159
+ const clean = cleanString(token);
160
+ if (!clean)
161
+ return null;
162
+ const previous = readStoredAuth(stateFile);
163
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
164
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
165
+ },
166
+ copyToProjectRoot(projectRoot2) {
167
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
168
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
169
+ },
118
170
  savePendingDevice(input) {
119
171
  const previous = readStoredAuth(stateFile);
120
172
  writeStoredAuth(stateFile, {