@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.
@@ -459,6 +459,7 @@ import {
459
459
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
460
460
 
461
461
  // packages/server/src/server-helpers/github-auth-store.ts
462
+ import { randomBytes } from "crypto";
462
463
  import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
463
464
  import { resolve as resolve6 } from "path";
464
465
  function cleanString(value) {
@@ -472,6 +473,24 @@ function cleanScopes(value) {
472
473
  return clean ? [clean] : [];
473
474
  });
474
475
  }
476
+ function parseApiSessions(value) {
477
+ if (!Array.isArray(value))
478
+ return [];
479
+ return value.flatMap((entry) => {
480
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
481
+ return [];
482
+ const record = entry;
483
+ const token = cleanString(record.token);
484
+ if (!token)
485
+ return [];
486
+ return [{
487
+ token,
488
+ login: cleanString(record.login),
489
+ userId: cleanString(record.userId),
490
+ createdAt: cleanString(record.createdAt) ?? undefined
491
+ }];
492
+ });
493
+ }
475
494
  function readStoredAuth(stateFile) {
476
495
  if (!existsSync3(stateFile))
477
496
  return {};
@@ -485,6 +504,7 @@ function readStoredAuth(stateFile) {
485
504
  selectedRepo: cleanString(parsed.selectedRepo),
486
505
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
487
506
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
507
+ apiSessions: parseApiSessions(parsed.apiSessions),
488
508
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
489
509
  };
490
510
  } catch {
@@ -503,6 +523,9 @@ function parsePendingDevice(value) {
503
523
  return null;
504
524
  return { pollId, deviceCode, expiresAt, intervalSeconds };
505
525
  }
526
+ function newApiSessionToken() {
527
+ return `rig_${randomBytes(32).toString("base64url")}`;
528
+ }
506
529
  function writeStoredAuth(stateFile, payload) {
507
530
  mkdirSync3(resolve6(stateFile, ".."), { recursive: true });
508
531
  writeFileSync3(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -545,9 +568,38 @@ function createGitHubAuthStore(projectRoot) {
545
568
  scopes: input.scopes ?? [],
546
569
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
547
570
  pendingDevice: null,
571
+ apiSessions: previous.apiSessions ?? [],
548
572
  updatedAt: new Date().toISOString()
549
573
  });
550
574
  },
575
+ createApiSession() {
576
+ const previous = readStoredAuth(stateFile);
577
+ const token = newApiSessionToken();
578
+ const session = {
579
+ token,
580
+ login: cleanString(previous.login),
581
+ userId: cleanString(previous.userId),
582
+ createdAt: new Date().toISOString()
583
+ };
584
+ writeStoredAuth(stateFile, {
585
+ ...previous,
586
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
587
+ updatedAt: new Date().toISOString()
588
+ });
589
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
590
+ },
591
+ readApiSession(token) {
592
+ const clean = cleanString(token);
593
+ if (!clean)
594
+ return null;
595
+ const previous = readStoredAuth(stateFile);
596
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
597
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
598
+ },
599
+ copyToProjectRoot(projectRoot2) {
600
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
601
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
602
+ },
551
603
  savePendingDevice(input) {
552
604
  const previous = readStoredAuth(stateFile);
553
605
  writeStoredAuth(stateFile, {
@@ -775,10 +827,10 @@ function extractGitHubIssueNodeId(task) {
775
827
  }
776
828
 
777
829
  // packages/server/src/server-helpers/http-router.ts
778
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
830
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
779
831
  var RIG_CONFIG_DEV_DEPENDENCIES = {
780
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
781
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
832
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
833
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
782
834
  };
783
835
 
784
836
  // packages/server/src/server-helpers/ws-router.ts
@@ -995,7 +1047,7 @@ async function readWorkspaceTasks(projectRoot) {
995
1047
  description: normalizeString(entry.description),
996
1048
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
997
1049
  status: normalizedStatus ?? "unknown",
998
- sourceStatus: normalizedStatus ? null : rawStatus,
1050
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
999
1051
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
1000
1052
  issueType: normalizeString(entry.issue_type),
1001
1053
  role: normalizeString(config.role) ?? null,
@@ -1120,15 +1172,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
1120
1172
  if (!run.taskId)
1121
1173
  return;
1122
1174
  const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
1123
- await syncGitHubProjectStatusForTaskUpdate({
1124
- taskId: run.taskId,
1125
- status,
1126
- issueNodeId,
1127
- token: createGitHubAuthStore(projectRoot).readToken(),
1128
- config
1129
- }).catch(() => {
1130
- return;
1131
- });
1175
+ try {
1176
+ const result = await syncGitHubProjectStatusForTaskUpdate({
1177
+ taskId: run.taskId,
1178
+ status,
1179
+ issueNodeId,
1180
+ token: createGitHubAuthStore(projectRoot).readToken(),
1181
+ config
1182
+ });
1183
+ if (!result.synced && result.reason !== "project-sync-disabled") {
1184
+ appendRunLogEntry(projectRoot, run.runId, {
1185
+ id: `log:${run.runId}:github-project-sync:${status}`,
1186
+ title: "GitHub Project sync skipped",
1187
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
1188
+ tone: "warn",
1189
+ status: "running",
1190
+ createdAt: new Date().toISOString(),
1191
+ payload: { reason: result.reason, issueNodeId }
1192
+ });
1193
+ }
1194
+ } catch (error) {
1195
+ appendRunLogEntry(projectRoot, run.runId, {
1196
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
1197
+ title: "GitHub Project sync failed",
1198
+ detail: error instanceof Error ? error.message : String(error),
1199
+ tone: "error",
1200
+ status: "running",
1201
+ createdAt: new Date().toISOString(),
1202
+ payload: { issueNodeId }
1203
+ });
1204
+ }
1132
1205
  }
1133
1206
  async function autoAssignRunIssue(projectRoot, run) {
1134
1207
  if (!run.taskId)
@@ -1402,7 +1475,11 @@ async function startLocalRun(state, runId, options) {
1402
1475
  RIG_SERVER_INTERNAL_EXEC: "1",
1403
1476
  ...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
1404
1477
  ...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
1405
- ...bridgeGitHubToken ? { RIG_GITHUB_TOKEN: bridgeGitHubToken } : {}
1478
+ ...bridgeGitHubToken ? {
1479
+ RIG_GITHUB_TOKEN: bridgeGitHubToken,
1480
+ GITHUB_TOKEN: bridgeGitHubToken,
1481
+ GH_TOKEN: bridgeGitHubToken
1482
+ } : {}
1406
1483
  },
1407
1484
  stdio: ["ignore", "pipe", "pipe"]
1408
1485
  });
@@ -699,7 +699,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
699
699
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
700
700
  ...toTaskSummary(workspace.id, {
701
701
  ...task,
702
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
702
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
703
703
  }),
704
704
  graphId: graph.id
705
705
  }));
