@h-rig/server 0.0.6-alpha.10 → 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 +620 -241
- package/dist/src/server-helpers/github-api-session-index.js +107 -0
- package/dist/src/server-helpers/github-auth-store.js +68 -24
- package/dist/src/server-helpers/github-user-namespace.js +102 -0
- package/dist/src/server-helpers/http-router.js +538 -159
- package/dist/src/server-helpers/project-registry.js +5 -0
- package/dist/src/server-helpers/run-mutations.js +68 -26
- package/dist/src/server.js +620 -241
- package/package.json +4 -4
package/dist/src/server.js
CHANGED
|
@@ -4,9 +4,9 @@ var __require = import.meta.require;
|
|
|
4
4
|
|
|
5
5
|
// packages/server/src/server.ts
|
|
6
6
|
import { spawn as spawn5 } from "child_process";
|
|
7
|
-
import { existsSync as
|
|
7
|
+
import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
|
|
8
8
|
import { open } from "fs/promises";
|
|
9
|
-
import { dirname as
|
|
9
|
+
import { dirname as dirname20, resolve as resolve24 } from "path";
|
|
10
10
|
import {
|
|
11
11
|
listAuthorityArtifactRoots,
|
|
12
12
|
listAuthorityRuns as listAuthorityRuns7,
|
|
@@ -3201,7 +3201,7 @@ function applyOrchestrationCommand(state, command) {
|
|
|
3201
3201
|
import { spawn as spawn3 } from "child_process";
|
|
3202
3202
|
import { loadConfig } from "@rig/core/load-config";
|
|
3203
3203
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3204
|
-
import { dirname as
|
|
3204
|
+
import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
|
|
3205
3205
|
import {
|
|
3206
3206
|
listAuthorityRuns as listAuthorityRuns4,
|
|
3207
3207
|
readAuthorityRun as readAuthorityRun4,
|
|
@@ -3325,8 +3325,8 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
3325
3325
|
|
|
3326
3326
|
// packages/server/src/server-helpers/github-auth-store.ts
|
|
3327
3327
|
import { randomBytes } from "crypto";
|
|
3328
|
-
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
3329
|
-
import { resolve as resolve13 } from "path";
|
|
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";
|
|
3330
3330
|
function cleanString(value) {
|
|
3331
3331
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3332
3332
|
}
|
|
@@ -3356,6 +3356,26 @@ function parseApiSessions(value) {
|
|
|
3356
3356
|
}];
|
|
3357
3357
|
});
|
|
3358
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
|
+
}
|
|
3359
3379
|
function readStoredAuth(stateFile) {
|
|
3360
3380
|
if (!existsSync6(stateFile))
|
|
3361
3381
|
return {};
|
|
@@ -3369,6 +3389,7 @@ function readStoredAuth(stateFile) {
|
|
|
3369
3389
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
3370
3390
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
3371
3391
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
3392
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
3372
3393
|
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
3373
3394
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
3374
3395
|
};
|
|
@@ -3376,34 +3397,36 @@ function readStoredAuth(stateFile) {
|
|
|
3376
3397
|
return {};
|
|
3377
3398
|
}
|
|
3378
3399
|
}
|
|
3379
|
-
function parsePendingDevice(value) {
|
|
3380
|
-
if (!value || typeof value !== "object")
|
|
3381
|
-
return null;
|
|
3382
|
-
const record = value;
|
|
3383
|
-
const pollId = cleanString(record.pollId);
|
|
3384
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
3385
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
3386
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3387
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3388
|
-
return null;
|
|
3389
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3390
|
-
}
|
|
3391
3400
|
function newApiSessionToken() {
|
|
3392
3401
|
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
3393
3402
|
}
|
|
3394
3403
|
function writeStoredAuth(stateFile, payload) {
|
|
3395
|
-
mkdirSync6(
|
|
3404
|
+
mkdirSync6(dirname8(stateFile), { recursive: true });
|
|
3396
3405
|
writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
3397
3406
|
`, { encoding: "utf8", mode: 384 });
|
|
3398
3407
|
try {
|
|
3399
3408
|
chmodSync(stateFile, 384);
|
|
3400
3409
|
} catch {}
|
|
3401
3410
|
}
|
|
3411
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
3412
|
+
return resolve13(projectRoot, ".rig", "state", "github-auth.json");
|
|
3413
|
+
}
|
|
3402
3414
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
3403
3415
|
return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
3404
3416
|
}
|
|
3405
|
-
function
|
|
3406
|
-
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) {
|
|
3407
3430
|
return {
|
|
3408
3431
|
stateFile,
|
|
3409
3432
|
status(options) {
|
|
@@ -3433,6 +3456,7 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3433
3456
|
scopes: input.scopes ?? [],
|
|
3434
3457
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
3435
3458
|
pendingDevice: null,
|
|
3459
|
+
pendingDevices: [],
|
|
3436
3460
|
apiSessions: previous.apiSessions ?? [],
|
|
3437
3461
|
updatedAt: new Date().toISOString()
|
|
3438
3462
|
});
|
|
@@ -3461,15 +3485,24 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3461
3485
|
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
3462
3486
|
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
3463
3487
|
},
|
|
3464
|
-
copyToProjectRoot(
|
|
3465
|
-
const targetFile = resolveGitHubAuthStateFile(
|
|
3488
|
+
copyToProjectRoot(projectRoot) {
|
|
3489
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
3466
3490
|
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
3467
3491
|
},
|
|
3492
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
3493
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
3494
|
+
},
|
|
3468
3495
|
savePendingDevice(input) {
|
|
3469
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);
|
|
3470
3502
|
writeStoredAuth(stateFile, {
|
|
3471
3503
|
...previous,
|
|
3472
|
-
pendingDevice:
|
|
3504
|
+
pendingDevice: null,
|
|
3505
|
+
pendingDevices,
|
|
3473
3506
|
updatedAt: new Date().toISOString()
|
|
3474
3507
|
});
|
|
3475
3508
|
},
|
|
@@ -3482,23 +3515,32 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3482
3515
|
});
|
|
3483
3516
|
},
|
|
3484
3517
|
readPendingDevice(pollId) {
|
|
3485
|
-
const
|
|
3486
|
-
|
|
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)
|
|
3487
3524
|
return null;
|
|
3488
3525
|
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
3489
3526
|
return null;
|
|
3490
3527
|
return pending;
|
|
3491
3528
|
},
|
|
3492
|
-
clearPendingDevice() {
|
|
3529
|
+
clearPendingDevice(pollId) {
|
|
3493
3530
|
const previous = readStoredAuth(stateFile);
|
|
3531
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
3494
3532
|
writeStoredAuth(stateFile, {
|
|
3495
3533
|
...previous,
|
|
3496
3534
|
pendingDevice: null,
|
|
3535
|
+
pendingDevices: remaining,
|
|
3497
3536
|
updatedAt: new Date().toISOString()
|
|
3498
3537
|
});
|
|
3499
3538
|
}
|
|
3500
3539
|
};
|
|
3501
3540
|
}
|
|
3541
|
+
function createGitHubAuthStore(projectRoot) {
|
|
3542
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
3543
|
+
}
|
|
3502
3544
|
|
|
3503
3545
|
// packages/server/src/server-helpers/github-projects.ts
|
|
3504
3546
|
function asRecord(value) {
|
|
@@ -4237,7 +4279,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
4237
4279
|
}
|
|
4238
4280
|
try {
|
|
4239
4281
|
const monorepoRoot = resolveMonorepoRoot3(projectRoot);
|
|
4240
|
-
const outerProjectRoot =
|
|
4282
|
+
const outerProjectRoot = dirname9(dirname9(monorepoRoot));
|
|
4241
4283
|
if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
4242
4284
|
return outerProjectRoot;
|
|
4243
4285
|
}
|
|
@@ -4451,8 +4493,8 @@ async function reconcileScheduler(state, reason) {
|
|
|
4451
4493
|
// packages/server/src/server-helpers/http-router.ts
|
|
4452
4494
|
import { randomUUID } from "crypto";
|
|
4453
4495
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4454
|
-
import { basename, dirname as
|
|
4455
|
-
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";
|
|
4456
4498
|
import {
|
|
4457
4499
|
listAuthorityRuns as listAuthorityRuns5,
|
|
4458
4500
|
readAuthorityRun as readAuthorityRun6,
|
|
@@ -4476,7 +4518,7 @@ import {
|
|
|
4476
4518
|
} from "@rig/runtime/control-plane/remote";
|
|
4477
4519
|
|
|
4478
4520
|
// packages/server/src/server-helpers/run-steering.ts
|
|
4479
|
-
import { dirname as
|
|
4521
|
+
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
4480
4522
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
4481
4523
|
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
4482
4524
|
var steeringSequence = 0;
|
|
@@ -4563,7 +4605,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
|
4563
4605
|
delivered: false
|
|
4564
4606
|
};
|
|
4565
4607
|
const path = runSteeringPath(projectRoot, runId);
|
|
4566
|
-
mkdirSync8(
|
|
4608
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
4567
4609
|
appendJsonlRecord2(path, entry);
|
|
4568
4610
|
appendRunTimelineEntry(projectRoot, runId, {
|
|
4569
4611
|
id: entry.id,
|
|
@@ -4600,6 +4642,187 @@ import {
|
|
|
4600
4642
|
updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
|
|
4601
4643
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
4602
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
|
+
|
|
4603
4826
|
// packages/server/src/server-helpers/inspector-agent-lifecycle.ts
|
|
4604
4827
|
function createInspectorAgentLifecycleController(options) {
|
|
4605
4828
|
const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
|
|
@@ -4703,21 +4926,21 @@ function inspectorAgentLifecycleSnapshot(input) {
|
|
|
4703
4926
|
// packages/server/src/server-helpers/project-registry.ts
|
|
4704
4927
|
import { createHash as createHash2 } from "crypto";
|
|
4705
4928
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4706
|
-
import { existsSync as
|
|
4707
|
-
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";
|
|
4708
4931
|
function normalizeRepoSlug(value) {
|
|
4709
4932
|
const trimmed = value.trim();
|
|
4710
4933
|
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
|
|
4711
4934
|
}
|
|
4712
4935
|
function registryPath(projectRoot) {
|
|
4713
|
-
return
|
|
4936
|
+
return resolve18(projectRoot, ".rig", "state", "projects.json");
|
|
4714
4937
|
}
|
|
4715
4938
|
function readRegistry(projectRoot) {
|
|
4716
4939
|
const path = registryPath(projectRoot);
|
|
4717
|
-
if (!
|
|
4940
|
+
if (!existsSync11(path))
|
|
4718
4941
|
return {};
|
|
4719
4942
|
try {
|
|
4720
|
-
const payload = JSON.parse(
|
|
4943
|
+
const payload = JSON.parse(readFileSync8(path, "utf8"));
|
|
4721
4944
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
4722
4945
|
return {};
|
|
4723
4946
|
const projects = payload.projects;
|
|
@@ -4728,14 +4951,14 @@ function readRegistry(projectRoot) {
|
|
|
4728
4951
|
}
|
|
4729
4952
|
function writeRegistry(projectRoot, projects) {
|
|
4730
4953
|
const path = registryPath(projectRoot);
|
|
4731
|
-
|
|
4732
|
-
|
|
4954
|
+
mkdirSync11(dirname13(path), { recursive: true });
|
|
4955
|
+
writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
|
|
4733
4956
|
`, "utf8");
|
|
4734
4957
|
}
|
|
4735
4958
|
function resolveConfigPath(projectRoot) {
|
|
4736
4959
|
for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
|
|
4737
|
-
const path =
|
|
4738
|
-
if (
|
|
4960
|
+
const path = resolve18(projectRoot, name);
|
|
4961
|
+
if (existsSync11(path))
|
|
4739
4962
|
return path;
|
|
4740
4963
|
}
|
|
4741
4964
|
return null;
|
|
@@ -4744,7 +4967,7 @@ function hashFile(path) {
|
|
|
4744
4967
|
if (!path)
|
|
4745
4968
|
return null;
|
|
4746
4969
|
try {
|
|
4747
|
-
return createHash2("sha256").update(
|
|
4970
|
+
return createHash2("sha256").update(readFileSync8(path)).digest("hex");
|
|
4748
4971
|
} catch {
|
|
4749
4972
|
return null;
|
|
4750
4973
|
}
|
|
@@ -4760,11 +4983,11 @@ function readDefaultBranch(projectRoot) {
|
|
|
4760
4983
|
return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
|
|
4761
4984
|
}
|
|
4762
4985
|
function buildRunSummary(projectRoot) {
|
|
4763
|
-
const runsDir =
|
|
4986
|
+
const runsDir = resolve18(projectRoot, ".rig", "runs");
|
|
4764
4987
|
try {
|
|
4765
4988
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
4766
4989
|
try {
|
|
4767
|
-
const run = JSON.parse(
|
|
4990
|
+
const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
|
|
4768
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 : "" }];
|
|
4769
4992
|
} catch {
|
|
4770
4993
|
return [];
|
|
@@ -4816,10 +5039,14 @@ function upsertProjectRecord(projectRoot, input) {
|
|
|
4816
5039
|
function linkProjectCheckout(projectRoot, repoSlug, checkout) {
|
|
4817
5040
|
return upsertProjectRecord(projectRoot, { repoSlug, checkout });
|
|
4818
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
|
+
}
|
|
4819
5046
|
|
|
4820
5047
|
// packages/server/src/server-helpers/remote-checkout.ts
|
|
4821
|
-
import { existsSync as
|
|
4822
|
-
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";
|
|
4823
5050
|
function safeSlugSegments(repoSlug) {
|
|
4824
5051
|
const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
|
|
4825
5052
|
if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
|
|
@@ -4836,7 +5063,7 @@ function safeCheckoutKey(value) {
|
|
|
4836
5063
|
}
|
|
4837
5064
|
function repoSlugPath(baseDir, repoSlug, checkoutKey) {
|
|
4838
5065
|
const key = safeCheckoutKey(checkoutKey);
|
|
4839
|
-
return
|
|
5066
|
+
return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
|
|
4840
5067
|
}
|
|
4841
5068
|
function sanitizeSnapshotId(value, fallback) {
|
|
4842
5069
|
const raw = (value ?? fallback).trim();
|
|
@@ -4844,7 +5071,7 @@ function sanitizeSnapshotId(value, fallback) {
|
|
|
4844
5071
|
return safe || fallback;
|
|
4845
5072
|
}
|
|
4846
5073
|
function assertWithinRoot(root, relativePath) {
|
|
4847
|
-
if (!relativePath ||
|
|
5074
|
+
if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
|
|
4848
5075
|
throw new Error(`Invalid snapshot file path: ${relativePath}`);
|
|
4849
5076
|
}
|
|
4850
5077
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
@@ -4852,9 +5079,9 @@ function assertWithinRoot(root, relativePath) {
|
|
|
4852
5079
|
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
4853
5080
|
throw new Error(`Unsafe snapshot file path: ${relativePath}`);
|
|
4854
5081
|
}
|
|
4855
|
-
const target =
|
|
4856
|
-
const rel =
|
|
4857
|
-
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)) {
|
|
4858
5085
|
throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
|
|
4859
5086
|
}
|
|
4860
5087
|
return target;
|
|
@@ -4872,17 +5099,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
|
|
|
4872
5099
|
function extractUploadedSnapshotArchive(input) {
|
|
4873
5100
|
const archive = decodeSnapshotArchive(input.archive);
|
|
4874
5101
|
const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
|
|
4875
|
-
const checkoutPath =
|
|
4876
|
-
|
|
5102
|
+
const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
|
|
5103
|
+
mkdirSync12(checkoutPath, { recursive: true });
|
|
4877
5104
|
for (const file of archive.files) {
|
|
4878
5105
|
if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
|
|
4879
5106
|
throw new Error("Invalid snapshot archive file entry");
|
|
4880
5107
|
}
|
|
4881
5108
|
const target = assertWithinRoot(checkoutPath, file.path);
|
|
4882
|
-
|
|
4883
|
-
|
|
5109
|
+
mkdirSync12(dirname14(target), { recursive: true });
|
|
5110
|
+
writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
|
|
4884
5111
|
}
|
|
4885
|
-
|
|
5112
|
+
writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
|
|
4886
5113
|
repoSlug: input.repoSlug,
|
|
4887
5114
|
snapshotId,
|
|
4888
5115
|
fileCount: archive.files.length,
|
|
@@ -4917,7 +5144,7 @@ function gitCredentialConfig(token) {
|
|
|
4917
5144
|
};
|
|
4918
5145
|
}
|
|
4919
5146
|
async function prepareRemoteCheckout(input) {
|
|
4920
|
-
const exists = input.exists ??
|
|
5147
|
+
const exists = input.exists ?? existsSync12;
|
|
4921
5148
|
const strategy = input.strategy;
|
|
4922
5149
|
if (strategy.kind === "uploaded-snapshot") {
|
|
4923
5150
|
return extractUploadedSnapshotArchive({
|
|
@@ -4929,7 +5156,7 @@ async function prepareRemoteCheckout(input) {
|
|
|
4929
5156
|
});
|
|
4930
5157
|
}
|
|
4931
5158
|
if (strategy.kind === "existing-path") {
|
|
4932
|
-
const checkoutPath2 =
|
|
5159
|
+
const checkoutPath2 = resolve19(strategy.path);
|
|
4933
5160
|
if (!exists(checkoutPath2)) {
|
|
4934
5161
|
throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
|
|
4935
5162
|
}
|
|
@@ -4965,9 +5192,9 @@ function buildServerControlStatus() {
|
|
|
4965
5192
|
};
|
|
4966
5193
|
}
|
|
4967
5194
|
function buildProjectConfigStatus(root) {
|
|
4968
|
-
const hasConfigTs =
|
|
4969
|
-
const hasConfigJson =
|
|
4970
|
-
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"));
|
|
4971
5198
|
let kind = "missing";
|
|
4972
5199
|
if (hasConfigTs)
|
|
4973
5200
|
kind = "rig-config-ts";
|
|
@@ -5003,24 +5230,24 @@ function repoParts(repoSlug) {
|
|
|
5003
5230
|
return { owner, repo, slug: `${owner}/${repo}` };
|
|
5004
5231
|
}
|
|
5005
5232
|
function repairDir(checkoutPath) {
|
|
5006
|
-
const dir =
|
|
5007
|
-
|
|
5233
|
+
const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
|
|
5234
|
+
mkdirSync13(dir, { recursive: true });
|
|
5008
5235
|
return dir;
|
|
5009
5236
|
}
|
|
5010
5237
|
function backupCheckoutFile(checkoutPath, relativePath) {
|
|
5011
|
-
const source =
|
|
5012
|
-
const backupPath =
|
|
5013
|
-
|
|
5014
|
-
|
|
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);
|
|
5015
5242
|
return backupPath;
|
|
5016
5243
|
}
|
|
5017
5244
|
function parsePackageJsonLosslessly(checkoutPath) {
|
|
5018
|
-
const packagePath =
|
|
5019
|
-
if (!
|
|
5245
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
5246
|
+
if (!existsSync13(packagePath)) {
|
|
5020
5247
|
return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
|
|
5021
5248
|
}
|
|
5022
5249
|
try {
|
|
5023
|
-
const parsed = JSON.parse(
|
|
5250
|
+
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
5024
5251
|
return {
|
|
5025
5252
|
existed: true,
|
|
5026
5253
|
packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
|
|
@@ -5034,9 +5261,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
|
|
|
5034
5261
|
}
|
|
5035
5262
|
}
|
|
5036
5263
|
function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
5037
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5038
|
-
const packagePath =
|
|
5039
|
-
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)) {
|
|
5040
5267
|
return { skipped: true, reason: "package.json and rig.config missing" };
|
|
5041
5268
|
}
|
|
5042
5269
|
const parsed = parsePackageJsonLosslessly(checkoutPath);
|
|
@@ -5055,7 +5282,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
|
5055
5282
|
}
|
|
5056
5283
|
const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
|
|
5057
5284
|
if (changed) {
|
|
5058
|
-
|
|
5285
|
+
writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
|
|
5059
5286
|
`, "utf8");
|
|
5060
5287
|
}
|
|
5061
5288
|
return {
|
|
@@ -5081,11 +5308,11 @@ function configLooksStructurallyUsable(source) {
|
|
|
5081
5308
|
return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
|
|
5082
5309
|
}
|
|
5083
5310
|
function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
|
|
5084
|
-
const configPath =
|
|
5085
|
-
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)));
|
|
5086
5313
|
if (existingConfigName) {
|
|
5087
|
-
const existingPath =
|
|
5088
|
-
const source =
|
|
5314
|
+
const existingPath = resolve20(checkoutPath, existingConfigName);
|
|
5315
|
+
const source = readFileSync9(existingPath, "utf8");
|
|
5089
5316
|
if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
|
|
5090
5317
|
return { path: existingPath, changed: false, reason: "config structurally complete" };
|
|
5091
5318
|
}
|
|
@@ -5099,7 +5326,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5099
5326
|
}
|
|
5100
5327
|
}
|
|
5101
5328
|
const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
|
|
5102
|
-
|
|
5329
|
+
writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
|
|
5103
5330
|
return {
|
|
5104
5331
|
path: configPath,
|
|
5105
5332
|
changed: true,
|
|
@@ -5108,7 +5335,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5108
5335
|
};
|
|
5109
5336
|
}
|
|
5110
5337
|
function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
5111
|
-
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)));
|
|
5112
5339
|
if (!configFile)
|
|
5113
5340
|
return { ok: false, error: "missing rig config" };
|
|
5114
5341
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5130,7 +5357,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
|
5130
5357
|
return { ok: true, configFile };
|
|
5131
5358
|
}
|
|
5132
5359
|
function installRemoteCheckoutPackages(checkoutPath) {
|
|
5133
|
-
if (!
|
|
5360
|
+
if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
|
|
5134
5361
|
return { skipped: true, reason: "package.json missing" };
|
|
5135
5362
|
}
|
|
5136
5363
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5143,8 +5370,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
|
|
|
5143
5370
|
return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
|
|
5144
5371
|
}
|
|
5145
5372
|
function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
|
|
5146
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5147
|
-
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"));
|
|
5148
5375
|
if (!hasConfig && !hasPackage) {
|
|
5149
5376
|
return {
|
|
5150
5377
|
packageJson: { skipped: true, reason: "package.json and rig.config missing" },
|
|
@@ -5242,26 +5469,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
|
|
|
5242
5469
|
}
|
|
5243
5470
|
function readGitHeadCommit(projectRoot) {
|
|
5244
5471
|
try {
|
|
5245
|
-
let gitDir =
|
|
5472
|
+
let gitDir = resolve20(projectRoot, ".git");
|
|
5246
5473
|
try {
|
|
5247
|
-
const dotGit =
|
|
5474
|
+
const dotGit = readFileSync9(gitDir, "utf8").trim();
|
|
5248
5475
|
const gitDirPrefix = "gitdir:";
|
|
5249
5476
|
if (dotGit.startsWith(gitDirPrefix)) {
|
|
5250
|
-
gitDir =
|
|
5477
|
+
gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
|
|
5251
5478
|
}
|
|
5252
5479
|
} catch {}
|
|
5253
|
-
const head =
|
|
5480
|
+
const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
|
|
5254
5481
|
const refPrefix = "ref:";
|
|
5255
5482
|
if (!head.startsWith(refPrefix)) {
|
|
5256
5483
|
return normalizeCommit(head);
|
|
5257
5484
|
}
|
|
5258
5485
|
const ref = head.slice(refPrefix.length).trim();
|
|
5259
|
-
const refPath =
|
|
5260
|
-
if (
|
|
5261
|
-
return normalizeCommit(
|
|
5486
|
+
const refPath = resolve20(gitDir, ref);
|
|
5487
|
+
if (existsSync13(refPath)) {
|
|
5488
|
+
return normalizeCommit(readFileSync9(refPath, "utf8").trim());
|
|
5262
5489
|
}
|
|
5263
|
-
const commonDir = normalizeString(
|
|
5264
|
-
return commonDir ? normalizeCommit(
|
|
5490
|
+
const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
|
|
5491
|
+
return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
|
|
5265
5492
|
} catch {
|
|
5266
5493
|
return null;
|
|
5267
5494
|
}
|
|
@@ -5299,9 +5526,9 @@ function configuredRepoFromTaskSource(taskSource) {
|
|
|
5299
5526
|
const repo = normalizeString(taskSource?.repo);
|
|
5300
5527
|
return owner && repo ? `${owner}/${repo}` : null;
|
|
5301
5528
|
}
|
|
5302
|
-
async function buildTaskSourceStatus(state, config) {
|
|
5529
|
+
async function buildTaskSourceStatus(state, config, requestAuth) {
|
|
5303
5530
|
const diagnostics = state.snapshotService.getTaskSourceErrors();
|
|
5304
|
-
const selectedRepo =
|
|
5531
|
+
const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
|
|
5305
5532
|
oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
|
|
5306
5533
|
}).selectedRepo;
|
|
5307
5534
|
try {
|
|
@@ -5399,31 +5626,83 @@ function normalizePrMode(value) {
|
|
|
5399
5626
|
const mode = normalizeString(value);
|
|
5400
5627
|
return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
|
|
5401
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
|
+
}
|
|
5402
5654
|
function authorizeRigHttpRequest(input) {
|
|
5403
5655
|
if (input.legacyAuthorized) {
|
|
5404
|
-
return { authorized: true, actor: "rig-local-server", reason: "server-token" };
|
|
5656
|
+
return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
|
|
5405
5657
|
}
|
|
5406
5658
|
const bearer = bearerTokenFromRequest(input.req);
|
|
5407
5659
|
const store = createGitHubAuthStore(input.projectRoot);
|
|
5408
5660
|
const storedToken = store.readToken();
|
|
5409
5661
|
const session = bearer ? store.readApiSession(bearer) : null;
|
|
5410
5662
|
if (session) {
|
|
5411
|
-
return {
|
|
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
|
+
});
|
|
5412
5671
|
}
|
|
5413
5672
|
if (bearer && storedToken && bearer === storedToken) {
|
|
5414
5673
|
const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
5415
|
-
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
|
+
});
|
|
5416
5695
|
}
|
|
5417
5696
|
if (isPublicRigAuthBootstrapRoute(input.pathname)) {
|
|
5418
|
-
return { authorized: true, actor: null, reason: "public-bootstrap" };
|
|
5697
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
|
|
5419
5698
|
}
|
|
5420
5699
|
if (!input.serverAuthToken && !storedToken) {
|
|
5421
5700
|
if (isLoopbackRequest(input.req)) {
|
|
5422
|
-
return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
|
|
5701
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
|
|
5423
5702
|
}
|
|
5424
|
-
return { authorized: false, actor: null, reason: "auth-required" };
|
|
5703
|
+
return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
|
|
5425
5704
|
}
|
|
5426
|
-
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" });
|
|
5427
5706
|
}
|
|
5428
5707
|
async function fetchGitHubUserInfo(token) {
|
|
5429
5708
|
const response = await fetch("https://api.github.com/user", {
|
|
@@ -5443,6 +5722,67 @@ async function fetchGitHubUserInfo(token) {
|
|
|
5443
5722
|
scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
|
|
5444
5723
|
};
|
|
5445
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
|
+
}
|
|
5446
5786
|
async function postGitHubForm(endpoint, body) {
|
|
5447
5787
|
const response = await fetch(endpoint, {
|
|
5448
5788
|
method: "POST",
|
|
@@ -5460,11 +5800,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
|
|
|
5460
5800
|
const requestedRoot = normalizeString(rawRoot);
|
|
5461
5801
|
if (!requestedRoot)
|
|
5462
5802
|
return currentRoot;
|
|
5463
|
-
if (!
|
|
5803
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
5464
5804
|
throw new Error("projectRoot must be an absolute path on the Rig server host");
|
|
5465
5805
|
}
|
|
5466
|
-
const normalizedRoot =
|
|
5467
|
-
if (!
|
|
5806
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
5807
|
+
if (!existsSync13(normalizedRoot)) {
|
|
5468
5808
|
throw new Error("projectRoot does not exist on the Rig server host");
|
|
5469
5809
|
}
|
|
5470
5810
|
return normalizedRoot;
|
|
@@ -6125,7 +6465,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6125
6465
|
}
|
|
6126
6466
|
if (url.pathname === "/api/server/status") {
|
|
6127
6467
|
const config = buildProjectConfigStatus(state.projectRoot);
|
|
6128
|
-
const taskSource = await buildTaskSourceStatus(state, config);
|
|
6468
|
+
const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
|
|
6129
6469
|
return deps.jsonResponse({
|
|
6130
6470
|
ok: true,
|
|
6131
6471
|
projectRoot: state.projectRoot,
|
|
@@ -6149,8 +6489,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6149
6489
|
path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
|
|
6150
6490
|
ref: normalizeString(rawCheckout?.ref) ?? undefined
|
|
6151
6491
|
} : undefined;
|
|
6152
|
-
const
|
|
6153
|
-
|
|
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) });
|
|
6154
6495
|
}
|
|
6155
6496
|
const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
|
|
6156
6497
|
if (snapshotUploadMatch && req.method === "POST") {
|
|
@@ -6163,8 +6504,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6163
6504
|
if (!archiveContentBase64) {
|
|
6164
6505
|
return deps.badRequest("archiveContentBase64 is required");
|
|
6165
6506
|
}
|
|
6166
|
-
const
|
|
6167
|
-
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);
|
|
6168
6516
|
try {
|
|
6169
6517
|
const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
|
|
6170
6518
|
const checkout = extractUploadedSnapshotArchive({
|
|
@@ -6177,14 +6525,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6177
6525
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6178
6526
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6179
6527
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6180
|
-
const project = linkProjectCheckout(
|
|
6528
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6181
6529
|
kind: "uploaded-snapshot",
|
|
6182
6530
|
path: checkout.path,
|
|
6183
6531
|
ref: checkout.snapshotId
|
|
6184
6532
|
});
|
|
6185
6533
|
deps.snapshotService.invalidate("uploaded-snapshot-checkout");
|
|
6186
6534
|
deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
|
|
6187
|
-
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) });
|
|
6188
6536
|
} catch (error) {
|
|
6189
6537
|
return deps.jsonResponse({
|
|
6190
6538
|
ok: false,
|
|
@@ -6204,10 +6552,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6204
6552
|
if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
|
|
6205
6553
|
return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
|
|
6206
6554
|
}
|
|
6207
|
-
const
|
|
6208
|
-
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);
|
|
6209
6564
|
const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
|
|
6210
|
-
const credentialToken =
|
|
6565
|
+
const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
|
|
6211
6566
|
try {
|
|
6212
6567
|
const checkout = await prepareRemoteCheckout({
|
|
6213
6568
|
command: runRemoteCheckoutCommand,
|
|
@@ -6216,14 +6571,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6216
6571
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6217
6572
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6218
6573
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6219
|
-
const project = linkProjectCheckout(
|
|
6574
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6220
6575
|
kind: checkout.kind,
|
|
6221
6576
|
path: checkout.path,
|
|
6222
6577
|
ref: checkout.ref ?? checkout.snapshotId ?? undefined
|
|
6223
6578
|
});
|
|
6224
6579
|
deps.snapshotService.invalidate("remote-checkout-prepared");
|
|
6225
6580
|
deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
|
|
6226
|
-
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) });
|
|
6227
6582
|
} catch (error) {
|
|
6228
6583
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
|
|
6229
6584
|
}
|
|
@@ -6240,16 +6595,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6240
6595
|
if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
|
|
6241
6596
|
return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
|
|
6242
6597
|
}
|
|
6243
|
-
const
|
|
6598
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6599
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6244
6600
|
kind,
|
|
6245
6601
|
path: normalizeString(body.path) ?? state.projectRoot,
|
|
6246
6602
|
ref: normalizeString(body.ref) ?? undefined
|
|
6247
6603
|
});
|
|
6248
|
-
return deps.jsonResponse({ ok: true, project });
|
|
6604
|
+
return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6249
6605
|
}
|
|
6250
6606
|
if (req.method === "GET") {
|
|
6251
|
-
const
|
|
6252
|
-
|
|
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();
|
|
6253
6610
|
}
|
|
6254
6611
|
}
|
|
6255
6612
|
if (url.pathname === "/api/server/project-root" && req.method === "POST") {
|
|
@@ -6258,13 +6615,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6258
6615
|
if (!requestedRoot) {
|
|
6259
6616
|
return deps.badRequest("projectRoot is required");
|
|
6260
6617
|
}
|
|
6261
|
-
if (!
|
|
6618
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
6262
6619
|
return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
|
|
6263
6620
|
}
|
|
6264
|
-
const normalizedRoot =
|
|
6265
|
-
const exists =
|
|
6266
|
-
if (exists) {
|
|
6267
|
-
|
|
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);
|
|
6268
6638
|
}
|
|
6269
6639
|
const control = buildServerControlStatus();
|
|
6270
6640
|
const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
|
|
@@ -6279,7 +6649,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6279
6649
|
message: "Requested project root does not exist on the Rig server host."
|
|
6280
6650
|
}, 404);
|
|
6281
6651
|
}
|
|
6282
|
-
if (!
|
|
6652
|
+
if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
|
|
6283
6653
|
return deps.jsonResponse({
|
|
6284
6654
|
ok: false,
|
|
6285
6655
|
projectRoot: state.projectRoot,
|
|
@@ -6314,6 +6684,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6314
6684
|
exists,
|
|
6315
6685
|
control,
|
|
6316
6686
|
requiresRestart: false,
|
|
6687
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace),
|
|
6317
6688
|
message: "Project-root switch accepted. Rig server restart has been scheduled."
|
|
6318
6689
|
}, 202);
|
|
6319
6690
|
}
|
|
@@ -6328,11 +6699,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6328
6699
|
}, 409);
|
|
6329
6700
|
}
|
|
6330
6701
|
if (url.pathname === "/api/github/auth/status") {
|
|
6331
|
-
const store =
|
|
6332
|
-
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) });
|
|
6333
6704
|
}
|
|
6334
6705
|
if (url.pathname === "/api/github/repo/permissions") {
|
|
6335
|
-
const store =
|
|
6706
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6336
6707
|
const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6337
6708
|
if (!auth.signedIn) {
|
|
6338
6709
|
return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
|
|
@@ -6360,24 +6731,20 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6360
6731
|
}
|
|
6361
6732
|
try {
|
|
6362
6733
|
const user = await fetchGitHubUserInfo(token);
|
|
6363
|
-
const
|
|
6364
|
-
state.projectRoot,
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
}
|
|
6378
|
-
const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
|
|
6379
|
-
const apiSession = store.createApiSession();
|
|
6380
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
|
|
6734
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
6735
|
+
projectRoot: state.projectRoot,
|
|
6736
|
+
token,
|
|
6737
|
+
tokenSource: "manual-token",
|
|
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)
|
|
6747
|
+
});
|
|
6381
6748
|
} catch (error) {
|
|
6382
6749
|
const message = error instanceof Error ? error.message : String(error);
|
|
6383
6750
|
return deps.jsonResponse({ ok: false, error: message }, 400);
|
|
@@ -6444,9 +6811,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6444
6811
|
}
|
|
6445
6812
|
const token = result.payload.access_token;
|
|
6446
6813
|
const user = await fetchGitHubUserInfo(token);
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
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
|
+
});
|
|
6450
6829
|
}
|
|
6451
6830
|
if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
|
|
6452
6831
|
const body = await deps.readJsonBody(req);
|
|
@@ -6455,7 +6834,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6455
6834
|
if (!owner || !repo) {
|
|
6456
6835
|
return deps.badRequest("owner and repo are required");
|
|
6457
6836
|
}
|
|
6458
|
-
const store =
|
|
6837
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6459
6838
|
const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6460
6839
|
const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
|
|
6461
6840
|
return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
|
|
@@ -6476,7 +6855,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6476
6855
|
return deps.badRequest(error instanceof Error ? error.message : String(error));
|
|
6477
6856
|
}
|
|
6478
6857
|
const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6479
|
-
const configPath =
|
|
6858
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6480
6859
|
const source = buildGitHubProjectConfigSource({
|
|
6481
6860
|
projectName: rawProjectName,
|
|
6482
6861
|
owner,
|
|
@@ -6488,8 +6867,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6488
6867
|
ok: true,
|
|
6489
6868
|
projectRoot: targetRoot,
|
|
6490
6869
|
configPath,
|
|
6491
|
-
exists:
|
|
6492
|
-
requiresOverwrite:
|
|
6870
|
+
exists: existsSync13(configPath),
|
|
6871
|
+
requiresOverwrite: existsSync13(configPath),
|
|
6493
6872
|
source,
|
|
6494
6873
|
owner,
|
|
6495
6874
|
repo,
|
|
@@ -6525,8 +6904,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6525
6904
|
assignee,
|
|
6526
6905
|
githubUserId: authStatus.userId ?? authStatus.login
|
|
6527
6906
|
});
|
|
6528
|
-
const configPath =
|
|
6529
|
-
if (
|
|
6907
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6908
|
+
if (existsSync13(configPath) && !overwrite) {
|
|
6530
6909
|
return deps.jsonResponse({
|
|
6531
6910
|
ok: false,
|
|
6532
6911
|
error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
|
|
@@ -6542,11 +6921,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6542
6921
|
return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
|
|
6543
6922
|
}
|
|
6544
6923
|
let backupPath = null;
|
|
6545
|
-
if (
|
|
6924
|
+
if (existsSync13(configPath)) {
|
|
6546
6925
|
backupPath = backupConfigPath(configPath);
|
|
6547
|
-
|
|
6926
|
+
copyFileSync2(configPath, backupPath);
|
|
6548
6927
|
}
|
|
6549
|
-
|
|
6928
|
+
writeFileSync12(configPath, source, "utf8");
|
|
6550
6929
|
const selectedRepo = `${owner}/${repo}`;
|
|
6551
6930
|
store.saveSelectedRepo(selectedRepo);
|
|
6552
6931
|
const targetStore = createGitHubAuthStore(targetRoot);
|
|
@@ -6832,7 +7211,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6832
7211
|
}
|
|
6833
7212
|
if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
|
|
6834
7213
|
const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
|
|
6835
|
-
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";
|
|
6836
7215
|
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
6837
7216
|
return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
|
|
6838
7217
|
}
|
|
@@ -7148,9 +7527,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7148
7527
|
} catch {
|
|
7149
7528
|
return deps.badRequest("Invalid artifact path");
|
|
7150
7529
|
}
|
|
7151
|
-
|
|
7530
|
+
mkdirSync13(dirname15(artifactPath), { recursive: true });
|
|
7152
7531
|
const bytes = Buffer.from(contentBase64, "base64");
|
|
7153
|
-
|
|
7532
|
+
writeFileSync12(artifactPath, bytes);
|
|
7154
7533
|
writeJsonFile4(`${artifactPath}.json`, {
|
|
7155
7534
|
workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
|
|
7156
7535
|
runId,
|
|
@@ -7312,12 +7691,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7312
7691
|
try {
|
|
7313
7692
|
const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
|
|
7314
7693
|
const runRoot = deps.normalizeRelativePath(runsRoot, runId);
|
|
7315
|
-
const artifactsRoot =
|
|
7694
|
+
const artifactsRoot = resolve20(runRoot, "remote-artifacts");
|
|
7316
7695
|
artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
|
|
7317
7696
|
} catch {
|
|
7318
7697
|
return deps.badRequest("Invalid artifact path");
|
|
7319
7698
|
}
|
|
7320
|
-
if (!
|
|
7699
|
+
if (!existsSync13(artifactPath)) {
|
|
7321
7700
|
return deps.notFound();
|
|
7322
7701
|
}
|
|
7323
7702
|
return new Response(Bun.file(artifactPath));
|
|
@@ -8210,8 +8589,8 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
8210
8589
|
}
|
|
8211
8590
|
|
|
8212
8591
|
// packages/server/src/server-helpers/inspector-jobs.ts
|
|
8213
|
-
import { existsSync as
|
|
8214
|
-
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";
|
|
8215
8594
|
import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
|
|
8216
8595
|
import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
|
|
8217
8596
|
import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -8319,8 +8698,8 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8319
8698
|
|
|
8320
8699
|
// packages/server/src/inspector/mission.ts
|
|
8321
8700
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8322
|
-
import { appendFileSync, existsSync as
|
|
8323
|
-
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";
|
|
8324
8703
|
function isJsonValue(value) {
|
|
8325
8704
|
if (value === null)
|
|
8326
8705
|
return true;
|
|
@@ -8360,7 +8739,7 @@ function isRecord2(value) {
|
|
|
8360
8739
|
}
|
|
8361
8740
|
function readJsonRecord(path) {
|
|
8362
8741
|
try {
|
|
8363
|
-
const parsed = JSON.parse(
|
|
8742
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
8364
8743
|
if (!isRecord2(parsed)) {
|
|
8365
8744
|
return { ok: false, error: `Mission file ${path} does not contain an object` };
|
|
8366
8745
|
}
|
|
@@ -8440,14 +8819,14 @@ function missionActionDetails(mission) {
|
|
|
8440
8819
|
};
|
|
8441
8820
|
}
|
|
8442
8821
|
function writeJsonFile5(path, value) {
|
|
8443
|
-
|
|
8822
|
+
mkdirSync14(dirname16(path), { recursive: true });
|
|
8444
8823
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
8445
|
-
|
|
8824
|
+
writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
|
|
8446
8825
|
`, "utf8");
|
|
8447
8826
|
renameSync(tempPath, path);
|
|
8448
8827
|
}
|
|
8449
8828
|
function resolveInspectorMissionPaths(projectRoot) {
|
|
8450
|
-
const inspectorDir =
|
|
8829
|
+
const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
|
|
8451
8830
|
return {
|
|
8452
8831
|
inspectorDir,
|
|
8453
8832
|
missionsDir: join(inspectorDir, "missions"),
|
|
@@ -8456,8 +8835,8 @@ function resolveInspectorMissionPaths(projectRoot) {
|
|
|
8456
8835
|
}
|
|
8457
8836
|
function createInspectorMissionController(options) {
|
|
8458
8837
|
const paths = resolveInspectorMissionPaths(options.projectRoot);
|
|
8459
|
-
|
|
8460
|
-
|
|
8838
|
+
mkdirSync14(paths.missionsDir, { recursive: true });
|
|
8839
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8461
8840
|
const now = options.now ?? (() => new Date().toISOString());
|
|
8462
8841
|
const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
|
|
8463
8842
|
function missionPath(missionId) {
|
|
@@ -8467,15 +8846,15 @@ function createInspectorMissionController(options) {
|
|
|
8467
8846
|
return join(paths.journalsDir, `${missionId}.jsonl`);
|
|
8468
8847
|
}
|
|
8469
8848
|
function appendMissionJournal(entry) {
|
|
8470
|
-
|
|
8849
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8471
8850
|
appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
|
|
8472
8851
|
`, "utf8");
|
|
8473
8852
|
}
|
|
8474
8853
|
function listMissionJournal(missionId) {
|
|
8475
8854
|
const path = journalPath(missionId);
|
|
8476
|
-
if (!
|
|
8855
|
+
if (!existsSync14(path))
|
|
8477
8856
|
return [];
|
|
8478
|
-
return
|
|
8857
|
+
return readFileSync10(path, "utf8").split(`
|
|
8479
8858
|
`).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
|
|
8480
8859
|
id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
|
|
8481
8860
|
missionId,
|
|
@@ -8491,7 +8870,7 @@ function createInspectorMissionController(options) {
|
|
|
8491
8870
|
}
|
|
8492
8871
|
function readMissionOnly(missionId) {
|
|
8493
8872
|
const path = missionPath(missionId);
|
|
8494
|
-
if (!
|
|
8873
|
+
if (!existsSync14(path)) {
|
|
8495
8874
|
return { ok: false, error: `Mission ${missionId} was not found` };
|
|
8496
8875
|
}
|
|
8497
8876
|
const read = readJsonRecord(path);
|
|
@@ -8542,7 +8921,7 @@ function createInspectorMissionController(options) {
|
|
|
8542
8921
|
const source = cloneJsonRecord(input.sourceTask);
|
|
8543
8922
|
const missionId = nextId();
|
|
8544
8923
|
const path = missionPath(missionId);
|
|
8545
|
-
if (
|
|
8924
|
+
if (existsSync14(path)) {
|
|
8546
8925
|
const existing = readMissionOnly(missionId);
|
|
8547
8926
|
if (!existing.ok)
|
|
8548
8927
|
return existing;
|
|
@@ -10142,8 +10521,8 @@ function createCodexInspectorTransport(options) {
|
|
|
10142
10521
|
const sendRequest = async (method, params) => {
|
|
10143
10522
|
const id = nextRequestId;
|
|
10144
10523
|
nextRequestId += 1;
|
|
10145
|
-
const response = new Promise((
|
|
10146
|
-
pendingResponses.set(id, { resolve:
|
|
10524
|
+
const response = new Promise((resolve22, reject) => {
|
|
10525
|
+
pendingResponses.set(id, { resolve: resolve22, reject });
|
|
10147
10526
|
});
|
|
10148
10527
|
response.catch(() => {});
|
|
10149
10528
|
try {
|
|
@@ -10453,9 +10832,9 @@ function createCodexInspectorTransport(options) {
|
|
|
10453
10832
|
}
|
|
10454
10833
|
lastAssistantMessage = null;
|
|
10455
10834
|
lastError = null;
|
|
10456
|
-
const turnResult = new Promise((
|
|
10835
|
+
const turnResult = new Promise((resolve22, reject) => {
|
|
10457
10836
|
currentTurn = {
|
|
10458
|
-
resolve:
|
|
10837
|
+
resolve: resolve22,
|
|
10459
10838
|
reject,
|
|
10460
10839
|
events: []
|
|
10461
10840
|
};
|
|
@@ -10515,13 +10894,13 @@ function createCodexInspectorTransport(options) {
|
|
|
10515
10894
|
};
|
|
10516
10895
|
}
|
|
10517
10896
|
function writeChildLine(child, line) {
|
|
10518
|
-
return new Promise((
|
|
10897
|
+
return new Promise((resolve22, reject) => {
|
|
10519
10898
|
child.stdin.write(line, (error) => {
|
|
10520
10899
|
if (error) {
|
|
10521
10900
|
reject(error);
|
|
10522
10901
|
return;
|
|
10523
10902
|
}
|
|
10524
|
-
|
|
10903
|
+
resolve22();
|
|
10525
10904
|
});
|
|
10526
10905
|
});
|
|
10527
10906
|
}
|
|
@@ -10534,10 +10913,10 @@ function terminateChild(child) {
|
|
|
10534
10913
|
} catch {}
|
|
10535
10914
|
}
|
|
10536
10915
|
async function waitForChildSpawn(child) {
|
|
10537
|
-
await new Promise((
|
|
10916
|
+
await new Promise((resolve22, reject) => {
|
|
10538
10917
|
const onSpawn = () => {
|
|
10539
10918
|
cleanup();
|
|
10540
|
-
|
|
10919
|
+
resolve22();
|
|
10541
10920
|
};
|
|
10542
10921
|
const onError = (error) => {
|
|
10543
10922
|
cleanup();
|
|
@@ -11049,8 +11428,8 @@ function createGlobalInspectorService(options) {
|
|
|
11049
11428
|
|
|
11050
11429
|
// packages/server/src/inspector/upstream-sync.ts
|
|
11051
11430
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11052
|
-
import { existsSync as
|
|
11053
|
-
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";
|
|
11054
11433
|
import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
|
|
11055
11434
|
var UPSTREAM_VALIDATION_DESCRIPTIONS = {
|
|
11056
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.",
|
|
@@ -11188,34 +11567,34 @@ function defaultGitRunner(repoRoot, args) {
|
|
|
11188
11567
|
}
|
|
11189
11568
|
function upstreamStatePath(projectRoot, override) {
|
|
11190
11569
|
if (override) {
|
|
11191
|
-
return
|
|
11570
|
+
return resolve22(override);
|
|
11192
11571
|
}
|
|
11193
|
-
return
|
|
11572
|
+
return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
|
|
11194
11573
|
}
|
|
11195
11574
|
function readUpstreamState(projectRoot, statePath) {
|
|
11196
11575
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11197
|
-
if (!
|
|
11576
|
+
if (!existsSync15(path)) {
|
|
11198
11577
|
return null;
|
|
11199
11578
|
}
|
|
11200
11579
|
try {
|
|
11201
|
-
return JSON.parse(
|
|
11580
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
11202
11581
|
} catch {
|
|
11203
11582
|
return null;
|
|
11204
11583
|
}
|
|
11205
11584
|
}
|
|
11206
11585
|
function writeUpstreamState(projectRoot, state, statePath) {
|
|
11207
11586
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11208
|
-
|
|
11209
|
-
|
|
11587
|
+
mkdirSync15(dirname17(path), { recursive: true });
|
|
11588
|
+
writeFileSync14(path, `${JSON.stringify(state, null, 2)}
|
|
11210
11589
|
`, "utf8");
|
|
11211
11590
|
}
|
|
11212
11591
|
function readImportedRevision(projectRoot, upstreamsDocPath) {
|
|
11213
11592
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11214
|
-
const docPath = upstreamsDocPath ?
|
|
11215
|
-
if (!
|
|
11593
|
+
const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
|
|
11594
|
+
if (!existsSync15(docPath)) {
|
|
11216
11595
|
throw new Error(`UPSTREAMS.md not found at ${docPath}`);
|
|
11217
11596
|
}
|
|
11218
|
-
const docContent =
|
|
11597
|
+
const docContent = readFileSync11(docPath, "utf-8");
|
|
11219
11598
|
const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
|
|
11220
11599
|
if (!revision) {
|
|
11221
11600
|
throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
|
|
@@ -11237,7 +11616,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
|
|
|
11237
11616
|
return null;
|
|
11238
11617
|
}
|
|
11239
11618
|
function isGitCheckout(path, gitRunner) {
|
|
11240
|
-
if (!
|
|
11619
|
+
if (!existsSync15(resolve22(path, ".git"))) {
|
|
11241
11620
|
return false;
|
|
11242
11621
|
}
|
|
11243
11622
|
const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -11246,12 +11625,12 @@ function isGitCheckout(path, gitRunner) {
|
|
|
11246
11625
|
function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
|
|
11247
11626
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11248
11627
|
const candidates = [
|
|
11249
|
-
explicitCheckout ?
|
|
11250
|
-
process.env.UPSTREAM_CHECKOUT?.trim() ?
|
|
11251
|
-
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ?
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
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")
|
|
11255
11634
|
].filter(Boolean);
|
|
11256
11635
|
for (const candidate of candidates) {
|
|
11257
11636
|
if (isGitCheckout(candidate, gitRunner)) {
|
|
@@ -11487,10 +11866,10 @@ async function runUpstreamSyncScan(options) {
|
|
|
11487
11866
|
}
|
|
11488
11867
|
|
|
11489
11868
|
// packages/server/src/server-helpers/task-config.ts
|
|
11490
|
-
import { existsSync as
|
|
11869
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11491
11870
|
async function readTaskConfig(projectRoot) {
|
|
11492
11871
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
11493
|
-
if (!
|
|
11872
|
+
if (!existsSync16(taskConfigPath)) {
|
|
11494
11873
|
return {};
|
|
11495
11874
|
}
|
|
11496
11875
|
try {
|
|
@@ -11526,11 +11905,11 @@ function resolveFollowupSourceCommit(input) {
|
|
|
11526
11905
|
}
|
|
11527
11906
|
async function createInspectorFollowupTask(projectRoot, input) {
|
|
11528
11907
|
const monorepoRoot = resolveMonorepoRoot5(projectRoot);
|
|
11529
|
-
const issuesPath =
|
|
11530
|
-
const taskStatePath =
|
|
11531
|
-
const taskConfigPath =
|
|
11532
|
-
|
|
11533
|
-
|
|
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 });
|
|
11534
11913
|
const summary = normalizeString(input.summary) ?? "Inspector follow-up";
|
|
11535
11914
|
const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
|
|
11536
11915
|
const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
|
|
@@ -11549,7 +11928,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11549
11928
|
const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
|
|
11550
11929
|
const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
|
|
11551
11930
|
const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
|
|
11552
|
-
const existingIssueLines =
|
|
11931
|
+
const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
11553
11932
|
const existingIssues = existingIssueLines.map((line) => {
|
|
11554
11933
|
try {
|
|
11555
11934
|
return JSON.parse(line);
|
|
@@ -11558,7 +11937,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11558
11937
|
}
|
|
11559
11938
|
}).filter((value) => value !== null);
|
|
11560
11939
|
const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
|
|
11561
|
-
const rawTaskState =
|
|
11940
|
+
const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
|
|
11562
11941
|
const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
|
|
11563
11942
|
const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
|
|
11564
11943
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
@@ -11584,7 +11963,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11584
11963
|
updated_at: createdAt,
|
|
11585
11964
|
labels: mergedLabels
|
|
11586
11965
|
};
|
|
11587
|
-
|
|
11966
|
+
writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
|
|
11588
11967
|
`)}
|
|
11589
11968
|
${JSON.stringify(issueRecord)}
|
|
11590
11969
|
` : `${JSON.stringify(issueRecord)}
|
|
@@ -11602,7 +11981,7 @@ ${JSON.stringify(issueRecord)}
|
|
|
11602
11981
|
labels: mergedLabels
|
|
11603
11982
|
};
|
|
11604
11983
|
});
|
|
11605
|
-
|
|
11984
|
+
writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
|
|
11606
11985
|
`)}
|
|
11607
11986
|
`, "utf8");
|
|
11608
11987
|
}
|
|
@@ -11625,14 +12004,14 @@ ${JSON.stringify(issueRecord)}
|
|
|
11625
12004
|
}
|
|
11626
12005
|
};
|
|
11627
12006
|
}
|
|
11628
|
-
|
|
12007
|
+
writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
|
|
11629
12008
|
`, "utf8");
|
|
11630
12009
|
tasks[taskId] = {
|
|
11631
12010
|
status,
|
|
11632
12011
|
sourceCommit: resolveFollowupSourceCommit(input),
|
|
11633
12012
|
...sourceKey ? { sourceKey } : {}
|
|
11634
12013
|
};
|
|
11635
|
-
|
|
12014
|
+
writeFileSync15(taskStatePath, `${JSON.stringify({
|
|
11636
12015
|
schemaVersion: 1,
|
|
11637
12016
|
baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
|
|
11638
12017
|
tasks
|
|
@@ -11940,12 +12319,12 @@ function isAuthorizedLinearWebhookRequest(req) {
|
|
|
11940
12319
|
}
|
|
11941
12320
|
|
|
11942
12321
|
// packages/server/src/server-helpers/notifications.ts
|
|
11943
|
-
import { existsSync as
|
|
11944
|
-
import { dirname as
|
|
12322
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
|
|
12323
|
+
import { dirname as dirname19 } from "path";
|
|
11945
12324
|
async function loadNotificationConfig(path) {
|
|
11946
|
-
if (!
|
|
12325
|
+
if (!existsSync18(path)) {
|
|
11947
12326
|
const defaultConfig = { targets: [] };
|
|
11948
|
-
|
|
12327
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
11949
12328
|
await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
|
|
11950
12329
|
`);
|
|
11951
12330
|
return defaultConfig;
|
|
@@ -11960,10 +12339,10 @@ async function loadNotificationConfig(path) {
|
|
|
11960
12339
|
}
|
|
11961
12340
|
}
|
|
11962
12341
|
function readRecentEvents(file, limit) {
|
|
11963
|
-
if (!
|
|
12342
|
+
if (!existsSync18(file)) {
|
|
11964
12343
|
return [];
|
|
11965
12344
|
}
|
|
11966
|
-
const lines =
|
|
12345
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
|
|
11967
12346
|
const events = [];
|
|
11968
12347
|
for (const line of lines) {
|
|
11969
12348
|
try {
|
|
@@ -12058,11 +12437,11 @@ function extractObjectLiteralBlock(source, property) {
|
|
|
12058
12437
|
}
|
|
12059
12438
|
function readFallbackIssueAnalysisConfig(projectRoot) {
|
|
12060
12439
|
for (const fileName of ["rig.config.ts", "rig.config.json"]) {
|
|
12061
|
-
const path =
|
|
12062
|
-
if (!
|
|
12440
|
+
const path = resolve24(projectRoot, fileName);
|
|
12441
|
+
if (!existsSync19(path))
|
|
12063
12442
|
continue;
|
|
12064
12443
|
try {
|
|
12065
|
-
const source =
|
|
12444
|
+
const source = readFileSync14(path, "utf8");
|
|
12066
12445
|
if (fileName.endsWith(".json"))
|
|
12067
12446
|
return JSON.parse(source);
|
|
12068
12447
|
const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
|
|
@@ -12195,8 +12574,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
|
|
|
12195
12574
|
async function withServerPathEnv(projectRoot, fn) {
|
|
12196
12575
|
const waitForTurn = serverPathEnvQueue;
|
|
12197
12576
|
let releaseTurn;
|
|
12198
|
-
serverPathEnvQueue = new Promise((
|
|
12199
|
-
releaseTurn =
|
|
12577
|
+
serverPathEnvQueue = new Promise((resolve25) => {
|
|
12578
|
+
releaseTurn = resolve25;
|
|
12200
12579
|
});
|
|
12201
12580
|
await waitForTurn;
|
|
12202
12581
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -12232,9 +12611,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
12232
12611
|
return withServerPathEnv(projectRoot, fn);
|
|
12233
12612
|
}
|
|
12234
12613
|
async function readWorkspaceTasks(projectRoot) {
|
|
12235
|
-
const issuesPath =
|
|
12614
|
+
const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
|
|
12236
12615
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
12237
|
-
if (!
|
|
12616
|
+
if (!existsSync19(issuesPath)) {
|
|
12238
12617
|
return [];
|
|
12239
12618
|
}
|
|
12240
12619
|
const latestById = new Map;
|
|
@@ -12308,11 +12687,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
|
|
|
12308
12687
|
continue;
|
|
12309
12688
|
add(run.artifactRoot);
|
|
12310
12689
|
if (run.worktreePath) {
|
|
12311
|
-
add(
|
|
12690
|
+
add(resolve24(run.worktreePath, "artifacts", taskId));
|
|
12312
12691
|
}
|
|
12313
12692
|
}
|
|
12314
12693
|
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
12315
|
-
add(
|
|
12694
|
+
add(resolve24(artifactsRoot, taskId));
|
|
12316
12695
|
}
|
|
12317
12696
|
return candidates;
|
|
12318
12697
|
}
|
|
@@ -12326,7 +12705,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12326
12705
|
}
|
|
12327
12706
|
}
|
|
12328
12707
|
return taskIds.flatMap((currentTaskId) => {
|
|
12329
|
-
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) =>
|
|
12708
|
+
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
|
|
12330
12709
|
if (!currentRoot) {
|
|
12331
12710
|
return [];
|
|
12332
12711
|
}
|
|
@@ -12338,7 +12717,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12338
12717
|
taskId: currentTaskId,
|
|
12339
12718
|
kind: "file",
|
|
12340
12719
|
label: fileName,
|
|
12341
|
-
path:
|
|
12720
|
+
path: resolve24(currentRoot, fileName),
|
|
12342
12721
|
url: null,
|
|
12343
12722
|
metadata: {
|
|
12344
12723
|
fileName
|
|
@@ -12381,11 +12760,11 @@ function buildInspectorStreamPayload(state, sequence) {
|
|
|
12381
12760
|
}
|
|
12382
12761
|
function listRemoteRunArtifacts(projectRoot, runId) {
|
|
12383
12762
|
const root = remoteArtifactsRoot(projectRoot, runId);
|
|
12384
|
-
if (!
|
|
12763
|
+
if (!existsSync19(root)) {
|
|
12385
12764
|
return [];
|
|
12386
12765
|
}
|
|
12387
12766
|
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
|
|
12388
|
-
const artifactPath =
|
|
12767
|
+
const artifactPath = resolve24(root, entry.name);
|
|
12389
12768
|
const stat = statSync6(artifactPath);
|
|
12390
12769
|
const meta = readJsonFile4(`${artifactPath}.json`, null);
|
|
12391
12770
|
return {
|
|
@@ -12627,8 +13006,8 @@ function fileStats(path) {
|
|
|
12627
13006
|
}
|
|
12628
13007
|
}
|
|
12629
13008
|
function runFileCursor(projectRoot, run) {
|
|
12630
|
-
const runDir =
|
|
12631
|
-
const runJson = fileStats(
|
|
13009
|
+
const runDir = dirname20(runLogsPath(projectRoot, run.runId));
|
|
13010
|
+
const runJson = fileStats(resolve24(runDir, "run.json"));
|
|
12632
13011
|
const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
|
|
12633
13012
|
const logs = fileStats(runLogsPath(projectRoot, run.runId));
|
|
12634
13013
|
return {
|
|
@@ -12678,10 +13057,10 @@ function startRunFileWatcher(state, pollMs) {
|
|
|
12678
13057
|
}, Math.max(250, Math.min(pollMs, 1000)));
|
|
12679
13058
|
}
|
|
12680
13059
|
function startPoller(state, pollMs) {
|
|
12681
|
-
let offset =
|
|
13060
|
+
let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
|
|
12682
13061
|
return setInterval(async () => {
|
|
12683
13062
|
try {
|
|
12684
|
-
if (!
|
|
13063
|
+
if (!existsSync19(state.eventsFile)) {
|
|
12685
13064
|
return;
|
|
12686
13065
|
}
|
|
12687
13066
|
const file = await open(state.eventsFile, "r");
|
|
@@ -12809,7 +13188,7 @@ function resolveProjectRoot() {
|
|
|
12809
13188
|
return resolveRigProjectRoot({
|
|
12810
13189
|
envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
|
|
12811
13190
|
cwd: process.cwd(),
|
|
12812
|
-
fallbackRoot:
|
|
13191
|
+
fallbackRoot: resolve24(import.meta.dir, "../..")
|
|
12813
13192
|
});
|
|
12814
13193
|
}
|
|
12815
13194
|
var __testOnly = {
|