@h-rig/server 0.0.6-alpha.0 → 0.0.6-alpha.2

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
  }));
@@ -3811,6 +3811,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
3811
3811
  }
3812
3812
 
3813
3813
  // packages/server/src/server-helpers/github-auth-store.ts
3814
+ import { randomBytes } from "crypto";
3814
3815
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3815
3816
  import { resolve as resolve13 } from "path";
3816
3817
  function cleanString(value) {
@@ -3824,6 +3825,24 @@ function cleanScopes(value) {
3824
3825
  return clean ? [clean] : [];
3825
3826
  });
3826
3827
  }
3828
+ function parseApiSessions(value) {
3829
+ if (!Array.isArray(value))
3830
+ return [];
3831
+ return value.flatMap((entry) => {
3832
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3833
+ return [];
3834
+ const record = entry;
3835
+ const token = cleanString(record.token);
3836
+ if (!token)
3837
+ return [];
3838
+ return [{
3839
+ token,
3840
+ login: cleanString(record.login),
3841
+ userId: cleanString(record.userId),
3842
+ createdAt: cleanString(record.createdAt) ?? undefined
3843
+ }];
3844
+ });
3845
+ }
3827
3846
  function readStoredAuth(stateFile) {
3828
3847
  if (!existsSync6(stateFile))
3829
3848
  return {};
@@ -3837,6 +3856,7 @@ function readStoredAuth(stateFile) {
3837
3856
  selectedRepo: cleanString(parsed.selectedRepo),
3838
3857
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
3839
3858
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
3859
+ apiSessions: parseApiSessions(parsed.apiSessions),
3840
3860
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
3841
3861
  };
3842
3862
  } catch {
@@ -3855,6 +3875,9 @@ function parsePendingDevice(value) {
3855
3875
  return null;
3856
3876
  return { pollId, deviceCode, expiresAt, intervalSeconds };
3857
3877
  }
3878
+ function newApiSessionToken() {
3879
+ return `rig_${randomBytes(32).toString("base64url")}`;
3880
+ }
3858
3881
  function writeStoredAuth(stateFile, payload) {
3859
3882
  mkdirSync6(resolve13(stateFile, ".."), { recursive: true });
3860
3883
  writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -3897,9 +3920,38 @@ function createGitHubAuthStore(projectRoot) {
3897
3920
  scopes: input.scopes ?? [],
3898
3921
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
3899
3922
  pendingDevice: null,
3923
+ apiSessions: previous.apiSessions ?? [],
3900
3924
  updatedAt: new Date().toISOString()
3901
3925
  });
3902
3926
  },
3927
+ createApiSession() {
3928
+ const previous = readStoredAuth(stateFile);
3929
+ const token = newApiSessionToken();
3930
+ const session = {
3931
+ token,
3932
+ login: cleanString(previous.login),
3933
+ userId: cleanString(previous.userId),
3934
+ createdAt: new Date().toISOString()
3935
+ };
3936
+ writeStoredAuth(stateFile, {
3937
+ ...previous,
3938
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
3939
+ updatedAt: new Date().toISOString()
3940
+ });
3941
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
3942
+ },
3943
+ readApiSession(token) {
3944
+ const clean = cleanString(token);
3945
+ if (!clean)
3946
+ return null;
3947
+ const previous = readStoredAuth(stateFile);
3948
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
3949
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
3950
+ },
3951
+ copyToProjectRoot(projectRoot2) {
3952
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
3953
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
3954
+ },
3903
3955
  savePendingDevice(input) {
3904
3956
  const previous = readStoredAuth(stateFile);
3905
3957
  writeStoredAuth(stateFile, {
@@ -4197,15 +4249,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
4197
4249
  if (!run.taskId)
4198
4250
  return;
4199
4251
  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
- });
4252
+ try {
4253
+ const result = await syncGitHubProjectStatusForTaskUpdate({
4254
+ taskId: run.taskId,
4255
+ status,
4256
+ issueNodeId,
4257
+ token: createGitHubAuthStore(projectRoot).readToken(),
4258
+ config
4259
+ });
4260
+ if (!result.synced && result.reason !== "project-sync-disabled") {
4261
+ appendRunLogEntry(projectRoot, run.runId, {
4262
+ id: `log:${run.runId}:github-project-sync:${status}`,
4263
+ title: "GitHub Project sync skipped",
4264
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
4265
+ tone: "warn",
4266
+ status: "running",
4267
+ createdAt: new Date().toISOString(),
4268
+ payload: { reason: result.reason, issueNodeId }
4269
+ });
4270
+ }
4271
+ } catch (error) {
4272
+ appendRunLogEntry(projectRoot, run.runId, {
4273
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
4274
+ title: "GitHub Project sync failed",
4275
+ detail: error instanceof Error ? error.message : String(error),
4276
+ tone: "error",
4277
+ status: "running",
4278
+ createdAt: new Date().toISOString(),
4279
+ payload: { issueNodeId }
4280
+ });
4281
+ }
4209
4282
  }