@@ -711,7 +711,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
711
711
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
712
712
  ...toTaskSummary(workspace.id, {
713
713
  ...task,
714
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
714
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
715
715
  }),
716
716
  graphId: graph.id
717
717
  }));
@@ -437,10 +437,10 @@ import {
437
437
  buildTaskRunLifecycleComment as buildTaskRunLifecycleComment2,
438
438
  updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
439
439
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
440
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
440
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
441
441
  var RIG_CONFIG_DEV_DEPENDENCIES = {
442
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
443
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
442
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
443
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
444
444
  };
445
445
 
446
446
  // packages/server/src/server-helpers/inspector-jobs.ts
@@ -1786,7 +1786,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
1786
1786
  const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
1787
1787
  ...toTaskSummary(workspace.id, {
1788
1788
  ...task,
1789
- status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
1789
+ status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
1790
1790
  }),
1791
1791
  graphId: graph.id
1792
1792
  }));
@@ -2463,7 +2463,10 @@ function createPiIssueAnalyzer(input = {}) {
2463
2463
  const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
2464
2464
  return async ({ prompt }) => {
2465
2465
  const args = ["--print", "--mode", "json", "--no-session"];
2466
- const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || "openai-codex/gpt-5.5";
2466
+ const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
2467
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
2468
+ if (provider)
2469
+ args.push("--provider", provider);
2467
2470
  if (model)
2468
2471
  args.push("--model", model);
2469
2472
  args.push(prompt);
@@ -3304,6 +3307,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
3304
3307
  }
3305
3308
 
3306
3309
  // packages/server/src/server-helpers/github-auth-store.ts
3310
+ import { randomBytes } from "crypto";
3307
3311
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3308
3312
  import { resolve as resolve13 } from "path";
3309
3313
  function cleanString(value) {
@@ -3317,6 +3321,24 @@ function cleanScopes(value) {
3317
3321
  return clean ? [clean] : [];
3318
3322
  });
3319
3323
  }
