@h-rig/server 0.0.6-alpha.10 → 0.0.6-alpha.12
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 -242
- 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 -160
- 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 -242
- package/package.json +4 -4
package/dist/src/index.js
CHANGED
|
@@ -663,9 +663,9 @@ function createRemoteOrchestrationSummary(input) {
|
|
|
663
663
|
}
|
|
664
664
|
// packages/server/src/server.ts
|
|
665
665
|
import { spawn as spawn5 } from "child_process";
|
|
666
|
-
import { existsSync as
|
|
666
|
+
import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
|
|
667
667
|
import { open } from "fs/promises";
|
|
668
|
-
import { dirname as
|
|
668
|
+
import { dirname as dirname20, resolve as resolve24 } from "path";
|
|
669
669
|
import {
|
|
670
670
|
listAuthorityArtifactRoots,
|
|
671
671
|
listAuthorityRuns as listAuthorityRuns7,
|
|
@@ -3707,7 +3707,7 @@ function applyOrchestrationCommand2(state, command) {
|
|
|
3707
3707
|
import { spawn as spawn3 } from "child_process";
|
|
3708
3708
|
import { loadConfig } from "@rig/core/load-config";
|
|
3709
3709
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3710
|
-
import { dirname as
|
|
3710
|
+
import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
|
|
3711
3711
|
import {
|
|
3712
3712
|
listAuthorityRuns as listAuthorityRuns4,
|
|
3713
3713
|
readAuthorityRun as readAuthorityRun4,
|
|
@@ -3831,8 +3831,8 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
3831
3831
|
|
|
3832
3832
|
// packages/server/src/server-helpers/github-auth-store.ts
|
|
3833
3833
|
import { randomBytes } from "crypto";
|
|
3834
|
-
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
3835
|
-
import { resolve as resolve13 } from "path";
|
|
3834
|
+
import { chmodSync, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
3835
|
+
import { dirname as dirname8, resolve as resolve13 } from "path";
|
|
3836
3836
|
function cleanString(value) {
|
|
3837
3837
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3838
3838
|
}
|
|
@@ -3862,6 +3862,26 @@ function parseApiSessions(value) {
|
|
|
3862
3862
|
}];
|
|
3863
3863
|
});
|
|
3864
3864
|
}
|
|
3865
|
+
function parsePendingDevice(value) {
|
|
3866
|
+
if (!value || typeof value !== "object")
|
|
3867
|
+
return null;
|
|
3868
|
+
const record = value;
|
|
3869
|
+
const pollId = cleanString(record.pollId);
|
|
3870
|
+
const deviceCode = cleanString(record.deviceCode);
|
|
3871
|
+
const expiresAt = cleanString(record.expiresAt);
|
|
3872
|
+
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3873
|
+
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3874
|
+
return null;
|
|
3875
|
+
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3876
|
+
}
|
|
3877
|
+
function parsePendingDevices(value) {
|
|
3878
|
+
if (!Array.isArray(value))
|
|
3879
|
+
return [];
|
|
3880
|
+
return value.flatMap((entry) => {
|
|
3881
|
+
const pending = parsePendingDevice(entry);
|
|
3882
|
+
return pending ? [pending] : [];
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3865
3885
|
function readStoredAuth(stateFile) {
|
|
3866
3886
|
if (!existsSync6(stateFile))
|
|
3867
3887
|
return {};
|
|
@@ -3875,6 +3895,7 @@ function readStoredAuth(stateFile) {
|
|
|
3875
3895
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
3876
3896
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
3877
3897
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
3898
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
3878
3899
|
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
3879
3900
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
3880
3901
|
};
|
|
@@ -3882,34 +3903,36 @@ function readStoredAuth(stateFile) {
|
|
|
3882
3903
|
return {};
|
|
3883
3904
|
}
|
|
3884
3905
|
}
|
|
3885
|
-
function parsePendingDevice(value) {
|
|
3886
|
-
if (!value || typeof value !== "object")
|
|
3887
|
-
return null;
|
|
3888
|
-
const record = value;
|
|
3889
|
-
const pollId = cleanString(record.pollId);
|
|
3890
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
3891
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
3892
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3893
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3894
|
-
return null;
|
|
3895
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3896
|
-
}
|
|
3897
3906
|
function newApiSessionToken() {
|
|
3898
3907
|
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
3899
3908
|
}
|
|
3900
3909
|
function writeStoredAuth(stateFile, payload) {
|
|
3901
|
-
mkdirSync6(
|
|
3910
|
+
mkdirSync6(dirname8(stateFile), { recursive: true });
|
|
3902
3911
|
writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
3903
3912
|
`, { encoding: "utf8", mode: 384 });
|
|
3904
3913
|
try {
|
|
3905
3914
|
chmodSync(stateFile, 384);
|
|
3906
3915
|
} catch {}
|
|
3907
3916
|
}
|
|
3917
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
3918
|
+
return resolve13(projectRoot, ".rig", "state", "github-auth.json");
|
|
3919
|
+
}
|
|
3908
3920
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
3909
3921
|
return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
3910
3922
|
}
|
|
3911
|
-
function
|
|
3912
|
-
const
|
|
3923
|
+
function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
|
|
3924
|
+
const targetFile = localProjectAuthStateFile(projectRoot);
|
|
3925
|
+
mkdirSync6(dirname8(targetFile), { recursive: true });
|
|
3926
|
+
if (existsSync6(stateFile)) {
|
|
3927
|
+
copyFileSync(stateFile, targetFile);
|
|
3928
|
+
try {
|
|
3929
|
+
chmodSync(targetFile, 384);
|
|
3930
|
+
} catch {}
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
writeStoredAuth(targetFile, {});
|
|
3934
|
+
}
|
|
3935
|
+
function createGitHubAuthStoreFromStateFile(stateFile) {
|
|
3913
3936
|
return {
|
|
3914
3937
|
stateFile,
|
|
3915
3938
|
status(options) {
|
|
@@ -3939,6 +3962,7 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3939
3962
|
scopes: input.scopes ?? [],
|
|
3940
3963
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
3941
3964
|
pendingDevice: null,
|
|
3965
|
+
pendingDevices: [],
|
|
3942
3966
|
apiSessions: previous.apiSessions ?? [],
|
|
3943
3967
|
updatedAt: new Date().toISOString()
|
|
3944
3968
|
});
|
|
@@ -3967,15 +3991,24 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3967
3991
|
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
3968
3992
|
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
3969
3993
|
},
|
|
3970
|
-
copyToProjectRoot(
|
|
3971
|
-
const targetFile = resolveGitHubAuthStateFile(
|
|
3994
|
+
copyToProjectRoot(projectRoot) {
|
|
3995
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
3972
3996
|
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
3973
3997
|
},
|
|
3998
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
3999
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
4000
|
+
},
|
|
3974
4001
|
savePendingDevice(input) {
|
|
3975
4002
|
const previous = readStoredAuth(stateFile);
|
|
4003
|
+
const pendingDevices = [
|
|
4004
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
4005
|
+
...previous.pendingDevices ?? [],
|
|
4006
|
+
input
|
|
4007
|
+
].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
|
|
3976
4008
|
writeStoredAuth(stateFile, {
|
|
3977
4009
|
...previous,
|
|
3978
|
-
pendingDevice:
|
|
4010
|
+
pendingDevice: null,
|
|
4011
|
+
pendingDevices,
|
|
3979
4012
|
updatedAt: new Date().toISOString()
|
|
3980
4013
|
});
|
|
3981
4014
|
},
|
|
@@ -3988,23 +4021,32 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3988
4021
|
});
|
|
3989
4022
|
},
|
|
3990
4023
|
readPendingDevice(pollId) {
|
|
3991
|
-
const
|
|
3992
|
-
|
|
4024
|
+
const previous = readStoredAuth(stateFile);
|
|
4025
|
+
const pending = [
|
|
4026
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
4027
|
+
...previous.pendingDevices ?? []
|
|
4028
|
+
].find((entry) => entry.pollId === pollId) ?? null;
|
|
4029
|
+
if (!pending)
|
|
3993
4030
|
return null;
|
|
3994
4031
|
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
3995
4032
|
return null;
|
|
3996
4033
|
return pending;
|
|
3997
4034
|
},
|
|
3998
|
-
clearPendingDevice() {
|
|
4035
|
+
clearPendingDevice(pollId) {
|
|
3999
4036
|
const previous = readStoredAuth(stateFile);
|
|
4037
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
4000
4038
|
writeStoredAuth(stateFile, {
|
|
4001
4039
|
...previous,
|
|
4002
4040
|
pendingDevice: null,
|
|
4041
|
+
pendingDevices: remaining,
|
|
4003
4042
|
updatedAt: new Date().toISOString()
|
|
4004
4043
|
});
|
|
4005
4044
|
}
|
|
4006
4045
|
};
|
|
4007
4046
|
}
|
|
4047
|
+
function createGitHubAuthStore(projectRoot) {
|
|
4048
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
4049
|
+
}
|
|
4008
4050
|
|
|
4009
4051
|
// packages/server/src/server-helpers/github-projects.ts
|
|
4010
4052
|
function asRecord(value) {
|
|
@@ -4743,7 +4785,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
4743
4785
|
}
|
|
4744
4786
|
try {
|
|
4745
4787
|
const monorepoRoot = resolveMonorepoRoot3(projectRoot);
|
|
4746
|
-
const outerProjectRoot =
|
|
4788
|
+
const outerProjectRoot = dirname9(dirname9(monorepoRoot));
|
|
4747
4789
|
if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
4748
4790
|
return outerProjectRoot;
|
|
4749
4791
|
}
|
|
@@ -4957,8 +4999,8 @@ async function reconcileScheduler(state, reason) {
|
|
|
4957
4999
|
// packages/server/src/server-helpers/http-router.ts
|
|
4958
5000
|
import { randomUUID } from "crypto";
|
|
4959
5001
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4960
|
-
import { basename, dirname as
|
|
4961
|
-
import { copyFileSync, existsSync as
|
|
5002
|
+
import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as resolve20 } from "path";
|
|
5003
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
4962
5004
|
import {
|
|
4963
5005
|
listAuthorityRuns as listAuthorityRuns5,
|
|
4964
5006
|
readAuthorityRun as readAuthorityRun6,
|
|
@@ -4982,7 +5024,7 @@ import {
|
|
|
4982
5024
|
} from "@rig/runtime/control-plane/remote";
|
|
4983
5025
|
|
|
4984
5026
|
// packages/server/src/server-helpers/run-steering.ts
|
|
4985
|
-
import { dirname as
|
|
5027
|
+
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
4986
5028
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
4987
5029
|
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
4988
5030
|
var steeringSequence = 0;
|
|
@@ -5069,7 +5111,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
|
5069
5111
|
delivered: false
|
|
5070
5112
|
};
|
|
5071
5113
|
const path = runSteeringPath(projectRoot, runId);
|
|
5072
|
-
mkdirSync8(
|
|
5114
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
5073
5115
|
appendJsonlRecord2(path, entry);
|
|
5074
5116
|
appendRunTimelineEntry(projectRoot, runId, {
|
|
5075
5117
|
id: entry.id,
|
|
@@ -5106,6 +5148,187 @@ import {
|
|
|
5106
5148
|
updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
|
|
5107
5149
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
5108
5150
|
|
|
5151
|
+
// packages/server/src/server-helpers/github-api-session-index.ts
|
|
5152
|
+
import { chmodSync as chmodSync3, existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
5153
|
+
import { dirname as dirname12, resolve as resolve17 } from "path";
|
|
5154
|
+
|
|
5155
|
+
// packages/server/src/server-helpers/github-user-namespace.ts
|
|
5156
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
5157
|
+
import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve16 } from "path";
|
|
5158
|
+
function cleanString3(value) {
|
|
5159
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
5160
|
+
}
|
|
5161
|
+
function sanitizePathSegment(value) {
|
|
5162
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
5163
|
+
}
|
|
5164
|
+
function deriveGitHubUserNamespaceKey(identity) {
|
|
5165
|
+
const userId = cleanString3(identity.userId);
|
|
5166
|
+
if (userId) {
|
|
5167
|
+
const safeId = sanitizePathSegment(userId);
|
|
5168
|
+
if (safeId)
|
|
5169
|
+
return `ghu-${safeId}`;
|
|
5170
|
+
}
|
|
5171
|
+
const login = cleanString3(identity.login);
|
|
5172
|
+
if (login) {
|
|
5173
|
+
const safeLogin = sanitizePathSegment(login);
|
|
5174
|
+
if (safeLogin)
|
|
5175
|
+
return `ghu-login-${safeLogin}`;
|
|
5176
|
+
}
|
|
5177
|
+
throw new Error("GitHub user namespace requires a user id or login");
|
|
5178
|
+
}
|
|
5179
|
+
function resolveRemoteUserNamespacesRoot(projectRoot) {
|
|
5180
|
+
const explicitRoot = cleanString3(process.env.RIG_REMOTE_USER_NAMESPACE_ROOT);
|
|
5181
|
+
if (explicitRoot)
|
|
5182
|
+
return resolve16(explicitRoot);
|
|
5183
|
+
const stateDir2 = cleanString3(process.env.RIG_STATE_DIR);
|
|
5184
|
+
if (stateDir2)
|
|
5185
|
+
return resolve16(dirname11(resolve16(stateDir2)), "users");
|
|
5186
|
+
return resolve16(projectRoot, ".rig", "users");
|
|
5187
|
+
}
|
|
5188
|
+
function resolveRemoteUserNamespace(projectRoot, identity) {
|
|
5189
|
+
const key = deriveGitHubUserNamespaceKey(identity);
|
|
5190
|
+
const root = resolve16(resolveRemoteUserNamespacesRoot(projectRoot), key);
|
|
5191
|
+
const stateDir2 = resolve16(root, ".rig", "state");
|
|
5192
|
+
return {
|
|
5193
|
+
key,
|
|
5194
|
+
userId: cleanString3(identity.userId),
|
|
5195
|
+
login: cleanString3(identity.login),
|
|
5196
|
+
root,
|
|
5197
|
+
stateDir: stateDir2,
|
|
5198
|
+
authStateFile: resolve16(stateDir2, "github-auth.json"),
|
|
5199
|
+
metadataFile: resolve16(stateDir2, "user-namespace.json"),
|
|
5200
|
+
checkoutBaseDir: resolve16(root, "remote-checkouts"),
|
|
5201
|
+
snapshotBaseDir: resolve16(root, "remote-snapshots")
|
|
5202
|
+
};
|
|
5203
|
+
}
|
|
5204
|
+
function serializeRemoteUserNamespace(namespace) {
|
|
5205
|
+
return {
|
|
5206
|
+
key: namespace.key,
|
|
5207
|
+
userId: namespace.userId,
|
|
5208
|
+
login: namespace.login,
|
|
5209
|
+
root: namespace.root,
|
|
5210
|
+
checkoutBaseDir: namespace.checkoutBaseDir,
|
|
5211
|
+
snapshotBaseDir: namespace.snapshotBaseDir
|
|
5212
|
+
};
|
|
5213
|
+
}
|
|
5214
|
+
function isPathInsideNamespace(namespaceRoot, candidatePath) {
|
|
5215
|
+
const root = resolve16(namespaceRoot);
|
|
5216
|
+
const candidate = resolve16(candidatePath);
|
|
5217
|
+
const rel = relative3(root, candidate);
|
|
5218
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
5219
|
+
}
|
|
5220
|
+
function writeRemoteUserNamespaceMetadata(namespace) {
|
|
5221
|
+
mkdirSync9(namespace.stateDir, { recursive: true });
|
|
5222
|
+
const previous = (() => {
|
|
5223
|
+
if (!existsSync9(namespace.metadataFile))
|
|
5224
|
+
return null;
|
|
5225
|
+
try {
|
|
5226
|
+
const parsed = JSON.parse(readFileSync6(namespace.metadataFile, "utf8"));
|
|
5227
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5228
|
+
} catch {
|
|
5229
|
+
return null;
|
|
5230
|
+
}
|
|
5231
|
+
})();
|
|
5232
|
+
const now = new Date().toISOString();
|
|
5233
|
+
writeFileSync8(namespace.metadataFile, `${JSON.stringify({
|
|
5234
|
+
key: namespace.key,
|
|
5235
|
+
userId: namespace.userId,
|
|
5236
|
+
login: namespace.login,
|
|
5237
|
+
root: namespace.root,
|
|
5238
|
+
checkoutBaseDir: namespace.checkoutBaseDir,
|
|
5239
|
+
snapshotBaseDir: namespace.snapshotBaseDir,
|
|
5240
|
+
createdAt: typeof previous?.createdAt === "string" ? previous.createdAt : now,
|
|
5241
|
+
updatedAt: now
|
|
5242
|
+
}, null, 2)}
|
|
5243
|
+
`, { encoding: "utf8", mode: 384 });
|
|
5244
|
+
try {
|
|
5245
|
+
chmodSync2(namespace.metadataFile, 384);
|
|
5246
|
+
} catch {}
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
// packages/server/src/server-helpers/github-api-session-index.ts
|
|
5250
|
+
function cleanString4(value) {
|
|
5251
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
5252
|
+
}
|
|
5253
|
+
function resolveGitHubApiSessionIndexFile(projectRoot) {
|
|
5254
|
+
return resolve17(resolveRemoteUserNamespacesRoot(projectRoot), ".api-sessions.json");
|
|
5255
|
+
}
|
|
5256
|
+
function parseEntry(value) {
|
|
5257
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5258
|
+
return null;
|
|
5259
|
+
const record = value;
|
|
5260
|
+
const token = cleanString4(record.token);
|
|
5261
|
+
const namespaceKey = cleanString4(record.namespaceKey);
|
|
5262
|
+
const namespaceRoot = cleanString4(record.namespaceRoot);
|
|
5263
|
+
const authStateFile = cleanString4(record.authStateFile);
|
|
5264
|
+
const checkoutBaseDir = cleanString4(record.checkoutBaseDir);
|
|
5265
|
+
const snapshotBaseDir = cleanString4(record.snapshotBaseDir);
|
|
5266
|
+
const createdAt = cleanString4(record.createdAt);
|
|
5267
|
+
if (!token || !namespaceKey || !namespaceRoot || !authStateFile || !checkoutBaseDir || !snapshotBaseDir || !createdAt)
|
|
5268
|
+
return null;
|
|
5269
|
+
return {
|
|
5270
|
+
token,
|
|
5271
|
+
namespaceKey,
|
|
5272
|
+
namespaceRoot,
|
|
5273
|
+
authStateFile,
|
|
5274
|
+
checkoutBaseDir,
|
|
5275
|
+
snapshotBaseDir,
|
|
5276
|
+
createdAt,
|
|
5277
|
+
login: cleanString4(record.login),
|
|
5278
|
+
userId: cleanString4(record.userId),
|
|
5279
|
+
selectedRepo: cleanString4(record.selectedRepo)
|
|
5280
|
+
};
|
|
5281
|
+
}
|
|
5282
|
+
function readIndex(indexFile) {
|
|
5283
|
+
if (!existsSync10(indexFile))
|
|
5284
|
+
return [];
|
|
5285
|
+
try {
|
|
5286
|
+
const parsed = JSON.parse(readFileSync7(indexFile, "utf8"));
|
|
5287
|
+
return Array.isArray(parsed.sessions) ? parsed.sessions.flatMap((entry) => {
|
|
5288
|
+
const parsedEntry = parseEntry(entry);
|
|
5289
|
+
return parsedEntry ? [parsedEntry] : [];
|
|
5290
|
+
}) : [];
|
|
5291
|
+
} catch {
|
|
5292
|
+
return [];
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
function writeIndex(indexFile, sessions) {
|
|
5296
|
+
mkdirSync10(dirname12(indexFile), { recursive: true });
|
|
5297
|
+
writeFileSync9(indexFile, `${JSON.stringify({ sessions }, null, 2)}
|
|
5298
|
+
`, { encoding: "utf8", mode: 384 });
|
|
5299
|
+
try {
|
|
5300
|
+
chmodSync3(indexFile, 384);
|
|
5301
|
+
} catch {}
|
|
5302
|
+
}
|
|
5303
|
+
function registerGitHubApiSession(input) {
|
|
5304
|
+
const cleanToken = cleanString4(input.token);
|
|
5305
|
+
if (!cleanToken)
|
|
5306
|
+
throw new Error("GitHub API session token is required");
|
|
5307
|
+
const indexFile = resolveGitHubApiSessionIndexFile(input.projectRoot);
|
|
5308
|
+
const createdAt = new Date().toISOString();
|
|
5309
|
+
const entry = {
|
|
5310
|
+
token: cleanToken,
|
|
5311
|
+
login: input.namespace.login,
|
|
5312
|
+
userId: input.namespace.userId,
|
|
5313
|
+
namespaceKey: input.namespace.key,
|
|
5314
|
+
namespaceRoot: input.namespace.root,
|
|
5315
|
+
authStateFile: input.namespace.authStateFile,
|
|
5316
|
+
checkoutBaseDir: input.namespace.checkoutBaseDir,
|
|
5317
|
+
snapshotBaseDir: input.namespace.snapshotBaseDir,
|
|
5318
|
+
selectedRepo: cleanString4(input.selectedRepo),
|
|
5319
|
+
createdAt
|
|
5320
|
+
};
|
|
5321
|
+
const previous = readIndex(indexFile).filter((session) => session.token !== cleanToken);
|
|
5322
|
+
writeIndex(indexFile, [...previous.slice(-199), entry]);
|
|
5323
|
+
return entry;
|
|
5324
|
+
}
|
|
5325
|
+
function readGitHubApiSession(input) {
|
|
5326
|
+
const cleanToken = cleanString4(input.token);
|
|
5327
|
+
if (!cleanToken)
|
|
5328
|
+
return null;
|
|
5329
|
+
return readIndex(resolveGitHubApiSessionIndexFile(input.projectRoot)).find((entry) => entry.token === cleanToken) ?? null;
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5109
5332
|
// packages/server/src/server-helpers/inspector-agent-lifecycle.ts
|
|
5110
5333
|
function createInspectorAgentLifecycleController(options) {
|
|
5111
5334
|
const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
|
|
@@ -5209,21 +5432,21 @@ function inspectorAgentLifecycleSnapshot(input) {
|
|
|
5209
5432
|
// packages/server/src/server-helpers/project-registry.ts
|
|
5210
5433
|
import { createHash as createHash2 } from "crypto";
|
|
5211
5434
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5212
|
-
import { existsSync as
|
|
5213
|
-
import { dirname as
|
|
5435
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync8, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
5436
|
+
import { dirname as dirname13, resolve as resolve18 } from "path";
|
|
5214
5437
|
function normalizeRepoSlug(value) {
|
|
5215
5438
|
const trimmed = value.trim();
|
|
5216
5439
|
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
|
|
5217
5440
|
}
|
|
5218
5441
|
function registryPath(projectRoot) {
|
|
5219
|
-
return
|
|
5442
|
+
return resolve18(projectRoot, ".rig", "state", "projects.json");
|
|
5220
5443
|
}
|
|
5221
5444
|
function readRegistry(projectRoot) {
|
|
5222
5445
|
const path = registryPath(projectRoot);
|
|
5223
|
-
if (!
|
|
5446
|
+
if (!existsSync11(path))
|
|
5224
5447
|
return {};
|
|
5225
5448
|
try {
|
|
5226
|
-
const payload = JSON.parse(
|
|
5449
|
+
const payload = JSON.parse(readFileSync8(path, "utf8"));
|
|
5227
5450
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
5228
5451
|
return {};
|
|
5229
5452
|
const projects = payload.projects;
|
|
@@ -5234,14 +5457,14 @@ function readRegistry(projectRoot) {
|
|
|
5234
5457
|
}
|
|
5235
5458
|
function writeRegistry(projectRoot, projects) {
|
|
5236
5459
|
const path = registryPath(projectRoot);
|
|
5237
|
-
|
|
5238
|
-
|
|
5460
|
+
mkdirSync11(dirname13(path), { recursive: true });
|
|
5461
|
+
writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
|
|
5239
5462
|
`, "utf8");
|
|
5240
5463
|
}
|
|
5241
5464
|
function resolveConfigPath(projectRoot) {
|
|
5242
5465
|
for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
|
|
5243
|
-
const path =
|
|
5244
|
-
if (
|
|
5466
|
+
const path = resolve18(projectRoot, name);
|
|
5467
|
+
if (existsSync11(path))
|
|
5245
5468
|
return path;
|
|
5246
5469
|
}
|
|
5247
5470
|
return null;
|
|
@@ -5250,7 +5473,7 @@ function hashFile(path) {
|
|
|
5250
5473
|
if (!path)
|
|
5251
5474
|
return null;
|
|
5252
5475
|
try {
|
|
5253
|
-
return createHash2("sha256").update(
|
|
5476
|
+
return createHash2("sha256").update(readFileSync8(path)).digest("hex");
|
|
5254
5477
|
} catch {
|
|
5255
5478
|
return null;
|
|
5256
5479
|
}
|
|
@@ -5266,11 +5489,11 @@ function readDefaultBranch(projectRoot) {
|
|
|
5266
5489
|
return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
|
|
5267
5490
|
}
|
|
5268
5491
|
function buildRunSummary(projectRoot) {
|
|
5269
|
-
const runsDir =
|
|
5492
|
+
const runsDir = resolve18(projectRoot, ".rig", "runs");
|
|
5270
5493
|
try {
|
|
5271
5494
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
5272
5495
|
try {
|
|
5273
|
-
const run = JSON.parse(
|
|
5496
|
+
const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
|
|
5274
5497
|
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 : "" }];
|
|
5275
5498
|
} catch {
|
|
5276
5499
|
return [];
|
|
@@ -5322,10 +5545,14 @@ function upsertProjectRecord(projectRoot, input) {
|
|
|
5322
5545
|
function linkProjectCheckout(projectRoot, repoSlug, checkout) {
|
|
5323
5546
|
return upsertProjectRecord(projectRoot, { repoSlug, checkout });
|
|
5324
5547
|
}
|
|
5548
|
+
function projectRegistryContainsCheckout(projectRoot, checkoutPath) {
|
|
5549
|
+
const target = resolve18(checkoutPath);
|
|
5550
|
+
return Object.values(readRegistry(projectRoot)).some((project) => project.checkouts.some((checkout) => checkout.path ? resolve18(checkout.path) === target : false));
|
|
5551
|
+
}
|
|
5325
5552
|
|
|
5326
5553
|
// packages/server/src/server-helpers/remote-checkout.ts
|
|
5327
|
-
import { existsSync as
|
|
5328
|
-
import { dirname as
|
|
5554
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
5555
|
+
import { dirname as dirname14, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve19 } from "path";
|
|
5329
5556
|
function safeSlugSegments(repoSlug) {
|
|
5330
5557
|
const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
|
|
5331
5558
|
if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
|
|
@@ -5342,7 +5569,7 @@ function safeCheckoutKey(value) {
|
|
|
5342
5569
|
}
|
|
5343
5570
|
function repoSlugPath(baseDir, repoSlug, checkoutKey) {
|
|
5344
5571
|
const key = safeCheckoutKey(checkoutKey);
|
|
5345
|
-
return
|
|
5572
|
+
return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
|
|
5346
5573
|
}
|
|
5347
5574
|
function sanitizeSnapshotId(value, fallback) {
|
|
5348
5575
|
const raw = (value ?? fallback).trim();
|
|
@@ -5350,7 +5577,7 @@ function sanitizeSnapshotId(value, fallback) {
|
|
|
5350
5577
|
return safe || fallback;
|
|
5351
5578
|
}
|
|
5352
5579
|
function assertWithinRoot(root, relativePath) {
|
|
5353
|
-
if (!relativePath ||
|
|
5580
|
+
if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
|
|
5354
5581
|
throw new Error(`Invalid snapshot file path: ${relativePath}`);
|
|
5355
5582
|
}
|
|
5356
5583
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
@@ -5358,9 +5585,9 @@ function assertWithinRoot(root, relativePath) {
|
|
|
5358
5585
|
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
5359
5586
|
throw new Error(`Unsafe snapshot file path: ${relativePath}`);
|
|
5360
5587
|
}
|
|
5361
|
-
const target =
|
|
5362
|
-
const rel =
|
|
5363
|
-
if (rel === ".." || rel.split(/[\\/]/)[0] === ".." ||
|
|
5588
|
+
const target = resolve19(root, ...segments);
|
|
5589
|
+
const rel = relative4(root, target);
|
|
5590
|
+
if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute3(rel)) {
|
|
5364
5591
|
throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
|
|
5365
5592
|
}
|
|
5366
5593
|
return target;
|
|
@@ -5378,17 +5605,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
|
|
|
5378
5605
|
function extractUploadedSnapshotArchive(input) {
|
|
5379
5606
|
const archive = decodeSnapshotArchive(input.archive);
|
|
5380
5607
|
const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
|
|
5381
|
-
const checkoutPath =
|
|
5382
|
-
|
|
5608
|
+
const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
|
|
5609
|
+
mkdirSync12(checkoutPath, { recursive: true });
|
|
5383
5610
|
for (const file of archive.files) {
|
|
5384
5611
|
if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
|
|
5385
5612
|
throw new Error("Invalid snapshot archive file entry");
|
|
5386
5613
|
}
|
|
5387
5614
|
const target = assertWithinRoot(checkoutPath, file.path);
|
|
5388
|
-
|
|
5389
|
-
|
|
5615
|
+
mkdirSync12(dirname14(target), { recursive: true });
|
|
5616
|
+
writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
|
|
5390
5617
|
}
|
|
5391
|
-
|
|
5618
|
+
writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
|
|
5392
5619
|
repoSlug: input.repoSlug,
|
|
5393
5620
|
snapshotId,
|
|
5394
5621
|
fileCount: archive.files.length,
|
|
@@ -5423,7 +5650,7 @@ function gitCredentialConfig(token) {
|
|
|
5423
5650
|
};
|
|
5424
5651
|
}
|
|
5425
5652
|
async function prepareRemoteCheckout(input) {
|
|
5426
|
-
const exists = input.exists ??
|
|
5653
|
+
const exists = input.exists ?? existsSync12;
|
|
5427
5654
|
const strategy = input.strategy;
|
|
5428
5655
|
if (strategy.kind === "uploaded-snapshot") {
|
|
5429
5656
|
return extractUploadedSnapshotArchive({
|
|
@@ -5435,7 +5662,7 @@ async function prepareRemoteCheckout(input) {
|
|
|
5435
5662
|
});
|
|
5436
5663
|
}
|
|
5437
5664
|
if (strategy.kind === "existing-path") {
|
|
5438
|
-
const checkoutPath2 =
|
|
5665
|
+
const checkoutPath2 = resolve19(strategy.path);
|
|
5439
5666
|
if (!exists(checkoutPath2)) {
|
|
5440
5667
|
throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
|
|
5441
5668
|
}
|
|
@@ -5471,9 +5698,9 @@ function buildServerControlStatus() {
|
|
|
5471
5698
|
};
|
|
5472
5699
|
}
|
|
5473
5700
|
function buildProjectConfigStatus(root) {
|
|
5474
|
-
const hasConfigTs =
|
|
5475
|
-
const hasConfigJson =
|
|
5476
|
-
const hasLegacyTaskConfig =
|
|
5701
|
+
const hasConfigTs = existsSync13(resolve20(root, "rig.config.ts"));
|
|
5702
|
+
const hasConfigJson = existsSync13(resolve20(root, "rig.config.json"));
|
|
5703
|
+
const hasLegacyTaskConfig = existsSync13(resolve20(root, ".rig", "task-config.json"));
|
|
5477
5704
|
let kind = "missing";
|
|
5478
5705
|
if (hasConfigTs)
|
|
5479
5706
|
kind = "rig-config-ts";
|
|
@@ -5509,24 +5736,24 @@ function repoParts(repoSlug) {
|
|
|
5509
5736
|
return { owner, repo, slug: `${owner}/${repo}` };
|
|
5510
5737
|
}
|
|
5511
5738
|
function repairDir(checkoutPath) {
|
|
5512
|
-
const dir =
|
|
5513
|
-
|
|
5739
|
+
const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
|
|
5740
|
+
mkdirSync13(dir, { recursive: true });
|
|
5514
5741
|
return dir;
|
|
5515
5742
|
}
|
|
5516
5743
|
function backupCheckoutFile(checkoutPath, relativePath) {
|
|
5517
|
-
const source =
|
|
5518
|
-
const backupPath =
|
|
5519
|
-
|
|
5520
|
-
|
|
5744
|
+
const source = resolve20(checkoutPath, relativePath);
|
|
5745
|
+
const backupPath = resolve20(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
|
|
5746
|
+
mkdirSync13(dirname15(backupPath), { recursive: true });
|
|
5747
|
+
copyFileSync2(source, backupPath);
|
|
5521
5748
|
return backupPath;
|
|
5522
5749
|
}
|
|
5523
5750
|
function parsePackageJsonLosslessly(checkoutPath) {
|
|
5524
|
-
const packagePath =
|
|
5525
|
-
if (!
|
|
5751
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
5752
|
+
if (!existsSync13(packagePath)) {
|
|
5526
5753
|
return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
|
|
5527
5754
|
}
|
|
5528
5755
|
try {
|
|
5529
|
-
const parsed = JSON.parse(
|
|
5756
|
+
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
5530
5757
|
return {
|
|
5531
5758
|
existed: true,
|
|
5532
5759
|
packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
|
|
@@ -5540,9 +5767,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
|
|
|
5540
5767
|
}
|
|
5541
5768
|
}
|
|
5542
5769
|
function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
5543
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5544
|
-
const packagePath =
|
|
5545
|
-
if (!hasConfig && !
|
|
5770
|
+
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5771
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
5772
|
+
if (!hasConfig && !existsSync13(packagePath)) {
|
|
5546
5773
|
return { skipped: true, reason: "package.json and rig.config missing" };
|
|
5547
5774
|
}
|
|
5548
5775
|
const parsed = parsePackageJsonLosslessly(checkoutPath);
|
|
@@ -5561,7 +5788,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
|
5561
5788
|
}
|
|
5562
5789
|
const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
|
|
5563
5790
|
if (changed) {
|
|
5564
|
-
|
|
5791
|
+
writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
|
|
5565
5792
|
`, "utf8");
|
|
5566
5793
|
}
|
|
5567
5794
|
return {
|
|
@@ -5587,11 +5814,11 @@ function configLooksStructurallyUsable(source) {
|
|
|
5587
5814
|
return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
|
|
5588
5815
|
}
|
|
5589
5816
|
function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
|
|
5590
|
-
const configPath =
|
|
5591
|
-
const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) =>
|
|
5817
|
+
const configPath = resolve20(checkoutPath, "rig.config.ts");
|
|
5818
|
+
const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5592
5819
|
if (existingConfigName) {
|
|
5593
|
-
const existingPath =
|
|
5594
|
-
const source =
|
|
5820
|
+
const existingPath = resolve20(checkoutPath, existingConfigName);
|
|
5821
|
+
const source = readFileSync9(existingPath, "utf8");
|
|
5595
5822
|
if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
|
|
5596
5823
|
return { path: existingPath, changed: false, reason: "config structurally complete" };
|
|
5597
5824
|
}
|
|
@@ -5605,7 +5832,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5605
5832
|
}
|
|
5606
5833
|
}
|
|
5607
5834
|
const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
|
|
5608
|
-
|
|
5835
|
+
writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
|
|
5609
5836
|
return {
|
|
5610
5837
|
path: configPath,
|
|
5611
5838
|
changed: true,
|
|
@@ -5614,7 +5841,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5614
5841
|
};
|
|
5615
5842
|
}
|
|
5616
5843
|
function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
5617
|
-
const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) =>
|
|
5844
|
+
const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5618
5845
|
if (!configFile)
|
|
5619
5846
|
return { ok: false, error: "missing rig config" };
|
|
5620
5847
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5636,7 +5863,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
|
5636
5863
|
return { ok: true, configFile };
|
|
5637
5864
|
}
|
|
5638
5865
|
function installRemoteCheckoutPackages(checkoutPath) {
|
|
5639
|
-
if (!
|
|
5866
|
+
if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
|
|
5640
5867
|
return { skipped: true, reason: "package.json missing" };
|
|
5641
5868
|
}
|
|
5642
5869
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5649,8 +5876,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
|
|
|
5649
5876
|
return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
|
|
5650
5877
|
}
|
|
5651
5878
|
function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
|
|
5652
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5653
|
-
const hasPackage =
|
|
5879
|
+
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5880
|
+
const hasPackage = existsSync13(resolve20(checkoutPath, "package.json"));
|
|
5654
5881
|
if (!hasConfig && !hasPackage) {
|
|
5655
5882
|
return {
|
|
5656
5883
|
packageJson: { skipped: true, reason: "package.json and rig.config missing" },
|
|
@@ -5748,26 +5975,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
|
|
|
5748
5975
|
}
|
|
5749
5976
|
function readGitHeadCommit(projectRoot) {
|
|
5750
5977
|
try {
|
|
5751
|
-
let gitDir =
|
|
5978
|
+
let gitDir = resolve20(projectRoot, ".git");
|
|
5752
5979
|
try {
|
|
5753
|
-
const dotGit =
|
|
5980
|
+
const dotGit = readFileSync9(gitDir, "utf8").trim();
|
|
5754
5981
|
const gitDirPrefix = "gitdir:";
|
|
5755
5982
|
if (dotGit.startsWith(gitDirPrefix)) {
|
|
5756
|
-
gitDir =
|
|
5983
|
+
gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
|
|
5757
5984
|
}
|
|
5758
5985
|
} catch {}
|
|
5759
|
-
const head =
|
|
5986
|
+
const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
|
|
5760
5987
|
const refPrefix = "ref:";
|
|
5761
5988
|
if (!head.startsWith(refPrefix)) {
|
|
5762
5989
|
return normalizeCommit(head);
|
|
5763
5990
|
}
|
|
5764
5991
|
const ref = head.slice(refPrefix.length).trim();
|
|
5765
|
-
const refPath =
|
|
5766
|
-
if (
|
|
5767
|
-
return normalizeCommit(
|
|
5992
|
+
const refPath = resolve20(gitDir, ref);
|
|
5993
|
+
if (existsSync13(refPath)) {
|
|
5994
|
+
return normalizeCommit(readFileSync9(refPath, "utf8").trim());
|
|
5768
5995
|
}
|
|
5769
|
-
const commonDir = normalizeString(
|
|
5770
|
-
return commonDir ? normalizeCommit(
|
|
5996
|
+
const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
|
|
5997
|
+
return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
|
|
5771
5998
|
} catch {
|
|
5772
5999
|
return null;
|
|
5773
6000
|
}
|
|
@@ -5805,9 +6032,9 @@ function configuredRepoFromTaskSource(taskSource) {
|
|
|
5805
6032
|
const repo = normalizeString(taskSource?.repo);
|
|
5806
6033
|
return owner && repo ? `${owner}/${repo}` : null;
|
|
5807
6034
|
}
|
|
5808
|
-
async function buildTaskSourceStatus(state, config) {
|
|
6035
|
+
async function buildTaskSourceStatus(state, config, requestAuth) {
|
|
5809
6036
|
const diagnostics = state.snapshotService.getTaskSourceErrors();
|
|
5810
|
-
const selectedRepo =
|
|
6037
|
+
const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
|
|
5811
6038
|
oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
|
|
5812
6039
|
}).selectedRepo;
|
|
5813
6040
|
try {
|
|
@@ -5905,31 +6132,83 @@ function normalizePrMode(value) {
|
|
|
5905
6132
|
const mode = normalizeString(value);
|
|
5906
6133
|
return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
|
|
5907
6134
|
}
|
|
6135
|
+
function requestAuthResult(input) {
|
|
6136
|
+
return {
|
|
6137
|
+
authorized: input.authorized,
|
|
6138
|
+
actor: input.actor ?? null,
|
|
6139
|
+
reason: input.reason,
|
|
6140
|
+
login: input.login ?? null,
|
|
6141
|
+
userId: input.userId ?? null,
|
|
6142
|
+
userNamespace: input.userNamespace ?? null,
|
|
6143
|
+
authStateFile: input.authStateFile ?? null
|
|
6144
|
+
};
|
|
6145
|
+
}
|
|
6146
|
+
function namespaceFromSessionIndex(entry) {
|
|
6147
|
+
const stateDir2 = dirname15(entry.authStateFile);
|
|
6148
|
+
return {
|
|
6149
|
+
key: entry.namespaceKey,
|
|
6150
|
+
userId: entry.userId,
|
|
6151
|
+
login: entry.login,
|
|
6152
|
+
root: entry.namespaceRoot,
|
|
6153
|
+
stateDir: stateDir2,
|
|
6154
|
+
authStateFile: entry.authStateFile,
|
|
6155
|
+
metadataFile: resolve20(stateDir2, "user-namespace.json"),
|
|
6156
|
+
checkoutBaseDir: entry.checkoutBaseDir,
|
|
6157
|
+
snapshotBaseDir: entry.snapshotBaseDir
|
|
6158
|
+
};
|
|
6159
|
+
}
|
|
5908
6160
|
function authorizeRigHttpRequest(input) {
|
|
5909
6161
|
if (input.legacyAuthorized) {
|
|
5910
|
-
return { authorized: true, actor: "rig-local-server", reason: "server-token" };
|
|
6162
|
+
return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
|
|
5911
6163
|
}
|
|
5912
6164
|
const bearer = bearerTokenFromRequest(input.req);
|
|
5913
6165
|
const store = createGitHubAuthStore(input.projectRoot);
|
|
5914
6166
|
const storedToken = store.readToken();
|
|
5915
6167
|
const session = bearer ? store.readApiSession(bearer) : null;
|
|
5916
6168
|
if (session) {
|
|
5917
|
-
return {
|
|
6169
|
+
return requestAuthResult({
|
|
6170
|
+
authorized: true,
|
|
6171
|
+
actor: session.login ?? "github-operator",
|
|
6172
|
+
reason: "github-session",
|
|
6173
|
+
login: session.login,
|
|
6174
|
+
userId: session.userId,
|
|
6175
|
+
authStateFile: store.stateFile
|
|
6176
|
+
});
|
|
5918
6177
|
}
|
|
5919
6178
|
if (bearer && storedToken && bearer === storedToken) {
|
|
5920
6179
|
const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
5921
|
-
return {
|
|
6180
|
+
return requestAuthResult({
|
|
6181
|
+
authorized: true,
|
|
6182
|
+
actor: status.login ?? "github-operator",
|
|
6183
|
+
reason: "github-token",
|
|
6184
|
+
login: status.login,
|
|
6185
|
+
userId: status.userId,
|
|
6186
|
+
authStateFile: store.stateFile
|
|
6187
|
+
});
|
|
6188
|
+
}
|
|
6189
|
+
const indexedSession = readGitHubApiSession({ projectRoot: input.projectRoot, token: bearer });
|
|
6190
|
+
if (indexedSession) {
|
|
6191
|
+
const userNamespace = namespaceFromSessionIndex(indexedSession);
|
|
6192
|
+
return requestAuthResult({
|
|
6193
|
+
authorized: true,
|
|
6194
|
+
actor: indexedSession.login ?? "github-operator",
|
|
6195
|
+
reason: "github-user-session",
|
|
6196
|
+
login: indexedSession.login,
|
|
6197
|
+
userId: indexedSession.userId,
|
|
6198
|
+
userNamespace,
|
|
6199
|
+
authStateFile: indexedSession.authStateFile
|
|
6200
|
+
});
|
|
5922
6201
|
}
|
|
5923
6202
|
if (isPublicRigAuthBootstrapRoute(input.pathname)) {
|
|
5924
|
-
return { authorized: true, actor: null, reason: "public-bootstrap" };
|
|
6203
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
|
|
5925
6204
|
}
|
|
5926
6205
|
if (!input.serverAuthToken && !storedToken) {
|
|
5927
6206
|
if (isLoopbackRequest(input.req)) {
|
|
5928
|
-
return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
|
|
6207
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
|
|
5929
6208
|
}
|
|
5930
|
-
return { authorized: false, actor: null, reason: "auth-required" };
|
|
6209
|
+
return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
|
|
5931
6210
|
}
|
|
5932
|
-
return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
|
|
6211
|
+
return requestAuthResult({ authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" });
|
|
5933
6212
|
}
|
|
5934
6213
|
async function fetchGitHubUserInfo(token) {
|
|
5935
6214
|
const response = await fetch("https://api.github.com/user", {
|
|
@@ -5949,6 +6228,67 @@ async function fetchGitHubUserInfo(token) {
|
|
|
5949
6228
|
scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
|
|
5950
6229
|
};
|
|
5951
6230
|
}
|
|
6231
|
+
function shouldWriteRootAuthCompat(projectRoot) {
|
|
6232
|
+
if (process.env.RIG_REMOTE_USER_NAMESPACE_ROOT?.trim())
|
|
6233
|
+
return false;
|
|
6234
|
+
const stateDir2 = normalizeString(process.env.RIG_STATE_DIR);
|
|
6235
|
+
if (!stateDir2)
|
|
6236
|
+
return true;
|
|
6237
|
+
return resolve20(stateDir2) === resolve20(projectRoot, ".rig", "state");
|
|
6238
|
+
}
|
|
6239
|
+
function requestScopedRegistryRoot(stateProjectRoot, requestAuth) {
|
|
6240
|
+
return requestAuth.userNamespace?.root ?? stateProjectRoot;
|
|
6241
|
+
}
|
|
6242
|
+
function requestScopedAuthStore(stateProjectRoot, requestAuth) {
|
|
6243
|
+
return requestAuth.authStateFile ? createGitHubAuthStoreFromStateFile(requestAuth.authStateFile) : createGitHubAuthStore(stateProjectRoot);
|
|
6244
|
+
}
|
|
6245
|
+
function userNamespaceResponse(namespace) {
|
|
6246
|
+
return namespace ? serializeRemoteUserNamespace(namespace) : undefined;
|
|
6247
|
+
}
|
|
6248
|
+
function resolveNamespacedBaseDir(input) {
|
|
6249
|
+
if (input.explicitBaseDir)
|
|
6250
|
+
return input.explicitBaseDir;
|
|
6251
|
+
const envBase = normalizeString(process.env[input.envName]);
|
|
6252
|
+
if (input.userNamespace) {
|
|
6253
|
+
return envBase ? resolve20(envBase, input.userNamespace.key) : input.userNamespace[input.legacySubdir === "remote-checkouts" ? "checkoutBaseDir" : "snapshotBaseDir"];
|
|
6254
|
+
}
|
|
6255
|
+
return envBase ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve20(normalizeString(process.env.RIG_STATE_DIR), input.legacySubdir) : resolve20(input.legacyProjectRoot, ".rig", input.legacySubdir));
|
|
6256
|
+
}
|
|
6257
|
+
function explicitCheckoutKey(body, checkoutInput, requestAuth) {
|
|
6258
|
+
return normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? (requestAuth.userNamespace ? undefined : "default");
|
|
6259
|
+
}
|
|
6260
|
+
function saveGitHubTokenForRemoteUser(input) {
|
|
6261
|
+
const namespace = resolveRemoteUserNamespace(input.projectRoot, { userId: input.user.userId, login: input.user.login });
|
|
6262
|
+
writeRemoteUserNamespaceMetadata(namespace);
|
|
6263
|
+
const store = createGitHubAuthStoreFromStateFile(namespace.authStateFile);
|
|
6264
|
+
store.saveToken({
|
|
6265
|
+
token: input.token,
|
|
6266
|
+
tokenSource: input.tokenSource,
|
|
6267
|
+
login: input.user.login,
|
|
6268
|
+
userId: input.user.userId,
|
|
6269
|
+
scopes: input.user.scopes,
|
|
6270
|
+
selectedRepo: input.selectedRepo
|
|
6271
|
+
});
|
|
6272
|
+
const apiSession = store.createApiSession();
|
|
6273
|
+
registerGitHubApiSession({ projectRoot: input.projectRoot, token: apiSession.token, namespace, selectedRepo: input.selectedRepo });
|
|
6274
|
+
const requestedRoot = normalizeString(input.requestedProjectRoot);
|
|
6275
|
+
if (requestedRoot && isAbsolute4(requestedRoot) && existsSync13(resolve20(requestedRoot))) {
|
|
6276
|
+
copyGitHubAuthStateToLocalProjectRoot(namespace.authStateFile, resolve20(requestedRoot));
|
|
6277
|
+
}
|
|
6278
|
+
if (shouldWriteRootAuthCompat(input.projectRoot)) {
|
|
6279
|
+
const rootStore = createGitHubAuthStore(input.projectRoot);
|
|
6280
|
+
rootStore.saveToken({
|
|
6281
|
+
token: input.token,
|
|
6282
|
+
tokenSource: input.tokenSource,
|
|
6283
|
+
login: input.user.login,
|
|
6284
|
+
userId: input.user.userId,
|
|
6285
|
+
scopes: input.user.scopes,
|
|
6286
|
+
selectedRepo: input.selectedRepo
|
|
6287
|
+
});
|
|
6288
|
+
rootStore.createApiSession();
|
|
6289
|
+
}
|
|
6290
|
+
return { store, namespace, apiSessionToken: apiSession.token };
|
|
6291
|
+
}
|
|
5952
6292
|
async function postGitHubForm(endpoint, body) {
|
|
5953
6293
|
const response = await fetch(endpoint, {
|
|
5954
6294
|
method: "POST",
|
|
@@ -5966,11 +6306,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
|
|
|
5966
6306
|
const requestedRoot = normalizeString(rawRoot);
|
|
5967
6307
|
if (!requestedRoot)
|
|
5968
6308
|
return currentRoot;
|
|
5969
|
-
if (!
|
|
6309
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
5970
6310
|
throw new Error("projectRoot must be an absolute path on the Rig server host");
|
|
5971
6311
|
}
|
|
5972
|
-
const normalizedRoot =
|
|
5973
|
-
if (!
|
|
6312
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
6313
|
+
if (!existsSync13(normalizedRoot)) {
|
|
5974
6314
|
throw new Error("projectRoot does not exist on the Rig server host");
|
|
5975
6315
|
}
|
|
5976
6316
|
return normalizedRoot;
|
|
@@ -6631,7 +6971,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6631
6971
|
}
|
|
6632
6972
|
if (url.pathname === "/api/server/status") {
|
|
6633
6973
|
const config = buildProjectConfigStatus(state.projectRoot);
|
|
6634
|
-
const taskSource = await buildTaskSourceStatus(state, config);
|
|
6974
|
+
const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
|
|
6635
6975
|
return deps.jsonResponse({
|
|
6636
6976
|
ok: true,
|
|
6637
6977
|
projectRoot: state.projectRoot,
|
|
@@ -6655,8 +6995,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6655
6995
|
path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
|
|
6656
6996
|
ref: normalizeString(rawCheckout?.ref) ?? undefined
|
|
6657
6997
|
} : undefined;
|
|
6658
|
-
const
|
|
6659
|
-
|
|
6998
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
6999
|
+
const record = upsertProjectRecord(registryRoot, { repoSlug, checkout });
|
|
7000
|
+
return deps.jsonResponse({ ok: true, project: record, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6660
7001
|
}
|
|
6661
7002
|
const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
|
|
6662
7003
|
if (snapshotUploadMatch && req.method === "POST") {
|
|
@@ -6669,8 +7010,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6669
7010
|
if (!archiveContentBase64) {
|
|
6670
7011
|
return deps.badRequest("archiveContentBase64 is required");
|
|
6671
7012
|
}
|
|
6672
|
-
const
|
|
6673
|
-
const
|
|
7013
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7014
|
+
const baseDir = resolveNamespacedBaseDir({
|
|
7015
|
+
explicitBaseDir: normalizeString(body.baseDir),
|
|
7016
|
+
envName: "RIG_REMOTE_SNAPSHOT_BASE_DIR",
|
|
7017
|
+
userNamespace: requestAuth.userNamespace,
|
|
7018
|
+
legacyProjectRoot: state.projectRoot,
|
|
7019
|
+
legacySubdir: "remote-snapshots"
|
|
7020
|
+
});
|
|
7021
|
+
const checkoutKey = explicitCheckoutKey(body, body, requestAuth);
|
|
6674
7022
|
try {
|
|
6675
7023
|
const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
|
|
6676
7024
|
const checkout = extractUploadedSnapshotArchive({
|
|
@@ -6683,14 +7031,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6683
7031
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6684
7032
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6685
7033
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6686
|
-
const project = linkProjectCheckout(
|
|
7034
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6687
7035
|
kind: "uploaded-snapshot",
|
|
6688
7036
|
path: checkout.path,
|
|
6689
7037
|
ref: checkout.snapshotId
|
|
6690
7038
|
});
|
|
6691
7039
|
deps.snapshotService.invalidate("uploaded-snapshot-checkout");
|
|
6692
7040
|
deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
|
|
6693
|
-
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
|
|
7041
|
+
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6694
7042
|
} catch (error) {
|
|
6695
7043
|
return deps.jsonResponse({
|
|
6696
7044
|
ok: false,
|
|
@@ -6710,10 +7058,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6710
7058
|
if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
|
|
6711
7059
|
return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
|
|
6712
7060
|
}
|
|
6713
|
-
const
|
|
6714
|
-
const
|
|
7061
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7062
|
+
const baseDir = resolveNamespacedBaseDir({
|
|
7063
|
+
explicitBaseDir: normalizeString(body.baseDir) ?? normalizeString(checkoutInput.baseDir),
|
|
7064
|
+
envName: "RIG_REMOTE_CHECKOUT_BASE_DIR",
|
|
7065
|
+
userNamespace: requestAuth.userNamespace,
|
|
7066
|
+
legacyProjectRoot: state.projectRoot,
|
|
7067
|
+
legacySubdir: "remote-checkouts"
|
|
7068
|
+
});
|
|
7069
|
+
const checkoutKey = explicitCheckoutKey(body, checkoutInput, requestAuth);
|
|
6715
7070
|
const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
|
|
6716
|
-
const credentialToken =
|
|
7071
|
+
const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
|
|
6717
7072
|
try {
|
|
6718
7073
|
const checkout = await prepareRemoteCheckout({
|
|
6719
7074
|
command: runRemoteCheckoutCommand,
|
|
@@ -6722,14 +7077,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6722
7077
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6723
7078
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6724
7079
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6725
|
-
const project = linkProjectCheckout(
|
|
7080
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6726
7081
|
kind: checkout.kind,
|
|
6727
7082
|
path: checkout.path,
|
|
6728
7083
|
ref: checkout.ref ?? checkout.snapshotId ?? undefined
|
|
6729
7084
|
});
|
|
6730
7085
|
deps.snapshotService.invalidate("remote-checkout-prepared");
|
|
6731
7086
|
deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
|
|
6732
|
-
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
|
|
7087
|
+
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6733
7088
|
} catch (error) {
|
|
6734
7089
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
|
|
6735
7090
|
}
|
|
@@ -6746,16 +7101,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6746
7101
|
if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
|
|
6747
7102
|
return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
|
|
6748
7103
|
}
|
|
6749
|
-
const
|
|
7104
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7105
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6750
7106
|
kind,
|
|
6751
7107
|
path: normalizeString(body.path) ?? state.projectRoot,
|
|
6752
7108
|
ref: normalizeString(body.ref) ?? undefined
|
|
6753
7109
|
});
|
|
6754
|
-
return deps.jsonResponse({ ok: true, project });
|
|
7110
|
+
return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6755
7111
|
}
|
|
6756
7112
|
if (req.method === "GET") {
|
|
6757
|
-
const
|
|
6758
|
-
|
|
7113
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7114
|
+
const project = getProjectRecord(registryRoot, repoSlug);
|
|
7115
|
+
return project ? deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) }) : deps.notFound();
|
|
6759
7116
|
}
|
|
6760
7117
|
}
|
|
6761
7118
|
if (url.pathname === "/api/server/project-root" && req.method === "POST") {
|
|
@@ -6764,13 +7121,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6764
7121
|
if (!requestedRoot) {
|
|
6765
7122
|
return deps.badRequest("projectRoot is required");
|
|
6766
7123
|
}
|
|
6767
|
-
if (!
|
|
7124
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
6768
7125
|
return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
|
|
6769
7126
|
}
|
|
6770
|
-
const normalizedRoot =
|
|
6771
|
-
const exists =
|
|
6772
|
-
if (exists) {
|
|
6773
|
-
|
|
7127
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
7128
|
+
const exists = existsSync13(normalizedRoot);
|
|
7129
|
+
if (exists && requestAuth.userNamespace) {
|
|
7130
|
+
const allowedByNamespace = isPathInsideNamespace(requestAuth.userNamespace.root, normalizedRoot);
|
|
7131
|
+
const allowedByRegistry = projectRegistryContainsCheckout(requestAuth.userNamespace.root, normalizedRoot);
|
|
7132
|
+
if (!allowedByNamespace && !allowedByRegistry) {
|
|
7133
|
+
return deps.jsonResponse({
|
|
7134
|
+
ok: false,
|
|
7135
|
+
error: "Requested project root is outside the authenticated GitHub user namespace.",
|
|
7136
|
+
projectRoot: state.projectRoot,
|
|
7137
|
+
requestedProjectRoot: normalizedRoot,
|
|
7138
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace)
|
|
7139
|
+
}, 403);
|
|
7140
|
+
}
|
|
7141
|
+
copyGitHubAuthStateToLocalProjectRoot(requestAuth.userNamespace.authStateFile, normalizedRoot);
|
|
7142
|
+
} else if (exists) {
|
|
7143
|
+
createGitHubAuthStore(state.projectRoot).copyToLocalProjectRoot(normalizedRoot);
|
|
6774
7144
|
}
|
|
6775
7145
|
const control = buildServerControlStatus();
|
|
6776
7146
|
const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
|
|
@@ -6785,7 +7155,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6785
7155
|
message: "Requested project root does not exist on the Rig server host."
|
|
6786
7156
|
}, 404);
|
|
6787
7157
|
}
|
|
6788
|
-
if (!
|
|
7158
|
+
if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
|
|
6789
7159
|
return deps.jsonResponse({
|
|
6790
7160
|
ok: false,
|
|
6791
7161
|
projectRoot: state.projectRoot,
|
|
@@ -6820,6 +7190,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6820
7190
|
exists,
|
|
6821
7191
|
control,
|
|
6822
7192
|
requiresRestart: false,
|
|
7193
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace),
|
|
6823
7194
|
message: "Project-root switch accepted. Rig server restart has been scheduled."
|
|
6824
7195
|
}, 202);
|
|
6825
7196
|
}
|
|
@@ -6834,11 +7205,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6834
7205
|
}, 409);
|
|
6835
7206
|
}
|
|
6836
7207
|
if (url.pathname === "/api/github/auth/status") {
|
|
6837
|
-
const store =
|
|
6838
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
|
|
7208
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
7209
|
+
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6839
7210
|
}
|
|
6840
7211
|
if (url.pathname === "/api/github/repo/permissions") {
|
|
6841
|
-
const store =
|
|
7212
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6842
7213
|
const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6843
7214
|
if (!auth.signedIn) {
|
|
6844
7215
|
return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
|
|
@@ -6866,24 +7237,20 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6866
7237
|
}
|
|
6867
7238
|
try {
|
|
6868
7239
|
const user = await fetchGitHubUserInfo(token);
|
|
6869
|
-
const
|
|
6870
|
-
state.projectRoot,
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
}
|
|
6884
|
-
const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
|
|
6885
|
-
const apiSession = store.createApiSession();
|
|
6886
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
|
|
7240
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
7241
|
+
projectRoot: state.projectRoot,
|
|
7242
|
+
token,
|
|
7243
|
+
tokenSource: "manual-token",
|
|
7244
|
+
user,
|
|
7245
|
+
selectedRepo,
|
|
7246
|
+
requestedProjectRoot
|
|
7247
|
+
});
|
|
7248
|
+
return deps.jsonResponse({
|
|
7249
|
+
ok: true,
|
|
7250
|
+
...saved.store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }),
|
|
7251
|
+
apiSessionToken: saved.apiSessionToken,
|
|
7252
|
+
userNamespace: userNamespaceResponse(saved.namespace)
|
|
7253
|
+
});
|
|
6887
7254
|
} catch (error) {
|
|
6888
7255
|
const message = error instanceof Error ? error.message : String(error);
|
|
6889
7256
|
return deps.jsonResponse({ ok: false, error: message }, 400);
|
|
@@ -6950,9 +7317,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6950
7317
|
}
|
|
6951
7318
|
const token = result.payload.access_token;
|
|
6952
7319
|
const user = await fetchGitHubUserInfo(token);
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
7320
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
7321
|
+
projectRoot: state.projectRoot,
|
|
7322
|
+
token,
|
|
7323
|
+
tokenSource: "oauth-device",
|
|
7324
|
+
user,
|
|
7325
|
+
selectedRepo: null
|
|
7326
|
+
});
|
|
7327
|
+
store.clearPendingDevice(pollId);
|
|
7328
|
+
return deps.jsonResponse({
|
|
7329
|
+
ok: true,
|
|
7330
|
+
status: "signed-in",
|
|
7331
|
+
...saved.store.status({ oauthConfigured: true }),
|
|
7332
|
+
apiSessionToken: saved.apiSessionToken,
|
|
7333
|
+
userNamespace: userNamespaceResponse(saved.namespace)
|
|
7334
|
+
});
|
|
6956
7335
|
}
|
|
6957
7336
|
if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
|
|
6958
7337
|
const body = await deps.readJsonBody(req);
|
|
@@ -6961,7 +7340,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6961
7340
|
if (!owner || !repo) {
|
|
6962
7341
|
return deps.badRequest("owner and repo are required");
|
|
6963
7342
|
}
|
|
6964
|
-
const store =
|
|
7343
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6965
7344
|
const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6966
7345
|
const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
|
|
6967
7346
|
return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
|
|
@@ -6982,7 +7361,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6982
7361
|
return deps.badRequest(error instanceof Error ? error.message : String(error));
|
|
6983
7362
|
}
|
|
6984
7363
|
const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6985
|
-
const configPath =
|
|
7364
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6986
7365
|
const source = buildGitHubProjectConfigSource({
|
|
6987
7366
|
projectName: rawProjectName,
|
|
6988
7367
|
owner,
|
|
@@ -6994,8 +7373,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6994
7373
|
ok: true,
|
|
6995
7374
|
projectRoot: targetRoot,
|
|
6996
7375
|
configPath,
|
|
6997
|
-
exists:
|
|
6998
|
-
requiresOverwrite:
|
|
7376
|
+
exists: existsSync13(configPath),
|
|
7377
|
+
requiresOverwrite: existsSync13(configPath),
|
|
6999
7378
|
source,
|
|
7000
7379
|
owner,
|
|
7001
7380
|
repo,
|
|
@@ -7031,8 +7410,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7031
7410
|
assignee,
|
|
7032
7411
|
githubUserId: authStatus.userId ?? authStatus.login
|
|
7033
7412
|
});
|
|
7034
|
-
const configPath =
|
|
7035
|
-
if (
|
|
7413
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
7414
|
+
if (existsSync13(configPath) && !overwrite) {
|
|
7036
7415
|
return deps.jsonResponse({
|
|
7037
7416
|
ok: false,
|
|
7038
7417
|
error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
|
|
@@ -7048,11 +7427,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7048
7427
|
return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
|
|
7049
7428
|
}
|
|
7050
7429
|
let backupPath = null;
|
|
7051
|
-
if (
|
|
7430
|
+
if (existsSync13(configPath)) {
|
|
7052
7431
|
backupPath = backupConfigPath(configPath);
|
|
7053
|
-
|
|
7432
|
+
copyFileSync2(configPath, backupPath);
|
|
7054
7433
|
}
|
|
7055
|
-
|
|
7434
|
+
writeFileSync12(configPath, source, "utf8");
|
|
7056
7435
|
const selectedRepo = `${owner}/${repo}`;
|
|
7057
7436
|
store.saveSelectedRepo(selectedRepo);
|
|
7058
7437
|
const targetStore = createGitHubAuthStore(targetRoot);
|
|
@@ -7338,7 +7717,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7338
7717
|
}
|
|
7339
7718
|
if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
|
|
7340
7719
|
const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
|
|
7341
|
-
const packageSource = configuredPackageSource ?? [process.env.RIG_HOST_PROJECT_ROOT, process.cwd(), state.projectRoot].map((root) => normalizeString(root)).filter((root) => Boolean(root)).map((root) =>
|
|
7720
|
+
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";
|
|
7342
7721
|
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
7343
7722
|
return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
|
|
7344
7723
|
}
|
|
@@ -7654,9 +8033,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7654
8033
|
} catch {
|
|
7655
8034
|
return deps.badRequest("Invalid artifact path");
|
|
7656
8035
|
}
|
|
7657
|
-
|
|
8036
|
+
mkdirSync13(dirname15(artifactPath), { recursive: true });
|
|
7658
8037
|
const bytes = Buffer.from(contentBase64, "base64");
|
|
7659
|
-
|
|
8038
|
+
writeFileSync12(artifactPath, bytes);
|
|
7660
8039
|
writeJsonFile4(`${artifactPath}.json`, {
|
|
7661
8040
|
workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
|
|
7662
8041
|
runId,
|
|
@@ -7699,7 +8078,6 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7699
8078
|
hostId,
|
|
7700
8079
|
endpointId: leaseId
|
|
7701
8080
|
});
|
|
7702
|
-
await updateRemoteRunTaskSourceLifecycle(state.projectRoot, { ...run, status: "completed", completedAt, hostId, endpointId: leaseId }, "closed", "Remote Rig task run completed and closed this task.");
|
|
7703
8081
|
await deps.enqueueRunLinearEvent(state.projectRoot, {
|
|
7704
8082
|
type: "run.completed",
|
|
7705
8083
|
runId,
|
|
@@ -7818,12 +8196,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7818
8196
|
try {
|
|
7819
8197
|
const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
|
|
7820
8198
|
const runRoot = deps.normalizeRelativePath(runsRoot, runId);
|
|
7821
|
-
const artifactsRoot =
|
|
8199
|
+
const artifactsRoot = resolve20(runRoot, "remote-artifacts");
|
|
7822
8200
|
artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
|
|
7823
8201
|
} catch {
|
|
7824
8202
|
return deps.badRequest("Invalid artifact path");
|
|
7825
8203
|
}
|
|
7826
|
-
if (!
|
|
8204
|
+
if (!existsSync13(artifactPath)) {
|
|
7827
8205
|
return deps.notFound();
|
|
7828
8206
|
}
|
|
7829
8207
|
return new Response(Bun.file(artifactPath));
|
|
@@ -8716,8 +9094,8 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
8716
9094
|
}
|
|
8717
9095
|
|
|
8718
9096
|
// packages/server/src/server-helpers/inspector-jobs.ts
|
|
8719
|
-
import { existsSync as
|
|
8720
|
-
import { dirname as
|
|
9097
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
9098
|
+
import { dirname as dirname18, resolve as resolve23 } from "path";
|
|
8721
9099
|
import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
|
|
8722
9100
|
import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
|
|
8723
9101
|
import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -8825,8 +9203,8 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8825
9203
|
|
|
8826
9204
|
// packages/server/src/inspector/mission.ts
|
|
8827
9205
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8828
|
-
import { appendFileSync, existsSync as
|
|
8829
|
-
import { dirname as
|
|
9206
|
+
import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync13 } from "fs";
|
|
9207
|
+
import { dirname as dirname16, join, resolve as resolve21 } from "path";
|
|
8830
9208
|
function isJsonValue(value) {
|
|
8831
9209
|
if (value === null)
|
|
8832
9210
|
return true;
|
|
@@ -8866,7 +9244,7 @@ function isRecord2(value) {
|
|
|
8866
9244
|
}
|
|
8867
9245
|
function readJsonRecord(path) {
|
|
8868
9246
|
try {
|
|
8869
|
-
const parsed = JSON.parse(
|
|
9247
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
8870
9248
|
if (!isRecord2(parsed)) {
|
|
8871
9249
|
return { ok: false, error: `Mission file ${path} does not contain an object` };
|
|
8872
9250
|
}
|
|
@@ -8946,14 +9324,14 @@ function missionActionDetails(mission) {
|
|
|
8946
9324
|
};
|
|
8947
9325
|
}
|
|
8948
9326
|
function writeJsonFile5(path, value) {
|
|
8949
|
-
|
|
9327
|
+
mkdirSync14(dirname16(path), { recursive: true });
|
|
8950
9328
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
8951
|
-
|
|
9329
|
+
writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
|
|
8952
9330
|
`, "utf8");
|
|
8953
9331
|
renameSync(tempPath, path);
|
|
8954
9332
|
}
|
|
8955
9333
|
function resolveInspectorMissionPaths(projectRoot) {
|
|
8956
|
-
const inspectorDir =
|
|
9334
|
+
const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
|
|
8957
9335
|
return {
|
|
8958
9336
|
inspectorDir,
|
|
8959
9337
|
missionsDir: join(inspectorDir, "missions"),
|
|
@@ -8962,8 +9340,8 @@ function resolveInspectorMissionPaths(projectRoot) {
|
|
|
8962
9340
|
}
|
|
8963
9341
|
function createInspectorMissionController(options) {
|
|
8964
9342
|
const paths = resolveInspectorMissionPaths(options.projectRoot);
|
|
8965
|
-
|
|
8966
|
-
|
|
9343
|
+
mkdirSync14(paths.missionsDir, { recursive: true });
|
|
9344
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8967
9345
|
const now = options.now ?? (() => new Date().toISOString());
|
|
8968
9346
|
const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
|
|
8969
9347
|
function missionPath(missionId) {
|
|
@@ -8973,15 +9351,15 @@ function createInspectorMissionController(options) {
|
|
|
8973
9351
|
return join(paths.journalsDir, `${missionId}.jsonl`);
|
|
8974
9352
|
}
|
|
8975
9353
|
function appendMissionJournal(entry) {
|
|
8976
|
-
|
|
9354
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8977
9355
|
appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
|
|
8978
9356
|
`, "utf8");
|
|
8979
9357
|
}
|
|
8980
9358
|
function listMissionJournal(missionId) {
|
|
8981
9359
|
const path = journalPath(missionId);
|
|
8982
|
-
if (!
|
|
9360
|
+
if (!existsSync14(path))
|
|
8983
9361
|
return [];
|
|
8984
|
-
return
|
|
9362
|
+
return readFileSync10(path, "utf8").split(`
|
|
8985
9363
|
`).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
|
|
8986
9364
|
id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
|
|
8987
9365
|
missionId,
|
|
@@ -8997,7 +9375,7 @@ function createInspectorMissionController(options) {
|
|
|
8997
9375
|
}
|
|
8998
9376
|
function readMissionOnly(missionId) {
|
|
8999
9377
|
const path = missionPath(missionId);
|
|
9000
|
-
if (!
|
|
9378
|
+
if (!existsSync14(path)) {
|
|
9001
9379
|
return { ok: false, error: `Mission ${missionId} was not found` };
|
|
9002
9380
|
}
|
|
9003
9381
|
const read = readJsonRecord(path);
|
|
@@ -9048,7 +9426,7 @@ function createInspectorMissionController(options) {
|
|
|
9048
9426
|
const source = cloneJsonRecord(input.sourceTask);
|
|
9049
9427
|
const missionId = nextId();
|
|
9050
9428
|
const path = missionPath(missionId);
|
|
9051
|
-
if (
|
|
9429
|
+
if (existsSync14(path)) {
|
|
9052
9430
|
const existing = readMissionOnly(missionId);
|
|
9053
9431
|
if (!existing.ok)
|
|
9054
9432
|
return existing;
|
|
@@ -10648,8 +11026,8 @@ function createCodexInspectorTransport(options) {
|
|
|
10648
11026
|
const sendRequest = async (method, params) => {
|
|
10649
11027
|
const id = nextRequestId;
|
|
10650
11028
|
nextRequestId += 1;
|
|
10651
|
-
const response = new Promise((
|
|
10652
|
-
pendingResponses.set(id, { resolve:
|
|
11029
|
+
const response = new Promise((resolve22, reject) => {
|
|
11030
|
+
pendingResponses.set(id, { resolve: resolve22, reject });
|
|
10653
11031
|
});
|
|
10654
11032
|
response.catch(() => {});
|
|
10655
11033
|
try {
|
|
@@ -10959,9 +11337,9 @@ function createCodexInspectorTransport(options) {
|
|
|
10959
11337
|
}
|
|
10960
11338
|
lastAssistantMessage = null;
|
|
10961
11339
|
lastError = null;
|
|
10962
|
-
const turnResult = new Promise((
|
|
11340
|
+
const turnResult = new Promise((resolve22, reject) => {
|
|
10963
11341
|
currentTurn = {
|
|
10964
|
-
resolve:
|
|
11342
|
+
resolve: resolve22,
|
|
10965
11343
|
reject,
|
|
10966
11344
|
events: []
|
|
10967
11345
|
};
|
|
@@ -11021,13 +11399,13 @@ function createCodexInspectorTransport(options) {
|
|
|
11021
11399
|
};
|
|
11022
11400
|
}
|
|
11023
11401
|
function writeChildLine(child, line) {
|
|
11024
|
-
return new Promise((
|
|
11402
|
+
return new Promise((resolve22, reject) => {
|
|
11025
11403
|
child.stdin.write(line, (error) => {
|
|
11026
11404
|
if (error) {
|
|
11027
11405
|
reject(error);
|
|
11028
11406
|
return;
|
|
11029
11407
|
}
|
|
11030
|
-
|
|
11408
|
+
resolve22();
|
|
11031
11409
|
});
|
|
11032
11410
|
});
|
|
11033
11411
|
}
|
|
@@ -11040,10 +11418,10 @@ function terminateChild(child) {
|
|
|
11040
11418
|
} catch {}
|
|
11041
11419
|
}
|
|
11042
11420
|
async function waitForChildSpawn(child) {
|
|
11043
|
-
await new Promise((
|
|
11421
|
+
await new Promise((resolve22, reject) => {
|
|
11044
11422
|
const onSpawn = () => {
|
|
11045
11423
|
cleanup();
|
|
11046
|
-
|
|
11424
|
+
resolve22();
|
|
11047
11425
|
};
|
|
11048
11426
|
const onError = (error) => {
|
|
11049
11427
|
cleanup();
|
|
@@ -11555,8 +11933,8 @@ function createGlobalInspectorService(options) {
|
|
|
11555
11933
|
|
|
11556
11934
|
// packages/server/src/inspector/upstream-sync.ts
|
|
11557
11935
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11558
|
-
import { existsSync as
|
|
11559
|
-
import { dirname as
|
|
11936
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
11937
|
+
import { dirname as dirname17, resolve as resolve22 } from "path";
|
|
11560
11938
|
import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
|
|
11561
11939
|
var UPSTREAM_VALIDATION_DESCRIPTIONS = {
|
|
11562
11940
|
"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.",
|
|
@@ -11694,34 +12072,34 @@ function defaultGitRunner(repoRoot, args) {
|
|
|
11694
12072
|
}
|
|
11695
12073
|
function upstreamStatePath(projectRoot, override) {
|
|
11696
12074
|
if (override) {
|
|
11697
|
-
return
|
|
12075
|
+
return resolve22(override);
|
|
11698
12076
|
}
|
|
11699
|
-
return
|
|
12077
|
+
return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
|
|
11700
12078
|
}
|
|
11701
12079
|
function readUpstreamState(projectRoot, statePath) {
|
|
11702
12080
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11703
|
-
if (!
|
|
12081
|
+
if (!existsSync15(path)) {
|
|
11704
12082
|
return null;
|
|
11705
12083
|
}
|
|
11706
12084
|
try {
|
|
11707
|
-
return JSON.parse(
|
|
12085
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
11708
12086
|
} catch {
|
|
11709
12087
|
return null;
|
|
11710
12088
|
}
|
|
11711
12089
|
}
|
|
11712
12090
|
function writeUpstreamState(projectRoot, state, statePath) {
|
|
11713
12091
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11714
|
-
|
|
11715
|
-
|
|
12092
|
+
mkdirSync15(dirname17(path), { recursive: true });
|
|
12093
|
+
writeFileSync14(path, `${JSON.stringify(state, null, 2)}
|
|
11716
12094
|
`, "utf8");
|
|
11717
12095
|
}
|
|
11718
12096
|
function readImportedRevision(projectRoot, upstreamsDocPath) {
|
|
11719
12097
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11720
|
-
const docPath = upstreamsDocPath ?
|
|
11721
|
-
if (!
|
|
12098
|
+
const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
|
|
12099
|
+
if (!existsSync15(docPath)) {
|
|
11722
12100
|
throw new Error(`UPSTREAMS.md not found at ${docPath}`);
|
|
11723
12101
|
}
|
|
11724
|
-
const docContent =
|
|
12102
|
+
const docContent = readFileSync11(docPath, "utf-8");
|
|
11725
12103
|
const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
|
|
11726
12104
|
if (!revision) {
|
|
11727
12105
|
throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
|
|
@@ -11743,7 +12121,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
|
|
|
11743
12121
|
return null;
|
|
11744
12122
|
}
|
|
11745
12123
|
function isGitCheckout(path, gitRunner) {
|
|
11746
|
-
if (!
|
|
12124
|
+
if (!existsSync15(resolve22(path, ".git"))) {
|
|
11747
12125
|
return false;
|
|
11748
12126
|
}
|
|
11749
12127
|
const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -11752,12 +12130,12 @@ function isGitCheckout(path, gitRunner) {
|
|
|
11752
12130
|
function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
|
|
11753
12131
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11754
12132
|
const candidates = [
|
|
11755
|
-
explicitCheckout ?
|
|
11756
|
-
process.env.UPSTREAM_CHECKOUT?.trim() ?
|
|
11757
|
-
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ?
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
12133
|
+
explicitCheckout ? resolve22(explicitCheckout) : "",
|
|
12134
|
+
process.env.UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.UPSTREAM_CHECKOUT.trim()) : "",
|
|
12135
|
+
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
|
|
12136
|
+
resolve22(projectRoot, "..", "humoongate"),
|
|
12137
|
+
resolve22(monorepoRoot, "..", "humoongate"),
|
|
12138
|
+
resolve22(monorepoRoot, "humoongate")
|
|
11761
12139
|
].filter(Boolean);
|
|
11762
12140
|
for (const candidate of candidates) {
|
|
11763
12141
|
if (isGitCheckout(candidate, gitRunner)) {
|
|
@@ -11993,10 +12371,10 @@ async function runUpstreamSyncScan(options) {
|
|
|
11993
12371
|
}
|
|
11994
12372
|
|
|
11995
12373
|
// packages/server/src/server-helpers/task-config.ts
|
|
11996
|
-
import { existsSync as
|
|
12374
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11997
12375
|
async function readTaskConfig(projectRoot) {
|
|
11998
12376
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
11999
|
-
if (!
|
|
12377
|
+
if (!existsSync16(taskConfigPath)) {
|
|
12000
12378
|
return {};
|
|
12001
12379
|
}
|
|
12002
12380
|
try {
|
|
@@ -12032,11 +12410,11 @@ function resolveFollowupSourceCommit(input) {
|
|
|
12032
12410
|
}
|
|
12033
12411
|
async function createInspectorFollowupTask(projectRoot, input) {
|
|
12034
12412
|
const monorepoRoot = resolveMonorepoRoot5(projectRoot);
|
|
12035
|
-
const issuesPath =
|
|
12036
|
-
const taskStatePath =
|
|
12037
|
-
const taskConfigPath =
|
|
12038
|
-
|
|
12039
|
-
|
|
12413
|
+
const issuesPath = resolve23(monorepoRoot, ".beads", "issues.jsonl");
|
|
12414
|
+
const taskStatePath = resolve23(monorepoRoot, ".beads", "task-state.json");
|
|
12415
|
+
const taskConfigPath = resolve23(monorepoRoot, ".rig", "task-config.json");
|
|
12416
|
+
mkdirSync16(dirname18(issuesPath), { recursive: true });
|
|
12417
|
+
mkdirSync16(dirname18(taskConfigPath), { recursive: true });
|
|
12040
12418
|
const summary = normalizeString(input.summary) ?? "Inspector follow-up";
|
|
12041
12419
|
const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
|
|
12042
12420
|
const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
|
|
@@ -12055,7 +12433,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
12055
12433
|
const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
|
|
12056
12434
|
const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
|
|
12057
12435
|
const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
|
|
12058
|
-
const existingIssueLines =
|
|
12436
|
+
const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
12059
12437
|
const existingIssues = existingIssueLines.map((line) => {
|
|
12060
12438
|
try {
|
|
12061
12439
|
return JSON.parse(line);
|
|
@@ -12064,7 +12442,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
12064
12442
|
}
|
|
12065
12443
|
}).filter((value) => value !== null);
|
|
12066
12444
|
const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
|
|
12067
|
-
const rawTaskState =
|
|
12445
|
+
const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
|
|
12068
12446
|
const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
|
|
12069
12447
|
const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
|
|
12070
12448
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
@@ -12090,7 +12468,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
12090
12468
|
updated_at: createdAt,
|
|
12091
12469
|
labels: mergedLabels
|
|
12092
12470
|
};
|
|
12093
|
-
|
|
12471
|
+
writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
|
|
12094
12472
|
`)}
|
|
12095
12473
|
${JSON.stringify(issueRecord)}
|
|
12096
12474
|
` : `${JSON.stringify(issueRecord)}
|
|
@@ -12108,7 +12486,7 @@ ${JSON.stringify(issueRecord)}
|
|
|
12108
12486
|
labels: mergedLabels
|
|
12109
12487
|
};
|
|
12110
12488
|
});
|
|
12111
|
-
|
|
12489
|
+
writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
|
|
12112
12490
|
`)}
|
|
12113
12491
|
`, "utf8");
|
|
12114
12492
|
}
|
|
@@ -12131,14 +12509,14 @@ ${JSON.stringify(issueRecord)}
|
|
|
12131
12509
|
}
|
|
12132
12510
|
};
|
|
12133
12511
|
}
|
|
12134
|
-
|
|
12512
|
+
writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
|
|
12135
12513
|
`, "utf8");
|
|
12136
12514
|
tasks[taskId] = {
|
|
12137
12515
|
status,
|
|
12138
12516
|
sourceCommit: resolveFollowupSourceCommit(input),
|
|
12139
12517
|
...sourceKey ? { sourceKey } : {}
|
|
12140
12518
|
};
|
|
12141
|
-
|
|
12519
|
+
writeFileSync15(taskStatePath, `${JSON.stringify({
|
|
12142
12520
|
schemaVersion: 1,
|
|
12143
12521
|
baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
|
|
12144
12522
|
tasks
|
|
@@ -12446,12 +12824,12 @@ function isAuthorizedLinearWebhookRequest(req) {
|
|
|
12446
12824
|
}
|
|
12447
12825
|
|
|
12448
12826
|
// packages/server/src/server-helpers/notifications.ts
|
|
12449
|
-
import { existsSync as
|
|
12450
|
-
import { dirname as
|
|
12827
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
|
|
12828
|
+
import { dirname as dirname19 } from "path";
|
|
12451
12829
|
async function loadNotificationConfig(path) {
|
|
12452
|
-
if (!
|
|
12830
|
+
if (!existsSync18(path)) {
|
|
12453
12831
|
const defaultConfig = { targets: [] };
|
|
12454
|
-
|
|
12832
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
12455
12833
|
await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
|
|
12456
12834
|
`);
|
|
12457
12835
|
return defaultConfig;
|
|
@@ -12466,10 +12844,10 @@ async function loadNotificationConfig(path) {
|
|
|
12466
12844
|
}
|
|
12467
12845
|
}
|
|
12468
12846
|
function readRecentEvents(file, limit) {
|
|
12469
|
-
if (!
|
|
12847
|
+
if (!existsSync18(file)) {
|
|
12470
12848
|
return [];
|
|
12471
12849
|
}
|
|
12472
|
-
const lines =
|
|
12850
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
|
|
12473
12851
|
const events = [];
|
|
12474
12852
|
for (const line of lines) {
|
|
12475
12853
|
try {
|
|
@@ -12564,11 +12942,11 @@ function extractObjectLiteralBlock(source, property) {
|
|
|
12564
12942
|
}
|
|
12565
12943
|
function readFallbackIssueAnalysisConfig(projectRoot) {
|
|
12566
12944
|
for (const fileName of ["rig.config.ts", "rig.config.json"]) {
|
|
12567
|
-
const path =
|
|
12568
|
-
if (!
|
|
12945
|
+
const path = resolve24(projectRoot, fileName);
|
|
12946
|
+
if (!existsSync19(path))
|
|
12569
12947
|
continue;
|
|
12570
12948
|
try {
|
|
12571
|
-
const source =
|
|
12949
|
+
const source = readFileSync14(path, "utf8");
|
|
12572
12950
|
if (fileName.endsWith(".json"))
|
|
12573
12951
|
return JSON.parse(source);
|
|
12574
12952
|
const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
|
|
@@ -12701,8 +13079,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
|
|
|
12701
13079
|
async function withServerPathEnv(projectRoot, fn) {
|
|
12702
13080
|
const waitForTurn = serverPathEnvQueue;
|
|
12703
13081
|
let releaseTurn;
|
|
12704
|
-
serverPathEnvQueue = new Promise((
|
|
12705
|
-
releaseTurn =
|
|
13082
|
+
serverPathEnvQueue = new Promise((resolve25) => {
|
|
13083
|
+
releaseTurn = resolve25;
|
|
12706
13084
|
});
|
|
12707
13085
|
await waitForTurn;
|
|
12708
13086
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -12738,9 +13116,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
12738
13116
|
return withServerPathEnv(projectRoot, fn);
|
|
12739
13117
|
}
|
|
12740
13118
|
async function readWorkspaceTasks(projectRoot) {
|
|
12741
|
-
const issuesPath =
|
|
13119
|
+
const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
|
|
12742
13120
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
12743
|
-
if (!
|
|
13121
|
+
if (!existsSync19(issuesPath)) {
|
|
12744
13122
|
return [];
|
|
12745
13123
|
}
|
|
12746
13124
|
const latestById = new Map;
|
|
@@ -12814,11 +13192,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
|
|
|
12814
13192
|
continue;
|
|
12815
13193
|
add(run.artifactRoot);
|
|
12816
13194
|
if (run.worktreePath) {
|
|
12817
|
-
add(
|
|
13195
|
+
add(resolve24(run.worktreePath, "artifacts", taskId));
|
|
12818
13196
|
}
|
|
12819
13197
|
}
|
|
12820
13198
|
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
12821
|
-
add(
|
|
13199
|
+
add(resolve24(artifactsRoot, taskId));
|
|
12822
13200
|
}
|
|
12823
13201
|
return candidates;
|
|
12824
13202
|
}
|
|
@@ -12832,7 +13210,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12832
13210
|
}
|
|
12833
13211
|
}
|
|
12834
13212
|
return taskIds.flatMap((currentTaskId) => {
|
|
12835
|
-
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) =>
|
|
13213
|
+
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
|
|
12836
13214
|
if (!currentRoot) {
|
|
12837
13215
|
return [];
|
|
12838
13216
|
}
|
|
@@ -12844,7 +13222,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12844
13222
|
taskId: currentTaskId,
|
|
12845
13223
|
kind: "file",
|
|
12846
13224
|
label: fileName,
|
|
12847
|
-
path:
|
|
13225
|
+
path: resolve24(currentRoot, fileName),
|
|
12848
13226
|
url: null,
|
|
12849
13227
|
metadata: {
|
|
12850
13228
|
fileName
|
|
@@ -12887,11 +13265,11 @@ function buildInspectorStreamPayload(state, sequence) {
|
|
|
12887
13265
|
}
|
|
12888
13266
|
function listRemoteRunArtifacts(projectRoot, runId) {
|
|
12889
13267
|
const root = remoteArtifactsRoot(projectRoot, runId);
|
|
12890
|
-
if (!
|
|
13268
|
+
if (!existsSync19(root)) {
|
|
12891
13269
|
return [];
|
|
12892
13270
|
}
|
|
12893
13271
|
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
|
|
12894
|
-
const artifactPath =
|
|
13272
|
+
const artifactPath = resolve24(root, entry.name);
|
|
12895
13273
|
const stat = statSync6(artifactPath);
|
|
12896
13274
|
const meta = readJsonFile4(`${artifactPath}.json`, null);
|
|
12897
13275
|
return {
|
|
@@ -13133,8 +13511,8 @@ function fileStats(path) {
|
|
|
13133
13511
|
}
|
|
13134
13512
|
}
|
|
13135
13513
|
function runFileCursor(projectRoot, run) {
|
|
13136
|
-
const runDir =
|
|
13137
|
-
const runJson = fileStats(
|
|
13514
|
+
const runDir = dirname20(runLogsPath(projectRoot, run.runId));
|
|
13515
|
+
const runJson = fileStats(resolve24(runDir, "run.json"));
|
|
13138
13516
|
const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
|
|
13139
13517
|
const logs = fileStats(runLogsPath(projectRoot, run.runId));
|
|
13140
13518
|
return {
|
|
@@ -13184,10 +13562,10 @@ function startRunFileWatcher(state, pollMs) {
|
|
|
13184
13562
|
}, Math.max(250, Math.min(pollMs, 1000)));
|
|
13185
13563
|
}
|
|
13186
13564
|
function startPoller(state, pollMs) {
|
|
13187
|
-
let offset =
|
|
13565
|
+
let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
|
|
13188
13566
|
return setInterval(async () => {
|
|
13189
13567
|
try {
|
|
13190
|
-
if (!
|
|
13568
|
+
if (!existsSync19(state.eventsFile)) {
|
|
13191
13569
|
return;
|
|
13192
13570
|
}
|
|
13193
13571
|
const file = await open(state.eventsFile, "r");
|
|
@@ -13315,7 +13693,7 @@ function resolveProjectRoot() {
|
|
|
13315
13693
|
return resolveRigProjectRoot({
|
|
13316
13694
|
envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
|
|
13317
13695
|
cwd: process.cwd(),
|
|
13318
|
-
fallbackRoot:
|
|
13696
|
+
fallbackRoot: resolve24(import.meta.dir, "../..")
|
|
13319
13697
|
});
|
|
13320
13698
|
}
|
|
13321
13699
|
var __testOnly = {
|