@h-rig/server 0.0.6-alpha.1 → 0.0.6-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/src/index.js +920 -287
- package/dist/src/server-helpers/github-api-session-index.js +107 -0
- package/dist/src/server-helpers/github-auth-store.js +117 -21
- package/dist/src/server-helpers/github-user-namespace.js +102 -0
- package/dist/src/server-helpers/http-router.js +998 -151
- package/dist/src/server-helpers/issue-analysis.js +30 -11
- package/dist/src/server-helpers/project-registry.js +5 -0
- package/dist/src/server-helpers/run-mutations.js +248 -103
- 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 +4 -4
- package/dist/src/server.js +921 -287
- package/package.json +4 -4
package/dist/src/server.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
1
2
|
// @bun
|
|
2
3
|
var __require = import.meta.require;
|
|
3
4
|
|
|
4
5
|
// packages/server/src/server.ts
|
|
5
6
|
import { spawn as spawn5 } from "child_process";
|
|
6
|
-
import { existsSync as
|
|
7
|
+
import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
|
|
7
8
|
import { open } from "fs/promises";
|
|
8
|
-
import { dirname as
|
|
9
|
+
import { dirname as dirname20, resolve as resolve24 } from "path";
|
|
9
10
|
import {
|
|
10
11
|
listAuthorityArtifactRoots,
|
|
11
12
|
listAuthorityRuns as listAuthorityRuns7,
|
|
@@ -1786,7 +1787,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
|
|
|
1786
1787
|
const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
|
|
1787
1788
|
...toTaskSummary(workspace.id, {
|
|
1788
1789
|
...task,
|
|
1789
|
-
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
|
|
1790
|
+
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
|
|
1790
1791
|
}),
|
|
1791
1792
|
graphId: graph.id
|
|
1792
1793
|
}));
|
|
@@ -2312,7 +2313,6 @@ function createGitHubTaskReconciler(input) {
|
|
|
2312
2313
|
}
|
|
2313
2314
|
|
|
2314
2315
|
// packages/server/src/server-helpers/issue-analysis.ts
|
|
2315
|
-
import { execFile } from "child_process";
|
|
2316
2316
|
import { createHash } from "crypto";
|
|
2317
2317
|
function stableIssueHash(issue) {
|
|
2318
2318
|
const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
|
|
@@ -2446,16 +2446,33 @@ function parseIssueAnalysisResult(raw) {
|
|
|
2446
2446
|
return result;
|
|
2447
2447
|
}
|
|
2448
2448
|
function createDefaultPiIssueAnalysisCommandRunner() {
|
|
2449
|
-
return (command, args, options) =>
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
|
|
2456
|
-
resolve11({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
|
|
2449
|
+
return async (command, args, options) => {
|
|
2450
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
2451
|
+
const proc = Bun.spawn([command, ...args], {
|
|
2452
|
+
stdout: "pipe",
|
|
2453
|
+
stderr: "pipe",
|
|
2454
|
+
env
|
|
2457
2455
|
});
|
|
2458
|
-
|
|
2456
|
+
let timedOut = false;
|
|
2457
|
+
const timer = setTimeout(() => {
|
|
2458
|
+
timedOut = true;
|
|
2459
|
+
proc.kill();
|
|
2460
|
+
}, options.timeoutMs);
|
|
2461
|
+
try {
|
|
2462
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
2463
|
+
new Response(proc.stdout).text(),
|
|
2464
|
+
new Response(proc.stderr).text(),
|
|
2465
|
+
proc.exited
|
|
2466
|
+
]);
|
|
2467
|
+
return {
|
|
2468
|
+
exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
|
|
2469
|
+
stdout,
|
|
2470
|
+
stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
|
|
2471
|
+
};
|
|
2472
|
+
} finally {
|
|
2473
|
+
clearTimeout(timer);
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2459
2476
|
}
|
|
2460
2477
|
function createPiIssueAnalyzer(input = {}) {
|
|
2461
2478
|
const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
|
|
@@ -2463,7 +2480,10 @@ function createPiIssueAnalyzer(input = {}) {
|
|
|
2463
2480
|
const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
|
|
2464
2481
|
return async ({ prompt }) => {
|
|
2465
2482
|
const args = ["--print", "--mode", "json", "--no-session"];
|
|
2466
|
-
const
|
|
2483
|
+
const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
|
|
2484
|
+
const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
|
|
2485
|
+
if (provider)
|
|
2486
|
+
args.push("--provider", provider);
|
|
2467
2487
|
if (model)
|
|
2468
2488
|
args.push("--model", model);
|
|
2469
2489
|
args.push(prompt);
|
|
@@ -3181,7 +3201,7 @@ function applyOrchestrationCommand(state, command) {
|
|
|
3181
3201
|
import { spawn as spawn3 } from "child_process";
|
|
3182
3202
|
import { loadConfig } from "@rig/core/load-config";
|
|
3183
3203
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3184
|
-
import { dirname as
|
|
3204
|
+
import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
|
|
3185
3205
|
import {
|
|
3186
3206
|
listAuthorityRuns as listAuthorityRuns4,
|
|
3187
3207
|
readAuthorityRun as readAuthorityRun4,
|
|
@@ -3304,8 +3324,9 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
3304
3324
|
}
|
|
3305
3325
|
|
|
3306
3326
|
// packages/server/src/server-helpers/github-auth-store.ts
|
|
3307
|
-
import {
|
|
3308
|
-
import {
|
|
3327
|
+
import { randomBytes } from "crypto";
|
|
3328
|
+
import { chmodSync, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
3329
|
+
import { dirname as dirname8, resolve as resolve13 } from "path";
|
|
3309
3330
|
function cleanString(value) {
|
|
3310
3331
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3311
3332
|
}
|
|
@@ -3317,6 +3338,44 @@ function cleanScopes(value) {
|
|
|
3317
3338
|
return clean ? [clean] : [];
|
|
3318
3339
|
});
|
|
3319
3340
|
}
|
|
3341
|
+
function parseApiSessions(value) {
|
|
3342
|
+
if (!Array.isArray(value))
|
|
3343
|
+
return [];
|
|
3344
|
+
return value.flatMap((entry) => {
|
|
3345
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
3346
|
+
return [];
|
|
3347
|
+
const record = entry;
|
|
3348
|
+
const token = cleanString(record.token);
|
|
3349
|
+
if (!token)
|
|
3350
|
+
return [];
|
|
3351
|
+
return [{
|
|
3352
|
+
token,
|
|
3353
|
+
login: cleanString(record.login),
|
|
3354
|
+
userId: cleanString(record.userId),
|
|
3355
|
+
createdAt: cleanString(record.createdAt) ?? undefined
|
|
3356
|
+
}];
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
function parsePendingDevice(value) {
|
|
3360
|
+
if (!value || typeof value !== "object")
|
|
3361
|
+
return null;
|
|
3362
|
+
const record = value;
|
|
3363
|
+
const pollId = cleanString(record.pollId);
|
|
3364
|
+
const deviceCode = cleanString(record.deviceCode);
|
|
3365
|
+
const expiresAt = cleanString(record.expiresAt);
|
|
3366
|
+
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3367
|
+
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3368
|
+
return null;
|
|
3369
|
+
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3370
|
+
}
|
|
3371
|
+
function parsePendingDevices(value) {
|
|
3372
|
+
if (!Array.isArray(value))
|
|
3373
|
+
return [];
|
|
3374
|
+
return value.flatMap((entry) => {
|
|
3375
|
+
const pending = parsePendingDevice(entry);
|
|
3376
|
+
return pending ? [pending] : [];
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3320
3379
|
function readStoredAuth(stateFile) {
|
|
3321
3380
|
if (!existsSync6(stateFile))
|
|
3322
3381
|
return {};
|
|
@@ -3330,37 +3389,44 @@ function readStoredAuth(stateFile) {
|
|
|
3330
3389
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
3331
3390
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
3332
3391
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
3392
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
3393
|
+
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
3333
3394
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
3334
3395
|
};
|
|
3335
3396
|
} catch {
|
|
3336
3397
|
return {};
|
|
3337
3398
|
}
|
|
3338
3399
|
}
|
|
3339
|
-
function
|
|
3340
|
-
|
|
3341
|
-
return null;
|
|
3342
|
-
const record = value;
|
|
3343
|
-
const pollId = cleanString(record.pollId);
|
|
3344
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
3345
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
3346
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3347
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3348
|
-
return null;
|
|
3349
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3400
|
+
function newApiSessionToken() {
|
|
3401
|
+
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
3350
3402
|
}
|
|
3351
3403
|
function writeStoredAuth(stateFile, payload) {
|
|
3352
|
-
mkdirSync6(
|
|
3404
|
+
mkdirSync6(dirname8(stateFile), { recursive: true });
|
|
3353
3405
|
writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
3354
3406
|
`, { encoding: "utf8", mode: 384 });
|
|
3355
3407
|
try {
|
|
3356
3408
|
chmodSync(stateFile, 384);
|
|
3357
3409
|
} catch {}
|
|
3358
3410
|
}
|
|
3411
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
3412
|
+
return resolve13(projectRoot, ".rig", "state", "github-auth.json");
|
|
3413
|
+
}
|
|
3359
3414
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
3360
3415
|
return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
3361
3416
|
}
|
|
3362
|
-
function
|
|
3363
|
-
const
|
|
3417
|
+
function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
|
|
3418
|
+
const targetFile = localProjectAuthStateFile(projectRoot);
|
|
3419
|
+
mkdirSync6(dirname8(targetFile), { recursive: true });
|
|
3420
|
+
if (existsSync6(stateFile)) {
|
|
3421
|
+
copyFileSync(stateFile, targetFile);
|
|
3422
|
+
try {
|
|
3423
|
+
chmodSync(targetFile, 384);
|
|
3424
|
+
} catch {}
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
writeStoredAuth(targetFile, {});
|
|
3428
|
+
}
|
|
3429
|
+
function createGitHubAuthStoreFromStateFile(stateFile) {
|
|
3364
3430
|
return {
|
|
3365
3431
|
stateFile,
|
|
3366
3432
|
status(options) {
|
|
@@ -3390,14 +3456,53 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3390
3456
|
scopes: input.scopes ?? [],
|
|
3391
3457
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
3392
3458
|
pendingDevice: null,
|
|
3459
|
+
pendingDevices: [],
|
|
3460
|
+
apiSessions: previous.apiSessions ?? [],
|
|
3461
|
+
updatedAt: new Date().toISOString()
|
|
3462
|
+
});
|
|
3463
|
+
},
|
|
3464
|
+
createApiSession() {
|
|
3465
|
+
const previous = readStoredAuth(stateFile);
|
|
3466
|
+
const token = newApiSessionToken();
|
|
3467
|
+
const session = {
|
|
3468
|
+
token,
|
|
3469
|
+
login: cleanString(previous.login),
|
|
3470
|
+
userId: cleanString(previous.userId),
|
|
3471
|
+
createdAt: new Date().toISOString()
|
|
3472
|
+
};
|
|
3473
|
+
writeStoredAuth(stateFile, {
|
|
3474
|
+
...previous,
|
|
3475
|
+
apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
|
|
3393
3476
|
updatedAt: new Date().toISOString()
|
|
3394
3477
|
});
|
|
3478
|
+
return { token, login: session.login ?? null, userId: session.userId ?? null };
|
|
3479
|
+
},
|
|
3480
|
+
readApiSession(token) {
|
|
3481
|
+
const clean = cleanString(token);
|
|
3482
|
+
if (!clean)
|
|
3483
|
+
return null;
|
|
3484
|
+
const previous = readStoredAuth(stateFile);
|
|
3485
|
+
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
3486
|
+
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
3487
|
+
},
|
|
3488
|
+
copyToProjectRoot(projectRoot) {
|
|
3489
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
3490
|
+
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
3491
|
+
},
|
|
3492
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
3493
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
3395
3494
|
},
|
|
3396
3495
|
savePendingDevice(input) {
|
|
3397
3496
|
const previous = readStoredAuth(stateFile);
|
|
3497
|
+
const pendingDevices = [
|
|
3498
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
3499
|
+
...previous.pendingDevices ?? [],
|
|
3500
|
+
input
|
|
3501
|
+
].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
|
|
3398
3502
|
writeStoredAuth(stateFile, {
|
|
3399
3503
|
...previous,
|
|
3400
|
-
pendingDevice:
|
|
3504
|
+
pendingDevice: null,
|
|
3505
|
+
pendingDevices,
|
|
3401
3506
|
updatedAt: new Date().toISOString()
|
|
3402
3507
|
});
|
|
3403
3508
|
},
|
|
@@ -3410,23 +3515,32 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3410
3515
|
});
|
|
3411
3516
|
},
|
|
3412
3517
|
readPendingDevice(pollId) {
|
|
3413
|
-
const
|
|
3414
|
-
|
|
3518
|
+
const previous = readStoredAuth(stateFile);
|
|
3519
|
+
const pending = [
|
|
3520
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
3521
|
+
...previous.pendingDevices ?? []
|
|
3522
|
+
].find((entry) => entry.pollId === pollId) ?? null;
|
|
3523
|
+
if (!pending)
|
|
3415
3524
|
return null;
|
|
3416
3525
|
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
3417
3526
|
return null;
|
|
3418
3527
|
return pending;
|
|
3419
3528
|
},
|
|
3420
|
-
clearPendingDevice() {
|
|
3529
|
+
clearPendingDevice(pollId) {
|
|
3421
3530
|
const previous = readStoredAuth(stateFile);
|
|
3531
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
3422
3532
|
writeStoredAuth(stateFile, {
|
|
3423
3533
|
...previous,
|
|
3424
3534
|
pendingDevice: null,
|
|
3535
|
+
pendingDevices: remaining,
|
|
3425
3536
|
updatedAt: new Date().toISOString()
|
|
3426
3537
|
});
|
|
3427
3538
|
}
|
|
3428
3539
|
};
|
|
3429
3540
|
}
|
|
3541
|
+
function createGitHubAuthStore(projectRoot) {
|
|
3542
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
3543
|
+
}
|
|
3430
3544
|
|
|
3431
3545
|
// packages/server/src/server-helpers/github-projects.ts
|
|
3432
3546
|
function asRecord(value) {
|
|
@@ -3690,15 +3804,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
|
|
|
3690
3804
|
if (!run.taskId)
|
|
3691
3805
|
return;
|
|
3692
3806
|
const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3807
|
+
try {
|
|
3808
|
+
const result = await syncGitHubProjectStatusForTaskUpdate({
|
|
3809
|
+
taskId: run.taskId,
|
|
3810
|
+
status,
|
|
3811
|
+
issueNodeId,
|
|
3812
|
+
token: createGitHubAuthStore(projectRoot).readToken(),
|
|
3813
|
+
config
|
|
3814
|
+
});
|
|
3815
|
+
if (!result.synced && result.reason !== "project-sync-disabled") {
|
|
3816
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
3817
|
+
id: `log:${run.runId}:github-project-sync:${status}`,
|
|
3818
|
+
title: "GitHub Project sync skipped",
|
|
3819
|
+
detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
|
|
3820
|
+
tone: "warn",
|
|
3821
|
+
status: "running",
|
|
3822
|
+
createdAt: new Date().toISOString(),
|
|
3823
|
+
payload: { reason: result.reason, issueNodeId }
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
} catch (error) {
|
|
3827
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
3828
|
+
id: `log:${run.runId}:github-project-sync-error:${status}`,
|
|
3829
|
+
title: "GitHub Project sync failed",
|
|
3830
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
3831
|
+
tone: "error",
|
|
3832
|
+
status: "running",
|
|
3833
|
+
createdAt: new Date().toISOString(),
|
|
3834
|
+
payload: { issueNodeId }
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3702
3837
|
}
|
|
3703
3838
|
async function autoAssignRunIssue(projectRoot, run) {
|
|
3704
3839
|
if (!run.taskId)
|
|
@@ -3800,11 +3935,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
|
|
|
3800
3935
|
return;
|
|
3801
3936
|
throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
|
|
3802
3937
|
}
|
|
3938
|
+
async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
|
|
3939
|
+
const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
3940
|
+
if (fromReader)
|
|
3941
|
+
return fromReader;
|
|
3942
|
+
const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
|
|
3943
|
+
if (projected)
|
|
3944
|
+
return projected;
|
|
3945
|
+
if (readTasks !== readWorkspaceTasks) {
|
|
3946
|
+
return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
3947
|
+
}
|
|
3948
|
+
return null;
|
|
3949
|
+
}
|
|
3803
3950
|
async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
|
|
3804
3951
|
if ("taskId" in input && input.taskId) {
|
|
3805
3952
|
assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
|
|
3806
3953
|
}
|
|
3807
|
-
const sourceTask = "taskId" in input && input.taskId ?
|
|
3954
|
+
const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
|
|
3808
3955
|
const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
|
|
3809
3956
|
const runDir = resolveAuthorityRunDir3(projectRoot, input.runId);
|
|
3810
3957
|
const runRecord = {
|
|
@@ -3876,6 +4023,7 @@ async function startLocalRun(state, runId, options) {
|
|
|
3876
4023
|
throw new Error(`Run not found: ${runId}`);
|
|
3877
4024
|
}
|
|
3878
4025
|
const startedAt = new Date().toISOString();
|
|
4026
|
+
const resumeMode = options?.resume === true;
|
|
3879
4027
|
state.runProcesses.set(runId, {
|
|
3880
4028
|
runId,
|
|
3881
4029
|
child: null,
|
|
@@ -3892,9 +4040,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
3892
4040
|
summary: run.title
|
|
3893
4041
|
});
|
|
3894
4042
|
appendRunLogEntry(state.projectRoot, runId, {
|
|
3895
|
-
id: `log:${runId}:prepare`,
|
|
3896
|
-
title: "Rig task run starting",
|
|
3897
|
-
detail: run.taskId ?? run.title,
|
|
4043
|
+
id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
|
|
4044
|
+
title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
|
|
4045
|
+
detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
|
|
3898
4046
|
tone: "info",
|
|
3899
4047
|
status: "preparing",
|
|
3900
4048
|
createdAt: startedAt
|
|
@@ -3972,7 +4120,15 @@ async function startLocalRun(state, runId, options) {
|
|
|
3972
4120
|
RIG_SERVER_INTERNAL_EXEC: "1",
|
|
3973
4121
|
...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
|
|
3974
4122
|
...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
|
|
3975
|
-
...bridgeGitHubToken ? {
|
|
4123
|
+
...bridgeGitHubToken ? {
|
|
4124
|
+
RIG_GITHUB_TOKEN: bridgeGitHubToken,
|
|
4125
|
+
GITHUB_TOKEN: bridgeGitHubToken,
|
|
4126
|
+
GH_TOKEN: bridgeGitHubToken
|
|
4127
|
+
} : {},
|
|
4128
|
+
...resumeMode ? {
|
|
4129
|
+
RIG_RUN_RESUME: "1",
|
|
4130
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
4131
|
+
} : {}
|
|
3976
4132
|
},
|
|
3977
4133
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3978
4134
|
});
|
|
@@ -4123,7 +4279,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
4123
4279
|
}
|
|
4124
4280
|
try {
|
|
4125
4281
|
const monorepoRoot = resolveMonorepoRoot3(projectRoot);
|
|
4126
|
-
const outerProjectRoot =
|
|
4282
|
+
const outerProjectRoot = dirname9(dirname9(monorepoRoot));
|
|
4127
4283
|
if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
4128
4284
|
return outerProjectRoot;
|
|
4129
4285
|
}
|
|
@@ -4144,7 +4300,7 @@ async function resumeRunRecord(state, input) {
|
|
|
4144
4300
|
if (run.status === "completed") {
|
|
4145
4301
|
throw new Error("Completed runs cannot be resumed.");
|
|
4146
4302
|
}
|
|
4147
|
-
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
|
|
4303
|
+
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
4148
4304
|
}
|
|
4149
4305
|
function appendRunMessage(projectRoot, input) {
|
|
4150
4306
|
const run = readAuthorityRun4(projectRoot, input.runId);
|
|
@@ -4228,34 +4384,12 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
|
|
|
4228
4384
|
writeQueueState(projectRoot, next);
|
|
4229
4385
|
return next;
|
|
4230
4386
|
}
|
|
4231
|
-
var
|
|
4232
|
-
function
|
|
4233
|
-
|
|
4234
|
-
for (const run of runs) {
|
|
4387
|
+
var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
4388
|
+
function collectResumableLocalRuns(state, runs) {
|
|
4389
|
+
return runs.filter((run) => {
|
|
4235
4390
|
const status = normalizeString(run.status)?.toLowerCase() ?? "";
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
if (run.mode !== "local" || !wasStartedByRigServer || !ORPHANABLE_LOCAL_RUN_STATUSES.has(status) || state.runProcesses.has(run.runId)) {
|
|
4239
|
-
continue;
|
|
4240
|
-
}
|
|
4241
|
-
const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
|
|
4242
|
-
patchRunRecord(state.projectRoot, run.runId, {
|
|
4243
|
-
status: "failed",
|
|
4244
|
-
completedAt: run.completedAt ?? nowIso,
|
|
4245
|
-
updatedAt: nowIso,
|
|
4246
|
-
errorText: detail
|
|
4247
|
-
});
|
|
4248
|
-
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
4249
|
-
id: `log:${run.runId}:stale-local-run`,
|
|
4250
|
-
title: "Run marked stale after server restart",
|
|
4251
|
-
detail,
|
|
4252
|
-
tone: "error",
|
|
4253
|
-
status: "failed",
|
|
4254
|
-
createdAt: nowIso
|
|
4255
|
-
});
|
|
4256
|
-
changed = true;
|
|
4257
|
-
}
|
|
4258
|
-
return changed;
|
|
4391
|
+
return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
|
|
4392
|
+
});
|
|
4259
4393
|
}
|
|
4260
4394
|
async function reconcileScheduler(state, reason) {
|
|
4261
4395
|
if (state.scheduler.reconciling) {
|
|
@@ -4270,7 +4404,20 @@ async function reconcileScheduler(state, reason) {
|
|
|
4270
4404
|
const queue = readQueueState(state.projectRoot);
|
|
4271
4405
|
const tasks = await state.snapshotService.getWorkspaceTasks();
|
|
4272
4406
|
let runs = listAuthorityRuns4(state.projectRoot);
|
|
4273
|
-
let changed =
|
|
4407
|
+
let changed = false;
|
|
4408
|
+
const resumableRuns = collectResumableLocalRuns(state, runs);
|
|
4409
|
+
for (const run of resumableRuns) {
|
|
4410
|
+
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
4411
|
+
id: `log:${run.runId}:auto-resume:${Date.now()}`,
|
|
4412
|
+
title: "Run auto-resume scheduled",
|
|
4413
|
+
detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
|
|
4414
|
+
tone: "info",
|
|
4415
|
+
status: "preparing",
|
|
4416
|
+
createdAt: new Date().toISOString()
|
|
4417
|
+
});
|
|
4418
|
+
await startLocalRun(state, run.runId, { resume: true });
|
|
4419
|
+
changed = true;
|
|
4420
|
+
}
|
|
4274
4421
|
if (changed) {
|
|
4275
4422
|
runs = listAuthorityRuns4(state.projectRoot);
|
|
4276
4423
|
}
|
|
@@ -4346,8 +4493,8 @@ async function reconcileScheduler(state, reason) {
|
|
|
4346
4493
|
// packages/server/src/server-helpers/http-router.ts
|
|
4347
4494
|
import { randomUUID } from "crypto";
|
|
4348
4495
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4349
|
-
import { basename, dirname as
|
|
4350
|
-
import { copyFileSync, existsSync as
|
|
4496
|
+
import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as resolve20 } from "path";
|
|
4497
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
4351
4498
|
import {
|
|
4352
4499
|
listAuthorityRuns as listAuthorityRuns5,
|
|
4353
4500
|
readAuthorityRun as readAuthorityRun6,
|
|
@@ -4371,7 +4518,7 @@ import {
|
|
|
4371
4518
|
} from "@rig/runtime/control-plane/remote";
|
|
4372
4519
|
|
|
4373
4520
|
// packages/server/src/server-helpers/run-steering.ts
|
|
4374
|
-
import { dirname as
|
|
4521
|
+
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
4375
4522
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
4376
4523
|
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
4377
4524
|
var steeringSequence = 0;
|
|
@@ -4458,7 +4605,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
|
4458
4605
|
delivered: false
|
|
4459
4606
|
};
|
|
4460
4607
|
const path = runSteeringPath(projectRoot, runId);
|
|
4461
|
-
mkdirSync8(
|
|
4608
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
4462
4609
|
appendJsonlRecord2(path, entry);
|
|
4463
4610
|
appendRunTimelineEntry(projectRoot, runId, {
|
|
4464
4611
|
id: entry.id,
|
|
@@ -4495,6 +4642,187 @@ import {
|
|
|
4495
4642
|
updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
|
|
4496
4643
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
4497
4644
|
|
|
4645
|
+
// packages/server/src/server-helpers/github-api-session-index.ts
|
|
4646
|
+
import { chmodSync as chmodSync3, existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
4647
|
+
import { dirname as dirname12, resolve as resolve17 } from "path";
|
|
4648
|
+
|
|
4649
|
+
// packages/server/src/server-helpers/github-user-namespace.ts
|
|
4650
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
4651
|
+
import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve16 } from "path";
|
|
4652
|
+
function cleanString3(value) {
|
|
4653
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
4654
|
+
}
|
|
4655
|
+
function sanitizePathSegment(value) {
|
|
4656
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
4657
|
+
}
|
|
4658
|
+
function deriveGitHubUserNamespaceKey(identity) {
|
|
4659
|
+
const userId = cleanString3(identity.userId);
|
|
4660
|
+
if (userId) {
|
|
4661
|
+
const safeId = sanitizePathSegment(userId);
|
|
4662
|
+
if (safeId)
|
|
4663
|
+
return `ghu-${safeId}`;
|
|
4664
|
+
}
|
|
4665
|
+
const login = cleanString3(identity.login);
|
|
4666
|
+
if (login) {
|
|
4667
|
+
const safeLogin = sanitizePathSegment(login);
|
|
4668
|
+
if (safeLogin)
|
|
4669
|
+
return `ghu-login-${safeLogin}`;
|
|
4670
|
+
}
|
|
4671
|
+
throw new Error("GitHub user namespace requires a user id or login");
|
|
4672
|
+
}
|
|
4673
|
+
function resolveRemoteUserNamespacesRoot(projectRoot) {
|
|
4674
|
+
const explicitRoot = cleanString3(process.env.RIG_REMOTE_USER_NAMESPACE_ROOT);
|
|
4675
|
+
if (explicitRoot)
|
|
4676
|
+
return resolve16(explicitRoot);
|
|
4677
|
+
const stateDir2 = cleanString3(process.env.RIG_STATE_DIR);
|
|
4678
|
+
if (stateDir2)
|
|
4679
|
+
return resolve16(dirname11(resolve16(stateDir2)), "users");
|
|
4680
|
+
return resolve16(projectRoot, ".rig", "users");
|
|
4681
|
+
}
|
|
4682
|
+
function resolveRemoteUserNamespace(projectRoot, identity) {
|
|
4683
|
+
const key = deriveGitHubUserNamespaceKey(identity);
|
|
4684
|
+
const root = resolve16(resolveRemoteUserNamespacesRoot(projectRoot), key);
|
|
4685
|
+
const stateDir2 = resolve16(root, ".rig", "state");
|
|
4686
|
+
return {
|
|
4687
|
+
key,
|
|
4688
|
+
userId: cleanString3(identity.userId),
|
|
4689
|
+
login: cleanString3(identity.login),
|
|
4690
|
+
root,
|
|
4691
|
+
stateDir: stateDir2,
|
|
4692
|
+
authStateFile: resolve16(stateDir2, "github-auth.json"),
|
|
4693
|
+
metadataFile: resolve16(stateDir2, "user-namespace.json"),
|
|
4694
|
+
checkoutBaseDir: resolve16(root, "remote-checkouts"),
|
|
4695
|
+
snapshotBaseDir: resolve16(root, "remote-snapshots")
|
|
4696
|
+
};
|
|
4697
|
+
}
|
|
4698
|
+
function serializeRemoteUserNamespace(namespace) {
|
|
4699
|
+
return {
|
|
4700
|
+
key: namespace.key,
|
|
4701
|
+
userId: namespace.userId,
|
|
4702
|
+
login: namespace.login,
|
|
4703
|
+
root: namespace.root,
|
|
4704
|
+
checkoutBaseDir: namespace.checkoutBaseDir,
|
|
4705
|
+
snapshotBaseDir: namespace.snapshotBaseDir
|
|
4706
|
+
};
|
|
4707
|
+
}
|
|
4708
|
+
function isPathInsideNamespace(namespaceRoot, candidatePath) {
|
|
4709
|
+
const root = resolve16(namespaceRoot);
|
|
4710
|
+
const candidate = resolve16(candidatePath);
|
|
4711
|
+
const rel = relative3(root, candidate);
|
|
4712
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
4713
|
+
}
|
|
4714
|
+
function writeRemoteUserNamespaceMetadata(namespace) {
|
|
4715
|
+
mkdirSync9(namespace.stateDir, { recursive: true });
|
|
4716
|
+
const previous = (() => {
|
|
4717
|
+
if (!existsSync9(namespace.metadataFile))
|
|
4718
|
+
return null;
|
|
4719
|
+
try {
|
|
4720
|
+
const parsed = JSON.parse(readFileSync6(namespace.metadataFile, "utf8"));
|
|
4721
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4722
|
+
} catch {
|
|
4723
|
+
return null;
|
|
4724
|
+
}
|
|
4725
|
+
})();
|
|
4726
|
+
const now = new Date().toISOString();
|
|
4727
|
+
writeFileSync8(namespace.metadataFile, `${JSON.stringify({
|
|
4728
|
+
key: namespace.key,
|
|
4729
|
+
userId: namespace.userId,
|
|
4730
|
+
login: namespace.login,
|
|
4731
|
+
root: namespace.root,
|
|
4732
|
+
checkoutBaseDir: namespace.checkoutBaseDir,
|
|
4733
|
+
snapshotBaseDir: namespace.snapshotBaseDir,
|
|
4734
|
+
createdAt: typeof previous?.createdAt === "string" ? previous.createdAt : now,
|
|
4735
|
+
updatedAt: now
|
|
4736
|
+
}, null, 2)}
|
|
4737
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4738
|
+
try {
|
|
4739
|
+
chmodSync2(namespace.metadataFile, 384);
|
|
4740
|
+
} catch {}
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
// packages/server/src/server-helpers/github-api-session-index.ts
|
|
4744
|
+
function cleanString4(value) {
|
|
4745
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
4746
|
+
}
|
|
4747
|
+
function resolveGitHubApiSessionIndexFile(projectRoot) {
|
|
4748
|
+
return resolve17(resolveRemoteUserNamespacesRoot(projectRoot), ".api-sessions.json");
|
|
4749
|
+
}
|
|
4750
|
+
function parseEntry(value) {
|
|
4751
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4752
|
+
return null;
|
|
4753
|
+
const record = value;
|
|
4754
|
+
const token = cleanString4(record.token);
|
|
4755
|
+
const namespaceKey = cleanString4(record.namespaceKey);
|
|
4756
|
+
const namespaceRoot = cleanString4(record.namespaceRoot);
|
|
4757
|
+
const authStateFile = cleanString4(record.authStateFile);
|
|
4758
|
+
const checkoutBaseDir = cleanString4(record.checkoutBaseDir);
|
|
4759
|
+
const snapshotBaseDir = cleanString4(record.snapshotBaseDir);
|
|
4760
|
+
const createdAt = cleanString4(record.createdAt);
|
|
4761
|
+
if (!token || !namespaceKey || !namespaceRoot || !authStateFile || !checkoutBaseDir || !snapshotBaseDir || !createdAt)
|
|
4762
|
+
return null;
|
|
4763
|
+
return {
|
|
4764
|
+
token,
|
|
4765
|
+
namespaceKey,
|
|
4766
|
+
namespaceRoot,
|
|
4767
|
+
authStateFile,
|
|
4768
|
+
checkoutBaseDir,
|
|
4769
|
+
snapshotBaseDir,
|
|
4770
|
+
createdAt,
|
|
4771
|
+
login: cleanString4(record.login),
|
|
4772
|
+
userId: cleanString4(record.userId),
|
|
4773
|
+
selectedRepo: cleanString4(record.selectedRepo)
|
|
4774
|
+
};
|
|
4775
|
+
}
|
|
4776
|
+
function readIndex(indexFile) {
|
|
4777
|
+
if (!existsSync10(indexFile))
|
|
4778
|
+
return [];
|
|
4779
|
+
try {
|
|
4780
|
+
const parsed = JSON.parse(readFileSync7(indexFile, "utf8"));
|
|
4781
|
+
return Array.isArray(parsed.sessions) ? parsed.sessions.flatMap((entry) => {
|
|
4782
|
+
const parsedEntry = parseEntry(entry);
|
|
4783
|
+
return parsedEntry ? [parsedEntry] : [];
|
|
4784
|
+
}) : [];
|
|
4785
|
+
} catch {
|
|
4786
|
+
return [];
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
function writeIndex(indexFile, sessions) {
|
|
4790
|
+
mkdirSync10(dirname12(indexFile), { recursive: true });
|
|
4791
|
+
writeFileSync9(indexFile, `${JSON.stringify({ sessions }, null, 2)}
|
|
4792
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4793
|
+
try {
|
|
4794
|
+
chmodSync3(indexFile, 384);
|
|
4795
|
+
} catch {}
|
|
4796
|
+
}
|
|
4797
|
+
function registerGitHubApiSession(input) {
|
|
4798
|
+
const cleanToken = cleanString4(input.token);
|
|
4799
|
+
if (!cleanToken)
|
|
4800
|
+
throw new Error("GitHub API session token is required");
|
|
4801
|
+
const indexFile = resolveGitHubApiSessionIndexFile(input.projectRoot);
|
|
4802
|
+
const createdAt = new Date().toISOString();
|
|
4803
|
+
const entry = {
|
|
4804
|
+
token: cleanToken,
|
|
4805
|
+
login: input.namespace.login,
|
|
4806
|
+
userId: input.namespace.userId,
|
|
4807
|
+
namespaceKey: input.namespace.key,
|
|
4808
|
+
namespaceRoot: input.namespace.root,
|
|
4809
|
+
authStateFile: input.namespace.authStateFile,
|
|
4810
|
+
checkoutBaseDir: input.namespace.checkoutBaseDir,
|
|
4811
|
+
snapshotBaseDir: input.namespace.snapshotBaseDir,
|
|
4812
|
+
selectedRepo: cleanString4(input.selectedRepo),
|
|
4813
|
+
createdAt
|
|
4814
|
+
};
|
|
4815
|
+
const previous = readIndex(indexFile).filter((session) => session.token !== cleanToken);
|
|
4816
|
+
writeIndex(indexFile, [...previous.slice(-199), entry]);
|
|
4817
|
+
return entry;
|
|
4818
|
+
}
|
|
4819
|
+
function readGitHubApiSession(input) {
|
|
4820
|
+
const cleanToken = cleanString4(input.token);
|
|
4821
|
+
if (!cleanToken)
|
|
4822
|
+
return null;
|
|
4823
|
+
return readIndex(resolveGitHubApiSessionIndexFile(input.projectRoot)).find((entry) => entry.token === cleanToken) ?? null;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4498
4826
|
// packages/server/src/server-helpers/inspector-agent-lifecycle.ts
|
|
4499
4827
|
function createInspectorAgentLifecycleController(options) {
|
|
4500
4828
|
const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
|
|
@@ -4598,21 +4926,21 @@ function inspectorAgentLifecycleSnapshot(input) {
|
|
|
4598
4926
|
// packages/server/src/server-helpers/project-registry.ts
|
|
4599
4927
|
import { createHash as createHash2 } from "crypto";
|
|
4600
4928
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4601
|
-
import { existsSync as
|
|
4602
|
-
import { dirname as
|
|
4929
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync8, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
4930
|
+
import { dirname as dirname13, resolve as resolve18 } from "path";
|
|
4603
4931
|
function normalizeRepoSlug(value) {
|
|
4604
4932
|
const trimmed = value.trim();
|
|
4605
4933
|
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
|
|
4606
4934
|
}
|
|
4607
4935
|
function registryPath(projectRoot) {
|
|
4608
|
-
return
|
|
4936
|
+
return resolve18(projectRoot, ".rig", "state", "projects.json");
|
|
4609
4937
|
}
|
|
4610
4938
|
function readRegistry(projectRoot) {
|
|
4611
4939
|
const path = registryPath(projectRoot);
|
|
4612
|
-
if (!
|
|
4940
|
+
if (!existsSync11(path))
|
|
4613
4941
|
return {};
|
|
4614
4942
|
try {
|
|
4615
|
-
const payload = JSON.parse(
|
|
4943
|
+
const payload = JSON.parse(readFileSync8(path, "utf8"));
|
|
4616
4944
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
4617
4945
|
return {};
|
|
4618
4946
|
const projects = payload.projects;
|
|
@@ -4623,14 +4951,14 @@ function readRegistry(projectRoot) {
|
|
|
4623
4951
|
}
|
|
4624
4952
|
function writeRegistry(projectRoot, projects) {
|
|
4625
4953
|
const path = registryPath(projectRoot);
|
|
4626
|
-
|
|
4627
|
-
|
|
4954
|
+
mkdirSync11(dirname13(path), { recursive: true });
|
|
4955
|
+
writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
|
|
4628
4956
|
`, "utf8");
|
|
4629
4957
|
}
|
|
4630
4958
|
function resolveConfigPath(projectRoot) {
|
|
4631
4959
|
for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
|
|
4632
|
-
const path =
|
|
4633
|
-
if (
|
|
4960
|
+
const path = resolve18(projectRoot, name);
|
|
4961
|
+
if (existsSync11(path))
|
|
4634
4962
|
return path;
|
|
4635
4963
|
}
|
|
4636
4964
|
return null;
|
|
@@ -4639,7 +4967,7 @@ function hashFile(path) {
|
|
|
4639
4967
|
if (!path)
|
|
4640
4968
|
return null;
|
|
4641
4969
|
try {
|
|
4642
|
-
return createHash2("sha256").update(
|
|
4970
|
+
return createHash2("sha256").update(readFileSync8(path)).digest("hex");
|
|
4643
4971
|
} catch {
|
|
4644
4972
|
return null;
|
|
4645
4973
|
}
|
|
@@ -4655,11 +4983,11 @@ function readDefaultBranch(projectRoot) {
|
|
|
4655
4983
|
return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
|
|
4656
4984
|
}
|
|
4657
4985
|
function buildRunSummary(projectRoot) {
|
|
4658
|
-
const runsDir =
|
|
4986
|
+
const runsDir = resolve18(projectRoot, ".rig", "runs");
|
|
4659
4987
|
try {
|
|
4660
4988
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
4661
4989
|
try {
|
|
4662
|
-
const run = JSON.parse(
|
|
4990
|
+
const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
|
|
4663
4991
|
return [{ runId: typeof run.runId === "string" ? run.runId : entry.name, status: typeof run.status === "string" ? run.status : "unknown", updatedAt: typeof run.updatedAt === "string" ? run.updatedAt : "" }];
|
|
4664
4992
|
} catch {
|
|
4665
4993
|
return [];
|
|
@@ -4711,10 +5039,14 @@ function upsertProjectRecord(projectRoot, input) {
|
|
|
4711
5039
|
function linkProjectCheckout(projectRoot, repoSlug, checkout) {
|
|
4712
5040
|
return upsertProjectRecord(projectRoot, { repoSlug, checkout });
|
|
4713
5041
|
}
|
|
5042
|
+
function projectRegistryContainsCheckout(projectRoot, checkoutPath) {
|
|
5043
|
+
const target = resolve18(checkoutPath);
|
|
5044
|
+
return Object.values(readRegistry(projectRoot)).some((project) => project.checkouts.some((checkout) => checkout.path ? resolve18(checkout.path) === target : false));
|
|
5045
|
+
}
|
|
4714
5046
|
|
|
4715
5047
|
// packages/server/src/server-helpers/remote-checkout.ts
|
|
4716
|
-
import { existsSync as
|
|
4717
|
-
import { dirname as
|
|
5048
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
5049
|
+
import { dirname as dirname14, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve19 } from "path";
|
|
4718
5050
|
function safeSlugSegments(repoSlug) {
|
|
4719
5051
|
const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
|
|
4720
5052
|
if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
|
|
@@ -4731,7 +5063,7 @@ function safeCheckoutKey(value) {
|
|
|
4731
5063
|
}
|
|
4732
5064
|
function repoSlugPath(baseDir, repoSlug, checkoutKey) {
|
|
4733
5065
|
const key = safeCheckoutKey(checkoutKey);
|
|
4734
|
-
return
|
|
5066
|
+
return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
|
|
4735
5067
|
}
|
|
4736
5068
|
function sanitizeSnapshotId(value, fallback) {
|
|
4737
5069
|
const raw = (value ?? fallback).trim();
|
|
@@ -4739,7 +5071,7 @@ function sanitizeSnapshotId(value, fallback) {
|
|
|
4739
5071
|
return safe || fallback;
|
|
4740
5072
|
}
|
|
4741
5073
|
function assertWithinRoot(root, relativePath) {
|
|
4742
|
-
if (!relativePath ||
|
|
5074
|
+
if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
|
|
4743
5075
|
throw new Error(`Invalid snapshot file path: ${relativePath}`);
|
|
4744
5076
|
}
|
|
4745
5077
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
@@ -4747,9 +5079,9 @@ function assertWithinRoot(root, relativePath) {
|
|
|
4747
5079
|
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
4748
5080
|
throw new Error(`Unsafe snapshot file path: ${relativePath}`);
|
|
4749
5081
|
}
|
|
4750
|
-
const target =
|
|
4751
|
-
const rel =
|
|
4752
|
-
if (rel === ".." || rel.split(/[\\/]/)[0] === ".." ||
|
|
5082
|
+
const target = resolve19(root, ...segments);
|
|
5083
|
+
const rel = relative4(root, target);
|
|
5084
|
+
if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute3(rel)) {
|
|
4753
5085
|
throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
|
|
4754
5086
|
}
|
|
4755
5087
|
return target;
|
|
@@ -4767,17 +5099,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
|
|
|
4767
5099
|
function extractUploadedSnapshotArchive(input) {
|
|
4768
5100
|
const archive = decodeSnapshotArchive(input.archive);
|
|
4769
5101
|
const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
|
|
4770
|
-
const checkoutPath =
|
|
4771
|
-
|
|
5102
|
+
const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
|
|
5103
|
+
mkdirSync12(checkoutPath, { recursive: true });
|
|
4772
5104
|
for (const file of archive.files) {
|
|
4773
5105
|
if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
|
|
4774
5106
|
throw new Error("Invalid snapshot archive file entry");
|
|
4775
5107
|
}
|
|
4776
5108
|
const target = assertWithinRoot(checkoutPath, file.path);
|
|
4777
|
-
|
|
4778
|
-
|
|
5109
|
+
mkdirSync12(dirname14(target), { recursive: true });
|
|
5110
|
+
writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
|
|
4779
5111
|
}
|
|
4780
|
-
|
|
5112
|
+
writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
|
|
4781
5113
|
repoSlug: input.repoSlug,
|
|
4782
5114
|
snapshotId,
|
|
4783
5115
|
fileCount: archive.files.length,
|
|
@@ -4812,7 +5144,7 @@ function gitCredentialConfig(token) {
|
|
|
4812
5144
|
};
|
|
4813
5145
|
}
|
|
4814
5146
|
async function prepareRemoteCheckout(input) {
|
|
4815
|
-
const exists = input.exists ??
|
|
5147
|
+
const exists = input.exists ?? existsSync12;
|
|
4816
5148
|
const strategy = input.strategy;
|
|
4817
5149
|
if (strategy.kind === "uploaded-snapshot") {
|
|
4818
5150
|
return extractUploadedSnapshotArchive({
|
|
@@ -4824,7 +5156,7 @@ async function prepareRemoteCheckout(input) {
|
|
|
4824
5156
|
});
|
|
4825
5157
|
}
|
|
4826
5158
|
if (strategy.kind === "existing-path") {
|
|
4827
|
-
const checkoutPath2 =
|
|
5159
|
+
const checkoutPath2 = resolve19(strategy.path);
|
|
4828
5160
|
if (!exists(checkoutPath2)) {
|
|
4829
5161
|
throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
|
|
4830
5162
|
}
|
|
@@ -4860,9 +5192,9 @@ function buildServerControlStatus() {
|
|
|
4860
5192
|
};
|
|
4861
5193
|
}
|
|
4862
5194
|
function buildProjectConfigStatus(root) {
|
|
4863
|
-
const hasConfigTs =
|
|
4864
|
-
const hasConfigJson =
|
|
4865
|
-
const hasLegacyTaskConfig =
|
|
5195
|
+
const hasConfigTs = existsSync13(resolve20(root, "rig.config.ts"));
|
|
5196
|
+
const hasConfigJson = existsSync13(resolve20(root, "rig.config.json"));
|
|
5197
|
+
const hasLegacyTaskConfig = existsSync13(resolve20(root, ".rig", "task-config.json"));
|
|
4866
5198
|
let kind = "missing";
|
|
4867
5199
|
if (hasConfigTs)
|
|
4868
5200
|
kind = "rig-config-ts";
|
|
@@ -4886,10 +5218,10 @@ function normalizeCommit(value) {
|
|
|
4886
5218
|
function asPlainRecord(value) {
|
|
4887
5219
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4888
5220
|
}
|
|
4889
|
-
var
|
|
5221
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
4890
5222
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
4891
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
4892
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
5223
|
+
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
5224
|
+
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
4893
5225
|
};
|
|
4894
5226
|
function repoParts(repoSlug) {
|
|
4895
5227
|
const [owner, repo] = repoSlug.split("/");
|
|
@@ -4898,24 +5230,24 @@ function repoParts(repoSlug) {
|
|
|
4898
5230
|
return { owner, repo, slug: `${owner}/${repo}` };
|
|
4899
5231
|
}
|
|
4900
5232
|
function repairDir(checkoutPath) {
|
|
4901
|
-
const dir =
|
|
4902
|
-
|
|
5233
|
+
const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
|
|
5234
|
+
mkdirSync13(dir, { recursive: true });
|
|
4903
5235
|
return dir;
|
|
4904
5236
|
}
|
|
4905
5237
|
function backupCheckoutFile(checkoutPath, relativePath) {
|
|
4906
|
-
const source =
|
|
4907
|
-
const backupPath =
|
|
4908
|
-
|
|
4909
|
-
|
|
5238
|
+
const source = resolve20(checkoutPath, relativePath);
|
|
5239
|
+
const backupPath = resolve20(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
|
|
5240
|
+
mkdirSync13(dirname15(backupPath), { recursive: true });
|
|
5241
|
+
copyFileSync2(source, backupPath);
|
|
4910
5242
|
return backupPath;
|
|
4911
5243
|
}
|
|
4912
5244
|
function parsePackageJsonLosslessly(checkoutPath) {
|
|
4913
|
-
const packagePath =
|
|
4914
|
-
if (!
|
|
5245
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
5246
|
+
if (!existsSync13(packagePath)) {
|
|
4915
5247
|
return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
|
|
4916
5248
|
}
|
|
4917
5249
|
try {
|
|
4918
|
-
const parsed = JSON.parse(
|
|
5250
|
+
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
4919
5251
|
return {
|
|
4920
5252
|
existed: true,
|
|
4921
5253
|
packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
|
|
@@ -4929,9 +5261,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
|
|
|
4929
5261
|
}
|
|
4930
5262
|
}
|
|
4931
5263
|
function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
4932
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
4933
|
-
const packagePath =
|
|
4934
|
-
if (!hasConfig && !
|
|
5264
|
+
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5265
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
5266
|
+
if (!hasConfig && !existsSync13(packagePath)) {
|
|
4935
5267
|
return { skipped: true, reason: "package.json and rig.config missing" };
|
|
4936
5268
|
}
|
|
4937
5269
|
const parsed = parsePackageJsonLosslessly(checkoutPath);
|
|
@@ -4950,7 +5282,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
|
4950
5282
|
}
|
|
4951
5283
|
const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
|
|
4952
5284
|
if (changed) {
|
|
4953
|
-
|
|
5285
|
+
writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
|
|
4954
5286
|
`, "utf8");
|
|
4955
5287
|
}
|
|
4956
5288
|
return {
|
|
@@ -4976,11 +5308,11 @@ function configLooksStructurallyUsable(source) {
|
|
|
4976
5308
|
return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
|
|
4977
5309
|
}
|
|
4978
5310
|
function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
|
|
4979
|
-
const configPath =
|
|
4980
|
-
const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) =>
|
|
5311
|
+
const configPath = resolve20(checkoutPath, "rig.config.ts");
|
|
5312
|
+
const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
4981
5313
|
if (existingConfigName) {
|
|
4982
|
-
const existingPath =
|
|
4983
|
-
const source =
|
|
5314
|
+
const existingPath = resolve20(checkoutPath, existingConfigName);
|
|
5315
|
+
const source = readFileSync9(existingPath, "utf8");
|
|
4984
5316
|
if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
|
|
4985
5317
|
return { path: existingPath, changed: false, reason: "config structurally complete" };
|
|
4986
5318
|
}
|
|
@@ -4994,7 +5326,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
4994
5326
|
}
|
|
4995
5327
|
}
|
|
4996
5328
|
const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
|
|
4997
|
-
|
|
5329
|
+
writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
|
|
4998
5330
|
return {
|
|
4999
5331
|
path: configPath,
|
|
5000
5332
|
changed: true,
|
|
@@ -5003,7 +5335,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5003
5335
|
};
|
|
5004
5336
|
}
|
|
5005
5337
|
function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
5006
|
-
const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) =>
|
|
5338
|
+
const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5007
5339
|
if (!configFile)
|
|
5008
5340
|
return { ok: false, error: "missing rig config" };
|
|
5009
5341
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5025,7 +5357,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
|
5025
5357
|
return { ok: true, configFile };
|
|
5026
5358
|
}
|
|
5027
5359
|
function installRemoteCheckoutPackages(checkoutPath) {
|
|
5028
|
-
if (!
|
|
5360
|
+
if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
|
|
5029
5361
|
return { skipped: true, reason: "package.json missing" };
|
|
5030
5362
|
}
|
|
5031
5363
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5038,8 +5370,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
|
|
|
5038
5370
|
return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
|
|
5039
5371
|
}
|
|
5040
5372
|
function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
|
|
5041
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5042
|
-
const hasPackage =
|
|
5373
|
+
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5374
|
+
const hasPackage = existsSync13(resolve20(checkoutPath, "package.json"));
|
|
5043
5375
|
if (!hasConfig && !hasPackage) {
|
|
5044
5376
|
return {
|
|
5045
5377
|
packageJson: { skipped: true, reason: "package.json and rig.config missing" },
|
|
@@ -5137,26 +5469,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
|
|
|
5137
5469
|
}
|
|
5138
5470
|
function readGitHeadCommit(projectRoot) {
|
|
5139
5471
|
try {
|
|
5140
|
-
let gitDir =
|
|
5472
|
+
let gitDir = resolve20(projectRoot, ".git");
|
|
5141
5473
|
try {
|
|
5142
|
-
const dotGit =
|
|
5474
|
+
const dotGit = readFileSync9(gitDir, "utf8").trim();
|
|
5143
5475
|
const gitDirPrefix = "gitdir:";
|
|
5144
5476
|
if (dotGit.startsWith(gitDirPrefix)) {
|
|
5145
|
-
gitDir =
|
|
5477
|
+
gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
|
|
5146
5478
|
}
|
|
5147
5479
|
} catch {}
|
|
5148
|
-
const head =
|
|
5480
|
+
const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
|
|
5149
5481
|
const refPrefix = "ref:";
|
|
5150
5482
|
if (!head.startsWith(refPrefix)) {
|
|
5151
5483
|
return normalizeCommit(head);
|
|
5152
5484
|
}
|
|
5153
5485
|
const ref = head.slice(refPrefix.length).trim();
|
|
5154
|
-
const refPath =
|
|
5155
|
-
if (
|
|
5156
|
-
return normalizeCommit(
|
|
5486
|
+
const refPath = resolve20(gitDir, ref);
|
|
5487
|
+
if (existsSync13(refPath)) {
|
|
5488
|
+
return normalizeCommit(readFileSync9(refPath, "utf8").trim());
|
|
5157
5489
|
}
|
|
5158
|
-
const commonDir = normalizeString(
|
|
5159
|
-
return commonDir ? normalizeCommit(
|
|
5490
|
+
const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
|
|
5491
|
+
return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
|
|
5160
5492
|
} catch {
|
|
5161
5493
|
return null;
|
|
5162
5494
|
}
|
|
@@ -5194,9 +5526,9 @@ function configuredRepoFromTaskSource(taskSource) {
|
|
|
5194
5526
|
const repo = normalizeString(taskSource?.repo);
|
|
5195
5527
|
return owner && repo ? `${owner}/${repo}` : null;
|
|
5196
5528
|
}
|
|
5197
|
-
async function buildTaskSourceStatus(state, config) {
|
|
5529
|
+
async function buildTaskSourceStatus(state, config, requestAuth) {
|
|
5198
5530
|
const diagnostics = state.snapshotService.getTaskSourceErrors();
|
|
5199
|
-
const selectedRepo =
|
|
5531
|
+
const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
|
|
5200
5532
|
oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
|
|
5201
5533
|
}).selectedRepo;
|
|
5202
5534
|
try {
|
|
@@ -5243,36 +5575,134 @@ function bearerTokenFromRequest(req) {
|
|
|
5243
5575
|
function isLoopbackRequest(req) {
|
|
5244
5576
|
try {
|
|
5245
5577
|
const hostname = new URL(req.url).hostname.toLowerCase();
|
|
5246
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
5578
|
+
return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
5247
5579
|
} catch {
|
|
5248
5580
|
return false;
|
|
5249
5581
|
}
|
|
5250
5582
|
}
|
|
5251
5583
|
function isPublicRigAuthBootstrapRoute(pathname) {
|
|
5252
|
-
return pathname === "/" || pathname === "/
|
|
5584
|
+
return pathname === "/" || pathname === "/install" || pathname === "/health" || pathname === "/api/health" || pathname === "/api/github/auth/status" || pathname === "/api/github/auth/token" || pathname === "/api/github/auth/device/start" || pathname === "/api/github/auth/device/poll";
|
|
5585
|
+
}
|
|
5586
|
+
function buildRigInstallScript() {
|
|
5587
|
+
return `#!/usr/bin/env bash
|
|
5588
|
+
set -euo pipefail
|
|
5589
|
+
|
|
5590
|
+
say() {
|
|
5591
|
+
printf 'rig-install: %s
|
|
5592
|
+
' "$*"
|
|
5593
|
+
}
|
|
5594
|
+
|
|
5595
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
5596
|
+
say "Bun not found; installing Bun first"
|
|
5597
|
+
curl -fsSL https://bun.sh/install | bash
|
|
5598
|
+
export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
|
|
5599
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
5600
|
+
fi
|
|
5601
|
+
|
|
5602
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
5603
|
+
printf 'rig-install: bun install completed, but bun is still not on PATH. Add ~/.bun/bin to PATH and retry.
|
|
5604
|
+
' >&2
|
|
5605
|
+
exit 1
|
|
5606
|
+
fi
|
|
5607
|
+
|
|
5608
|
+
say "Installing @h-rig/cli@latest"
|
|
5609
|
+
bun add -g @h-rig/cli@latest
|
|
5610
|
+
|
|
5611
|
+
export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
|
|
5612
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
5613
|
+
|
|
5614
|
+
if ! command -v rig >/dev/null 2>&1; then
|
|
5615
|
+
printf 'rig-install: rig installed, but rig is not on PATH. Add %s/bin to PATH and retry.
|
|
5616
|
+
' "$BUN_INSTALL" >&2
|
|
5617
|
+
exit 1
|
|
5618
|
+
fi
|
|
5619
|
+
|
|
5620
|
+
say "Verifying rig"
|
|
5621
|
+
rig --help >/dev/null
|
|
5622
|
+
say "Done. Run: rig --help"
|
|
5623
|
+
`;
|
|
5253
5624
|
}
|
|
5254
5625
|
function normalizePrMode(value) {
|
|
5255
5626
|
const mode = normalizeString(value);
|
|
5256
5627
|
return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
|
|
5257
5628
|
}
|
|
5629
|
+
function requestAuthResult(input) {
|
|
5630
|
+
return {
|
|
5631
|
+
authorized: input.authorized,
|
|
5632
|
+
actor: input.actor ?? null,
|
|
5633
|
+
reason: input.reason,
|
|
5634
|
+
login: input.login ?? null,
|
|
5635
|
+
userId: input.userId ?? null,
|
|
5636
|
+
userNamespace: input.userNamespace ?? null,
|
|
5637
|
+
authStateFile: input.authStateFile ?? null
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
function namespaceFromSessionIndex(entry) {
|
|
5641
|
+
const stateDir2 = dirname15(entry.authStateFile);
|
|
5642
|
+
return {
|
|
5643
|
+
key: entry.namespaceKey,
|
|
5644
|
+
userId: entry.userId,
|
|
5645
|
+
login: entry.login,
|
|
5646
|
+
root: entry.namespaceRoot,
|
|
5647
|
+
stateDir: stateDir2,
|
|
5648
|
+
authStateFile: entry.authStateFile,
|
|
5649
|
+
metadataFile: resolve20(stateDir2, "user-namespace.json"),
|
|
5650
|
+
checkoutBaseDir: entry.checkoutBaseDir,
|
|
5651
|
+
snapshotBaseDir: entry.snapshotBaseDir
|
|
5652
|
+
};
|
|
5653
|
+
}
|
|
5258
5654
|
function authorizeRigHttpRequest(input) {
|
|
5259
5655
|
if (input.legacyAuthorized) {
|
|
5260
|
-
return { authorized: true, actor: "rig-local-server", reason: "server-token" };
|
|
5656
|
+
return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
|
|
5261
5657
|
}
|
|
5262
5658
|
const bearer = bearerTokenFromRequest(input.req);
|
|
5263
5659
|
const store = createGitHubAuthStore(input.projectRoot);
|
|
5264
5660
|
const storedToken = store.readToken();
|
|
5661
|
+
const session = bearer ? store.readApiSession(bearer) : null;
|
|
5662
|
+
if (session) {
|
|
5663
|
+
return requestAuthResult({
|
|
5664
|
+
authorized: true,
|
|
5665
|
+
actor: session.login ?? "github-operator",
|
|
5666
|
+
reason: "github-session",
|
|
5667
|
+
login: session.login,
|
|
5668
|
+
userId: session.userId,
|
|
5669
|
+
authStateFile: store.stateFile
|
|
5670
|
+
});
|
|
5671
|
+
}
|
|
5265
5672
|
if (bearer && storedToken && bearer === storedToken) {
|
|
5266
5673
|
const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
5267
|
-
return {
|
|
5674
|
+
return requestAuthResult({
|
|
5675
|
+
authorized: true,
|
|
5676
|
+
actor: status.login ?? "github-operator",
|
|
5677
|
+
reason: "github-token",
|
|
5678
|
+
login: status.login,
|
|
5679
|
+
userId: status.userId,
|
|
5680
|
+
authStateFile: store.stateFile
|
|
5681
|
+
});
|
|
5682
|
+
}
|
|
5683
|
+
const indexedSession = readGitHubApiSession({ projectRoot: input.projectRoot, token: bearer });
|
|
5684
|
+
if (indexedSession) {
|
|
5685
|
+
const userNamespace = namespaceFromSessionIndex(indexedSession);
|
|
5686
|
+
return requestAuthResult({
|
|
5687
|
+
authorized: true,
|
|
5688
|
+
actor: indexedSession.login ?? "github-operator",
|
|
5689
|
+
reason: "github-user-session",
|
|
5690
|
+
login: indexedSession.login,
|
|
5691
|
+
userId: indexedSession.userId,
|
|
5692
|
+
userNamespace,
|
|
5693
|
+
authStateFile: indexedSession.authStateFile
|
|
5694
|
+
});
|
|
5268
5695
|
}
|
|
5269
5696
|
if (isPublicRigAuthBootstrapRoute(input.pathname)) {
|
|
5270
|
-
return { authorized: true, actor: null, reason: "public-bootstrap" };
|
|
5697
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
|
|
5271
5698
|
}
|
|
5272
|
-
if (!input.serverAuthToken && !storedToken
|
|
5273
|
-
|
|
5699
|
+
if (!input.serverAuthToken && !storedToken) {
|
|
5700
|
+
if (isLoopbackRequest(input.req)) {
|
|
5701
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
|
|
5702
|
+
}
|
|
5703
|
+
return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
|
|
5274
5704
|
}
|
|
5275
|
-
return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
|
|
5705
|
+
return requestAuthResult({ authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" });
|
|
5276
5706
|
}
|
|
5277
5707
|
async function fetchGitHubUserInfo(token) {
|
|
5278
5708
|
const response = await fetch("https://api.github.com/user", {
|
|
@@ -5292,6 +5722,67 @@ async function fetchGitHubUserInfo(token) {
|
|
|
5292
5722
|
scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
|
|
5293
5723
|
};
|
|
5294
5724
|
}
|
|
5725
|
+
function shouldWriteRootAuthCompat(projectRoot) {
|
|
5726
|
+
if (process.env.RIG_REMOTE_USER_NAMESPACE_ROOT?.trim())
|
|
5727
|
+
return false;
|
|
5728
|
+
const stateDir2 = normalizeString(process.env.RIG_STATE_DIR);
|
|
5729
|
+
if (!stateDir2)
|
|
5730
|
+
return true;
|
|
5731
|
+
return resolve20(stateDir2) === resolve20(projectRoot, ".rig", "state");
|
|
5732
|
+
}
|
|
5733
|
+
function requestScopedRegistryRoot(stateProjectRoot, requestAuth) {
|
|
5734
|
+
return requestAuth.userNamespace?.root ?? stateProjectRoot;
|
|
5735
|
+
}
|
|
5736
|
+
function requestScopedAuthStore(stateProjectRoot, requestAuth) {
|
|
5737
|
+
return requestAuth.authStateFile ? createGitHubAuthStoreFromStateFile(requestAuth.authStateFile) : createGitHubAuthStore(stateProjectRoot);
|
|
5738
|
+
}
|
|
5739
|
+
function userNamespaceResponse(namespace) {
|
|
5740
|
+
return namespace ? serializeRemoteUserNamespace(namespace) : undefined;
|
|
5741
|
+
}
|
|
5742
|
+
function resolveNamespacedBaseDir(input) {
|
|
5743
|
+
if (input.explicitBaseDir)
|
|
5744
|
+
return input.explicitBaseDir;
|
|
5745
|
+
const envBase = normalizeString(process.env[input.envName]);
|
|
5746
|
+
if (input.userNamespace) {
|
|
5747
|
+
return envBase ? resolve20(envBase, input.userNamespace.key) : input.userNamespace[input.legacySubdir === "remote-checkouts" ? "checkoutBaseDir" : "snapshotBaseDir"];
|
|
5748
|
+
}
|
|
5749
|
+
return envBase ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve20(normalizeString(process.env.RIG_STATE_DIR), input.legacySubdir) : resolve20(input.legacyProjectRoot, ".rig", input.legacySubdir));
|
|
5750
|
+
}
|
|
5751
|
+
function explicitCheckoutKey(body, checkoutInput, requestAuth) {
|
|
5752
|
+
return normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? (requestAuth.userNamespace ? undefined : "default");
|
|
5753
|
+
}
|
|
5754
|
+
function saveGitHubTokenForRemoteUser(input) {
|
|
5755
|
+
const namespace = resolveRemoteUserNamespace(input.projectRoot, { userId: input.user.userId, login: input.user.login });
|
|
5756
|
+
writeRemoteUserNamespaceMetadata(namespace);
|
|
5757
|
+
const store = createGitHubAuthStoreFromStateFile(namespace.authStateFile);
|
|
5758
|
+
store.saveToken({
|
|
5759
|
+
token: input.token,
|
|
5760
|
+
tokenSource: input.tokenSource,
|
|
5761
|
+
login: input.user.login,
|
|
5762
|
+
userId: input.user.userId,
|
|
5763
|
+
scopes: input.user.scopes,
|
|
5764
|
+
selectedRepo: input.selectedRepo
|
|
5765
|
+
});
|
|
5766
|
+
const apiSession = store.createApiSession();
|
|
5767
|
+
registerGitHubApiSession({ projectRoot: input.projectRoot, token: apiSession.token, namespace, selectedRepo: input.selectedRepo });
|
|
5768
|
+
const requestedRoot = normalizeString(input.requestedProjectRoot);
|
|
5769
|
+
if (requestedRoot && isAbsolute4(requestedRoot) && existsSync13(resolve20(requestedRoot))) {
|
|
5770
|
+
copyGitHubAuthStateToLocalProjectRoot(namespace.authStateFile, resolve20(requestedRoot));
|
|
5771
|
+
}
|
|
5772
|
+
if (shouldWriteRootAuthCompat(input.projectRoot)) {
|
|
5773
|
+
const rootStore = createGitHubAuthStore(input.projectRoot);
|
|
5774
|
+
rootStore.saveToken({
|
|
5775
|
+
token: input.token,
|
|
5776
|
+
tokenSource: input.tokenSource,
|
|
5777
|
+
login: input.user.login,
|
|
5778
|
+
userId: input.user.userId,
|
|
5779
|
+
scopes: input.user.scopes,
|
|
5780
|
+
selectedRepo: input.selectedRepo
|
|
5781
|
+
});
|
|
5782
|
+
rootStore.createApiSession();
|
|
5783
|
+
}
|
|
5784
|
+
return { store, namespace, apiSessionToken: apiSession.token };
|
|
5785
|
+
}
|
|
5295
5786
|
async function postGitHubForm(endpoint, body) {
|
|
5296
5787
|
const response = await fetch(endpoint, {
|
|
5297
5788
|
method: "POST",
|
|
@@ -5309,11 +5800,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
|
|
|
5309
5800
|
const requestedRoot = normalizeString(rawRoot);
|
|
5310
5801
|
if (!requestedRoot)
|
|
5311
5802
|
return currentRoot;
|
|
5312
|
-
if (!
|
|
5803
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
5313
5804
|
throw new Error("projectRoot must be an absolute path on the Rig server host");
|
|
5314
5805
|
}
|
|
5315
|
-
const normalizedRoot =
|
|
5316
|
-
if (!
|
|
5806
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
5807
|
+
if (!existsSync13(normalizedRoot)) {
|
|
5317
5808
|
throw new Error("projectRoot does not exist on the Rig server host");
|
|
5318
5809
|
}
|
|
5319
5810
|
return normalizedRoot;
|
|
@@ -5501,7 +5992,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
|
|
|
5501
5992
|
if (runnable.length === 0)
|
|
5502
5993
|
return null;
|
|
5503
5994
|
const queue = readQueueState(projectRoot);
|
|
5504
|
-
const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
|
|
5995
|
+
const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
|
|
5505
5996
|
return runnable.toSorted((left, right) => {
|
|
5506
5997
|
const leftId = taskIdOf(left) ?? "";
|
|
5507
5998
|
const rightId = taskIdOf(right) ?? "";
|
|
@@ -5541,6 +6032,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
|
|
|
5541
6032
|
}
|
|
5542
6033
|
return filtered;
|
|
5543
6034
|
}
|
|
6035
|
+
function issueAnalysisTargetFor(source) {
|
|
6036
|
+
if (!source)
|
|
6037
|
+
return null;
|
|
6038
|
+
const candidate = source;
|
|
6039
|
+
if (typeof candidate.updateTask !== "function")
|
|
6040
|
+
return null;
|
|
6041
|
+
return {
|
|
6042
|
+
...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
|
|
6043
|
+
updateTask: candidate.updateTask.bind(candidate),
|
|
6044
|
+
...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
|
|
6045
|
+
...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
|
|
6046
|
+
...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
|
|
6047
|
+
};
|
|
6048
|
+
}
|
|
6049
|
+
function uniqueStringList(value) {
|
|
6050
|
+
const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
6051
|
+
return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
|
|
6052
|
+
}
|
|
6053
|
+
function taskRecordId(task) {
|
|
6054
|
+
return String(task.id ?? "");
|
|
6055
|
+
}
|
|
5544
6056
|
function redactRemoteEndpoint(endpoint) {
|
|
5545
6057
|
const { token, ...rest } = endpoint;
|
|
5546
6058
|
return {
|
|
@@ -5625,9 +6137,16 @@ function createRigServerFetch(state, deps) {
|
|
|
5625
6137
|
notifications: state.targets.length
|
|
5626
6138
|
});
|
|
5627
6139
|
}
|
|
6140
|
+
if (url.pathname === "/install" && req.method === "GET") {
|
|
6141
|
+
return new Response(buildRigInstallScript(), {
|
|
6142
|
+
headers: {
|
|
6143
|
+
"Content-Type": "text/x-shellscript; charset=utf-8"
|
|
6144
|
+
}
|
|
6145
|
+
});
|
|
6146
|
+
}
|
|
5628
6147
|
const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
|
|
5629
6148
|
const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
|
|
5630
|
-
const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
|
|
6149
|
+
const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
|
|
5631
6150
|
const requestAuth = authorizeRigHttpRequest({
|
|
5632
6151
|
req,
|
|
5633
6152
|
pathname: url.pathname,
|
|
@@ -5883,9 +6402,70 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
5883
6402
|
note: "GitHub issue lifecycle labels are created on demand by the configured task source when supported."
|
|
5884
6403
|
});
|
|
5885
6404
|
}
|
|
6405
|
+
if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
|
|
6406
|
+
const body = await deps.readJsonBody(req);
|
|
6407
|
+
const ids = uniqueStringList(body.ids ?? body.id);
|
|
6408
|
+
const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
|
|
6409
|
+
if (ids.length === 0 && !analyzeAll) {
|
|
6410
|
+
return deps.badRequest("ids is required unless all=true");
|
|
6411
|
+
}
|
|
6412
|
+
const ctx = await getCachedPluginHostContext(state.projectRoot);
|
|
6413
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
6414
|
+
const target = issueAnalysisTargetFor(source);
|
|
6415
|
+
if (!source || !target) {
|
|
6416
|
+
return deps.badRequest("Configured task source does not support issue-analysis writeback");
|
|
6417
|
+
}
|
|
6418
|
+
const allTasks = [...await source.list()];
|
|
6419
|
+
const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
|
|
6420
|
+
const cached = allTasks.find((task) => taskRecordId(task) === id);
|
|
6421
|
+
if (cached)
|
|
6422
|
+
return cached;
|
|
6423
|
+
return typeof source.get === "function" ? await source.get(id) : undefined;
|
|
6424
|
+
}))).filter((task) => Boolean(task));
|
|
6425
|
+
if (issues.length === 0) {
|
|
6426
|
+
return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
|
|
6427
|
+
}
|
|
6428
|
+
const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
|
|
6429
|
+
const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
|
|
6430
|
+
const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
|
|
6431
|
+
const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
|
|
6432
|
+
const service = createIssueAnalysisService({
|
|
6433
|
+
analyzer: createPiIssueAnalyzer({
|
|
6434
|
+
...model ? { model } : {},
|
|
6435
|
+
env: { RIG_PROJECT_ROOT: state.projectRoot }
|
|
6436
|
+
}),
|
|
6437
|
+
writeBack: createIssueAnalysisWriteBack({ target })
|
|
6438
|
+
});
|
|
6439
|
+
const reason = normalizeString(body.reason) ?? "http-issue-analysis";
|
|
6440
|
+
let results;
|
|
6441
|
+
try {
|
|
6442
|
+
results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
|
|
6443
|
+
} catch (error) {
|
|
6444
|
+
return deps.jsonResponse({
|
|
6445
|
+
ok: false,
|
|
6446
|
+
error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
6447
|
+
reason,
|
|
6448
|
+
ids: issues.map((issue) => issue.id)
|
|
6449
|
+
}, 502);
|
|
6450
|
+
}
|
|
6451
|
+
deps.snapshotService.invalidate("issue-analysis-http-run");
|
|
6452
|
+
await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
|
|
6453
|
+
return;
|
|
6454
|
+
});
|
|
6455
|
+
deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
|
|
6456
|
+
return deps.jsonResponse({
|
|
6457
|
+
ok: true,
|
|
6458
|
+
reason,
|
|
6459
|
+
analyzed: results.map((entry) => ({
|
|
6460
|
+
id: entry.issue.id,
|
|
6461
|
+
title: entry.issue.title ?? null,
|
|
6462
|
+
result: entry.result
|
|
6463
|
+
}))
|
|
6464
|
+
});
|
|
6465
|
+
}
|
|
5886
6466
|
if (url.pathname === "/api/server/status") {
|
|
5887
6467
|
const config = buildProjectConfigStatus(state.projectRoot);
|
|
5888
|
-
const taskSource = await buildTaskSourceStatus(state, config);
|
|
6468
|
+
const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
|
|
5889
6469
|
return deps.jsonResponse({
|
|
5890
6470
|
ok: true,
|
|
5891
6471
|
projectRoot: state.projectRoot,
|
|
@@ -5909,8 +6489,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
5909
6489
|
path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
|
|
5910
6490
|
ref: normalizeString(rawCheckout?.ref) ?? undefined
|
|
5911
6491
|
} : undefined;
|
|
5912
|
-
const
|
|
5913
|
-
|
|
6492
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6493
|
+
const record = upsertProjectRecord(registryRoot, { repoSlug, checkout });
|
|
6494
|
+
return deps.jsonResponse({ ok: true, project: record, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
5914
6495
|
}
|
|
5915
6496
|
const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
|
|
5916
6497
|
if (snapshotUploadMatch && req.method === "POST") {
|
|
@@ -5923,8 +6504,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
5923
6504
|
if (!archiveContentBase64) {
|
|
5924
6505
|
return deps.badRequest("archiveContentBase64 is required");
|
|
5925
6506
|
}
|
|
5926
|
-
const
|
|
5927
|
-
const
|
|
6507
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6508
|
+
const baseDir = resolveNamespacedBaseDir({
|
|
6509
|
+
explicitBaseDir: normalizeString(body.baseDir),
|
|
6510
|
+
envName: "RIG_REMOTE_SNAPSHOT_BASE_DIR",
|
|
6511
|
+
userNamespace: requestAuth.userNamespace,
|
|
6512
|
+
legacyProjectRoot: state.projectRoot,
|
|
6513
|
+
legacySubdir: "remote-snapshots"
|
|
6514
|
+
});
|
|
6515
|
+
const checkoutKey = explicitCheckoutKey(body, body, requestAuth);
|
|
5928
6516
|
try {
|
|
5929
6517
|
const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
|
|
5930
6518
|
const checkout = extractUploadedSnapshotArchive({
|
|
@@ -5937,14 +6525,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
5937
6525
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
5938
6526
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
5939
6527
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
5940
|
-
const project = linkProjectCheckout(
|
|
6528
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
5941
6529
|
kind: "uploaded-snapshot",
|
|
5942
6530
|
path: checkout.path,
|
|
5943
6531
|
ref: checkout.snapshotId
|
|
5944
6532
|
});
|
|
5945
6533
|
deps.snapshotService.invalidate("uploaded-snapshot-checkout");
|
|
5946
6534
|
deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
|
|
5947
|
-
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
|
|
6535
|
+
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
5948
6536
|
} catch (error) {
|
|
5949
6537
|
return deps.jsonResponse({
|
|
5950
6538
|
ok: false,
|
|
@@ -5964,10 +6552,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
5964
6552
|
if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
|
|
5965
6553
|
return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
|
|
5966
6554
|
}
|
|
5967
|
-
const
|
|
5968
|
-
const
|
|
6555
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6556
|
+
const baseDir = resolveNamespacedBaseDir({
|
|
6557
|
+
explicitBaseDir: normalizeString(body.baseDir) ?? normalizeString(checkoutInput.baseDir),
|
|
6558
|
+
envName: "RIG_REMOTE_CHECKOUT_BASE_DIR",
|
|
6559
|
+
userNamespace: requestAuth.userNamespace,
|
|
6560
|
+
legacyProjectRoot: state.projectRoot,
|
|
6561
|
+
legacySubdir: "remote-checkouts"
|
|
6562
|
+
});
|
|
6563
|
+
const checkoutKey = explicitCheckoutKey(body, checkoutInput, requestAuth);
|
|
5969
6564
|
const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
|
|
5970
|
-
const credentialToken =
|
|
6565
|
+
const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
|
|
5971
6566
|
try {
|
|
5972
6567
|
const checkout = await prepareRemoteCheckout({
|
|
5973
6568
|
command: runRemoteCheckoutCommand,
|
|
@@ -5976,14 +6571,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
5976
6571
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
5977
6572
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
5978
6573
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
5979
|
-
const project = linkProjectCheckout(
|
|
6574
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
5980
6575
|
kind: checkout.kind,
|
|
5981
6576
|
path: checkout.path,
|
|
5982
6577
|
ref: checkout.ref ?? checkout.snapshotId ?? undefined
|
|
5983
6578
|
});
|
|
5984
6579
|
deps.snapshotService.invalidate("remote-checkout-prepared");
|
|
5985
6580
|
deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
|
|
5986
|
-
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
|
|
6581
|
+
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
5987
6582
|
} catch (error) {
|
|
5988
6583
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
|
|
5989
6584
|
}
|
|
@@ -6000,16 +6595,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6000
6595
|
if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
|
|
6001
6596
|
return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
|
|
6002
6597
|
}
|
|
6003
|
-
const
|
|
6598
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6599
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6004
6600
|
kind,
|
|
6005
6601
|
path: normalizeString(body.path) ?? state.projectRoot,
|
|
6006
6602
|
ref: normalizeString(body.ref) ?? undefined
|
|
6007
6603
|
});
|
|
6008
|
-
return deps.jsonResponse({ ok: true, project });
|
|
6604
|
+
return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6009
6605
|
}
|
|
6010
6606
|
if (req.method === "GET") {
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6607
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6608
|
+
const project = getProjectRecord(registryRoot, repoSlug);
|
|
6609
|
+
return project ? deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) }) : deps.notFound();
|
|
6013
6610
|
}
|
|
6014
6611
|
}
|
|
6015
6612
|
if (url.pathname === "/api/server/project-root" && req.method === "POST") {
|
|
@@ -6018,11 +6615,27 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6018
6615
|
if (!requestedRoot) {
|
|
6019
6616
|
return deps.badRequest("projectRoot is required");
|
|
6020
6617
|
}
|
|
6021
|
-
if (!
|
|
6618
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
6022
6619
|
return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
|
|
6023
6620
|
}
|
|
6024
|
-
const normalizedRoot =
|
|
6025
|
-
const exists =
|
|
6621
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
6622
|
+
const exists = existsSync13(normalizedRoot);
|
|
6623
|
+
if (exists && requestAuth.userNamespace) {
|
|
6624
|
+
const allowedByNamespace = isPathInsideNamespace(requestAuth.userNamespace.root, normalizedRoot);
|
|
6625
|
+
const allowedByRegistry = projectRegistryContainsCheckout(requestAuth.userNamespace.root, normalizedRoot);
|
|
6626
|
+
if (!allowedByNamespace && !allowedByRegistry) {
|
|
6627
|
+
return deps.jsonResponse({
|
|
6628
|
+
ok: false,
|
|
6629
|
+
error: "Requested project root is outside the authenticated GitHub user namespace.",
|
|
6630
|
+
projectRoot: state.projectRoot,
|
|
6631
|
+
requestedProjectRoot: normalizedRoot,
|
|
6632
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace)
|
|
6633
|
+
}, 403);
|
|
6634
|
+
}
|
|
6635
|
+
copyGitHubAuthStateToLocalProjectRoot(requestAuth.userNamespace.authStateFile, normalizedRoot);
|
|
6636
|
+
} else if (exists) {
|
|
6637
|
+
createGitHubAuthStore(state.projectRoot).copyToLocalProjectRoot(normalizedRoot);
|
|
6638
|
+
}
|
|
6026
6639
|
const control = buildServerControlStatus();
|
|
6027
6640
|
const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
|
|
6028
6641
|
if (!exists) {
|
|
@@ -6036,7 +6649,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6036
6649
|
message: "Requested project root does not exist on the Rig server host."
|
|
6037
6650
|
}, 404);
|
|
6038
6651
|
}
|
|
6039
|
-
if (!
|
|
6652
|
+
if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
|
|
6040
6653
|
return deps.jsonResponse({
|
|
6041
6654
|
ok: false,
|
|
6042
6655
|
projectRoot: state.projectRoot,
|
|
@@ -6071,6 +6684,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6071
6684
|
exists,
|
|
6072
6685
|
control,
|
|
6073
6686
|
requiresRestart: false,
|
|
6687
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace),
|
|
6074
6688
|
message: "Project-root switch accepted. Rig server restart has been scheduled."
|
|
6075
6689
|
}, 202);
|
|
6076
6690
|
}
|
|
@@ -6085,11 +6699,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6085
6699
|
}, 409);
|
|
6086
6700
|
}
|
|
6087
6701
|
if (url.pathname === "/api/github/auth/status") {
|
|
6088
|
-
const store =
|
|
6089
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
|
|
6702
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6703
|
+
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6090
6704
|
}
|
|
6091
6705
|
if (url.pathname === "/api/github/repo/permissions") {
|
|
6092
|
-
const store =
|
|
6706
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6093
6707
|
const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6094
6708
|
if (!auth.signedIn) {
|
|
6095
6709
|
return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
|
|
@@ -6111,21 +6725,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6111
6725
|
const body = await deps.readJsonBody(req);
|
|
6112
6726
|
const token = normalizeString(body.token);
|
|
6113
6727
|
const selectedRepo = normalizeString(body.selectedRepo);
|
|
6728
|
+
const requestedProjectRoot = normalizeString(body.projectRoot);
|
|
6114
6729
|
if (!token) {
|
|
6115
6730
|
return deps.badRequest("token is required");
|
|
6116
6731
|
}
|
|
6117
6732
|
try {
|
|
6118
6733
|
const user = await fetchGitHubUserInfo(token);
|
|
6119
|
-
const
|
|
6120
|
-
|
|
6734
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
6735
|
+
projectRoot: state.projectRoot,
|
|
6121
6736
|
token,
|
|
6122
6737
|
tokenSource: "manual-token",
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6738
|
+
user,
|
|
6739
|
+
selectedRepo,
|
|
6740
|
+
requestedProjectRoot
|
|
6741
|
+
});
|
|
6742
|
+
return deps.jsonResponse({
|
|
6743
|
+
ok: true,
|
|
6744
|
+
...saved.store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }),
|
|
6745
|
+
apiSessionToken: saved.apiSessionToken,
|
|
6746
|
+
userNamespace: userNamespaceResponse(saved.namespace)
|
|
6127
6747
|
});
|
|
6128
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
|
|
6129
6748
|
} catch (error) {
|
|
6130
6749
|
const message = error instanceof Error ? error.message : String(error);
|
|
6131
6750
|
return deps.jsonResponse({ ok: false, error: message }, 400);
|
|
@@ -6192,8 +6811,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6192
6811
|
}
|
|
6193
6812
|
const token = result.payload.access_token;
|
|
6194
6813
|
const user = await fetchGitHubUserInfo(token);
|
|
6195
|
-
|
|
6196
|
-
|
|
6814
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
6815
|
+
projectRoot: state.projectRoot,
|
|
6816
|
+
token,
|
|
6817
|
+
tokenSource: "oauth-device",
|
|
6818
|
+
user,
|
|
6819
|
+
selectedRepo: null
|
|
6820
|
+
});
|
|
6821
|
+
store.clearPendingDevice(pollId);
|
|
6822
|
+
return deps.jsonResponse({
|
|
6823
|
+
ok: true,
|
|
6824
|
+
status: "signed-in",
|
|
6825
|
+
...saved.store.status({ oauthConfigured: true }),
|
|
6826
|
+
apiSessionToken: saved.apiSessionToken,
|
|
6827
|
+
userNamespace: userNamespaceResponse(saved.namespace)
|
|
6828
|
+
});
|
|
6197
6829
|
}
|
|
6198
6830
|
if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
|
|
6199
6831
|
const body = await deps.readJsonBody(req);
|
|
@@ -6202,7 +6834,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6202
6834
|
if (!owner || !repo) {
|
|
6203
6835
|
return deps.badRequest("owner and repo are required");
|
|
6204
6836
|
}
|
|
6205
|
-
const store =
|
|
6837
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6206
6838
|
const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6207
6839
|
const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
|
|
6208
6840
|
return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
|
|
@@ -6223,7 +6855,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6223
6855
|
return deps.badRequest(error instanceof Error ? error.message : String(error));
|
|
6224
6856
|
}
|
|
6225
6857
|
const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6226
|
-
const configPath =
|
|
6858
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6227
6859
|
const source = buildGitHubProjectConfigSource({
|
|
6228
6860
|
projectName: rawProjectName,
|
|
6229
6861
|
owner,
|
|
@@ -6235,8 +6867,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6235
6867
|
ok: true,
|
|
6236
6868
|
projectRoot: targetRoot,
|
|
6237
6869
|
configPath,
|
|
6238
|
-
exists:
|
|
6239
|
-
requiresOverwrite:
|
|
6870
|
+
exists: existsSync13(configPath),
|
|
6871
|
+
requiresOverwrite: existsSync13(configPath),
|
|
6240
6872
|
source,
|
|
6241
6873
|
owner,
|
|
6242
6874
|
repo,
|
|
@@ -6272,8 +6904,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6272
6904
|
assignee,
|
|
6273
6905
|
githubUserId: authStatus.userId ?? authStatus.login
|
|
6274
6906
|
});
|
|
6275
|
-
const configPath =
|
|
6276
|
-
if (
|
|
6907
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6908
|
+
if (existsSync13(configPath) && !overwrite) {
|
|
6277
6909
|
return deps.jsonResponse({
|
|
6278
6910
|
ok: false,
|
|
6279
6911
|
error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
|
|
@@ -6289,11 +6921,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6289
6921
|
return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
|
|
6290
6922
|
}
|
|
6291
6923
|
let backupPath = null;
|
|
6292
|
-
if (
|
|
6924
|
+
if (existsSync13(configPath)) {
|
|
6293
6925
|
backupPath = backupConfigPath(configPath);
|
|
6294
|
-
|
|
6926
|
+
copyFileSync2(configPath, backupPath);
|
|
6295
6927
|
}
|
|
6296
|
-
|
|
6928
|
+
writeFileSync12(configPath, source, "utf8");
|
|
6297
6929
|
const selectedRepo = `${owner}/${repo}`;
|
|
6298
6930
|
store.saveSelectedRepo(selectedRepo);
|
|
6299
6931
|
const targetStore = createGitHubAuthStore(targetRoot);
|
|
@@ -6565,11 +7197,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6565
7197
|
const runId = normalizeString(body.runId);
|
|
6566
7198
|
const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
|
|
6567
7199
|
const promptOverride = normalizeString(body.promptOverride);
|
|
7200
|
+
const restart = body.restart === true;
|
|
6568
7201
|
if (!runId) {
|
|
6569
7202
|
return deps.badRequest("runId is required");
|
|
6570
7203
|
}
|
|
6571
7204
|
try {
|
|
6572
|
-
await deps.resumeRunRecord(state, { runId, createdAt, promptOverride });
|
|
7205
|
+
await deps.resumeRunRecord(state, { runId, createdAt, promptOverride, restart });
|
|
6573
7206
|
deps.broadcastSnapshotInvalidation(state);
|
|
6574
7207
|
return deps.jsonResponse({ ok: true, runId, createdAt });
|
|
6575
7208
|
} catch (error) {
|
|
@@ -6578,7 +7211,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6578
7211
|
}
|
|
6579
7212
|
if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
|
|
6580
7213
|
const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
|
|
6581
|
-
const packageSource = configuredPackageSource ?? [process.env.RIG_HOST_PROJECT_ROOT, process.cwd(), state.projectRoot].map((root) => normalizeString(root)).filter((root) => Boolean(root)).map((root) =>
|
|
7214
|
+
const packageSource = configuredPackageSource ?? [process.env.RIG_HOST_PROJECT_ROOT, process.cwd(), state.projectRoot].map((root) => normalizeString(root)).filter((root) => Boolean(root)).map((root) => resolve20(root, "packages", "pi-rig")).find((candidate) => existsSync13(resolve20(candidate, "package.json"))) ?? "npm:@rig/pi-rig";
|
|
6582
7215
|
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
6583
7216
|
return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
|
|
6584
7217
|
}
|
|
@@ -6894,9 +7527,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6894
7527
|
} catch {
|
|
6895
7528
|
return deps.badRequest("Invalid artifact path");
|
|
6896
7529
|
}
|
|
6897
|
-
|
|
7530
|
+
mkdirSync13(dirname15(artifactPath), { recursive: true });
|
|
6898
7531
|
const bytes = Buffer.from(contentBase64, "base64");
|
|
6899
|
-
|
|
7532
|
+
writeFileSync12(artifactPath, bytes);
|
|
6900
7533
|
writeJsonFile4(`${artifactPath}.json`, {
|
|
6901
7534
|
workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
|
|
6902
7535
|
runId,
|
|
@@ -7058,12 +7691,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7058
7691
|
try {
|
|
7059
7692
|
const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
|
|
7060
7693
|
const runRoot = deps.normalizeRelativePath(runsRoot, runId);
|
|
7061
|
-
const artifactsRoot =
|
|
7694
|
+
const artifactsRoot = resolve20(runRoot, "remote-artifacts");
|
|
7062
7695
|
artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
|
|
7063
7696
|
} catch {
|
|
7064
7697
|
return deps.badRequest("Invalid artifact path");
|
|
7065
7698
|
}
|
|
7066
|
-
if (!
|
|
7699
|
+
if (!existsSync13(artifactPath)) {
|
|
7067
7700
|
return deps.notFound();
|
|
7068
7701
|
}
|
|
7069
7702
|
return new Response(Bun.file(artifactPath));
|
|
@@ -7956,8 +8589,8 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
7956
8589
|
}
|
|
7957
8590
|
|
|
7958
8591
|
// packages/server/src/server-helpers/inspector-jobs.ts
|
|
7959
|
-
import { existsSync as
|
|
7960
|
-
import { dirname as
|
|
8592
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
8593
|
+
import { dirname as dirname18, resolve as resolve23 } from "path";
|
|
7961
8594
|
import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
|
|
7962
8595
|
import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
|
|
7963
8596
|
import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -8065,8 +8698,8 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8065
8698
|
|
|
8066
8699
|
// packages/server/src/inspector/mission.ts
|
|
8067
8700
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8068
|
-
import { appendFileSync, existsSync as
|
|
8069
|
-
import { dirname as
|
|
8701
|
+
import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync13 } from "fs";
|
|
8702
|
+
import { dirname as dirname16, join, resolve as resolve21 } from "path";
|
|
8070
8703
|
function isJsonValue(value) {
|
|
8071
8704
|
if (value === null)
|
|
8072
8705
|
return true;
|
|
@@ -8106,7 +8739,7 @@ function isRecord2(value) {
|
|
|
8106
8739
|
}
|
|
8107
8740
|
function readJsonRecord(path) {
|
|
8108
8741
|
try {
|
|
8109
|
-
const parsed = JSON.parse(
|
|
8742
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
8110
8743
|
if (!isRecord2(parsed)) {
|
|
8111
8744
|
return { ok: false, error: `Mission file ${path} does not contain an object` };
|
|
8112
8745
|
}
|
|
@@ -8186,14 +8819,14 @@ function missionActionDetails(mission) {
|
|
|
8186
8819
|
};
|
|
8187
8820
|
}
|
|
8188
8821
|
function writeJsonFile5(path, value) {
|
|
8189
|
-
|
|
8822
|
+
mkdirSync14(dirname16(path), { recursive: true });
|
|
8190
8823
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
8191
|
-
|
|
8824
|
+
writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
|
|
8192
8825
|
`, "utf8");
|
|
8193
8826
|
renameSync(tempPath, path);
|
|
8194
8827
|
}
|
|
8195
8828
|
function resolveInspectorMissionPaths(projectRoot) {
|
|
8196
|
-
const inspectorDir =
|
|
8829
|
+
const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
|
|
8197
8830
|
return {
|
|
8198
8831
|
inspectorDir,
|
|
8199
8832
|
missionsDir: join(inspectorDir, "missions"),
|
|
@@ -8202,8 +8835,8 @@ function resolveInspectorMissionPaths(projectRoot) {
|
|
|
8202
8835
|
}
|
|
8203
8836
|
function createInspectorMissionController(options) {
|
|
8204
8837
|
const paths = resolveInspectorMissionPaths(options.projectRoot);
|
|
8205
|
-
|
|
8206
|
-
|
|
8838
|
+
mkdirSync14(paths.missionsDir, { recursive: true });
|
|
8839
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8207
8840
|
const now = options.now ?? (() => new Date().toISOString());
|
|
8208
8841
|
const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
|
|
8209
8842
|
function missionPath(missionId) {
|
|
@@ -8213,15 +8846,15 @@ function createInspectorMissionController(options) {
|
|
|
8213
8846
|
return join(paths.journalsDir, `${missionId}.jsonl`);
|
|
8214
8847
|
}
|
|
8215
8848
|
function appendMissionJournal(entry) {
|
|
8216
|
-
|
|
8849
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8217
8850
|
appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
|
|
8218
8851
|
`, "utf8");
|
|
8219
8852
|
}
|
|
8220
8853
|
function listMissionJournal(missionId) {
|
|
8221
8854
|
const path = journalPath(missionId);
|
|
8222
|
-
if (!
|
|
8855
|
+
if (!existsSync14(path))
|
|
8223
8856
|
return [];
|
|
8224
|
-
return
|
|
8857
|
+
return readFileSync10(path, "utf8").split(`
|
|
8225
8858
|
`).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
|
|
8226
8859
|
id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
|
|
8227
8860
|
missionId,
|
|
@@ -8237,7 +8870,7 @@ function createInspectorMissionController(options) {
|
|
|
8237
8870
|
}
|
|
8238
8871
|
function readMissionOnly(missionId) {
|
|
8239
8872
|
const path = missionPath(missionId);
|
|
8240
|
-
if (!
|
|
8873
|
+
if (!existsSync14(path)) {
|
|
8241
8874
|
return { ok: false, error: `Mission ${missionId} was not found` };
|
|
8242
8875
|
}
|
|
8243
8876
|
const read = readJsonRecord(path);
|
|
@@ -8288,7 +8921,7 @@ function createInspectorMissionController(options) {
|
|
|
8288
8921
|
const source = cloneJsonRecord(input.sourceTask);
|
|
8289
8922
|
const missionId = nextId();
|
|
8290
8923
|
const path = missionPath(missionId);
|
|
8291
|
-
if (
|
|
8924
|
+
if (existsSync14(path)) {
|
|
8292
8925
|
const existing = readMissionOnly(missionId);
|
|
8293
8926
|
if (!existing.ok)
|
|
8294
8927
|
return existing;
|
|
@@ -9888,8 +10521,8 @@ function createCodexInspectorTransport(options) {
|
|
|
9888
10521
|
const sendRequest = async (method, params) => {
|
|
9889
10522
|
const id = nextRequestId;
|
|
9890
10523
|
nextRequestId += 1;
|
|
9891
|
-
const response = new Promise((
|
|
9892
|
-
pendingResponses.set(id, { resolve:
|
|
10524
|
+
const response = new Promise((resolve22, reject) => {
|
|
10525
|
+
pendingResponses.set(id, { resolve: resolve22, reject });
|
|
9893
10526
|
});
|
|
9894
10527
|
response.catch(() => {});
|
|
9895
10528
|
try {
|
|
@@ -10199,9 +10832,9 @@ function createCodexInspectorTransport(options) {
|
|
|
10199
10832
|
}
|
|
10200
10833
|
lastAssistantMessage = null;
|
|
10201
10834
|
lastError = null;
|
|
10202
|
-
const turnResult = new Promise((
|
|
10835
|
+
const turnResult = new Promise((resolve22, reject) => {
|
|
10203
10836
|
currentTurn = {
|
|
10204
|
-
resolve:
|
|
10837
|
+
resolve: resolve22,
|
|
10205
10838
|
reject,
|
|
10206
10839
|
events: []
|
|
10207
10840
|
};
|
|
@@ -10261,13 +10894,13 @@ function createCodexInspectorTransport(options) {
|
|
|
10261
10894
|
};
|
|
10262
10895
|
}
|
|
10263
10896
|
function writeChildLine(child, line) {
|
|
10264
|
-
return new Promise((
|
|
10897
|
+
return new Promise((resolve22, reject) => {
|
|
10265
10898
|
child.stdin.write(line, (error) => {
|
|
10266
10899
|
if (error) {
|
|
10267
10900
|
reject(error);
|
|
10268
10901
|
return;
|
|
10269
10902
|
}
|
|
10270
|
-
|
|
10903
|
+
resolve22();
|
|
10271
10904
|
});
|
|
10272
10905
|
});
|
|
10273
10906
|
}
|
|
@@ -10280,10 +10913,10 @@ function terminateChild(child) {
|
|
|
10280
10913
|
} catch {}
|
|
10281
10914
|
}
|
|
10282
10915
|
async function waitForChildSpawn(child) {
|
|
10283
|
-
await new Promise((
|
|
10916
|
+
await new Promise((resolve22, reject) => {
|
|
10284
10917
|
const onSpawn = () => {
|
|
10285
10918
|
cleanup();
|
|
10286
|
-
|
|
10919
|
+
resolve22();
|
|
10287
10920
|
};
|
|
10288
10921
|
const onError = (error) => {
|
|
10289
10922
|
cleanup();
|
|
@@ -10795,8 +11428,8 @@ function createGlobalInspectorService(options) {
|
|
|
10795
11428
|
|
|
10796
11429
|
// packages/server/src/inspector/upstream-sync.ts
|
|
10797
11430
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
10798
|
-
import { existsSync as
|
|
10799
|
-
import { dirname as
|
|
11431
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
11432
|
+
import { dirname as dirname17, resolve as resolve22 } from "path";
|
|
10800
11433
|
import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
|
|
10801
11434
|
var UPSTREAM_VALIDATION_DESCRIPTIONS = {
|
|
10802
11435
|
"integration:hg-auth-backport": "Preserves the upstream auth hardening cluster: nonce-backed node-client login, signature-aware JWT verification, shared-token onboarding semantics, and regression coverage.",
|
|
@@ -10934,34 +11567,34 @@ function defaultGitRunner(repoRoot, args) {
|
|
|
10934
11567
|
}
|
|
10935
11568
|
function upstreamStatePath(projectRoot, override) {
|
|
10936
11569
|
if (override) {
|
|
10937
|
-
return
|
|
11570
|
+
return resolve22(override);
|
|
10938
11571
|
}
|
|
10939
|
-
return
|
|
11572
|
+
return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
|
|
10940
11573
|
}
|
|
10941
11574
|
function readUpstreamState(projectRoot, statePath) {
|
|
10942
11575
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
10943
|
-
if (!
|
|
11576
|
+
if (!existsSync15(path)) {
|
|
10944
11577
|
return null;
|
|
10945
11578
|
}
|
|
10946
11579
|
try {
|
|
10947
|
-
return JSON.parse(
|
|
11580
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
10948
11581
|
} catch {
|
|
10949
11582
|
return null;
|
|
10950
11583
|
}
|
|
10951
11584
|
}
|
|
10952
11585
|
function writeUpstreamState(projectRoot, state, statePath) {
|
|
10953
11586
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
10954
|
-
|
|
10955
|
-
|
|
11587
|
+
mkdirSync15(dirname17(path), { recursive: true });
|
|
11588
|
+
writeFileSync14(path, `${JSON.stringify(state, null, 2)}
|
|
10956
11589
|
`, "utf8");
|
|
10957
11590
|
}
|
|
10958
11591
|
function readImportedRevision(projectRoot, upstreamsDocPath) {
|
|
10959
11592
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
10960
|
-
const docPath = upstreamsDocPath ?
|
|
10961
|
-
if (!
|
|
11593
|
+
const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
|
|
11594
|
+
if (!existsSync15(docPath)) {
|
|
10962
11595
|
throw new Error(`UPSTREAMS.md not found at ${docPath}`);
|
|
10963
11596
|
}
|
|
10964
|
-
const docContent =
|
|
11597
|
+
const docContent = readFileSync11(docPath, "utf-8");
|
|
10965
11598
|
const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
|
|
10966
11599
|
if (!revision) {
|
|
10967
11600
|
throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
|
|
@@ -10983,7 +11616,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
|
|
|
10983
11616
|
return null;
|
|
10984
11617
|
}
|
|
10985
11618
|
function isGitCheckout(path, gitRunner) {
|
|
10986
|
-
if (!
|
|
11619
|
+
if (!existsSync15(resolve22(path, ".git"))) {
|
|
10987
11620
|
return false;
|
|
10988
11621
|
}
|
|
10989
11622
|
const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -10992,12 +11625,12 @@ function isGitCheckout(path, gitRunner) {
|
|
|
10992
11625
|
function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
|
|
10993
11626
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
10994
11627
|
const candidates = [
|
|
10995
|
-
explicitCheckout ?
|
|
10996
|
-
process.env.UPSTREAM_CHECKOUT?.trim() ?
|
|
10997
|
-
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ?
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11628
|
+
explicitCheckout ? resolve22(explicitCheckout) : "",
|
|
11629
|
+
process.env.UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.UPSTREAM_CHECKOUT.trim()) : "",
|
|
11630
|
+
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
|
|
11631
|
+
resolve22(projectRoot, "..", "humoongate"),
|
|
11632
|
+
resolve22(monorepoRoot, "..", "humoongate"),
|
|
11633
|
+
resolve22(monorepoRoot, "humoongate")
|
|
11001
11634
|
].filter(Boolean);
|
|
11002
11635
|
for (const candidate of candidates) {
|
|
11003
11636
|
if (isGitCheckout(candidate, gitRunner)) {
|
|
@@ -11233,10 +11866,10 @@ async function runUpstreamSyncScan(options) {
|
|
|
11233
11866
|
}
|
|
11234
11867
|
|
|
11235
11868
|
// packages/server/src/server-helpers/task-config.ts
|
|
11236
|
-
import { existsSync as
|
|
11869
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11237
11870
|
async function readTaskConfig(projectRoot) {
|
|
11238
11871
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
11239
|
-
if (!
|
|
11872
|
+
if (!existsSync16(taskConfigPath)) {
|
|
11240
11873
|
return {};
|
|
11241
11874
|
}
|
|
11242
11875
|
try {
|
|
@@ -11272,11 +11905,11 @@ function resolveFollowupSourceCommit(input) {
|
|
|
11272
11905
|
}
|
|
11273
11906
|
async function createInspectorFollowupTask(projectRoot, input) {
|
|
11274
11907
|
const monorepoRoot = resolveMonorepoRoot5(projectRoot);
|
|
11275
|
-
const issuesPath =
|
|
11276
|
-
const taskStatePath =
|
|
11277
|
-
const taskConfigPath =
|
|
11278
|
-
|
|
11279
|
-
|
|
11908
|
+
const issuesPath = resolve23(monorepoRoot, ".beads", "issues.jsonl");
|
|
11909
|
+
const taskStatePath = resolve23(monorepoRoot, ".beads", "task-state.json");
|
|
11910
|
+
const taskConfigPath = resolve23(monorepoRoot, ".rig", "task-config.json");
|
|
11911
|
+
mkdirSync16(dirname18(issuesPath), { recursive: true });
|
|
11912
|
+
mkdirSync16(dirname18(taskConfigPath), { recursive: true });
|
|
11280
11913
|
const summary = normalizeString(input.summary) ?? "Inspector follow-up";
|
|
11281
11914
|
const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
|
|
11282
11915
|
const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
|
|
@@ -11295,7 +11928,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11295
11928
|
const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
|
|
11296
11929
|
const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
|
|
11297
11930
|
const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
|
|
11298
|
-
const existingIssueLines =
|
|
11931
|
+
const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
11299
11932
|
const existingIssues = existingIssueLines.map((line) => {
|
|
11300
11933
|
try {
|
|
11301
11934
|
return JSON.parse(line);
|
|
@@ -11304,7 +11937,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11304
11937
|
}
|
|
11305
11938
|
}).filter((value) => value !== null);
|
|
11306
11939
|
const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
|
|
11307
|
-
const rawTaskState =
|
|
11940
|
+
const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
|
|
11308
11941
|
const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
|
|
11309
11942
|
const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
|
|
11310
11943
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
@@ -11330,7 +11963,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11330
11963
|
updated_at: createdAt,
|
|
11331
11964
|
labels: mergedLabels
|
|
11332
11965
|
};
|
|
11333
|
-
|
|
11966
|
+
writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
|
|
11334
11967
|
`)}
|
|
11335
11968
|
${JSON.stringify(issueRecord)}
|
|
11336
11969
|
` : `${JSON.stringify(issueRecord)}
|
|
@@ -11348,7 +11981,7 @@ ${JSON.stringify(issueRecord)}
|
|
|
11348
11981
|
labels: mergedLabels
|
|
11349
11982
|
};
|
|
11350
11983
|
});
|
|
11351
|
-
|
|
11984
|
+
writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
|
|
11352
11985
|
`)}
|
|
11353
11986
|
`, "utf8");
|
|
11354
11987
|
}
|
|
@@ -11371,14 +12004,14 @@ ${JSON.stringify(issueRecord)}
|
|
|
11371
12004
|
}
|
|
11372
12005
|
};
|
|
11373
12006
|
}
|
|
11374
|
-
|
|
12007
|
+
writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
|
|
11375
12008
|
`, "utf8");
|
|
11376
12009
|
tasks[taskId] = {
|
|
11377
12010
|
status,
|
|
11378
12011
|
sourceCommit: resolveFollowupSourceCommit(input),
|
|
11379
12012
|
...sourceKey ? { sourceKey } : {}
|
|
11380
12013
|
};
|
|
11381
|
-
|
|
12014
|
+
writeFileSync15(taskStatePath, `${JSON.stringify({
|
|
11382
12015
|
schemaVersion: 1,
|
|
11383
12016
|
baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
|
|
11384
12017
|
tasks
|
|
@@ -11686,12 +12319,12 @@ function isAuthorizedLinearWebhookRequest(req) {
|
|
|
11686
12319
|
}
|
|
11687
12320
|
|
|
11688
12321
|
// packages/server/src/server-helpers/notifications.ts
|
|
11689
|
-
import { existsSync as
|
|
11690
|
-
import { dirname as
|
|
12322
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
|
|
12323
|
+
import { dirname as dirname19 } from "path";
|
|
11691
12324
|
async function loadNotificationConfig(path) {
|
|
11692
|
-
if (!
|
|
12325
|
+
if (!existsSync18(path)) {
|
|
11693
12326
|
const defaultConfig = { targets: [] };
|
|
11694
|
-
|
|
12327
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
11695
12328
|
await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
|
|
11696
12329
|
`);
|
|
11697
12330
|
return defaultConfig;
|
|
@@ -11706,10 +12339,10 @@ async function loadNotificationConfig(path) {
|
|
|
11706
12339
|
}
|
|
11707
12340
|
}
|
|
11708
12341
|
function readRecentEvents(file, limit) {
|
|
11709
|
-
if (!
|
|
12342
|
+
if (!existsSync18(file)) {
|
|
11710
12343
|
return [];
|
|
11711
12344
|
}
|
|
11712
|
-
const lines =
|
|
12345
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
|
|
11713
12346
|
const events = [];
|
|
11714
12347
|
for (const line of lines) {
|
|
11715
12348
|
try {
|
|
@@ -11804,11 +12437,11 @@ function extractObjectLiteralBlock(source, property) {
|
|
|
11804
12437
|
}
|
|
11805
12438
|
function readFallbackIssueAnalysisConfig(projectRoot) {
|
|
11806
12439
|
for (const fileName of ["rig.config.ts", "rig.config.json"]) {
|
|
11807
|
-
const path =
|
|
11808
|
-
if (!
|
|
12440
|
+
const path = resolve24(projectRoot, fileName);
|
|
12441
|
+
if (!existsSync19(path))
|
|
11809
12442
|
continue;
|
|
11810
12443
|
try {
|
|
11811
|
-
const source =
|
|
12444
|
+
const source = readFileSync14(path, "utf8");
|
|
11812
12445
|
if (fileName.endsWith(".json"))
|
|
11813
12446
|
return JSON.parse(source);
|
|
11814
12447
|
const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
|
|
@@ -11941,8 +12574,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
|
|
|
11941
12574
|
async function withServerPathEnv(projectRoot, fn) {
|
|
11942
12575
|
const waitForTurn = serverPathEnvQueue;
|
|
11943
12576
|
let releaseTurn;
|
|
11944
|
-
serverPathEnvQueue = new Promise((
|
|
11945
|
-
releaseTurn =
|
|
12577
|
+
serverPathEnvQueue = new Promise((resolve25) => {
|
|
12578
|
+
releaseTurn = resolve25;
|
|
11946
12579
|
});
|
|
11947
12580
|
await waitForTurn;
|
|
11948
12581
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -11978,9 +12611,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
11978
12611
|
return withServerPathEnv(projectRoot, fn);
|
|
11979
12612
|
}
|
|
11980
12613
|
async function readWorkspaceTasks(projectRoot) {
|
|
11981
|
-
const issuesPath =
|
|
12614
|
+
const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
|
|
11982
12615
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
11983
|
-
if (!
|
|
12616
|
+
if (!existsSync19(issuesPath)) {
|
|
11984
12617
|
return [];
|
|
11985
12618
|
}
|
|
11986
12619
|
const latestById = new Map;
|
|
@@ -11999,7 +12632,7 @@ async function readWorkspaceTasks(projectRoot) {
|
|
|
11999
12632
|
description: normalizeString(entry.description),
|
|
12000
12633
|
acceptanceCriteria: normalizeString(entry.acceptance_criteria),
|
|
12001
12634
|
status: normalizedStatus ?? "unknown",
|
|
12002
|
-
sourceStatus: normalizedStatus ? null : rawStatus,
|
|
12635
|
+
sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
|
|
12003
12636
|
priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
|
|
12004
12637
|
issueType: normalizeString(entry.issue_type),
|
|
12005
12638
|
role: normalizeString(config.role) ?? null,
|
|
@@ -12054,11 +12687,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
|
|
|
12054
12687
|
continue;
|
|
12055
12688
|
add(run.artifactRoot);
|
|
12056
12689
|
if (run.worktreePath) {
|
|
12057
|
-
add(
|
|
12690
|
+
add(resolve24(run.worktreePath, "artifacts", taskId));
|
|
12058
12691
|
}
|
|
12059
12692
|
}
|
|
12060
12693
|
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
12061
|
-
add(
|
|
12694
|
+
add(resolve24(artifactsRoot, taskId));
|
|
12062
12695
|
}
|
|
12063
12696
|
return candidates;
|
|
12064
12697
|
}
|
|
@@ -12072,7 +12705,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12072
12705
|
}
|
|
12073
12706
|
}
|
|
12074
12707
|
return taskIds.flatMap((currentTaskId) => {
|
|
12075
|
-
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) =>
|
|
12708
|
+
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
|
|
12076
12709
|
if (!currentRoot) {
|
|
12077
12710
|
return [];
|
|
12078
12711
|
}
|
|
@@ -12084,7 +12717,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12084
12717
|
taskId: currentTaskId,
|
|
12085
12718
|
kind: "file",
|
|
12086
12719
|
label: fileName,
|
|
12087
|
-
path:
|
|
12720
|
+
path: resolve24(currentRoot, fileName),
|
|
12088
12721
|
url: null,
|
|
12089
12722
|
metadata: {
|
|
12090
12723
|
fileName
|
|
@@ -12127,11 +12760,11 @@ function buildInspectorStreamPayload(state, sequence) {
|
|
|
12127
12760
|
}
|
|
12128
12761
|
function listRemoteRunArtifacts(projectRoot, runId) {
|
|
12129
12762
|
const root = remoteArtifactsRoot(projectRoot, runId);
|
|
12130
|
-
if (!
|
|
12763
|
+
if (!existsSync19(root)) {
|
|
12131
12764
|
return [];
|
|
12132
12765
|
}
|
|
12133
12766
|
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
|
|
12134
|
-
const artifactPath =
|
|
12767
|
+
const artifactPath = resolve24(root, entry.name);
|
|
12135
12768
|
const stat = statSync6(artifactPath);
|
|
12136
12769
|
const meta = readJsonFile4(`${artifactPath}.json`, null);
|
|
12137
12770
|
return {
|
|
@@ -12373,8 +13006,8 @@ function fileStats(path) {
|
|
|
12373
13006
|
}
|
|
12374
13007
|
}
|
|
12375
13008
|
function runFileCursor(projectRoot, run) {
|
|
12376
|
-
const runDir =
|
|
12377
|
-
const runJson = fileStats(
|
|
13009
|
+
const runDir = dirname20(runLogsPath(projectRoot, run.runId));
|
|
13010
|
+
const runJson = fileStats(resolve24(runDir, "run.json"));
|
|
12378
13011
|
const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
|
|
12379
13012
|
const logs = fileStats(runLogsPath(projectRoot, run.runId));
|
|
12380
13013
|
return {
|
|
@@ -12424,10 +13057,10 @@ function startRunFileWatcher(state, pollMs) {
|
|
|
12424
13057
|
}, Math.max(250, Math.min(pollMs, 1000)));
|
|
12425
13058
|
}
|
|
12426
13059
|
function startPoller(state, pollMs) {
|
|
12427
|
-
let offset =
|
|
13060
|
+
let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
|
|
12428
13061
|
return setInterval(async () => {
|
|
12429
13062
|
try {
|
|
12430
|
-
if (!
|
|
13063
|
+
if (!existsSync19(state.eventsFile)) {
|
|
12431
13064
|
return;
|
|
12432
13065
|
}
|
|
12433
13066
|
const file = await open(state.eventsFile, "r");
|
|
@@ -12479,6 +13112,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
12479
13112
|
const server = Bun.serve({
|
|
12480
13113
|
hostname: options.host,
|
|
12481
13114
|
port: options.port,
|
|
13115
|
+
idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
|
|
12482
13116
|
fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
|
|
12483
13117
|
websocket: {
|
|
12484
13118
|
open(ws) {
|
|
@@ -12554,7 +13188,7 @@ function resolveProjectRoot() {
|
|
|
12554
13188
|
return resolveRigProjectRoot({
|
|
12555
13189
|
envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
|
|
12556
13190
|
cwd: process.cwd(),
|
|
12557
|
-
fallbackRoot:
|
|
13191
|
+
fallbackRoot: resolve24(import.meta.dir, "../..")
|
|
12558
13192
|
});
|
|
12559
13193
|
}
|
|
12560
13194
|
var __testOnly = {
|