3324
+ function parseApiSessions(value) {
3325
+ if (!Array.isArray(value))
3326
+ return [];
3327
+ return value.flatMap((entry) => {
3328
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3329
+ return [];
3330
+ const record = entry;
3331
+ const token = cleanString(record.token);
3332
+ if (!token)
3333
+ return [];
3334
+ return [{
3335
+ token,
3336
+ login: cleanString(record.login),
3337
+ userId: cleanString(record.userId),
3338
+ createdAt: cleanString(record.createdAt) ?? undefined
3339
+ }];
3340
+ });
3341
+ }
3320
3342
  function readStoredAuth(stateFile) {
3321
3343
  if (!existsSync6(stateFile))
3322
3344
  return {};
@@ -3330,6 +3352,7 @@ function readStoredAuth(stateFile) {
3330
3352
  selectedRepo: cleanString(parsed.selectedRepo),
3331
3353
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
3332
3354
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
3355
+ apiSessions: parseApiSessions(parsed.apiSessions),
3333
3356
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
3334
3357
  };
3335
3358
  } catch {
@@ -3348,6 +3371,9 @@ function parsePendingDevice(value) {
3348
3371
  return null;
3349
3372
  return { pollId, deviceCode, expiresAt, intervalSeconds };
3350
3373
  }
3374
+ function newApiSessionToken() {
3375
+ return `rig_${randomBytes(32).toString("base64url")}`;
3376
+ }
3351
3377
  function writeStoredAuth(stateFile, payload) {
3352
3378
  mkdirSync6(resolve13(stateFile, ".."), { recursive: true });
3353
3379
  writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -3390,9 +3416,38 @@ function createGitHubAuthStore(projectRoot) {
3390
3416
  scopes: input.scopes ?? [],
3391
3417
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
3392
3418
  pendingDevice: null,
3419
+ apiSessions: previous.apiSessions ?? [],
3393
3420
  updatedAt: new Date().toISOString()
3394
3421
  });
3395
3422
  },
