@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 +216 -33
- package/dist/src/server-helpers/github-auth-store.js +52 -0
- package/dist/src/server-helpers/http-router.js +427 -22
- package/dist/src/server-helpers/issue-analysis.js +4 -1
- package/dist/src/server-helpers/run-mutations.js +91 -14
- package/dist/src/server-helpers/snapshot-orchestrator.js +1 -1
- package/dist/src/server-helpers/snapshot-service.js +1 -1
- package/dist/src/server-helpers/ws-router.js +3 -3
- package/dist/src/server.js +216 -33
- package/package.json +4 -4
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
|
|
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
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
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 ? {
|
|
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
|
|
5476
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
5397
5477
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
5398
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
5399
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
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/
|
|
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
|
|
5780
|
-
|
|
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
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
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
|
-
|
|
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, {
|