@h-rig/server 0.0.6-alpha.10 → 0.0.6-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/src/index.js +620 -241
- package/dist/src/server-helpers/github-api-session-index.js +107 -0
- package/dist/src/server-helpers/github-auth-store.js +68 -24
- package/dist/src/server-helpers/github-user-namespace.js +102 -0
- package/dist/src/server-helpers/http-router.js +538 -159
- package/dist/src/server-helpers/project-registry.js +5 -0
- package/dist/src/server-helpers/run-mutations.js +68 -26
- package/dist/src/server.js +620 -241
- package/package.json +4 -4
package/dist/src/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,
|
|
@@ -7818,12 +8197,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7818
8197
|
try {
|
|
7819
8198
|
const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
|
|
7820
8199
|
const runRoot = deps.normalizeRelativePath(runsRoot, runId);
|
|
7821
|
-
const artifactsRoot =
|
|
8200
|
+
const artifactsRoot = resolve20(runRoot, "remote-artifacts");
|
|
7822
8201
|
artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
|
|
7823
8202
|
} catch {
|
|
7824
8203
|
return deps.badRequest("Invalid artifact path");
|
|
7825
8204
|
}
|
|
7826
|
-
if (!
|
|
8205
|
+
if (!existsSync13(artifactPath)) {
|
|
7827
8206
|
return deps.notFound();
|
|
7828
8207
|
}
|
|
7829
8208
|
return new Response(Bun.file(artifactPath));
|
|
@@ -8716,8 +9095,8 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
8716
9095
|
}
|
|
8717
9096
|
|
|
8718
9097
|
// packages/server/src/server-helpers/inspector-jobs.ts
|
|
8719
|
-
import { existsSync as
|
|
8720
|
-
import { dirname as
|
|
9098
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
9099
|
+
import { dirname as dirname18, resolve as resolve23 } from "path";
|
|
8721
9100
|
import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
|
|
8722
9101
|
import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
|
|
8723
9102
|
import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -8825,8 +9204,8 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8825
9204
|
|
|
8826
9205
|
// packages/server/src/inspector/mission.ts
|
|
8827
9206
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8828
|
-
import { appendFileSync, existsSync as
|
|
8829
|
-
import { dirname as
|
|
9207
|
+
import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync13 } from "fs";
|
|
9208
|
+
import { dirname as dirname16, join, resolve as resolve21 } from "path";
|
|
8830
9209
|
function isJsonValue(value) {
|
|
8831
9210
|
if (value === null)
|
|
8832
9211
|
return true;
|
|
@@ -8866,7 +9245,7 @@ function isRecord2(value) {
|
|
|
8866
9245
|
}
|
|
8867
9246
|
function readJsonRecord(path) {
|
|
8868
9247
|
try {
|
|
8869
|
-
const parsed = JSON.parse(
|
|
9248
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
8870
9249
|
if (!isRecord2(parsed)) {
|
|
8871
9250
|
return { ok: false, error: `Mission file ${path} does not contain an object` };
|
|
8872
9251
|
}
|
|
@@ -8946,14 +9325,14 @@ function missionActionDetails(mission) {
|
|
|
8946
9325
|
};
|
|
8947
9326
|
}
|
|
8948
9327
|
function writeJsonFile5(path, value) {
|
|
8949
|
-
|
|
9328
|
+
mkdirSync14(dirname16(path), { recursive: true });
|
|
8950
9329
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
8951
|
-
|
|
9330
|
+
writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
|
|
8952
9331
|
`, "utf8");
|
|
8953
9332
|
renameSync(tempPath, path);
|
|
8954
9333
|
}
|
|
8955
9334
|
function resolveInspectorMissionPaths(projectRoot) {
|
|
8956
|
-
const inspectorDir =
|
|
9335
|
+
const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
|
|
8957
9336
|
return {
|
|
8958
9337
|
inspectorDir,
|
|
8959
9338
|
missionsDir: join(inspectorDir, "missions"),
|
|
@@ -8962,8 +9341,8 @@ function resolveInspectorMissionPaths(projectRoot) {
|
|
|
8962
9341
|
}
|
|
8963
9342
|
function createInspectorMissionController(options) {
|
|
8964
9343
|
const paths = resolveInspectorMissionPaths(options.projectRoot);
|
|
8965
|
-
|
|
8966
|
-
|
|
9344
|
+
mkdirSync14(paths.missionsDir, { recursive: true });
|
|
9345
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8967
9346
|
const now = options.now ?? (() => new Date().toISOString());
|
|
8968
9347
|
const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
|
|
8969
9348
|
function missionPath(missionId) {
|
|
@@ -8973,15 +9352,15 @@ function createInspectorMissionController(options) {
|
|
|
8973
9352
|
return join(paths.journalsDir, `${missionId}.jsonl`);
|
|
8974
9353
|
}
|
|
8975
9354
|
function appendMissionJournal(entry) {
|
|
8976
|
-
|
|
9355
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8977
9356
|
appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
|
|
8978
9357
|
`, "utf8");
|
|
8979
9358
|
}
|
|
8980
9359
|
function listMissionJournal(missionId) {
|
|
8981
9360
|
const path = journalPath(missionId);
|
|
8982
|
-
if (!
|
|
9361
|
+
if (!existsSync14(path))
|
|
8983
9362
|
return [];
|
|
8984
|
-
return
|
|
9363
|
+
return readFileSync10(path, "utf8").split(`
|
|
8985
9364
|
`).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
|
|
8986
9365
|
id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
|
|
8987
9366
|
missionId,
|
|
@@ -8997,7 +9376,7 @@ function createInspectorMissionController(options) {
|
|
|
8997
9376
|
}
|
|
8998
9377
|
function readMissionOnly(missionId) {
|
|
8999
9378
|
const path = missionPath(missionId);
|
|
9000
|
-
if (!
|
|
9379
|
+
if (!existsSync14(path)) {
|
|
9001
9380
|
return { ok: false, error: `Mission ${missionId} was not found` };
|
|
9002
9381
|
}
|
|
9003
9382
|
const read = readJsonRecord(path);
|
|
@@ -9048,7 +9427,7 @@ function createInspectorMissionController(options) {
|
|
|
9048
9427
|
const source = cloneJsonRecord(input.sourceTask);
|
|
9049
9428
|
const missionId = nextId();
|
|
9050
9429
|
const path = missionPath(missionId);
|
|
9051
|
-
if (
|
|
9430
|
+
if (existsSync14(path)) {
|
|
9052
9431
|
const existing = readMissionOnly(missionId);
|
|
9053
9432
|
if (!existing.ok)
|
|
9054
9433
|
return existing;
|
|
@@ -10648,8 +11027,8 @@ function createCodexInspectorTransport(options) {
|
|
|
10648
11027
|
const sendRequest = async (method, params) => {
|
|
10649
11028
|
const id = nextRequestId;
|
|
10650
11029
|
nextRequestId += 1;
|
|
10651
|
-
const response = new Promise((
|
|
10652
|
-
pendingResponses.set(id, { resolve:
|
|
11030
|
+
const response = new Promise((resolve22, reject) => {
|
|
11031
|
+
pendingResponses.set(id, { resolve: resolve22, reject });
|
|
10653
11032
|
});
|
|
10654
11033
|
response.catch(() => {});
|
|
10655
11034
|
try {
|
|
@@ -10959,9 +11338,9 @@ function createCodexInspectorTransport(options) {
|
|
|
10959
11338
|
}
|
|
10960
11339
|
lastAssistantMessage = null;
|
|
10961
11340
|
lastError = null;
|
|
10962
|
-
const turnResult = new Promise((
|
|
11341
|
+
const turnResult = new Promise((resolve22, reject) => {
|
|
10963
11342
|
currentTurn = {
|
|
10964
|
-
resolve:
|
|
11343
|
+
resolve: resolve22,
|
|
10965
11344
|
reject,
|
|
10966
11345
|
events: []
|
|
10967
11346
|
};
|
|
@@ -11021,13 +11400,13 @@ function createCodexInspectorTransport(options) {
|
|
|
11021
11400
|
};
|
|
11022
11401
|
}
|
|
11023
11402
|
function writeChildLine(child, line) {
|
|
11024
|
-
return new Promise((
|
|
11403
|
+
return new Promise((resolve22, reject) => {
|
|
11025
11404
|
child.stdin.write(line, (error) => {
|
|
11026
11405
|
if (error) {
|
|
11027
11406
|
reject(error);
|
|
11028
11407
|
return;
|
|
11029
11408
|
}
|
|
11030
|
-
|
|
11409
|
+
resolve22();
|
|
11031
11410
|
});
|
|
11032
11411
|
});
|
|
11033
11412
|
}
|
|
@@ -11040,10 +11419,10 @@ function terminateChild(child) {
|
|
|
11040
11419
|
} catch {}
|
|
11041
11420
|
}
|
|
11042
11421
|
async function waitForChildSpawn(child) {
|
|
11043
|
-
await new Promise((
|
|
11422
|
+
await new Promise((resolve22, reject) => {
|
|
11044
11423
|
const onSpawn = () => {
|
|
11045
11424
|
cleanup();
|
|
11046
|
-
|
|
11425
|
+
resolve22();
|
|
11047
11426
|
};
|
|
11048
11427
|
const onError = (error) => {
|
|
11049
11428
|
cleanup();
|
|
@@ -11555,8 +11934,8 @@ function createGlobalInspectorService(options) {
|
|
|
11555
11934
|
|
|
11556
11935
|
// packages/server/src/inspector/upstream-sync.ts
|
|
11557
11936
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11558
|
-
import { existsSync as
|
|
11559
|
-
import { dirname as
|
|
11937
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
11938
|
+
import { dirname as dirname17, resolve as resolve22 } from "path";
|
|
11560
11939
|
import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
|
|
11561
11940
|
var UPSTREAM_VALIDATION_DESCRIPTIONS = {
|
|
11562
11941
|
"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 +12073,34 @@ function defaultGitRunner(repoRoot, args) {
|
|
|
11694
12073
|
}
|
|
11695
12074
|
function upstreamStatePath(projectRoot, override) {
|
|
11696
12075
|
if (override) {
|
|
11697
|
-
return
|
|
12076
|
+
return resolve22(override);
|
|
11698
12077
|
}
|
|
11699
|
-
return
|
|
12078
|
+
return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
|
|
11700
12079
|
}
|
|
11701
12080
|
function readUpstreamState(projectRoot, statePath) {
|
|
11702
12081
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11703
|
-
if (!
|
|
12082
|
+
if (!existsSync15(path)) {
|
|
11704
12083
|
return null;
|
|
11705
12084
|
}
|
|
11706
12085
|
try {
|
|
11707
|
-
return JSON.parse(
|
|
12086
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
11708
12087
|
} catch {
|
|
11709
12088
|
return null;
|
|
11710
12089
|
}
|
|
11711
12090
|
}
|
|
11712
12091
|
function writeUpstreamState(projectRoot, state, statePath) {
|
|
11713
12092
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11714
|
-
|
|
11715
|
-
|
|
12093
|
+
mkdirSync15(dirname17(path), { recursive: true });
|
|
12094
|
+
writeFileSync14(path, `${JSON.stringify(state, null, 2)}
|
|
11716
12095
|
`, "utf8");
|
|
11717
12096
|
}
|
|
11718
12097
|
function readImportedRevision(projectRoot, upstreamsDocPath) {
|
|
11719
12098
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11720
|
-
const docPath = upstreamsDocPath ?
|
|
11721
|
-
if (!
|
|
12099
|
+
const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
|
|
12100
|
+
if (!existsSync15(docPath)) {
|
|
11722
12101
|
throw new Error(`UPSTREAMS.md not found at ${docPath}`);
|
|
11723
12102
|
}
|
|
11724
|
-
const docContent =
|
|
12103
|
+
const docContent = readFileSync11(docPath, "utf-8");
|
|
11725
12104
|
const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
|
|
11726
12105
|
if (!revision) {
|
|
11727
12106
|
throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
|
|
@@ -11743,7 +12122,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
|
|
|
11743
12122
|
return null;
|
|
11744
12123
|
}
|
|
11745
12124
|
function isGitCheckout(path, gitRunner) {
|
|
11746
|
-
if (!
|
|
12125
|
+
if (!existsSync15(resolve22(path, ".git"))) {
|
|
11747
12126
|
return false;
|
|
11748
12127
|
}
|
|
11749
12128
|
const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -11752,12 +12131,12 @@ function isGitCheckout(path, gitRunner) {
|
|
|
11752
12131
|
function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
|
|
11753
12132
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11754
12133
|
const candidates = [
|
|
11755
|
-
explicitCheckout ?
|
|
11756
|
-
process.env.UPSTREAM_CHECKOUT?.trim() ?
|
|
11757
|
-
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ?
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
12134
|
+
explicitCheckout ? resolve22(explicitCheckout) : "",
|
|
12135
|
+
process.env.UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.UPSTREAM_CHECKOUT.trim()) : "",
|
|
12136
|
+
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
|
|
12137
|
+
resolve22(projectRoot, "..", "humoongate"),
|
|
12138
|
+
resolve22(monorepoRoot, "..", "humoongate"),
|
|
12139
|
+
resolve22(monorepoRoot, "humoongate")
|
|
11761
12140
|
].filter(Boolean);
|
|
11762
12141
|
for (const candidate of candidates) {
|
|
11763
12142
|
if (isGitCheckout(candidate, gitRunner)) {
|
|
@@ -11993,10 +12372,10 @@ async function runUpstreamSyncScan(options) {
|
|
|
11993
12372
|
}
|
|
11994
12373
|
|
|
11995
12374
|
// packages/server/src/server-helpers/task-config.ts
|
|
11996
|
-
import { existsSync as
|
|
12375
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11997
12376
|
async function readTaskConfig(projectRoot) {
|
|
11998
12377
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
11999
|
-
if (!
|
|
12378
|
+
if (!existsSync16(taskConfigPath)) {
|
|
12000
12379
|
return {};
|
|
12001
12380
|
}
|
|
12002
12381
|
try {
|
|
@@ -12032,11 +12411,11 @@ function resolveFollowupSourceCommit(input) {
|
|
|
12032
12411
|
}
|
|
12033
12412
|
async function createInspectorFollowupTask(projectRoot, input) {
|
|
12034
12413
|
const monorepoRoot = resolveMonorepoRoot5(projectRoot);
|
|
12035
|
-
const issuesPath =
|
|
12036
|
-
const taskStatePath =
|
|
12037
|
-
const taskConfigPath =
|
|
12038
|
-
|
|
12039
|
-
|
|
12414
|
+
const issuesPath = resolve23(monorepoRoot, ".beads", "issues.jsonl");
|
|
12415
|
+
const taskStatePath = resolve23(monorepoRoot, ".beads", "task-state.json");
|
|
12416
|
+
const taskConfigPath = resolve23(monorepoRoot, ".rig", "task-config.json");
|
|
12417
|
+
mkdirSync16(dirname18(issuesPath), { recursive: true });
|
|
12418
|
+
mkdirSync16(dirname18(taskConfigPath), { recursive: true });
|
|
12040
12419
|
const summary = normalizeString(input.summary) ?? "Inspector follow-up";
|
|
12041
12420
|
const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
|
|
12042
12421
|
const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
|
|
@@ -12055,7 +12434,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
12055
12434
|
const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
|
|
12056
12435
|
const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
|
|
12057
12436
|
const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
|
|
12058
|
-
const existingIssueLines =
|
|
12437
|
+
const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
12059
12438
|
const existingIssues = existingIssueLines.map((line) => {
|
|
12060
12439
|
try {
|
|
12061
12440
|
return JSON.parse(line);
|
|
@@ -12064,7 +12443,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
12064
12443
|
}
|
|
12065
12444
|
}).filter((value) => value !== null);
|
|
12066
12445
|
const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
|
|
12067
|
-
const rawTaskState =
|
|
12446
|
+
const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
|
|
12068
12447
|
const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
|
|
12069
12448
|
const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
|
|
12070
12449
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
@@ -12090,7 +12469,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
12090
12469
|
updated_at: createdAt,
|
|
12091
12470
|
labels: mergedLabels
|
|
12092
12471
|
};
|
|
12093
|
-
|
|
12472
|
+
writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
|
|
12094
12473
|
`)}
|
|
12095
12474
|
${JSON.stringify(issueRecord)}
|
|
12096
12475
|
` : `${JSON.stringify(issueRecord)}
|
|
@@ -12108,7 +12487,7 @@ ${JSON.stringify(issueRecord)}
|
|
|
12108
12487
|
labels: mergedLabels
|
|
12109
12488
|
};
|
|
12110
12489
|
});
|
|
12111
|
-
|
|
12490
|
+
writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
|
|
12112
12491
|
`)}
|
|
12113
12492
|
`, "utf8");
|
|
12114
12493
|
}
|
|
@@ -12131,14 +12510,14 @@ ${JSON.stringify(issueRecord)}
|
|
|
12131
12510
|
}
|
|
12132
12511
|
};
|
|
12133
12512
|
}
|
|
12134
|
-
|
|
12513
|
+
writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
|
|
12135
12514
|
`, "utf8");
|
|
12136
12515
|
tasks[taskId] = {
|
|
12137
12516
|
status,
|
|
12138
12517
|
sourceCommit: resolveFollowupSourceCommit(input),
|
|
12139
12518
|
...sourceKey ? { sourceKey } : {}
|
|
12140
12519
|
};
|
|
12141
|
-
|
|
12520
|
+
writeFileSync15(taskStatePath, `${JSON.stringify({
|
|
12142
12521
|
schemaVersion: 1,
|
|
12143
12522
|
baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
|
|
12144
12523
|
tasks
|
|
@@ -12446,12 +12825,12 @@ function isAuthorizedLinearWebhookRequest(req) {
|
|
|
12446
12825
|
}
|
|
12447
12826
|
|
|
12448
12827
|
// packages/server/src/server-helpers/notifications.ts
|
|
12449
|
-
import { existsSync as
|
|
12450
|
-
import { dirname as
|
|
12828
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
|
|
12829
|
+
import { dirname as dirname19 } from "path";
|
|
12451
12830
|
async function loadNotificationConfig(path) {
|
|
12452
|
-
if (!
|
|
12831
|
+
if (!existsSync18(path)) {
|
|
12453
12832
|
const defaultConfig = { targets: [] };
|
|
12454
|
-
|
|
12833
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
12455
12834
|
await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
|
|
12456
12835
|
`);
|
|
12457
12836
|
return defaultConfig;
|
|
@@ -12466,10 +12845,10 @@ async function loadNotificationConfig(path) {
|
|
|
12466
12845
|
}
|
|
12467
12846
|
}
|
|
12468
12847
|
function readRecentEvents(file, limit) {
|
|
12469
|
-
if (!
|
|
12848
|
+
if (!existsSync18(file)) {
|
|
12470
12849
|
return [];
|
|
12471
12850
|
}
|
|
12472
|
-
const lines =
|
|
12851
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
|
|
12473
12852
|
const events = [];
|
|
12474
12853
|
for (const line of lines) {
|
|
12475
12854
|
try {
|
|
@@ -12564,11 +12943,11 @@ function extractObjectLiteralBlock(source, property) {
|
|
|
12564
12943
|
}
|
|
12565
12944
|
function readFallbackIssueAnalysisConfig(projectRoot) {
|
|
12566
12945
|
for (const fileName of ["rig.config.ts", "rig.config.json"]) {
|
|
12567
|
-
const path =
|
|
12568
|
-
if (!
|
|
12946
|
+
const path = resolve24(projectRoot, fileName);
|
|
12947
|
+
if (!existsSync19(path))
|
|
12569
12948
|
continue;
|
|
12570
12949
|
try {
|
|
12571
|
-
const source =
|
|
12950
|
+
const source = readFileSync14(path, "utf8");
|
|
12572
12951
|
if (fileName.endsWith(".json"))
|
|
12573
12952
|
return JSON.parse(source);
|
|
12574
12953
|
const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
|
|
@@ -12701,8 +13080,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
|
|
|
12701
13080
|
async function withServerPathEnv(projectRoot, fn) {
|
|
12702
13081
|
const waitForTurn = serverPathEnvQueue;
|
|
12703
13082
|
let releaseTurn;
|
|
12704
|
-
serverPathEnvQueue = new Promise((
|
|
12705
|
-
releaseTurn =
|
|
13083
|
+
serverPathEnvQueue = new Promise((resolve25) => {
|
|
13084
|
+
releaseTurn = resolve25;
|
|
12706
13085
|
});
|
|
12707
13086
|
await waitForTurn;
|
|
12708
13087
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -12738,9 +13117,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
12738
13117
|
return withServerPathEnv(projectRoot, fn);
|
|
12739
13118
|
}
|
|
12740
13119
|
async function readWorkspaceTasks(projectRoot) {
|
|
12741
|
-
const issuesPath =
|
|
13120
|
+
const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
|
|
12742
13121
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
12743
|
-
if (!
|
|
13122
|
+
if (!existsSync19(issuesPath)) {
|
|
12744
13123
|
return [];
|
|
12745
13124
|
}
|
|
12746
13125
|
const latestById = new Map;
|
|
@@ -12814,11 +13193,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
|
|
|
12814
13193
|
continue;
|
|
12815
13194
|
add(run.artifactRoot);
|
|
12816
13195
|
if (run.worktreePath) {
|
|
12817
|
-
add(
|
|
13196
|
+
add(resolve24(run.worktreePath, "artifacts", taskId));
|
|
12818
13197
|
}
|
|
12819
13198
|
}
|
|
12820
13199
|
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
12821
|
-
add(
|
|
13200
|
+
add(resolve24(artifactsRoot, taskId));
|
|
12822
13201
|
}
|
|
12823
13202
|
return candidates;
|
|
12824
13203
|
}
|
|
@@ -12832,7 +13211,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12832
13211
|
}
|
|
12833
13212
|
}
|
|
12834
13213
|
return taskIds.flatMap((currentTaskId) => {
|
|
12835
|
-
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) =>
|
|
13214
|
+
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
|
|
12836
13215
|
if (!currentRoot) {
|
|
12837
13216
|
return [];
|
|
12838
13217
|
}
|
|
@@ -12844,7 +13223,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12844
13223
|
taskId: currentTaskId,
|
|
12845
13224
|
kind: "file",
|
|
12846
13225
|
label: fileName,
|
|
12847
|
-
path:
|
|
13226
|
+
path: resolve24(currentRoot, fileName),
|
|
12848
13227
|
url: null,
|
|
12849
13228
|
metadata: {
|
|
12850
13229
|
fileName
|
|
@@ -12887,11 +13266,11 @@ function buildInspectorStreamPayload(state, sequence) {
|
|
|
12887
13266
|
}
|
|
12888
13267
|
function listRemoteRunArtifacts(projectRoot, runId) {
|
|
12889
13268
|
const root = remoteArtifactsRoot(projectRoot, runId);
|
|
12890
|
-
if (!
|
|
13269
|
+
if (!existsSync19(root)) {
|
|
12891
13270
|
return [];
|
|
12892
13271
|
}
|
|
12893
13272
|
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
|
|
12894
|
-
const artifactPath =
|
|
13273
|
+
const artifactPath = resolve24(root, entry.name);
|
|
12895
13274
|
const stat = statSync6(artifactPath);
|
|
12896
13275
|
const meta = readJsonFile4(`${artifactPath}.json`, null);
|
|
12897
13276
|
return {
|
|
@@ -13133,8 +13512,8 @@ function fileStats(path) {
|
|
|
13133
13512
|
}
|
|
13134
13513
|
}
|
|
13135
13514
|
function runFileCursor(projectRoot, run) {
|
|
13136
|
-
const runDir =
|
|
13137
|
-
const runJson = fileStats(
|
|
13515
|
+
const runDir = dirname20(runLogsPath(projectRoot, run.runId));
|
|
13516
|
+
const runJson = fileStats(resolve24(runDir, "run.json"));
|
|
13138
13517
|
const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
|
|
13139
13518
|
const logs = fileStats(runLogsPath(projectRoot, run.runId));
|
|
13140
13519
|
return {
|
|
@@ -13184,10 +13563,10 @@ function startRunFileWatcher(state, pollMs) {
|
|
|
13184
13563
|
}, Math.max(250, Math.min(pollMs, 1000)));
|
|
13185
13564
|
}
|
|
13186
13565
|
function startPoller(state, pollMs) {
|
|
13187
|
-
let offset =
|
|
13566
|
+
let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
|
|
13188
13567
|
return setInterval(async () => {
|
|
13189
13568
|
try {
|
|
13190
|
-
if (!
|
|
13569
|
+
if (!existsSync19(state.eventsFile)) {
|
|
13191
13570
|
return;
|
|
13192
13571
|
}
|
|
13193
13572
|
const file = await open(state.eventsFile, "r");
|
|
@@ -13315,7 +13694,7 @@ function resolveProjectRoot() {
|
|
|
13315
13694
|
return resolveRigProjectRoot({
|
|
13316
13695
|
envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
|
|
13317
13696
|
cwd: process.cwd(),
|
|
13318
|
-
fallbackRoot:
|
|
13697
|
+
fallbackRoot: resolve24(import.meta.dir, "../..")
|
|
13319
13698
|
});
|
|
13320
13699
|
}
|
|
13321
13700
|
var __testOnly = {
|