3423
+ createApiSession() {
3424
+ const previous = readStoredAuth(stateFile);
3425
+ const token = newApiSessionToken();
3426
+ const session = {
3427
+ token,
3428
+ login: cleanString(previous.login),
3429
+ userId: cleanString(previous.userId),
3430
+ createdAt: new Date().toISOString()
3431
+ };
3432
+ writeStoredAuth(stateFile, {
3433
+ ...previous,
3434
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
3435
+ updatedAt: new Date().toISOString()
3436
+ });
3437
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
3438
+ },
3439
+ readApiSession(token) {
3440
+ const clean = cleanString(token);
3441
+ if (!clean)
3442
+ return null;
3443
+ const previous = readStoredAuth(stateFile);
3444
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
3445
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
3446
+ },
3447
+ copyToProjectRoot(projectRoot2) {
3448
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
3449
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
3450
+ },
3396
3451
  savePendingDevice(input) {
3397
3452
  const previous = readStoredAuth(stateFile);
3398
3453
  writeStoredAuth(stateFile, {
@@ -3690,15 +3745,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
3690
3745
  if (!run.taskId)
3691
3746
  return;
3692
3747
  const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
3693
- await syncGitHubProjectStatusForTaskUpdate({
3694
- taskId: run.taskId,
3695
- status,
3696
- issueNodeId,
3697
- token: createGitHubAuthStore(projectRoot).readToken(),
3698
- config
3699
- }).catch(() => {
3700
- return;
3701
- });
3748
+ try {
3749
+ const result = await syncGitHubProjectStatusForTaskUpdate({
3750
+ taskId: run.taskId,
3751
+ status,
3752
+ issueNodeId,
3753
+ token: createGitHubAuthStore(projectRoot).readToken(),
3754
+ config
3755
+ });
3756
+ if (!result.synced && result.reason !== "project-sync-disabled") {
3757
+ appendRunLogEntry(projectRoot, run.runId, {
3758
+ id: `log:${run.runId}:github-project-sync:${status}`,
3759
+ title: "GitHub Project sync skipped",
3760
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
3761
+ tone: "warn",
3762
+ status: "running",
3763
+ createdAt: new Date().toISOString(),
3764
+ payload: { reason: result.reason, issueNodeId }
3765
+ });
3766
+ }
3767
+ } catch (error) {
3768
+ appendRunLogEntry(projectRoot, run.runId, {
3769
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
3770
+ title: "GitHub Project sync failed",
3771
+ detail: error instanceof Error ? error.message : String(error),
3772
+ tone: "error",
3773
+ status: "running",
3774
+ createdAt: new Date().toISOString(),
3775
+ payload: { issueNodeId }
3776
+ });
3777
+ }
3702
3778
  }
3703
3779
  async function autoAssignRunIssue(projectRoot, run) {
3704
3780
  if (!run.taskId)
@@ -3972,7 +4048,11 @@ async function startLocalRun(state, runId, options) {
3972
4048
  RIG_SERVER_INTERNAL_EXEC: "1",
3973
4049
  ...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
3974
4050
  ...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
3975
- ...bridgeGitHubToken ? { RIG_GITHUB_TOKEN: bridgeGitHubToken } : {}
4051
+ ...bridgeGitHubToken ? {
4052
+ RIG_GITHUB_TOKEN: bridgeGitHubToken,
4053
+ GITHUB_TOKEN: bridgeGitHubToken,
4054
+ GH_TOKEN: bridgeGitHubToken
4055
+ } : {}
3976
4056
  },
3977
4057
  stdio: ["ignore", "pipe", "pipe"]
3978
4058
  });
@@ -4886,10 +4966,10 @@ function normalizeCommit(value) {
4886
4966
  function asPlainRecord(value) {
4887
4967
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4888
4968
  }
4889
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
4969
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
4890
4970
  var RIG_CONFIG_DEV_DEPENDENCIES = {
4891
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
4892
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
4971
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
4972
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
4893
4973
  };
4894
4974
  function repoParts(repoSlug) {
4895
4975
  const [owner, repo] = repoSlug.split("/");
@@ -5243,13 +5323,13 @@ function bearerTokenFromRequest(req) {
5243
5323
  function isLoopbackRequest(req) {
5244
5324
  try {
5245
5325
  const hostname = new URL(req.url).hostname.toLowerCase();
5246
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5326
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5247
5327
  } catch {
5248
5328
  return false;
5249
5329
  }
5250
5330
  }
5251
5331
  function isPublicRigAuthBootstrapRoute(pathname) {
5252
- return pathname === "/" || pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/github/auth/status" || pathname === "/api/github/auth/token" || pathname === "/api/github/auth/device/start" || pathname === "/api/github/auth/device/poll";
5332
+ 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";
5253
5333
  }
5254
5334
  function normalizePrMode(value) {
5255
5335
  const mode = normalizeString(value);
@@ -5262,6 +5342,10 @@ function authorizeRigHttpRequest(input) {
5262
5342
  const bearer = bearerTokenFromRequest(input.req);
5263
5343
  const store = createGitHubAuthStore(input.projectRoot);
5264
5344
  const storedToken = store.readToken();
5345
+ const session = bearer ? store.readApiSession(bearer) : null;
5346
+ if (session) {
5347
+ return { authorized: true, actor: session.login ?? "github-operator", reason: "github-session" };
5348
+ }
5265
5349
  if (bearer && storedToken && bearer === storedToken) {
5266
5350
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
5267
5351
  return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
@@ -5269,8 +5353,11 @@ function authorizeRigHttpRequest(input) {
5269
5353
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
5270
5354
  return { authorized: true, actor: null, reason: "public-bootstrap" };
5271
5355
  }
5272
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
5273
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5356
+ if (!input.serverAuthToken && !storedToken) {
5357
+ if (isLoopbackRequest(input.req)) {
5358
+ return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5359
+ }
5360
+ return { authorized: false, actor: null, reason: "auth-required" };
5274
5361
  }
5275
5362
  return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
5276
5363
  }
@@ -5501,7 +5588,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
5501
5588
  if (runnable.length === 0)
5502
5589
  return null;
5503
5590
  const queue = readQueueState(projectRoot);
5504
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
5591
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
5505
5592
  return runnable.toSorted((left, right) => {
5506
5593
  const leftId = taskIdOf(left) ?? "";
5507
5594
  const rightId = taskIdOf(right) ?? "";
@@ -5541,6 +5628,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
5541
5628
  }
5542
5629
  return filtered;
5543
5630
  }
5631
+ function issueAnalysisTargetFor(source) {
5632
+ if (!source)
5633
+ return null;
5634
+ const candidate = source;
5635
+ if (typeof candidate.updateTask !== "function")
5636
+ return null;
5637
+ return {
5638
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
5639
+ updateTask: candidate.updateTask.bind(candidate),
5640
+ ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
5641
+ ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
5642
+ ...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
5643
+ };
5644
+ }
5645
+ function uniqueStringList(value) {
5646
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
5647
+ return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
5648
+ }
5649
+ function taskRecordId(task) {
5650
+ return String(task.id ?? "");
5651
+ }
5544
5652
  function redactRemoteEndpoint(endpoint) {
5545
5653
  const { token, ...rest } = endpoint;
5546
5654
  return {
@@ -5627,7 +5735,7 @@ function createRigServerFetch(state, deps) {
5627
5735
  }
5628
5736
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
5629
5737
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
5630
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
5738
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
5631
5739
  const requestAuth = authorizeRigHttpRequest({
5632
5740
  req,
5633
5741
  pathname: url.pathname,
@@ -5883,6 +5991,67 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
5883
5991
  note: "GitHub issue lifecycle labels are created on demand by the configured task source when supported."
5884
5992
  });
5885
5993
  }
5994
+ if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
5995
+ const body = await deps.readJsonBody(req);
5996
+ const ids = uniqueStringList(body.ids ?? body.id);
5997
+ const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
5998
+ if (ids.length === 0 && !analyzeAll) {
5999
+ return deps.badRequest("ids is required unless all=true");
6000
+ }
6001
+ const ctx = await getCachedPluginHostContext(state.projectRoot);
6002
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
6003
+ const target = issueAnalysisTargetFor(source);
6004
+ if (!source || !target) {
6005
+ return deps.badRequest("Configured task source does not support issue-analysis writeback");
6006
+ }
6007
+ const allTasks = [...await source.list()];
6008
+ const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
6009
+ const cached = allTasks.find((task) => taskRecordId(task) === id);
6010
+ if (cached)
6011
+ return cached;
6012
+ return typeof source.get === "function" ? await source.get(id) : undefined;
6013
+ }))).filter((task) => Boolean(task));
6014
+ if (issues.length === 0) {
6015
+ return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
6016
+ }
6017
+ const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
6018
+ const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
6019
+ const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
6020
+ const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
6021
+ const service = createIssueAnalysisService({
6022
+ analyzer: createPiIssueAnalyzer({
6023
+ ...model ? { model } : {},
6024
+ env: { RIG_PROJECT_ROOT: state.projectRoot }
6025
+ }),
6026
+ writeBack: createIssueAnalysisWriteBack({ target })
6027
+ });
6028
+ const reason = normalizeString(body.reason) ?? "http-issue-analysis";
6029
+ let results;
6030
+ try {
6031
+ results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
6032
+ } catch (error) {
6033
+ return deps.jsonResponse({
6034
+ ok: false,
6035
+ error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
6036
+ reason,
6037
+ ids: issues.map((issue) => issue.id)
6038
+ }, 502);
6039
+ }
6040
+ deps.snapshotService.invalidate("issue-analysis-http-run");
6041
+ await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
6042
+ return;
6043
+ });
6044
+ deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
6045
+ return deps.jsonResponse({
6046
+ ok: true,
6047
+ reason,
6048
+ analyzed: results.map((entry) => ({
6049
+ id: entry.issue.id,
6050
+ title: entry.issue.title ?? null,
6051
+ result: entry.result
6052
+ }))
6053
+ });
6054
+ }
5886
6055
  if (url.pathname === "/api/server/status") {
5887
6056
  const config = buildProjectConfigStatus(state.projectRoot);
5888
6057
  const taskSource = await buildTaskSourceStatus(state, config);
@@ -6023,6 +6192,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6023
6192
  }