4210
4283
  async function autoAssignRunIssue(projectRoot, run) {
4211
4284
  if (!run.taskId)
@@ -5393,10 +5466,10 @@ function normalizeCommit(value) {
5393
5466
  function asPlainRecord(value) {
5394
5467
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
5395
5468
  }
5396
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.0";
5469
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
5397
5470
  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}`
5471
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
5472
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
5400
5473
  };
5401
5474
  function repoParts(repoSlug) {
5402
5475
  const [owner, repo] = repoSlug.split("/");
@@ -5750,13 +5823,13 @@ function bearerTokenFromRequest(req) {
5750
5823
  function isLoopbackRequest(req) {
5751
5824
  try {
5752
5825
  const hostname = new URL(req.url).hostname.toLowerCase();
5753
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5826
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5754
5827
  } catch {
5755
5828
  return false;
5756
5829
  }
5757
5830
  }
5758
5831
  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";
5832
+ 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
5833
  }
5761
5834
  function normalizePrMode(value) {
5762
5835
  const mode = normalizeString(value);
@@ -5769,6 +5842,10 @@ function authorizeRigHttpRequest(input) {
5769
5842
  const bearer = bearerTokenFromRequest(input.req);
5770
5843
  const store = createGitHubAuthStore(input.projectRoot);
5771
5844
  const storedToken = store.readToken();
5845
+ const session = bearer ? store.readApiSession(bearer) : null;
5846
+ if (session) {
5847
+ return { authorized: true, actor: session.login ?? "github-operator", reason: "github-session" };
5848
+ }
5772
5849
  if (bearer && storedToken && bearer === storedToken) {
5773
5850
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
5774
5851
  return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
@@ -5776,8 +5853,11 @@ function authorizeRigHttpRequest(input) {
5776
5853
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
5777
5854
  return { authorized: true, actor: null, reason: "public-bootstrap" };
5778
5855
  }
5779
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
5780
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5856
+ if (!input.serverAuthToken && !storedToken) {
5857
+ if (isLoopbackRequest(input.req)) {
5858
+ return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5859
+ }
5860
+ return { authorized: false, actor: null, reason: "auth-required" };
5781
5861
  }
5782
5862
  return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
5783
5863
  }
@@ -6008,7 +6088,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
6008
6088
  if (runnable.length === 0)
6009
6089
  return null;
6010
6090
  const queue = readQueueState(projectRoot);
6011
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
6091
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
6012
6092
  return runnable.toSorted((left, right) => {
6013
6093
  const leftId = taskIdOf(left) ?? "";
6014
6094
  const rightId = taskIdOf(right) ?? "";
@@ -6134,7 +6214,7 @@ function createRigServerFetch(state, deps) {
6134
6214
  }
6135
6215
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
6136
6216
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
6137
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
6217
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
6138
6218
  const requestAuth = authorizeRigHttpRequest({
6139
6219
  req,
6140
6220
  pathname: url.pathname,
@@ -6530,6 +6610,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6530
6610
  }
6531
6611
  const normalizedRoot = resolve18(requestedRoot);
6532
6612
  const exists = existsSync11(normalizedRoot);
6613
+ if (exists) {
6614
+ createGitHubAuthStore(state.projectRoot).copyToProjectRoot(normalizedRoot);
6615
+ }
6533
6616
  const control = buildServerControlStatus();
6534
6617
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
6535
6618
  if (!exists) {
@@ -6618,21 +6701,30 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6618
6701
  const body = await deps.readJsonBody(req);
6619
6702
  const token = normalizeString(body.token);
6620
6703
  const selectedRepo = normalizeString(body.selectedRepo);
6704
+ const requestedProjectRoot = normalizeString(body.projectRoot);
6621
6705
  if (!token) {
6622
6706
  return deps.badRequest("token is required");
6623
6707
  }
6624
6708
  try {
6625
6709
  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()) }) });
6710
+ const storeRoots = [
6711
+ state.projectRoot,
6712
+ ...requestedProjectRoot && isAbsolute3(requestedProjectRoot) && existsSync11(resolve18(requestedProjectRoot)) ? [resolve18(requestedProjectRoot)] : []
6713
+ ].filter((root, index, roots) => roots.indexOf(root) === index);
6714
+ const stores = storeRoots.map((root) => createGitHubAuthStore(root));
6715
+ for (const store2 of stores) {
6716
+ store2.saveToken({
6717
+ token,
6718
+ tokenSource: "manual-token",
6719
+ login: user.login,
6720
+ userId: user.userId,
6721
+ scopes: user.scopes,
6722
+ selectedRepo
6723
+ });
6724
+ }
6725
+ const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
6726
+ const apiSession = store.createApiSession();
6727
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
6636
6728
  } catch (error) {
6637
6729
  const message = error instanceof Error ? error.message : String(error);
6638
6730
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -6700,7 +6792,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6700
6792
  const token = result.payload.access_token;
6701
6793
  const user = await fetchGitHubUserInfo(token);
6702
6794
  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 }) });
6795
+ const apiSession = store.createApiSession();
6796
+ return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }), apiSessionToken: apiSession.token });
6704
6797
  }
6705
6798
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
6706
6799
  const body = await deps.readJsonBody(req);
@@ -12506,7 +12599,7 @@ async function readWorkspaceTasks(projectRoot) {
12506
12599
  description: normalizeString(entry.description),
12507
12600
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
12508
12601
  status: normalizedStatus ?? "unknown",
12509
- sourceStatus: normalizedStatus ? null : rawStatus,
12602
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
12510
12603
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
12511
12604
  issueType: normalizeString(entry.issue_type),
12512
12605
  role: normalizeString(config.role) ?? null,
@@ -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, {
@@ -535,6 +535,7 @@ import {
535
535
  } from "@rig/runtime/control-plane/authority-files";
536
536
 
537
537
  // packages/server/src/server-helpers/github-auth-store.ts
538
+ import { randomBytes } from "crypto";
538
539
  import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
539
540
  import { resolve as resolve7 } from "path";
540
541
  function cleanString(value) {
@@ -548,6 +549,24 @@ function cleanScopes(value) {
548
549
  return clean ? [clean] : [];
549
550
  });
550
551
  }
552
+ function parseApiSessions(value) {
553
+ if (!Array.isArray(value))
554
+ return [];
555
+ return value.flatMap((entry) => {
556
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
557
+ return [];
558
+ const record = entry;
559
+ const token = cleanString(record.token);
560
+ if (!token)
561
+ return [];
562
+ return [{
563
+ token,
564
+ login: cleanString(record.login),
565
+ userId: cleanString(record.userId),
566
+ createdAt: cleanString(record.createdAt) ?? undefined
567
+ }];
568
+ });
569
+ }
551
570
  function readStoredAuth(stateFile) {
552
571
  if (!existsSync4(stateFile))
553
572
  return {};
@@ -561,6 +580,7 @@ function readStoredAuth(stateFile) {
561
580
  selectedRepo: cleanString(parsed.selectedRepo),
562
581
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
563
582
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
583
+ apiSessions: parseApiSessions(parsed.apiSessions),
564
584
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
565
585
  };
566
586
  } catch {
@@ -579,6 +599,9 @@ function parsePendingDevice(value) {
579
599
  return null;
580
600
  return { pollId, deviceCode, expiresAt, intervalSeconds };
581
601
  }
602
+ function newApiSessionToken() {
603
+ return `rig_${randomBytes(32).toString("base64url")}`;
604
+ }
582
605
  function writeStoredAuth(stateFile, payload) {
583
606
  mkdirSync3(resolve7(stateFile, ".."), { recursive: true });
584
607
  writeFileSync3(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -621,8 +644,37 @@ function createGitHubAuthStore(projectRoot) {
621
644
  scopes: input.scopes ?? [],
622
645
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
623
646
  pendingDevice: null,
647
+ apiSessions: previous.apiSessions ?? [],
648
+ updatedAt: new Date().toISOString()
649
+ });
650
+ },
651
+ createApiSession() {
652
+ const previous = readStoredAuth(stateFile);
653
+ const token = newApiSessionToken();
654
+ const session = {
655
+ token,
656
+ login: cleanString(previous.login),
657
+ userId: cleanString(previous.userId),
658
+ createdAt: new Date().toISOString()
659
+ };
660
+ writeStoredAuth(stateFile, {
661
+ ...previous,
662
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
624
663
  updatedAt: new Date().toISOString()
625
664
  });
665
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
666
+ },
667
+ readApiSession(token) {
668
+ const clean = cleanString(token);
669
+ if (!clean)
670
+ return null;
671
+ const previous = readStoredAuth(stateFile);
672
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
673
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
674
+ },
675
+ copyToProjectRoot(projectRoot2) {
676
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
677
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
626
678
  },
627
679
  savePendingDevice(input) {
628
680
  const previous = readStoredAuth(stateFile);
@@ -1461,10 +1513,10 @@ function normalizeCommit(value) {
1461
1513
  function asPlainRecord(value) {
1462
1514
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1463
1515
  }
1464
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.0";
1516
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
1465
1517
  var RIG_CONFIG_DEV_DEPENDENCIES = {
1466
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
1467
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
1518
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
1519
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
1468
1520
  };
1469
1521
  function repoParts(repoSlug) {
1470
1522
  const [owner, repo] = repoSlug.split("/");
@@ -1818,13 +1870,13 @@ function bearerTokenFromRequest(req) {
1818
1870
  function isLoopbackRequest(req) {
1819
1871
  try {
1820
1872
  const hostname = new URL(req.url).hostname.toLowerCase();
1821
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
1873
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
1822
1874
  } catch {
1823
1875
  return false;
1824
1876
  }
1825
1877
  }
1826
1878
  function isPublicRigAuthBootstrapRoute(pathname) {
1827
- 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";
1879
+ 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";
1828
1880
  }
1829
1881
  function normalizePrMode(value) {
1830
1882
  const mode = normalizeString(value);
@@ -1837,6 +1889,10 @@ function authorizeRigHttpRequest(input) {
1837
1889
  const bearer = bearerTokenFromRequest(input.req);
1838
1890
  const store = createGitHubAuthStore(input.projectRoot);
1839
1891
  const storedToken = store.readToken();
1892
+ const session = bearer ? store.readApiSession(bearer) : null;
1893
+ if (session) {
1894
+ return { authorized: true, actor: session.login ?? "github-operator", reason: "github-session" };
1895
+ }
1840
1896
  if (bearer && storedToken && bearer === storedToken) {
1841
1897
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
1842
1898
  return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
@@ -1844,8 +1900,11 @@ function authorizeRigHttpRequest(input) {
1844
1900
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
1845
1901
  return { authorized: true, actor: null, reason: "public-bootstrap" };
1846
1902
  }
1847
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
1848
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
1903
+ if (!input.serverAuthToken && !storedToken) {
1904
+ if (isLoopbackRequest(input.req)) {
1905
+ return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
1906
+ }
1907
+ return { authorized: false, actor: null, reason: "auth-required" };
1849
1908
  }
1850
1909
  return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
1851
1910
  }
@@ -2076,7 +2135,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
2076
2135
  if (runnable.length === 0)
2077
2136
  return null;
2078
2137
  const queue = readQueueState(projectRoot);
2079
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
2138
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
2080
2139
  return runnable.toSorted((left, right) => {
2081
2140
  const leftId = taskIdOf(left) ?? "";
2082
2141
  const rightId = taskIdOf(right) ?? "";
@@ -2202,7 +2261,7 @@ function createRigServerFetch(state, deps) {
2202
2261
  }
2203
2262
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
2204
2263
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
2205
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
2264
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
2206
2265
  const requestAuth = authorizeRigHttpRequest({
2207
2266
  req,
2208
2267
  pathname: url.pathname,
@@ -2598,6 +2657,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
2598
2657
  }
2599
2658
  const normalizedRoot = resolve11(requestedRoot);
2600
2659
  const exists = existsSync8(normalizedRoot);
2660
+ if (exists) {
2661
+ createGitHubAuthStore(state.projectRoot).copyToProjectRoot(normalizedRoot);
2662
+ }
2601
2663
  const control = buildServerControlStatus();
2602
2664
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
2603
2665
  if (!exists) {
@@ -2686,21 +2748,30 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
2686
2748
  const body = await deps.readJsonBody(req);
2687
2749
  const token = normalizeString(body.token);
2688
2750
  const selectedRepo = normalizeString(body.selectedRepo);
2751
+ const requestedProjectRoot = normalizeString(body.projectRoot);
2689
2752
  if (!token) {
2690
2753
  return deps.badRequest("token is required");
2691
2754
  }
2692
2755
  try {
2693
2756
  const user = await fetchGitHubUserInfo(token);
2694
- const store = createGitHubAuthStore(state.projectRoot);
2695
- store.saveToken({
2696
- token,
2697
- tokenSource: "manual-token",
2698
- login: user.login,
2699
- userId: user.userId,
2700
- scopes: user.scopes,
2701
- selectedRepo
2702
- });
2703
- return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
2757
+ const storeRoots = [
2758
+ state.projectRoot,
2759
+ ...requestedProjectRoot && isAbsolute2(requestedProjectRoot) && existsSync8(resolve11(requestedProjectRoot)) ? [resolve11(requestedProjectRoot)] : []
2760
+ ].filter((root, index, roots) => roots.indexOf(root) === index);
2761
+ const stores = storeRoots.map((root) => createGitHubAuthStore(root));
2762
+ for (const store2 of stores) {
2763
+ store2.saveToken({
2764
+ token,
2765
+ tokenSource: "manual-token",
2766
+ login: user.login,
2767
+ userId: user.userId,
2768
+ scopes: user.scopes,
2769
+ selectedRepo
2770
+ });
2771
+ }
2772
+ const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
2773
+ const apiSession = store.createApiSession();
2774
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
2704
2775
  } catch (error) {
2705
2776
  const message = error instanceof Error ? error.message : String(error);
2706
2777
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -2768,7 +2839,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
2768
2839
  const token = result.payload.access_token;
2769
2840
  const user = await fetchGitHubUserInfo(token);
2770
2841
  store.saveToken({ token, tokenSource: "oauth-device", login: user.login, userId: user.userId, scopes: user.scopes });
2771
- return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }) });
2842
+ const apiSession = store.createApiSession();
2843
+ return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }), apiSessionToken: apiSession.token });
2772
2844
  }
2773
2845
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
2774
2846
  const body = await deps.readJsonBody(req);
@@ -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.0";
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)
@@ -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.0";
440
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
441
441
  var RIG_CONFIG_DEV_DEPENDENCIES = {
442
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
443
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
442
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
443
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
444
444
  };
445
445
 
446
446
  // packages/server/src/server-helpers/inspector-jobs.ts
@@ -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
  }));
@@ -3304,6 +3304,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
3304
3304
  }
3305
3305
 
3306
3306
  // packages/server/src/server-helpers/github-auth-store.ts
3307
+ import { randomBytes } from "crypto";
3307
3308
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3308
3309
  import { resolve as resolve13 } from "path";
3309
3310
  function cleanString(value) {
@@ -3317,6 +3318,24 @@ function cleanScopes(value) {
3317
3318
  return clean ? [clean] : [];
3318
3319
  });
3319
3320
  }
3321
+ function parseApiSessions(value) {
3322
+ if (!Array.isArray(value))
3323
+ return [];
3324
+ return value.flatMap((entry) => {
3325
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3326
+ return [];
3327
+ const record = entry;
3328
+ const token = cleanString(record.token);
3329
+ if (!token)
3330
+ return [];
3331
+ return [{
3332
+ token,
3333
+ login: cleanString(record.login),
3334
+ userId: cleanString(record.userId),
3335
+ createdAt: cleanString(record.createdAt) ?? undefined
3336
+ }];
3337
+ });
3338
+ }
3320
3339
  function readStoredAuth(stateFile) {
3321
3340
  if (!existsSync6(stateFile))
3322
3341
  return {};
@@ -3330,6 +3349,7 @@ function readStoredAuth(stateFile) {
3330
3349
  selectedRepo: cleanString(parsed.selectedRepo),
3331
3350
  tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
3332
3351
  pendingDevice: parsePendingDevice(parsed.pendingDevice),
3352
+ apiSessions: parseApiSessions(parsed.apiSessions),
3333
3353
  updatedAt: cleanString(parsed.updatedAt) ?? undefined
3334
3354
  };
3335
3355
  } catch {
@@ -3348,6 +3368,9 @@ function parsePendingDevice(value) {
3348
3368
  return null;
3349
3369
  return { pollId, deviceCode, expiresAt, intervalSeconds };
3350
3370
  }
3371
+ function newApiSessionToken() {
3372
+ return `rig_${randomBytes(32).toString("base64url")}`;
3373
+ }
3351
3374
  function writeStoredAuth(stateFile, payload) {
3352
3375
  mkdirSync6(resolve13(stateFile, ".."), { recursive: true });
3353
3376
  writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
@@ -3390,9 +3413,38 @@ function createGitHubAuthStore(projectRoot) {
3390
3413
  scopes: input.scopes ?? [],
3391
3414
  selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
3392
3415
  pendingDevice: null,
3416
+ apiSessions: previous.apiSessions ?? [],
3393
3417
  updatedAt: new Date().toISOString()
3394
3418
  });
3395
3419
  },
3420
+ createApiSession() {
3421
+ const previous = readStoredAuth(stateFile);
3422
+ const token = newApiSessionToken();
3423
+ const session = {
3424
+ token,
3425
+ login: cleanString(previous.login),
3426
+ userId: cleanString(previous.userId),
3427
+ createdAt: new Date().toISOString()
3428
+ };
3429
+ writeStoredAuth(stateFile, {
3430
+ ...previous,
3431
+ apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
3432
+ updatedAt: new Date().toISOString()
3433
+ });
3434
+ return { token, login: session.login ?? null, userId: session.userId ?? null };
3435
+ },
3436
+ readApiSession(token) {
3437
+ const clean = cleanString(token);
3438
+ if (!clean)
3439
+ return null;
3440
+ const previous = readStoredAuth(stateFile);
3441
+ const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
3442
+ return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
3443
+ },
3444
+ copyToProjectRoot(projectRoot2) {
3445
+ const targetFile = resolveGitHubAuthStateFile(projectRoot2);
3446
+ writeStoredAuth(targetFile, readStoredAuth(stateFile));
3447
+ },
3396
3448
  savePendingDevice(input) {
3397
3449
  const previous = readStoredAuth(stateFile);
3398
3450
  writeStoredAuth(stateFile, {
@@ -3690,15 +3742,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
3690
3742
  if (!run.taskId)
3691
3743
  return;
3692
3744
  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
- });
3745
+ try {
3746
+ const result = await syncGitHubProjectStatusForTaskUpdate({
3747
+ taskId: run.taskId,
3748
+ status,
3749
+ issueNodeId,
3750
+ token: createGitHubAuthStore(projectRoot).readToken(),
3751
+ config
3752
+ });
3753
+ if (!result.synced && result.reason !== "project-sync-disabled") {
3754
+ appendRunLogEntry(projectRoot, run.runId, {
3755
+ id: `log:${run.runId}:github-project-sync:${status}`,
3756
+ title: "GitHub Project sync skipped",
3757
+ detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
3758
+ tone: "warn",
3759
+ status: "running",
3760
+ createdAt: new Date().toISOString(),
3761
+ payload: { reason: result.reason, issueNodeId }
3762
+ });
3763
+ }
3764
+ } catch (error) {
3765
+ appendRunLogEntry(projectRoot, run.runId, {
3766
+ id: `log:${run.runId}:github-project-sync-error:${status}`,
3767
+ title: "GitHub Project sync failed",
3768
+ detail: error instanceof Error ? error.message : String(error),
3769
+ tone: "error",
3770
+ status: "running",
3771
+ createdAt: new Date().toISOString(),
3772
+ payload: { issueNodeId }
3773
+ });
3774
+ }
3702
3775
  }
3703
3776
  async function autoAssignRunIssue(projectRoot, run) {
3704
3777
  if (!run.taskId)
@@ -4886,10 +4959,10 @@ function normalizeCommit(value) {
4886
4959
  function asPlainRecord(value) {
4887
4960
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4888
4961
  }
4889
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.0";
4962
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
4890
4963
  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}`
4964
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
4965
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
4893
4966
  };