6024
6193
  const normalizedRoot = resolve18(requestedRoot);
6025
6194
  const exists = existsSync11(normalizedRoot);
6195
+ if (exists) {
6196
+ createGitHubAuthStore(state.projectRoot).copyToProjectRoot(normalizedRoot);
6197
+ }
6026
6198
  const control = buildServerControlStatus();
6027
6199
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
6028
6200
  if (!exists) {
@@ -6111,21 +6283,30 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6111
6283
  const body = await deps.readJsonBody(req);
6112
6284
  const token = normalizeString(body.token);
6113
6285
  const selectedRepo = normalizeString(body.selectedRepo);
6286
+ const requestedProjectRoot = normalizeString(body.projectRoot);
6114
6287
  if (!token) {
6115
6288
  return deps.badRequest("token is required");
6116
6289
  }
6117
6290
  try {
6118
6291
  const user = await fetchGitHubUserInfo(token);
6119
- const store = createGitHubAuthStore(state.projectRoot);
6120
- store.saveToken({
6121
- token,
6122
- tokenSource: "manual-token",
6123
- login: user.login,
6124
- userId: user.userId,
6125
- scopes: user.scopes,
6126
- selectedRepo
6127
- });
6128
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
6292
+ const storeRoots = [
6293
+ state.projectRoot,
6294
+ ...requestedProjectRoot && isAbsolute3(requestedProjectRoot) && existsSync11(resolve18(requestedProjectRoot)) ? [resolve18(requestedProjectRoot)] : []
6295
+ ].filter((root, index, roots) => roots.indexOf(root) === index);
6296
+ const stores = storeRoots.map((root) => createGitHubAuthStore(root));
6297
+ for (const store2 of stores) {
6298
+ store2.saveToken({
6299
+ token,
6300
+ tokenSource: "manual-token",
6301
+ login: user.login,
6302
+ userId: user.userId,
6303
+ scopes: user.scopes,
6304
+ selectedRepo
6305
+ });
6306
+ }
6307
+ const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
6308
+ const apiSession = store.createApiSession();
6309
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
6129
6310
  } catch (error) {
6130
6311
  const message = error instanceof Error ? error.message : String(error);
6131
6312
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -6193,7 +6374,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6193
6374
  const token = result.payload.access_token;
6194
6375
  const user = await fetchGitHubUserInfo(token);
6195
6376
  store.saveToken({ token, tokenSource: "oauth-device", login: user.login, userId: user.userId, scopes: user.scopes });
6196
- return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }) });
6377
+ const apiSession = store.createApiSession();
6378
+ return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }), apiSessionToken: apiSession.token });
6197
6379
  }
6198
6380
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
6199
6381
  const body = await deps.readJsonBody(req);
@@ -11999,7 +12181,7 @@ async function readWorkspaceTasks(projectRoot) {
11999
12181
  description: normalizeString(entry.description),
12000
12182
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
12001
12183
  status: normalizedStatus ?? "unknown",
12002
- sourceStatus: normalizedStatus ? null : rawStatus,
12184
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
12003
12185
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
12004
12186
  issueType: normalizeString(entry.issue_type),
12005
12187
  role: normalizeString(config.role) ?? null,
@@ -12479,6 +12661,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
12479
12661
  const server = Bun.serve({
12480
12662
  hostname: options.host,
12481
12663
  port: options.port,
12664
+ idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
12482
12665
  fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
12483
12666
  websocket: {
12484
12667
  open(ws) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/server",
3
- "version": "0.0.6-alpha.1",
3
+ "version": "0.0.6-alpha.3",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -25,9 +25,9 @@
25
25
  "rig-server": "./dist/src/server.js"
26
26
  },
27
27
  "dependencies": {
28
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.1",
29
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.1",
30
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.1",
28
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.3",
29
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.3",
30
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.3",
31
31
  "effect": "4.0.0-beta.78"
32
32
  }
33
33
  }