4894
4967
  function repoParts(repoSlug) {
4895
4968
  const [owner, repo] = repoSlug.split("/");
@@ -5243,13 +5316,13 @@ function bearerTokenFromRequest(req) {
5243
5316
  function isLoopbackRequest(req) {
5244
5317
  try {
5245
5318
  const hostname = new URL(req.url).hostname.toLowerCase();
5246
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5319
+ return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5247
5320
  } catch {
5248
5321
  return false;
5249
5322
  }
5250
5323
  }
5251
5324
  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";
5325
+ 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
5326
  }
5254
5327
  function normalizePrMode(value) {
5255
5328
  const mode = normalizeString(value);
@@ -5262,6 +5335,10 @@ function authorizeRigHttpRequest(input) {
5262
5335
  const bearer = bearerTokenFromRequest(input.req);
5263
5336
  const store = createGitHubAuthStore(input.projectRoot);
5264
5337
  const storedToken = store.readToken();
5338
+ const session = bearer ? store.readApiSession(bearer) : null;
5339
+ if (session) {
5340
+ return { authorized: true, actor: session.login ?? "github-operator", reason: "github-session" };
5341
+ }
5265
5342
  if (bearer && storedToken && bearer === storedToken) {
5266
5343
  const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
5267
5344
  return { authorized: true, actor: status.login ?? "github-operator", reason: "github-token" };
@@ -5269,8 +5346,11 @@ function authorizeRigHttpRequest(input) {
5269
5346
  if (isPublicRigAuthBootstrapRoute(input.pathname)) {
5270
5347
  return { authorized: true, actor: null, reason: "public-bootstrap" };
5271
5348
  }
5272
- if (!input.serverAuthToken && !storedToken && isLoopbackRequest(input.req)) {
5273
- return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5349
+ if (!input.serverAuthToken && !storedToken) {
5350
+ if (isLoopbackRequest(input.req)) {
5351
+ return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
5352
+ }
5353
+ return { authorized: false, actor: null, reason: "auth-required" };
5274
5354
  }
5275
5355
  return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
5276
5356
  }
@@ -5501,7 +5581,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
5501
5581
  if (runnable.length === 0)
5502
5582
  return null;
5503
5583
  const queue = readQueueState(projectRoot);
5504
- const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
5584
+ const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
5505
5585
  return runnable.toSorted((left, right) => {
5506
5586
  const leftId = taskIdOf(left) ?? "";
5507
5587
  const rightId = taskIdOf(right) ?? "";
@@ -5627,7 +5707,7 @@ function createRigServerFetch(state, deps) {
5627
5707
  }
5628
5708
  const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
5629
5709
  const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
5630
- const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
5710
+ const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
5631
5711
  const requestAuth = authorizeRigHttpRequest({
5632
5712
  req,
5633
5713
  pathname: url.pathname,
@@ -6023,6 +6103,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6023
6103
  }
6024
6104
  const normalizedRoot = resolve18(requestedRoot);
6025
6105
  const exists = existsSync11(normalizedRoot);
6106
+ if (exists) {
6107
+ createGitHubAuthStore(state.projectRoot).copyToProjectRoot(normalizedRoot);
6108
+ }
6026
6109
  const control = buildServerControlStatus();
6027
6110
  const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
6028
6111
  if (!exists) {
@@ -6111,21 +6194,30 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6111
6194
  const body = await deps.readJsonBody(req);
6112
6195
  const token = normalizeString(body.token);
6113
6196
  const selectedRepo = normalizeString(body.selectedRepo);
6197
+ const requestedProjectRoot = normalizeString(body.projectRoot);
6114
6198
  if (!token) {
6115
6199
  return deps.badRequest("token is required");
6116
6200
  }
6117
6201
  try {
6118
6202
  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()) }) });
6203
+ const storeRoots = [
6204
+ state.projectRoot,
6205
+ ...requestedProjectRoot && isAbsolute3(requestedProjectRoot) && existsSync11(resolve18(requestedProjectRoot)) ? [resolve18(requestedProjectRoot)] : []
6206
+ ].filter((root, index, roots) => roots.indexOf(root) === index);
6207
+ const stores = storeRoots.map((root) => createGitHubAuthStore(root));
6208
+ for (const store2 of stores) {
6209
+ store2.saveToken({
6210
+ token,
6211
+ tokenSource: "manual-token",
6212
+ login: user.login,
6213
+ userId: user.userId,
6214
+ scopes: user.scopes,
6215
+ selectedRepo
6216
+ });
6217
+ }
6218
+ const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
6219
+ const apiSession = store.createApiSession();
6220
+ return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
6129
6221
  } catch (error) {
6130
6222
  const message = error instanceof Error ? error.message : String(error);
6131
6223
  return deps.jsonResponse({ ok: false, error: message }, 400);
@@ -6193,7 +6285,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
6193
6285
  const token = result.payload.access_token;
6194
6286
  const user = await fetchGitHubUserInfo(token);
6195
6287
  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 }) });
6288
+ const apiSession = store.createApiSession();
6289
+ return deps.jsonResponse({ ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }), apiSessionToken: apiSession.token });
6197
6290
  }
6198
6291
  if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
6199
6292
  const body = await deps.readJsonBody(req);
@@ -11999,7 +12092,7 @@ async function readWorkspaceTasks(projectRoot) {
11999
12092
  description: normalizeString(entry.description),
12000
12093
  acceptanceCriteria: normalizeString(entry.acceptance_criteria),
12001
12094
  status: normalizedStatus ?? "unknown",
12002
- sourceStatus: normalizedStatus ? null : rawStatus,
12095
+ sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
12003
12096
  priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
12004
12097
  issueType: normalizeString(entry.issue_type),
12005
12098
  role: normalizeString(config.role) ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/server",
3
- "version": "0.0.6-alpha.0",
3
+ "version": "0.0.6-alpha.2",
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.0",
29
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.0",
30
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.0",
28
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.2",
29
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.2",
30
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.2",
31
31
  "effect": "4.0.0-beta.78"
32
32
  }
33
33
  }