@h-rig/server 0.0.6-alpha.1 → 0.0.6-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/src/index.js +920 -287
- package/dist/src/server-helpers/github-api-session-index.js +107 -0
- package/dist/src/server-helpers/github-auth-store.js +117 -21
- package/dist/src/server-helpers/github-user-namespace.js +102 -0
- package/dist/src/server-helpers/http-router.js +998 -151
- package/dist/src/server-helpers/issue-analysis.js +30 -11
- package/dist/src/server-helpers/project-registry.js +5 -0
- package/dist/src/server-helpers/run-mutations.js +248 -103
- package/dist/src/server-helpers/snapshot-orchestrator.js +1 -1
- package/dist/src/server-helpers/snapshot-service.js +1 -1
- package/dist/src/server-helpers/ws-router.js +4 -4
- package/dist/src/server.js +921 -287
- package/package.json +4 -4
package/dist/src/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,
|
|
@@ -2293,7 +2293,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
|
|
|
2293
2293
|
const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
|
|
2294
2294
|
...toTaskSummary(workspace.id, {
|
|
2295
2295
|
...task,
|
|
2296
|
-
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
|
|
2296
|
+
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
|
|
2297
2297
|
}),
|
|
2298
2298
|
graphId: graph.id
|
|
2299
2299
|
}));
|
|
@@ -2819,7 +2819,6 @@ function createGitHubTaskReconciler(input) {
|
|
|
2819
2819
|
}
|
|
2820
2820
|
|
|
2821
2821
|
// packages/server/src/server-helpers/issue-analysis.ts
|
|
2822
|
-
import { execFile } from "child_process";
|
|
2823
2822
|
import { createHash } from "crypto";
|
|
2824
2823
|
function stableIssueHash(issue) {
|
|
2825
2824
|
const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
|
|
@@ -2953,16 +2952,33 @@ function parseIssueAnalysisResult(raw) {
|
|
|
2953
2952
|
return result;
|
|
2954
2953
|
}
|
|
2955
2954
|
function createDefaultPiIssueAnalysisCommandRunner() {
|
|
2956
|
-
return (command, args, options) =>
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
|
|
2963
|
-
resolve11({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
|
|
2955
|
+
return async (command, args, options) => {
|
|
2956
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
2957
|
+
const proc = Bun.spawn([command, ...args], {
|
|
2958
|
+
stdout: "pipe",
|
|
2959
|
+
stderr: "pipe",
|
|
2960
|
+
env
|
|
2964
2961
|
});
|
|
2965
|
-
|
|
2962
|
+
let timedOut = false;
|
|
2963
|
+
const timer = setTimeout(() => {
|
|
2964
|
+
timedOut = true;
|
|
2965
|
+
proc.kill();
|
|
2966
|
+
}, options.timeoutMs);
|
|
2967
|
+
try {
|
|
2968
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
2969
|
+
new Response(proc.stdout).text(),
|
|
2970
|
+
new Response(proc.stderr).text(),
|
|
2971
|
+
proc.exited
|
|
2972
|
+
]);
|
|
2973
|
+
return {
|
|
2974
|
+
exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
|
|
2975
|
+
stdout,
|
|
2976
|
+
stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
|
|
2977
|
+
};
|
|
2978
|
+
} finally {
|
|
2979
|
+
clearTimeout(timer);
|
|
2980
|
+
}
|
|
2981
|
+
};
|
|
2966
2982
|
}
|
|
2967
2983
|
function createPiIssueAnalyzer(input = {}) {
|
|
2968
2984
|
const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
|
|
@@ -2970,7 +2986,10 @@ function createPiIssueAnalyzer(input = {}) {
|
|
|
2970
2986
|
const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
|
|
2971
2987
|
return async ({ prompt }) => {
|
|
2972
2988
|
const args = ["--print", "--mode", "json", "--no-session"];
|
|
2973
|
-
const
|
|
2989
|
+
const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
|
|
2990
|
+
const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
|
|
2991
|
+
if (provider)
|
|
2992
|
+
args.push("--provider", provider);
|
|
2974
2993
|
if (model)
|
|
2975
2994
|
args.push("--model", model);
|
|
2976
2995
|
args.push(prompt);
|
|
@@ -3688,7 +3707,7 @@ function applyOrchestrationCommand2(state, command) {
|
|
|
3688
3707
|
import { spawn as spawn3 } from "child_process";
|
|
3689
3708
|
import { loadConfig } from "@rig/core/load-config";
|
|
3690
3709
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3691
|
-
import { dirname as
|
|
3710
|
+
import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
|
|
3692
3711
|
import {
|
|
3693
3712
|
listAuthorityRuns as listAuthorityRuns4,
|
|
3694
3713
|
readAuthorityRun as readAuthorityRun4,
|
|
@@ -3811,8 +3830,9 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
3811
3830
|
}
|
|
3812
3831
|
|
|
3813
3832
|
// packages/server/src/server-helpers/github-auth-store.ts
|
|
3814
|
-
import {
|
|
3815
|
-
import {
|
|
3833
|
+
import { randomBytes } from "crypto";
|
|
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";
|
|
3816
3836
|
function cleanString(value) {
|
|
3817
3837
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3818
3838
|
}
|
|
@@ -3824,6 +3844,44 @@ function cleanScopes(value) {
|
|
|
3824
3844
|
return clean ? [clean] : [];
|
|
3825
3845
|
});
|
|
3826
3846
|
}
|
|
3847
|
+
function parseApiSessions(value) {
|
|
3848
|
+
if (!Array.isArray(value))
|
|
3849
|
+
return [];
|
|
3850
|
+
return value.flatMap((entry) => {
|
|
3851
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
3852
|
+
return [];
|
|
3853
|
+
const record = entry;
|
|
3854
|
+
const token = cleanString(record.token);
|
|
3855
|
+
if (!token)
|
|
3856
|
+
return [];
|
|
3857
|
+
return [{
|
|
3858
|
+
token,
|
|
3859
|
+
login: cleanString(record.login),
|
|
3860
|
+
userId: cleanString(record.userId),
|
|
3861
|
+
createdAt: cleanString(record.createdAt) ?? undefined
|
|
3862
|
+
}];
|
|
3863
|
+
});
|
|
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
|
+
}
|
|
3827
3885
|
function readStoredAuth(stateFile) {
|
|
3828
3886
|
if (!existsSync6(stateFile))
|
|
3829
3887
|
return {};
|
|
@@ -3837,37 +3895,44 @@ function readStoredAuth(stateFile) {
|
|
|
3837
3895
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
3838
3896
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
3839
3897
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
3898
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
3899
|
+
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
3840
3900
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
3841
3901
|
};
|
|
3842
3902
|
} catch {
|
|
3843
3903
|
return {};
|
|
3844
3904
|
}
|
|
3845
3905
|
}
|
|
3846
|
-
function
|
|
3847
|
-
|
|
3848
|
-
return null;
|
|
3849
|
-
const record = value;
|
|
3850
|
-
const pollId = cleanString(record.pollId);
|
|
3851
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
3852
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
3853
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3854
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3855
|
-
return null;
|
|
3856
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3906
|
+
function newApiSessionToken() {
|
|
3907
|
+
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
3857
3908
|
}
|
|
3858
3909
|
function writeStoredAuth(stateFile, payload) {
|
|
3859
|
-
mkdirSync6(
|
|
3910
|
+
mkdirSync6(dirname8(stateFile), { recursive: true });
|
|
3860
3911
|
writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
3861
3912
|
`, { encoding: "utf8", mode: 384 });
|
|
3862
3913
|
try {
|
|
3863
3914
|
chmodSync(stateFile, 384);
|
|
3864
3915
|
} catch {}
|
|
3865
3916
|
}
|
|
3917
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
3918
|
+
return resolve13(projectRoot, ".rig", "state", "github-auth.json");
|
|
3919
|
+
}
|
|
3866
3920
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
3867
3921
|
return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
3868
3922
|
}
|
|
3869
|
-
function
|
|
3870
|
-
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) {
|
|
3871
3936
|
return {
|
|
3872
3937
|
stateFile,
|
|
3873
3938
|
status(options) {
|
|
@@ -3897,14 +3962,53 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3897
3962
|
scopes: input.scopes ?? [],
|
|
3898
3963
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
3899
3964
|
pendingDevice: null,
|
|
3965
|
+
pendingDevices: [],
|
|
3966
|
+
apiSessions: previous.apiSessions ?? [],
|
|
3967
|
+
updatedAt: new Date().toISOString()
|
|
3968
|
+
});
|
|
3969
|
+
},
|
|
3970
|
+
createApiSession() {
|
|
3971
|
+
const previous = readStoredAuth(stateFile);
|
|
3972
|
+
const token = newApiSessionToken();
|
|
3973
|
+
const session = {
|
|
3974
|
+
token,
|
|
3975
|
+
login: cleanString(previous.login),
|
|
3976
|
+
userId: cleanString(previous.userId),
|
|
3977
|
+
createdAt: new Date().toISOString()
|
|
3978
|
+
};
|
|
3979
|
+
writeStoredAuth(stateFile, {
|
|
3980
|
+
...previous,
|
|
3981
|
+
apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
|
|
3900
3982
|
updatedAt: new Date().toISOString()
|
|
3901
3983
|
});
|
|
3984
|
+
return { token, login: session.login ?? null, userId: session.userId ?? null };
|
|
3985
|
+
},
|
|
3986
|
+
readApiSession(token) {
|
|
3987
|
+
const clean = cleanString(token);
|
|
3988
|
+
if (!clean)
|
|
3989
|
+
return null;
|
|
3990
|
+
const previous = readStoredAuth(stateFile);
|
|
3991
|
+
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
3992
|
+
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
3993
|
+
},
|
|
3994
|
+
copyToProjectRoot(projectRoot) {
|
|
3995
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
3996
|
+
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
3997
|
+
},
|
|
3998
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
3999
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
3902
4000
|
},
|
|
3903
4001
|
savePendingDevice(input) {
|
|
3904
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);
|
|
3905
4008
|
writeStoredAuth(stateFile, {
|
|
3906
4009
|
...previous,
|
|
3907
|
-
pendingDevice:
|
|
4010
|
+
pendingDevice: null,
|
|
4011
|
+
pendingDevices,
|
|
3908
4012
|
updatedAt: new Date().toISOString()
|
|
3909
4013
|
});
|
|
3910
4014
|
},
|
|
@@ -3917,23 +4021,32 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3917
4021
|
});
|
|
3918
4022
|
},
|
|
3919
4023
|
readPendingDevice(pollId) {
|
|
3920
|
-
const
|
|
3921
|
-
|
|
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)
|
|
3922
4030
|
return null;
|
|
3923
4031
|
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
3924
4032
|
return null;
|
|
3925
4033
|
return pending;
|
|
3926
4034
|
},
|
|
3927
|
-
clearPendingDevice() {
|
|
4035
|
+
clearPendingDevice(pollId) {
|
|
3928
4036
|
const previous = readStoredAuth(stateFile);
|
|
4037
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
3929
4038
|
writeStoredAuth(stateFile, {
|
|
3930
4039
|
...previous,
|
|
3931
4040
|
pendingDevice: null,
|
|
4041
|
+
pendingDevices: remaining,
|
|
3932
4042
|
updatedAt: new Date().toISOString()
|
|
3933
4043
|
});
|
|
3934
4044
|
}
|
|
3935
4045
|
};
|
|
3936
4046
|
}
|
|
4047
|
+
function createGitHubAuthStore(projectRoot) {
|
|
4048
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
4049
|
+
}
|
|
3937
4050
|
|
|
3938
4051
|
// packages/server/src/server-helpers/github-projects.ts
|
|
3939
4052
|
function asRecord(value) {
|
|
@@ -4197,15 +4310,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
|
|
|
4197
4310
|
if (!run.taskId)
|
|
4198
4311
|
return;
|
|
4199
4312
|
const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4313
|
+
try {
|
|
4314
|
+
const result = await syncGitHubProjectStatusForTaskUpdate({
|
|
4315
|
+
taskId: run.taskId,
|
|
4316
|
+
status,
|
|
4317
|
+
issueNodeId,
|
|
4318
|
+
token: createGitHubAuthStore(projectRoot).readToken(),
|
|
4319
|
+
config
|
|
4320
|
+
});
|
|
4321
|
+
if (!result.synced && result.reason !== "project-sync-disabled") {
|
|
4322
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
4323
|
+
id: `log:${run.runId}:github-project-sync:${status}`,
|
|
4324
|
+
title: "GitHub Project sync skipped",
|
|
4325
|
+
detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
|
|
4326
|
+
tone: "warn",
|
|
4327
|
+
status: "running",
|
|
4328
|
+
createdAt: new Date().toISOString(),
|
|
4329
|
+
payload: { reason: result.reason, issueNodeId }
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
} catch (error) {
|
|
4333
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
4334
|
+
id: `log:${run.runId}:github-project-sync-error:${status}`,
|
|
4335
|
+
title: "GitHub Project sync failed",
|
|
4336
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
4337
|
+
tone: "error",
|
|
4338
|
+
status: "running",
|
|
4339
|
+
createdAt: new Date().toISOString(),
|
|
4340
|
+
payload: { issueNodeId }
|
|
4341
|
+
});
|
|
4342
|
+
}
|
|
4209
4343
|
}
|
|
4210
4344
|
async function autoAssignRunIssue(projectRoot, run) {
|
|
4211
4345
|
if (!run.taskId)
|
|
@@ -4307,11 +4441,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
|
|
|
4307
4441
|
return;
|
|
4308
4442
|
throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
|
|
4309
4443
|
}
|
|
4444
|
+
async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
|
|
4445
|
+
const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
4446
|
+
if (fromReader)
|
|
4447
|
+
return fromReader;
|
|
4448
|
+
const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
|
|
4449
|
+
if (projected)
|
|
4450
|
+
return projected;
|
|
4451
|
+
if (readTasks !== readWorkspaceTasks) {
|
|
4452
|
+
return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
4453
|
+
}
|
|
4454
|
+
return null;
|
|
4455
|
+
}
|
|
4310
4456
|
async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
|
|
4311
4457
|
if ("taskId" in input && input.taskId) {
|
|
4312
4458
|
assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
|
|
4313
4459
|
}
|
|
4314
|
-
const sourceTask = "taskId" in input && input.taskId ?
|
|
4460
|
+
const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
|
|
4315
4461
|
const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
|
|
4316
4462
|
const runDir = resolveAuthorityRunDir3(projectRoot, input.runId);
|
|
4317
4463
|
const runRecord = {
|
|
@@ -4383,6 +4529,7 @@ async function startLocalRun(state, runId, options) {
|
|
|
4383
4529
|
throw new Error(`Run not found: ${runId}`);
|
|
4384
4530
|
}
|
|
4385
4531
|
const startedAt = new Date().toISOString();
|
|
4532
|
+
const resumeMode = options?.resume === true;
|
|
4386
4533
|
state.runProcesses.set(runId, {
|
|
4387
4534
|
runId,
|
|
4388
4535
|
child: null,
|
|
@@ -4399,9 +4546,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
4399
4546
|
summary: run.title
|
|
4400
4547
|
});
|
|
4401
4548
|
appendRunLogEntry(state.projectRoot, runId, {
|
|
4402
|
-
id: `log:${runId}:prepare`,
|
|
4403
|
-
title: "Rig task run starting",
|
|
4404
|
-
detail: run.taskId ?? run.title,
|
|
4549
|
+
id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
|
|
4550
|
+
title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
|
|
4551
|
+
detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
|
|
4405
4552
|
tone: "info",
|
|
4406
4553
|
status: "preparing",
|
|
4407
4554
|
createdAt: startedAt
|
|
@@ -4479,7 +4626,15 @@ async function startLocalRun(state, runId, options) {
|
|
|
4479
4626
|
RIG_SERVER_INTERNAL_EXEC: "1",
|
|
4480
4627
|
...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
|
|
4481
4628
|
...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
|
|
4482
|
-
...bridgeGitHubToken ? {
|
|
4629
|
+
...bridgeGitHubToken ? {
|
|
4630
|
+
RIG_GITHUB_TOKEN: bridgeGitHubToken,
|
|
4631
|
+
GITHUB_TOKEN: bridgeGitHubToken,
|
|
4632
|
+
GH_TOKEN: bridgeGitHubToken
|
|
4633
|
+
} : {},
|
|
4634
|
+
...resumeMode ? {
|
|
4635
|
+
RIG_RUN_RESUME: "1",
|
|
4636
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
4637
|
+
} : {}
|
|
4483
4638
|
},
|
|
4484
4639
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4485
4640
|
});
|
|
@@ -4630,7 +4785,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
4630
4785
|
}
|
|
4631
4786
|
try {
|
|
4632
4787
|
const monorepoRoot = resolveMonorepoRoot3(projectRoot);
|
|
4633
|
-
const outerProjectRoot =
|
|
4788
|
+
const outerProjectRoot = dirname9(dirname9(monorepoRoot));
|
|
4634
4789
|
if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
4635
4790
|
return outerProjectRoot;
|
|
4636
4791
|
}
|
|
@@ -4651,7 +4806,7 @@ async function resumeRunRecord(state, input) {
|
|
|
4651
4806
|
if (run.status === "completed") {
|
|
4652
4807
|
throw new Error("Completed runs cannot be resumed.");
|
|
4653
4808
|
}
|
|
4654
|
-
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
|
|
4809
|
+
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
4655
4810
|
}
|
|
4656
4811
|
function appendRunMessage(projectRoot, input) {
|
|
4657
4812
|
const run = readAuthorityRun4(projectRoot, input.runId);
|
|
@@ -4735,34 +4890,12 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
|
|
|
4735
4890
|
writeQueueState(projectRoot, next);
|
|
4736
4891
|
return next;
|
|
4737
4892
|
}
|
|
4738
|
-
var
|
|
4739
|
-
function
|
|
4740
|
-
|
|
4741
|
-
for (const run of runs) {
|
|
4893
|
+
var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
4894
|
+
function collectResumableLocalRuns(state, runs) {
|
|
4895
|
+
return runs.filter((run) => {
|
|
4742
4896
|
const status = normalizeString(run.status)?.toLowerCase() ?? "";
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
if (run.mode !== "local" || !wasStartedByRigServer || !ORPHANABLE_LOCAL_RUN_STATUSES.has(status) || state.runProcesses.has(run.runId)) {
|
|
4746
|
-
continue;
|
|
4747
|
-
}
|
|
4748
|
-
const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
|
|
4749
|
-
patchRunRecord(state.projectRoot, run.runId, {
|
|
4750
|
-
status: "failed",
|
|
4751
|
-
completedAt: run.completedAt ?? nowIso2,
|
|
4752
|
-
updatedAt: nowIso2,
|
|
4753
|
-
errorText: detail
|
|
4754
|
-
});
|
|
4755
|
-
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
4756
|
-
id: `log:${run.runId}:stale-local-run`,
|
|
4757
|
-
title: "Run marked stale after server restart",
|
|
4758
|
-
detail,
|
|
4759
|
-
tone: "error",
|
|
4760
|
-
status: "failed",
|
|
4761
|
-
createdAt: nowIso2
|
|
4762
|
-
});
|
|
4763
|
-
changed = true;
|
|
4764
|
-
}
|
|
4765
|
-
return changed;
|
|
4897
|
+
return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
|
|
4898
|
+
});
|
|
4766
4899
|
}
|
|
4767
4900
|
async function reconcileScheduler(state, reason) {
|
|
4768
4901
|
if (state.scheduler.reconciling) {
|
|
@@ -4777,7 +4910,20 @@ async function reconcileScheduler(state, reason) {
|
|
|
4777
4910
|
const queue = readQueueState(state.projectRoot);
|
|
4778
4911
|
const tasks = await state.snapshotService.getWorkspaceTasks();
|
|
4779
4912
|
let runs = listAuthorityRuns4(state.projectRoot);
|
|
4780
|
-
let changed =
|
|
4913
|
+
let changed = false;
|
|
4914
|
+
const resumableRuns = collectResumableLocalRuns(state, runs);
|
|
4915
|
+
for (const run of resumableRuns) {
|
|
4916
|
+
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
4917
|
+
id: `log:${run.runId}:auto-resume:${Date.now()}`,
|
|
4918
|
+
title: "Run auto-resume scheduled",
|
|
4919
|
+
detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
|
|
4920
|
+
tone: "info",
|
|
4921
|
+
status: "preparing",
|
|
4922
|
+
createdAt: new Date().toISOString()
|
|
4923
|
+
});
|
|
4924
|
+
await startLocalRun(state, run.runId, { resume: true });
|
|
4925
|
+
changed = true;
|
|
4926
|
+
}
|
|
4781
4927
|
if (changed) {
|
|
4782
4928
|
runs = listAuthorityRuns4(state.projectRoot);
|
|
4783
4929
|
}
|
|
@@ -4853,8 +4999,8 @@ async function reconcileScheduler(state, reason) {
|
|
|
4853
4999
|
// packages/server/src/server-helpers/http-router.ts
|
|
4854
5000
|
import { randomUUID } from "crypto";
|
|
4855
5001
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4856
|
-
import { basename, dirname as
|
|
4857
|
-
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";
|
|
4858
5004
|
import {
|
|
4859
5005
|
listAuthorityRuns as listAuthorityRuns5,
|
|
4860
5006
|
readAuthorityRun as readAuthorityRun6,
|
|
@@ -4878,7 +5024,7 @@ import {
|
|
|
4878
5024
|
} from "@rig/runtime/control-plane/remote";
|
|
4879
5025
|
|
|
4880
5026
|
// packages/server/src/server-helpers/run-steering.ts
|
|
4881
|
-
import { dirname as
|
|
5027
|
+
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
4882
5028
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
4883
5029
|
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
4884
5030
|
var steeringSequence = 0;
|
|
@@ -4965,7 +5111,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
|
4965
5111
|
delivered: false
|
|
4966
5112
|
};
|
|
4967
5113
|
const path = runSteeringPath(projectRoot, runId);
|
|
4968
|
-
mkdirSync8(
|
|
5114
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
4969
5115
|
appendJsonlRecord2(path, entry);
|
|
4970
5116
|
appendRunTimelineEntry(projectRoot, runId, {
|
|
4971
5117
|
id: entry.id,
|
|
@@ -5002,6 +5148,187 @@ import {
|
|
|
5002
5148
|
updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
|
|
5003
5149
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
5004
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
|
+
|
|
5005
5332
|
// packages/server/src/server-helpers/inspector-agent-lifecycle.ts
|
|
5006
5333
|
function createInspectorAgentLifecycleController(options) {
|
|
5007
5334
|
const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
|
|
@@ -5105,21 +5432,21 @@ function inspectorAgentLifecycleSnapshot(input) {
|
|
|
5105
5432
|
// packages/server/src/server-helpers/project-registry.ts
|
|
5106
5433
|
import { createHash as createHash2 } from "crypto";
|
|
5107
5434
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5108
|
-
import { existsSync as
|
|
5109
|
-
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";
|
|
5110
5437
|
function normalizeRepoSlug(value) {
|
|
5111
5438
|
const trimmed = value.trim();
|
|
5112
5439
|
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
|
|
5113
5440
|
}
|
|
5114
5441
|
function registryPath(projectRoot) {
|
|
5115
|
-
return
|
|
5442
|
+
return resolve18(projectRoot, ".rig", "state", "projects.json");
|
|
5116
5443
|
}
|
|
5117
5444
|
function readRegistry(projectRoot) {
|
|
5118
5445
|
const path = registryPath(projectRoot);
|
|
5119
|
-
if (!
|
|
5446
|
+
if (!existsSync11(path))
|
|
5120
5447
|
return {};
|
|
5121
5448
|
try {
|
|
5122
|
-
const payload = JSON.parse(
|
|
5449
|
+
const payload = JSON.parse(readFileSync8(path, "utf8"));
|
|
5123
5450
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
5124
5451
|
return {};
|
|
5125
5452
|
const projects = payload.projects;
|
|
@@ -5130,14 +5457,14 @@ function readRegistry(projectRoot) {
|
|
|
5130
5457
|
}
|
|
5131
5458
|
function writeRegistry(projectRoot, projects) {
|
|
5132
5459
|
const path = registryPath(projectRoot);
|
|
5133
|
-
|
|
5134
|
-
|
|
5460
|
+
mkdirSync11(dirname13(path), { recursive: true });
|
|
5461
|
+
writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
|
|
5135
5462
|
`, "utf8");
|
|
5136
5463
|
}
|
|
5137
5464
|
function resolveConfigPath(projectRoot) {
|
|
5138
5465
|
for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
|
|
5139
|
-
const path =
|
|
5140
|
-
if (
|
|
5466
|
+
const path = resolve18(projectRoot, name);
|
|
5467
|
+
if (existsSync11(path))
|
|
5141
5468
|
return path;
|
|
5142
5469
|
}
|
|
5143
5470
|
return null;
|
|
@@ -5146,7 +5473,7 @@ function hashFile(path) {
|
|
|
5146
5473
|
if (!path)
|
|
5147
5474
|
return null;
|
|
5148
5475
|
try {
|
|
5149
|
-
return createHash2("sha256").update(
|
|
5476
|
+
return createHash2("sha256").update(readFileSync8(path)).digest("hex");
|
|
5150
5477
|
} catch {
|
|
5151
5478
|
return null;
|
|
5152
5479
|
}
|
|
@@ -5162,11 +5489,11 @@ function readDefaultBranch(projectRoot) {
|
|
|
5162
5489
|
return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
|
|
5163
5490
|
}
|
|
5164
5491
|
function buildRunSummary(projectRoot) {
|
|
5165
|
-
const runsDir =
|
|
5492
|
+
const runsDir = resolve18(projectRoot, ".rig", "runs");
|
|
5166
5493
|
try {
|
|
5167
5494
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
5168
5495
|
try {
|
|
5169
|
-
const run = JSON.parse(
|
|
5496
|
+
const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
|
|
5170
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 : "" }];
|
|
5171
5498
|
} catch {
|
|
5172
5499
|
return [];
|
|
@@ -5218,10 +5545,14 @@ function upsertProjectRecord(projectRoot, input) {
|
|
|
5218
5545
|
function linkProjectCheckout(projectRoot, repoSlug, checkout) {
|
|
5219
5546
|
return upsertProjectRecord(projectRoot, { repoSlug, checkout });
|
|
5220
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
|
+
}
|
|
5221
5552
|
|
|
5222
5553
|
// packages/server/src/server-helpers/remote-checkout.ts
|
|
5223
|
-
import { existsSync as
|
|
5224
|
-
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";
|
|
5225
5556
|
function safeSlugSegments(repoSlug) {
|
|
5226
5557
|
const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
|
|
5227
5558
|
if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
|
|
@@ -5238,7 +5569,7 @@ function safeCheckoutKey(value) {
|
|
|
5238
5569
|
}
|
|
5239
5570
|
function repoSlugPath(baseDir, repoSlug, checkoutKey) {
|
|
5240
5571
|
const key = safeCheckoutKey(checkoutKey);
|
|
5241
|
-
return
|
|
5572
|
+
return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
|
|
5242
5573
|
}
|
|
5243
5574
|
function sanitizeSnapshotId(value, fallback) {
|
|
5244
5575
|
const raw = (value ?? fallback).trim();
|
|
@@ -5246,7 +5577,7 @@ function sanitizeSnapshotId(value, fallback) {
|
|
|
5246
5577
|
return safe || fallback;
|
|
5247
5578
|
}
|
|
5248
5579
|
function assertWithinRoot(root, relativePath) {
|
|
5249
|
-
if (!relativePath ||
|
|
5580
|
+
if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
|
|
5250
5581
|
throw new Error(`Invalid snapshot file path: ${relativePath}`);
|
|
5251
5582
|
}
|
|
5252
5583
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
@@ -5254,9 +5585,9 @@ function assertWithinRoot(root, relativePath) {
|
|
|
5254
5585
|
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
5255
5586
|
throw new Error(`Unsafe snapshot file path: ${relativePath}`);
|
|
5256
5587
|
}
|
|
5257
|
-
const target =
|
|
5258
|
-
const rel =
|
|
5259
|
-
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)) {
|
|
5260
5591
|
throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
|
|
5261
5592
|
}
|
|
5262
5593
|
return target;
|
|
@@ -5274,17 +5605,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
|
|
|
5274
5605
|
function extractUploadedSnapshotArchive(input) {
|
|
5275
5606
|
const archive = decodeSnapshotArchive(input.archive);
|
|
5276
5607
|
const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
|
|
5277
|
-
const checkoutPath =
|
|
5278
|
-
|
|
5608
|
+
const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
|
|
5609
|
+
mkdirSync12(checkoutPath, { recursive: true });
|
|
5279
5610
|
for (const file of archive.files) {
|
|
5280
5611
|
if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
|
|
5281
5612
|
throw new Error("Invalid snapshot archive file entry");
|
|
5282
5613
|
}
|
|
5283
5614
|
const target = assertWithinRoot(checkoutPath, file.path);
|
|
5284
|
-
|
|
5285
|
-
|
|
5615
|
+
mkdirSync12(dirname14(target), { recursive: true });
|
|
5616
|
+
writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
|
|
5286
5617
|
}
|
|
5287
|
-
|
|
5618
|
+
writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
|
|
5288
5619
|
repoSlug: input.repoSlug,
|
|
5289
5620
|
snapshotId,
|
|
5290
5621
|
fileCount: archive.files.length,
|
|
@@ -5319,7 +5650,7 @@ function gitCredentialConfig(token) {
|
|
|
5319
5650
|
};
|
|
5320
5651
|
}
|
|
5321
5652
|
async function prepareRemoteCheckout(input) {
|
|
5322
|
-
const exists = input.exists ??
|
|
5653
|
+
const exists = input.exists ?? existsSync12;
|
|
5323
5654
|
const strategy = input.strategy;
|
|
5324
5655
|
if (strategy.kind === "uploaded-snapshot") {
|
|
5325
5656
|
return extractUploadedSnapshotArchive({
|
|
@@ -5331,7 +5662,7 @@ async function prepareRemoteCheckout(input) {
|
|
|
5331
5662
|
});
|
|
5332
5663
|
}
|
|
5333
5664
|
if (strategy.kind === "existing-path") {
|
|
5334
|
-
const checkoutPath2 =
|
|
5665
|
+
const checkoutPath2 = resolve19(strategy.path);
|
|
5335
5666
|
if (!exists(checkoutPath2)) {
|
|
5336
5667
|
throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
|
|
5337
5668
|
}
|
|
@@ -5367,9 +5698,9 @@ function buildServerControlStatus() {
|
|
|
5367
5698
|
};
|
|
5368
5699
|
}
|
|
5369
5700
|
function buildProjectConfigStatus(root) {
|
|
5370
|
-
const hasConfigTs =
|
|
5371
|
-
const hasConfigJson =
|
|
5372
|
-
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"));
|
|
5373
5704
|
let kind = "missing";
|
|
5374
5705
|
if (hasConfigTs)
|
|
5375
5706
|
kind = "rig-config-ts";
|
|
@@ -5393,10 +5724,10 @@ function normalizeCommit(value) {
|
|
|
5393
5724
|
function asPlainRecord(value) {
|
|
5394
5725
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5395
5726
|
}
|
|
5396
|
-
var
|
|
5727
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
5397
5728
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
5398
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
5399
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
5729
|
+
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
5730
|
+
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
5400
5731
|
};
|
|
5401
5732
|
function repoParts(repoSlug) {
|
|
5402
5733
|
const [owner, repo] = repoSlug.split("/");
|
|
@@ -5405,24 +5736,24 @@ function repoParts(repoSlug) {
|
|
|
5405
5736
|
return { owner, repo, slug: `${owner}/${repo}` };
|
|
5406
5737
|
}
|
|
5407
5738
|
function repairDir(checkoutPath) {
|
|
5408
|
-
const dir =
|
|
5409
|
-
|
|
5739
|
+
const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
|
|
5740
|
+
mkdirSync13(dir, { recursive: true });
|
|
5410
5741
|
return dir;
|
|
5411
5742
|
}
|
|
5412
5743
|
function backupCheckoutFile(checkoutPath, relativePath) {
|
|
5413
|
-
const source =
|
|
5414
|
-
const backupPath =
|
|
5415
|
-
|
|
5416
|
-
|
|
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);
|
|
5417
5748
|
return backupPath;
|
|
5418
5749
|
}
|
|
5419
5750
|
function parsePackageJsonLosslessly(checkoutPath) {
|
|
5420
|
-
const packagePath =
|
|
5421
|
-
if (!
|
|
5751
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
5752
|
+
if (!existsSync13(packagePath)) {
|
|
5422
5753
|
return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
|
|
5423
5754
|
}
|
|
5424
5755
|
try {
|
|
5425
|
-
const parsed = JSON.parse(
|
|
5756
|
+
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
5426
5757
|
return {
|
|
5427
5758
|
existed: true,
|
|
5428
5759
|
packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
|
|
@@ -5436,9 +5767,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
|
|
|
5436
5767
|
}
|
|
5437
5768
|
}
|
|
5438
5769
|
function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
5439
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5440
|
-
const packagePath =
|
|
5441
|
-
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)) {
|
|
5442
5773
|
return { skipped: true, reason: "package.json and rig.config missing" };
|
|
5443
5774
|
}
|
|
5444
5775
|
const parsed = parsePackageJsonLosslessly(checkoutPath);
|
|
@@ -5457,7 +5788,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
|
5457
5788
|
}
|
|
5458
5789
|
const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
|
|
5459
5790
|
if (changed) {
|
|
5460
|
-
|
|
5791
|
+
writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
|
|
5461
5792
|
`, "utf8");
|
|
5462
5793
|
}
|
|
5463
5794
|
return {
|
|
@@ -5483,11 +5814,11 @@ function configLooksStructurallyUsable(source) {
|
|
|
5483
5814
|
return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
|
|
5484
5815
|
}
|
|
5485
5816
|
function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
|
|
5486
|
-
const configPath =
|
|
5487
|
-
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)));
|
|
5488
5819
|
if (existingConfigName) {
|
|
5489
|
-
const existingPath =
|
|
5490
|
-
const source =
|
|
5820
|
+
const existingPath = resolve20(checkoutPath, existingConfigName);
|
|
5821
|
+
const source = readFileSync9(existingPath, "utf8");
|
|
5491
5822
|
if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
|
|
5492
5823
|
return { path: existingPath, changed: false, reason: "config structurally complete" };
|
|
5493
5824
|
}
|
|
@@ -5501,7 +5832,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5501
5832
|
}
|
|
5502
5833
|
}
|
|
5503
5834
|
const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
|
|
5504
|
-
|
|
5835
|
+
writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
|
|
5505
5836
|
return {
|
|
5506
5837
|
path: configPath,
|
|
5507
5838
|
changed: true,
|
|
@@ -5510,7 +5841,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5510
5841
|
};
|
|
5511
5842
|
}
|
|
5512
5843
|
function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
5513
|
-
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)));
|
|
5514
5845
|
if (!configFile)
|
|
5515
5846
|
return { ok: false, error: "missing rig config" };
|
|
5516
5847
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5532,7 +5863,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
|
5532
5863
|
return { ok: true, configFile };
|
|
5533
5864
|
}
|
|
5534
5865
|
function installRemoteCheckoutPackages(checkoutPath) {
|
|
5535
|
-
if (!
|
|
5866
|
+
if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
|
|
5536
5867
|
return { skipped: true, reason: "package.json missing" };
|
|
5537
5868
|
}
|
|
5538
5869
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5545,8 +5876,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
|
|
|
5545
5876
|
return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
|
|
5546
5877
|
}
|
|
5547
5878
|
function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
|
|
5548
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5549
|
-
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"));
|
|
5550
5881
|
if (!hasConfig && !hasPackage) {
|
|
5551
5882
|
return {
|
|
5552
5883
|
packageJson: { skipped: true, reason: "package.json and rig.config missing" },
|
|
@@ -5644,26 +5975,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
|
|
|
5644
5975
|
}
|
|
5645
5976
|
function readGitHeadCommit(projectRoot) {
|
|
5646
5977
|
try {
|
|
5647
|
-
let gitDir =
|
|
5978
|
+
let gitDir = resolve20(projectRoot, ".git");
|
|
5648
5979
|
try {
|
|
5649
|
-
const dotGit =
|
|
5980
|
+
const dotGit = readFileSync9(gitDir, "utf8").trim();
|
|
5650
5981
|
const gitDirPrefix = "gitdir:";
|
|
5651
5982
|
if (dotGit.startsWith(gitDirPrefix)) {
|
|
5652
|
-
gitDir =
|
|
5983
|
+
gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
|
|
5653
5984
|
}
|
|
5654
5985
|
} catch {}
|
|
5655
|
-
const head =
|
|
5986
|
+
const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
|
|
5656
5987
|
const refPrefix = "ref:";
|
|
5657
5988
|
if (!head.startsWith(refPrefix)) {
|
|
5658
5989
|
return normalizeCommit(head);
|
|
5659
5990
|
}
|
|
5660
5991
|
const ref = head.slice(refPrefix.length).trim();
|
|
5661
|
-
const refPath =
|
|
5662
|
-
if (
|
|
5663
|
-
return normalizeCommit(
|
|
5992
|
+
const refPath = resolve20(gitDir, ref);
|
|
5993
|
+
if (existsSync13(refPath)) {
|
|
5994
|
+
return normalizeCommit(readFileSync9(refPath, "utf8").trim());
|
|
5664
5995
|
}
|
|
5665
|
-
const commonDir = normalizeString(
|
|
5666
|
-
return commonDir ? normalizeCommit(
|
|
5996
|
+
const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
|
|
5997
|
+
return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
|
|
5667
5998
|
} catch {
|
|
5668
5999
|
return null;
|
|
5669
6000
|
}
|
|
@@ -5701,9 +6032,9 @@ function configuredRepoFromTaskSource(taskSource) {
|
|
|
5701
6032
|
const repo = normalizeString(taskSource?.repo);
|
|
5702
6033
|
return owner && repo ? `${owner}/${repo}` : null;
|
|
5703
6034
|
}
|
|
5704
|
-
async function buildTaskSourceStatus(state, config) {
|
|
6035
|
+
async function buildTaskSourceStatus(state, config, requestAuth) {
|
|
5705
6036
|
const diagnostics = state.snapshotService.getTaskSourceErrors();
|
|
5706
|
-
const selectedRepo =
|
|
6037
|
+
const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
|
|
5707
6038
|
oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
|
|
5708
6039
|
}).selectedRepo;
|
|
5709
6040
|
try {
|
|
@@ -5750,36 +6081,134 @@ function bearerTokenFromRequest(req) {
|
|
|
5750
6081
|
function isLoopbackRequest(req) {
|
|
5751
6082
|
try {
|
|
5752
6083
|
const hostname = new URL(req.url).hostname.toLowerCase();
|
|
5753
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
6084
|
+
return hostname === "localhost" || hostname === "rig.local" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
5754
6085
|
} catch {
|
|
5755
6086
|
return false;
|
|
5756
6087
|
}
|
|
5757
6088
|
}
|
|
5758
6089
|
function isPublicRigAuthBootstrapRoute(pathname) {
|
|
5759
|
-
return pathname === "/" || pathname === "/
|
|
6090
|
+
return pathname === "/" || pathname === "/install" || pathname === "/health" || pathname === "/api/health" || pathname === "/api/github/auth/status" || pathname === "/api/github/auth/token" || pathname === "/api/github/auth/device/start" || pathname === "/api/github/auth/device/poll";
|
|
6091
|
+
}
|
|
6092
|
+
function buildRigInstallScript() {
|
|
6093
|
+
return `#!/usr/bin/env bash
|
|
6094
|
+
set -euo pipefail
|
|
6095
|
+
|
|
6096
|
+
say() {
|
|
6097
|
+
printf 'rig-install: %s
|
|
6098
|
+
' "$*"
|
|
6099
|
+
}
|
|
6100
|
+
|
|
6101
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
6102
|
+
say "Bun not found; installing Bun first"
|
|
6103
|
+
curl -fsSL https://bun.sh/install | bash
|
|
6104
|
+
export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
|
|
6105
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
6106
|
+
fi
|
|
6107
|
+
|
|
6108
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
6109
|
+
printf 'rig-install: bun install completed, but bun is still not on PATH. Add ~/.bun/bin to PATH and retry.
|
|
6110
|
+
' >&2
|
|
6111
|
+
exit 1
|
|
6112
|
+
fi
|
|
6113
|
+
|
|
6114
|
+
say "Installing @h-rig/cli@latest"
|
|
6115
|
+
bun add -g @h-rig/cli@latest
|
|
6116
|
+
|
|
6117
|
+
export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
|
|
6118
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
6119
|
+
|
|
6120
|
+
if ! command -v rig >/dev/null 2>&1; then
|
|
6121
|
+
printf 'rig-install: rig installed, but rig is not on PATH. Add %s/bin to PATH and retry.
|
|
6122
|
+
' "$BUN_INSTALL" >&2
|
|
6123
|
+
exit 1
|
|
6124
|
+
fi
|
|
6125
|
+
|
|
6126
|
+
say "Verifying rig"
|
|
6127
|
+
rig --help >/dev/null
|
|
6128
|
+
say "Done. Run: rig --help"
|
|
6129
|
+
`;
|
|
5760
6130
|
}
|
|
5761
6131
|
function normalizePrMode(value) {
|
|
5762
6132
|
const mode = normalizeString(value);
|
|
5763
6133
|
return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
|
|
5764
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
|
+
}
|
|
5765
6160
|
function authorizeRigHttpRequest(input) {
|
|
5766
6161
|
if (input.legacyAuthorized) {
|
|
5767
|
-
return { authorized: true, actor: "rig-local-server", reason: "server-token" };
|
|
6162
|
+
return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
|
|
5768
6163
|
}
|
|
5769
6164
|
const bearer = bearerTokenFromRequest(input.req);
|
|
5770
6165
|
const store = createGitHubAuthStore(input.projectRoot);
|
|
5771
6166
|
const storedToken = store.readToken();
|
|
6167
|
+
const session = bearer ? store.readApiSession(bearer) : null;
|
|
6168
|
+
if (session) {
|
|
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
|
+
});
|
|
6177
|
+
}
|
|
5772
6178
|
if (bearer && storedToken && bearer === storedToken) {
|
|
5773
6179
|
const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
5774
|
-
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
|
+
});
|
|
5775
6201
|
}
|
|
5776
6202
|
if (isPublicRigAuthBootstrapRoute(input.pathname)) {
|
|
5777
|
-
return { authorized: true, actor: null, reason: "public-bootstrap" };
|
|
6203
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
|
|
5778
6204
|
}
|
|
5779
|
-
if (!input.serverAuthToken && !storedToken
|
|
5780
|
-
|
|
6205
|
+
if (!input.serverAuthToken && !storedToken) {
|
|
6206
|
+
if (isLoopbackRequest(input.req)) {
|
|
6207
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
|
|
6208
|
+
}
|
|
6209
|
+
return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
|
|
5781
6210
|
}
|
|
5782
|
-
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" });
|
|
5783
6212
|
}
|
|
5784
6213
|
async function fetchGitHubUserInfo(token) {
|
|
5785
6214
|
const response = await fetch("https://api.github.com/user", {
|
|
@@ -5799,6 +6228,67 @@ async function fetchGitHubUserInfo(token) {
|
|
|
5799
6228
|
scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
|
|
5800
6229
|
};
|
|
5801
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
|
+
}
|
|
5802
6292
|
async function postGitHubForm(endpoint, body) {
|
|
5803
6293
|
const response = await fetch(endpoint, {
|
|
5804
6294
|
method: "POST",
|
|
@@ -5816,11 +6306,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
|
|
|
5816
6306
|
const requestedRoot = normalizeString(rawRoot);
|
|
5817
6307
|
if (!requestedRoot)
|
|
5818
6308
|
return currentRoot;
|
|
5819
|
-
if (!
|
|
6309
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
5820
6310
|
throw new Error("projectRoot must be an absolute path on the Rig server host");
|
|
5821
6311
|
}
|
|
5822
|
-
const normalizedRoot =
|
|
5823
|
-
if (!
|
|
6312
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
6313
|
+
if (!existsSync13(normalizedRoot)) {
|
|
5824
6314
|
throw new Error("projectRoot does not exist on the Rig server host");
|
|
5825
6315
|
}
|
|
5826
6316
|
return normalizedRoot;
|
|
@@ -6008,7 +6498,7 @@ function selectNextWorkspaceTask(projectRoot, tasks) {
|
|
|
6008
6498
|
if (runnable.length === 0)
|
|
6009
6499
|
return null;
|
|
6010
6500
|
const queue = readQueueState(projectRoot);
|
|
6011
|
-
const queueRank = new Map(queue.map((entry, index) => [entry.taskId, { score: entry.score, position: index }]));
|
|
6501
|
+
const queueRank = new Map(queue.map((entry, index) => [String(entry.taskId), { score: entry.score, position: index }]));
|
|
6012
6502
|
return runnable.toSorted((left, right) => {
|
|
6013
6503
|
const leftId = taskIdOf(left) ?? "";
|
|
6014
6504
|
const rightId = taskIdOf(right) ?? "";
|
|
@@ -6048,6 +6538,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
|
|
|
6048
6538
|
}
|
|
6049
6539
|
return filtered;
|
|
6050
6540
|
}
|
|
6541
|
+
function issueAnalysisTargetFor(source) {
|
|
6542
|
+
if (!source)
|
|
6543
|
+
return null;
|
|
6544
|
+
const candidate = source;
|
|
6545
|
+
if (typeof candidate.updateTask !== "function")
|
|
6546
|
+
return null;
|
|
6547
|
+
return {
|
|
6548
|
+
...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
|
|
6549
|
+
updateTask: candidate.updateTask.bind(candidate),
|
|
6550
|
+
...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
|
|
6551
|
+
...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
|
|
6552
|
+
...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
|
|
6553
|
+
};
|
|
6554
|
+
}
|
|
6555
|
+
function uniqueStringList(value) {
|
|
6556
|
+
const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
6557
|
+
return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
|
|
6558
|
+
}
|
|
6559
|
+
function taskRecordId(task) {
|
|
6560
|
+
return String(task.id ?? "");
|
|
6561
|
+
}
|
|
6051
6562
|
function redactRemoteEndpoint(endpoint) {
|
|
6052
6563
|
const { token, ...rest } = endpoint;
|
|
6053
6564
|
return {
|
|
@@ -6132,9 +6643,16 @@ function createRigServerFetch(state, deps) {
|
|
|
6132
6643
|
notifications: state.targets.length
|
|
6133
6644
|
});
|
|
6134
6645
|
}
|
|
6646
|
+
if (url.pathname === "/install" && req.method === "GET") {
|
|
6647
|
+
return new Response(buildRigInstallScript(), {
|
|
6648
|
+
headers: {
|
|
6649
|
+
"Content-Type": "text/x-shellscript; charset=utf-8"
|
|
6650
|
+
}
|
|
6651
|
+
});
|
|
6652
|
+
}
|
|
6135
6653
|
const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
|
|
6136
6654
|
const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
|
|
6137
|
-
const legacyAuthorizedHttpRequest = isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken);
|
|
6655
|
+
const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
|
|
6138
6656
|
const requestAuth = authorizeRigHttpRequest({
|
|
6139
6657
|
req,
|
|
6140
6658
|
pathname: url.pathname,
|
|
@@ -6390,9 +6908,70 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6390
6908
|
note: "GitHub issue lifecycle labels are created on demand by the configured task source when supported."
|
|
6391
6909
|
});
|
|
6392
6910
|
}
|
|
6911
|
+
if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
|
|
6912
|
+
const body = await deps.readJsonBody(req);
|
|
6913
|
+
const ids = uniqueStringList(body.ids ?? body.id);
|
|
6914
|
+
const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
|
|
6915
|
+
if (ids.length === 0 && !analyzeAll) {
|
|
6916
|
+
return deps.badRequest("ids is required unless all=true");
|
|
6917
|
+
}
|
|
6918
|
+
const ctx = await getCachedPluginHostContext(state.projectRoot);
|
|
6919
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
6920
|
+
const target = issueAnalysisTargetFor(source);
|
|
6921
|
+
if (!source || !target) {
|
|
6922
|
+
return deps.badRequest("Configured task source does not support issue-analysis writeback");
|
|
6923
|
+
}
|
|
6924
|
+
const allTasks = [...await source.list()];
|
|
6925
|
+
const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
|
|
6926
|
+
const cached = allTasks.find((task) => taskRecordId(task) === id);
|
|
6927
|
+
if (cached)
|
|
6928
|
+
return cached;
|
|
6929
|
+
return typeof source.get === "function" ? await source.get(id) : undefined;
|
|
6930
|
+
}))).filter((task) => Boolean(task));
|
|
6931
|
+
if (issues.length === 0) {
|
|
6932
|
+
return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
|
|
6933
|
+
}
|
|
6934
|
+
const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
|
|
6935
|
+
const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
|
|
6936
|
+
const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
|
|
6937
|
+
const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
|
|
6938
|
+
const service = createIssueAnalysisService({
|
|
6939
|
+
analyzer: createPiIssueAnalyzer({
|
|
6940
|
+
...model ? { model } : {},
|
|
6941
|
+
env: { RIG_PROJECT_ROOT: state.projectRoot }
|
|
6942
|
+
}),
|
|
6943
|
+
writeBack: createIssueAnalysisWriteBack({ target })
|
|
6944
|
+
});
|
|
6945
|
+
const reason = normalizeString(body.reason) ?? "http-issue-analysis";
|
|
6946
|
+
let results;
|
|
6947
|
+
try {
|
|
6948
|
+
results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
|
|
6949
|
+
} catch (error) {
|
|
6950
|
+
return deps.jsonResponse({
|
|
6951
|
+
ok: false,
|
|
6952
|
+
error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
6953
|
+
reason,
|
|
6954
|
+
ids: issues.map((issue) => issue.id)
|
|
6955
|
+
}, 502);
|
|
6956
|
+
}
|
|
6957
|
+
deps.snapshotService.invalidate("issue-analysis-http-run");
|
|
6958
|
+
await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
|
|
6959
|
+
return;
|
|
6960
|
+
});
|
|
6961
|
+
deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
|
|
6962
|
+
return deps.jsonResponse({
|
|
6963
|
+
ok: true,
|
|
6964
|
+
reason,
|
|
6965
|
+
analyzed: results.map((entry) => ({
|
|
6966
|
+
id: entry.issue.id,
|
|
6967
|
+
title: entry.issue.title ?? null,
|
|
6968
|
+
result: entry.result
|
|
6969
|
+
}))
|
|
6970
|
+
});
|
|
6971
|
+
}
|
|
6393
6972
|
if (url.pathname === "/api/server/status") {
|
|
6394
6973
|
const config = buildProjectConfigStatus(state.projectRoot);
|
|
6395
|
-
const taskSource = await buildTaskSourceStatus(state, config);
|
|
6974
|
+
const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
|
|
6396
6975
|
return deps.jsonResponse({
|
|
6397
6976
|
ok: true,
|
|
6398
6977
|
projectRoot: state.projectRoot,
|
|
@@ -6416,8 +6995,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6416
6995
|
path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
|
|
6417
6996
|
ref: normalizeString(rawCheckout?.ref) ?? undefined
|
|
6418
6997
|
} : undefined;
|
|
6419
|
-
const
|
|
6420
|
-
|
|
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) });
|
|
6421
7001
|
}
|
|
6422
7002
|
const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
|
|
6423
7003
|
if (snapshotUploadMatch && req.method === "POST") {
|
|
@@ -6430,8 +7010,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6430
7010
|
if (!archiveContentBase64) {
|
|
6431
7011
|
return deps.badRequest("archiveContentBase64 is required");
|
|
6432
7012
|
}
|
|
6433
|
-
const
|
|
6434
|
-
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);
|
|
6435
7022
|
try {
|
|
6436
7023
|
const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
|
|
6437
7024
|
const checkout = extractUploadedSnapshotArchive({
|
|
@@ -6444,14 +7031,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6444
7031
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6445
7032
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6446
7033
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6447
|
-
const project = linkProjectCheckout(
|
|
7034
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6448
7035
|
kind: "uploaded-snapshot",
|
|
6449
7036
|
path: checkout.path,
|
|
6450
7037
|
ref: checkout.snapshotId
|
|
6451
7038
|
});
|
|
6452
7039
|
deps.snapshotService.invalidate("uploaded-snapshot-checkout");
|
|
6453
7040
|
deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
|
|
6454
|
-
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) });
|
|
6455
7042
|
} catch (error) {
|
|
6456
7043
|
return deps.jsonResponse({
|
|
6457
7044
|
ok: false,
|
|
@@ -6471,10 +7058,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6471
7058
|
if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
|
|
6472
7059
|
return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
|
|
6473
7060
|
}
|
|
6474
|
-
const
|
|
6475
|
-
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);
|
|
6476
7070
|
const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
|
|
6477
|
-
const credentialToken =
|
|
7071
|
+
const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
|
|
6478
7072
|
try {
|
|
6479
7073
|
const checkout = await prepareRemoteCheckout({
|
|
6480
7074
|
command: runRemoteCheckoutCommand,
|
|
@@ -6483,14 +7077,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6483
7077
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6484
7078
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6485
7079
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6486
|
-
const project = linkProjectCheckout(
|
|
7080
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6487
7081
|
kind: checkout.kind,
|
|
6488
7082
|
path: checkout.path,
|
|
6489
7083
|
ref: checkout.ref ?? checkout.snapshotId ?? undefined
|
|
6490
7084
|
});
|
|
6491
7085
|
deps.snapshotService.invalidate("remote-checkout-prepared");
|
|
6492
7086
|
deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
|
|
6493
|
-
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) });
|
|
6494
7088
|
} catch (error) {
|
|
6495
7089
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
|
|
6496
7090
|
}
|
|
@@ -6507,16 +7101,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6507
7101
|
if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
|
|
6508
7102
|
return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
|
|
6509
7103
|
}
|
|
6510
|
-
const
|
|
7104
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7105
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6511
7106
|
kind,
|
|
6512
7107
|
path: normalizeString(body.path) ?? state.projectRoot,
|
|
6513
7108
|
ref: normalizeString(body.ref) ?? undefined
|
|
6514
7109
|
});
|
|
6515
|
-
return deps.jsonResponse({ ok: true, project });
|
|
7110
|
+
return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6516
7111
|
}
|
|
6517
7112
|
if (req.method === "GET") {
|
|
6518
|
-
const
|
|
6519
|
-
|
|
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();
|
|
6520
7116
|
}
|
|
6521
7117
|
}
|
|
6522
7118
|
if (url.pathname === "/api/server/project-root" && req.method === "POST") {
|
|
@@ -6525,11 +7121,27 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6525
7121
|
if (!requestedRoot) {
|
|
6526
7122
|
return deps.badRequest("projectRoot is required");
|
|
6527
7123
|
}
|
|
6528
|
-
if (!
|
|
7124
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
6529
7125
|
return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
|
|
6530
7126
|
}
|
|
6531
|
-
const normalizedRoot =
|
|
6532
|
-
const exists =
|
|
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);
|
|
7144
|
+
}
|
|
6533
7145
|
const control = buildServerControlStatus();
|
|
6534
7146
|
const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
|
|
6535
7147
|
if (!exists) {
|
|
@@ -6543,7 +7155,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6543
7155
|
message: "Requested project root does not exist on the Rig server host."
|
|
6544
7156
|
}, 404);
|
|
6545
7157
|
}
|
|
6546
|
-
if (!
|
|
7158
|
+
if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
|
|
6547
7159
|
return deps.jsonResponse({
|
|
6548
7160
|
ok: false,
|
|
6549
7161
|
projectRoot: state.projectRoot,
|
|
@@ -6578,6 +7190,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6578
7190
|
exists,
|
|
6579
7191
|
control,
|
|
6580
7192
|
requiresRestart: false,
|
|
7193
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace),
|
|
6581
7194
|
message: "Project-root switch accepted. Rig server restart has been scheduled."
|
|
6582
7195
|
}, 202);
|
|
6583
7196
|
}
|
|
@@ -6592,11 +7205,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6592
7205
|
}, 409);
|
|
6593
7206
|
}
|
|
6594
7207
|
if (url.pathname === "/api/github/auth/status") {
|
|
6595
|
-
const store =
|
|
6596
|
-
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) });
|
|
6597
7210
|
}
|
|
6598
7211
|
if (url.pathname === "/api/github/repo/permissions") {
|
|
6599
|
-
const store =
|
|
7212
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6600
7213
|
const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6601
7214
|
if (!auth.signedIn) {
|
|
6602
7215
|
return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
|
|
@@ -6618,21 +7231,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6618
7231
|
const body = await deps.readJsonBody(req);
|
|
6619
7232
|
const token = normalizeString(body.token);
|
|
6620
7233
|
const selectedRepo = normalizeString(body.selectedRepo);
|
|
7234
|
+
const requestedProjectRoot = normalizeString(body.projectRoot);
|
|
6621
7235
|
if (!token) {
|
|
6622
7236
|
return deps.badRequest("token is required");
|
|
6623
7237
|
}
|
|
6624
7238
|
try {
|
|
6625
7239
|
const user = await fetchGitHubUserInfo(token);
|
|
6626
|
-
const
|
|
6627
|
-
|
|
7240
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
7241
|
+
projectRoot: state.projectRoot,
|
|
6628
7242
|
token,
|
|
6629
7243
|
tokenSource: "manual-token",
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
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)
|
|
6634
7253
|
});
|
|
6635
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
|
|
6636
7254
|
} catch (error) {
|
|
6637
7255
|
const message = error instanceof Error ? error.message : String(error);
|
|
6638
7256
|
return deps.jsonResponse({ ok: false, error: message }, 400);
|
|
@@ -6699,8 +7317,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6699
7317
|
}
|
|
6700
7318
|
const token = result.payload.access_token;
|
|
6701
7319
|
const user = await fetchGitHubUserInfo(token);
|
|
6702
|
-
|
|
6703
|
-
|
|
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
|
+
});
|
|
6704
7335
|
}
|
|
6705
7336
|
if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
|
|
6706
7337
|
const body = await deps.readJsonBody(req);
|
|
@@ -6709,7 +7340,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6709
7340
|
if (!owner || !repo) {
|
|
6710
7341
|
return deps.badRequest("owner and repo are required");
|
|
6711
7342
|
}
|
|
6712
|
-
const store =
|
|
7343
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6713
7344
|
const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6714
7345
|
const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
|
|
6715
7346
|
return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
|
|
@@ -6730,7 +7361,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6730
7361
|
return deps.badRequest(error instanceof Error ? error.message : String(error));
|
|
6731
7362
|
}
|
|
6732
7363
|
const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6733
|
-
const configPath =
|
|
7364
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6734
7365
|
const source = buildGitHubProjectConfigSource({
|
|
6735
7366
|
projectName: rawProjectName,
|
|
6736
7367
|
owner,
|
|
@@ -6742,8 +7373,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6742
7373
|
ok: true,
|
|
6743
7374
|
projectRoot: targetRoot,
|
|
6744
7375
|
configPath,
|
|
6745
|
-
exists:
|
|
6746
|
-
requiresOverwrite:
|
|
7376
|
+
exists: existsSync13(configPath),
|
|
7377
|
+
requiresOverwrite: existsSync13(configPath),
|
|
6747
7378
|
source,
|
|
6748
7379
|
owner,
|
|
6749
7380
|
repo,
|
|
@@ -6779,8 +7410,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6779
7410
|
assignee,
|
|
6780
7411
|
githubUserId: authStatus.userId ?? authStatus.login
|
|
6781
7412
|
});
|
|
6782
|
-
const configPath =
|
|
6783
|
-
if (
|
|
7413
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
7414
|
+
if (existsSync13(configPath) && !overwrite) {
|
|
6784
7415
|
return deps.jsonResponse({
|
|
6785
7416
|
ok: false,
|
|
6786
7417
|
error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
|
|
@@ -6796,11 +7427,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6796
7427
|
return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
|
|
6797
7428
|
}
|
|
6798
7429
|
let backupPath = null;
|
|
6799
|
-
if (
|
|
7430
|
+
if (existsSync13(configPath)) {
|
|
6800
7431
|
backupPath = backupConfigPath(configPath);
|
|
6801
|
-
|
|
7432
|
+
copyFileSync2(configPath, backupPath);
|
|
6802
7433
|
}
|
|
6803
|
-
|
|
7434
|
+
writeFileSync12(configPath, source, "utf8");
|
|
6804
7435
|
const selectedRepo = `${owner}/${repo}`;
|
|
6805
7436
|
store.saveSelectedRepo(selectedRepo);
|
|
6806
7437
|
const targetStore = createGitHubAuthStore(targetRoot);
|
|
@@ -7072,11 +7703,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7072
7703
|
const runId = normalizeString(body.runId);
|
|
7073
7704
|
const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
|
|
7074
7705
|
const promptOverride = normalizeString(body.promptOverride);
|
|
7706
|
+
const restart = body.restart === true;
|
|
7075
7707
|
if (!runId) {
|
|
7076
7708
|
return deps.badRequest("runId is required");
|
|
7077
7709
|
}
|
|
7078
7710
|
try {
|
|
7079
|
-
await deps.resumeRunRecord(state, { runId, createdAt, promptOverride });
|
|
7711
|
+
await deps.resumeRunRecord(state, { runId, createdAt, promptOverride, restart });
|
|
7080
7712
|
deps.broadcastSnapshotInvalidation(state);
|
|
7081
7713
|
return deps.jsonResponse({ ok: true, runId, createdAt });
|
|
7082
7714
|
} catch (error) {
|
|
@@ -7085,7 +7717,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7085
7717
|
}
|
|
7086
7718
|
if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
|
|
7087
7719
|
const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
|
|
7088
|
-
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";
|
|
7089
7721
|
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
7090
7722
|
return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
|
|
7091
7723
|
}
|
|
@@ -7401,9 +8033,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7401
8033
|
} catch {
|
|
7402
8034
|
return deps.badRequest("Invalid artifact path");
|
|
7403
8035
|
}
|
|
7404
|
-
|
|
8036
|
+
mkdirSync13(dirname15(artifactPath), { recursive: true });
|
|
7405
8037
|
const bytes = Buffer.from(contentBase64, "base64");
|
|
7406
|
-
|
|
8038
|
+
writeFileSync12(artifactPath, bytes);
|
|
7407
8039
|
writeJsonFile4(`${artifactPath}.json`, {
|
|
7408
8040
|
workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
|
|
7409
8041
|
runId,
|
|
@@ -7565,12 +8197,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7565
8197
|
try {
|
|
7566
8198
|
const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
|
|
7567
8199
|
const runRoot = deps.normalizeRelativePath(runsRoot, runId);
|
|
7568
|
-
const artifactsRoot =
|
|
8200
|
+
const artifactsRoot = resolve20(runRoot, "remote-artifacts");
|
|
7569
8201
|
artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
|
|
7570
8202
|
} catch {
|
|
7571
8203
|
return deps.badRequest("Invalid artifact path");
|
|
7572
8204
|
}
|
|
7573
|
-
if (!
|
|
8205
|
+
if (!existsSync13(artifactPath)) {
|
|
7574
8206
|
return deps.notFound();
|
|
7575
8207
|
}
|
|
7576
8208
|
return new Response(Bun.file(artifactPath));
|
|
@@ -8463,8 +9095,8 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
8463
9095
|
}
|
|
8464
9096
|
|
|
8465
9097
|
// packages/server/src/server-helpers/inspector-jobs.ts
|
|
8466
|
-
import { existsSync as
|
|
8467
|
-
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";
|
|
8468
9100
|
import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
|
|
8469
9101
|
import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
|
|
8470
9102
|
import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -8572,8 +9204,8 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8572
9204
|
|
|
8573
9205
|
// packages/server/src/inspector/mission.ts
|
|
8574
9206
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8575
|
-
import { appendFileSync, existsSync as
|
|
8576
|
-
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";
|
|
8577
9209
|
function isJsonValue(value) {
|
|
8578
9210
|
if (value === null)
|
|
8579
9211
|
return true;
|
|
@@ -8613,7 +9245,7 @@ function isRecord2(value) {
|
|
|
8613
9245
|
}
|
|
8614
9246
|
function readJsonRecord(path) {
|
|
8615
9247
|
try {
|
|
8616
|
-
const parsed = JSON.parse(
|
|
9248
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
8617
9249
|
if (!isRecord2(parsed)) {
|
|
8618
9250
|
return { ok: false, error: `Mission file ${path} does not contain an object` };
|
|
8619
9251
|
}
|
|
@@ -8693,14 +9325,14 @@ function missionActionDetails(mission) {
|
|
|
8693
9325
|
};
|
|
8694
9326
|
}
|
|
8695
9327
|
function writeJsonFile5(path, value) {
|
|
8696
|
-
|
|
9328
|
+
mkdirSync14(dirname16(path), { recursive: true });
|
|
8697
9329
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
8698
|
-
|
|
9330
|
+
writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
|
|
8699
9331
|
`, "utf8");
|
|
8700
9332
|
renameSync(tempPath, path);
|
|
8701
9333
|
}
|
|
8702
9334
|
function resolveInspectorMissionPaths(projectRoot) {
|
|
8703
|
-
const inspectorDir =
|
|
9335
|
+
const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
|
|
8704
9336
|
return {
|
|
8705
9337
|
inspectorDir,
|
|
8706
9338
|
missionsDir: join(inspectorDir, "missions"),
|
|
@@ -8709,8 +9341,8 @@ function resolveInspectorMissionPaths(projectRoot) {
|
|
|
8709
9341
|
}
|
|
8710
9342
|
function createInspectorMissionController(options) {
|
|
8711
9343
|
const paths = resolveInspectorMissionPaths(options.projectRoot);
|
|
8712
|
-
|
|
8713
|
-
|
|
9344
|
+
mkdirSync14(paths.missionsDir, { recursive: true });
|
|
9345
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8714
9346
|
const now = options.now ?? (() => new Date().toISOString());
|
|
8715
9347
|
const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
|
|
8716
9348
|
function missionPath(missionId) {
|
|
@@ -8720,15 +9352,15 @@ function createInspectorMissionController(options) {
|
|
|
8720
9352
|
return join(paths.journalsDir, `${missionId}.jsonl`);
|
|
8721
9353
|
}
|
|
8722
9354
|
function appendMissionJournal(entry) {
|
|
8723
|
-
|
|
9355
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8724
9356
|
appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
|
|
8725
9357
|
`, "utf8");
|
|
8726
9358
|
}
|
|
8727
9359
|
function listMissionJournal(missionId) {
|
|
8728
9360
|
const path = journalPath(missionId);
|
|
8729
|
-
if (!
|
|
9361
|
+
if (!existsSync14(path))
|
|
8730
9362
|
return [];
|
|
8731
|
-
return
|
|
9363
|
+
return readFileSync10(path, "utf8").split(`
|
|
8732
9364
|
`).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
|
|
8733
9365
|
id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
|
|
8734
9366
|
missionId,
|
|
@@ -8744,7 +9376,7 @@ function createInspectorMissionController(options) {
|
|
|
8744
9376
|
}
|
|
8745
9377
|
function readMissionOnly(missionId) {
|
|
8746
9378
|
const path = missionPath(missionId);
|
|
8747
|
-
if (!
|
|
9379
|
+
if (!existsSync14(path)) {
|
|
8748
9380
|
return { ok: false, error: `Mission ${missionId} was not found` };
|
|
8749
9381
|
}
|
|
8750
9382
|
const read = readJsonRecord(path);
|
|
@@ -8795,7 +9427,7 @@ function createInspectorMissionController(options) {
|
|
|
8795
9427
|
const source = cloneJsonRecord(input.sourceTask);
|
|
8796
9428
|
const missionId = nextId();
|
|
8797
9429
|
const path = missionPath(missionId);
|
|
8798
|
-
if (
|
|
9430
|
+
if (existsSync14(path)) {
|
|
8799
9431
|
const existing = readMissionOnly(missionId);
|
|
8800
9432
|
if (!existing.ok)
|
|
8801
9433
|
return existing;
|
|
@@ -10395,8 +11027,8 @@ function createCodexInspectorTransport(options) {
|
|
|
10395
11027
|
const sendRequest = async (method, params) => {
|
|
10396
11028
|
const id = nextRequestId;
|
|
10397
11029
|
nextRequestId += 1;
|
|
10398
|
-
const response = new Promise((
|
|
10399
|
-
pendingResponses.set(id, { resolve:
|
|
11030
|
+
const response = new Promise((resolve22, reject) => {
|
|
11031
|
+
pendingResponses.set(id, { resolve: resolve22, reject });
|
|
10400
11032
|
});
|
|
10401
11033
|
response.catch(() => {});
|
|
10402
11034
|
try {
|
|
@@ -10706,9 +11338,9 @@ function createCodexInspectorTransport(options) {
|
|
|
10706
11338
|
}
|
|
10707
11339
|
lastAssistantMessage = null;
|
|
10708
11340
|
lastError = null;
|
|
10709
|
-
const turnResult = new Promise((
|
|
11341
|
+
const turnResult = new Promise((resolve22, reject) => {
|
|
10710
11342
|
currentTurn = {
|
|
10711
|
-
resolve:
|
|
11343
|
+
resolve: resolve22,
|
|
10712
11344
|
reject,
|
|
10713
11345
|
events: []
|
|
10714
11346
|
};
|
|
@@ -10768,13 +11400,13 @@ function createCodexInspectorTransport(options) {
|
|
|
10768
11400
|
};
|
|
10769
11401
|
}
|
|
10770
11402
|
function writeChildLine(child, line) {
|
|
10771
|
-
return new Promise((
|
|
11403
|
+
return new Promise((resolve22, reject) => {
|
|
10772
11404
|
child.stdin.write(line, (error) => {
|
|
10773
11405
|
if (error) {
|
|
10774
11406
|
reject(error);
|
|
10775
11407
|
return;
|
|
10776
11408
|
}
|
|
10777
|
-
|
|
11409
|
+
resolve22();
|
|
10778
11410
|
});
|
|
10779
11411
|
});
|
|
10780
11412
|
}
|
|
@@ -10787,10 +11419,10 @@ function terminateChild(child) {
|
|
|
10787
11419
|
} catch {}
|
|
10788
11420
|
}
|
|
10789
11421
|
async function waitForChildSpawn(child) {
|
|
10790
|
-
await new Promise((
|
|
11422
|
+
await new Promise((resolve22, reject) => {
|
|
10791
11423
|
const onSpawn = () => {
|
|
10792
11424
|
cleanup();
|
|
10793
|
-
|
|
11425
|
+
resolve22();
|
|
10794
11426
|
};
|
|
10795
11427
|
const onError = (error) => {
|
|
10796
11428
|
cleanup();
|
|
@@ -11302,8 +11934,8 @@ function createGlobalInspectorService(options) {
|
|
|
11302
11934
|
|
|
11303
11935
|
// packages/server/src/inspector/upstream-sync.ts
|
|
11304
11936
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11305
|
-
import { existsSync as
|
|
11306
|
-
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";
|
|
11307
11939
|
import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
|
|
11308
11940
|
var UPSTREAM_VALIDATION_DESCRIPTIONS = {
|
|
11309
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.",
|
|
@@ -11441,34 +12073,34 @@ function defaultGitRunner(repoRoot, args) {
|
|
|
11441
12073
|
}
|
|
11442
12074
|
function upstreamStatePath(projectRoot, override) {
|
|
11443
12075
|
if (override) {
|
|
11444
|
-
return
|
|
12076
|
+
return resolve22(override);
|
|
11445
12077
|
}
|
|
11446
|
-
return
|
|
12078
|
+
return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
|
|
11447
12079
|
}
|
|
11448
12080
|
function readUpstreamState(projectRoot, statePath) {
|
|
11449
12081
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11450
|
-
if (!
|
|
12082
|
+
if (!existsSync15(path)) {
|
|
11451
12083
|
return null;
|
|
11452
12084
|
}
|
|
11453
12085
|
try {
|
|
11454
|
-
return JSON.parse(
|
|
12086
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
11455
12087
|
} catch {
|
|
11456
12088
|
return null;
|
|
11457
12089
|
}
|
|
11458
12090
|
}
|
|
11459
12091
|
function writeUpstreamState(projectRoot, state, statePath) {
|
|
11460
12092
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11461
|
-
|
|
11462
|
-
|
|
12093
|
+
mkdirSync15(dirname17(path), { recursive: true });
|
|
12094
|
+
writeFileSync14(path, `${JSON.stringify(state, null, 2)}
|
|
11463
12095
|
`, "utf8");
|
|
11464
12096
|
}
|
|
11465
12097
|
function readImportedRevision(projectRoot, upstreamsDocPath) {
|
|
11466
12098
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11467
|
-
const docPath = upstreamsDocPath ?
|
|
11468
|
-
if (!
|
|
12099
|
+
const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
|
|
12100
|
+
if (!existsSync15(docPath)) {
|
|
11469
12101
|
throw new Error(`UPSTREAMS.md not found at ${docPath}`);
|
|
11470
12102
|
}
|
|
11471
|
-
const docContent =
|
|
12103
|
+
const docContent = readFileSync11(docPath, "utf-8");
|
|
11472
12104
|
const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
|
|
11473
12105
|
if (!revision) {
|
|
11474
12106
|
throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
|
|
@@ -11490,7 +12122,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
|
|
|
11490
12122
|
return null;
|
|
11491
12123
|
}
|
|
11492
12124
|
function isGitCheckout(path, gitRunner) {
|
|
11493
|
-
if (!
|
|
12125
|
+
if (!existsSync15(resolve22(path, ".git"))) {
|
|
11494
12126
|
return false;
|
|
11495
12127
|
}
|
|
11496
12128
|
const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -11499,12 +12131,12 @@ function isGitCheckout(path, gitRunner) {
|
|
|
11499
12131
|
function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
|
|
11500
12132
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11501
12133
|
const candidates = [
|
|
11502
|
-
explicitCheckout ?
|
|
11503
|
-
process.env.UPSTREAM_CHECKOUT?.trim() ?
|
|
11504
|
-
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ?
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
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")
|
|
11508
12140
|
].filter(Boolean);
|
|
11509
12141
|
for (const candidate of candidates) {
|
|
11510
12142
|
if (isGitCheckout(candidate, gitRunner)) {
|
|
@@ -11740,10 +12372,10 @@ async function runUpstreamSyncScan(options) {
|
|
|
11740
12372
|
}
|
|
11741
12373
|
|
|
11742
12374
|
// packages/server/src/server-helpers/task-config.ts
|
|
11743
|
-
import { existsSync as
|
|
12375
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11744
12376
|
async function readTaskConfig(projectRoot) {
|
|
11745
12377
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
11746
|
-
if (!
|
|
12378
|
+
if (!existsSync16(taskConfigPath)) {
|
|
11747
12379
|
return {};
|
|
11748
12380
|
}
|
|
11749
12381
|
try {
|
|
@@ -11779,11 +12411,11 @@ function resolveFollowupSourceCommit(input) {
|
|
|
11779
12411
|
}
|
|
11780
12412
|
async function createInspectorFollowupTask(projectRoot, input) {
|
|
11781
12413
|
const monorepoRoot = resolveMonorepoRoot5(projectRoot);
|
|
11782
|
-
const issuesPath =
|
|
11783
|
-
const taskStatePath =
|
|
11784
|
-
const taskConfigPath =
|
|
11785
|
-
|
|
11786
|
-
|
|
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 });
|
|
11787
12419
|
const summary = normalizeString(input.summary) ?? "Inspector follow-up";
|
|
11788
12420
|
const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
|
|
11789
12421
|
const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
|
|
@@ -11802,7 +12434,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11802
12434
|
const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
|
|
11803
12435
|
const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
|
|
11804
12436
|
const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
|
|
11805
|
-
const existingIssueLines =
|
|
12437
|
+
const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
11806
12438
|
const existingIssues = existingIssueLines.map((line) => {
|
|
11807
12439
|
try {
|
|
11808
12440
|
return JSON.parse(line);
|
|
@@ -11811,7 +12443,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11811
12443
|
}
|
|
11812
12444
|
}).filter((value) => value !== null);
|
|
11813
12445
|
const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
|
|
11814
|
-
const rawTaskState =
|
|
12446
|
+
const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
|
|
11815
12447
|
const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
|
|
11816
12448
|
const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
|
|
11817
12449
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
@@ -11837,7 +12469,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11837
12469
|
updated_at: createdAt,
|
|
11838
12470
|
labels: mergedLabels
|
|
11839
12471
|
};
|
|
11840
|
-
|
|
12472
|
+
writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
|
|
11841
12473
|
`)}
|
|
11842
12474
|
${JSON.stringify(issueRecord)}
|
|
11843
12475
|
` : `${JSON.stringify(issueRecord)}
|
|
@@ -11855,7 +12487,7 @@ ${JSON.stringify(issueRecord)}
|
|
|
11855
12487
|
labels: mergedLabels
|
|
11856
12488
|
};
|
|
11857
12489
|
});
|
|
11858
|
-
|
|
12490
|
+
writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
|
|
11859
12491
|
`)}
|
|
11860
12492
|
`, "utf8");
|
|
11861
12493
|
}
|
|
@@ -11878,14 +12510,14 @@ ${JSON.stringify(issueRecord)}
|
|
|
11878
12510
|
}
|
|
11879
12511
|
};
|
|
11880
12512
|
}
|
|
11881
|
-
|
|
12513
|
+
writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
|
|
11882
12514
|
`, "utf8");
|
|
11883
12515
|
tasks[taskId] = {
|
|
11884
12516
|
status,
|
|
11885
12517
|
sourceCommit: resolveFollowupSourceCommit(input),
|
|
11886
12518
|
...sourceKey ? { sourceKey } : {}
|
|
11887
12519
|
};
|
|
11888
|
-
|
|
12520
|
+
writeFileSync15(taskStatePath, `${JSON.stringify({
|
|
11889
12521
|
schemaVersion: 1,
|
|
11890
12522
|
baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
|
|
11891
12523
|
tasks
|
|
@@ -12193,12 +12825,12 @@ function isAuthorizedLinearWebhookRequest(req) {
|
|
|
12193
12825
|
}
|
|
12194
12826
|
|
|
12195
12827
|
// packages/server/src/server-helpers/notifications.ts
|
|
12196
|
-
import { existsSync as
|
|
12197
|
-
import { dirname as
|
|
12828
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
|
|
12829
|
+
import { dirname as dirname19 } from "path";
|
|
12198
12830
|
async function loadNotificationConfig(path) {
|
|
12199
|
-
if (!
|
|
12831
|
+
if (!existsSync18(path)) {
|
|
12200
12832
|
const defaultConfig = { targets: [] };
|
|
12201
|
-
|
|
12833
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
12202
12834
|
await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
|
|
12203
12835
|
`);
|
|
12204
12836
|
return defaultConfig;
|
|
@@ -12213,10 +12845,10 @@ async function loadNotificationConfig(path) {
|
|
|
12213
12845
|
}
|
|
12214
12846
|
}
|
|
12215
12847
|
function readRecentEvents(file, limit) {
|
|
12216
|
-
if (!
|
|
12848
|
+
if (!existsSync18(file)) {
|
|
12217
12849
|
return [];
|
|
12218
12850
|
}
|
|
12219
|
-
const lines =
|
|
12851
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
|
|
12220
12852
|
const events = [];
|
|
12221
12853
|
for (const line of lines) {
|
|
12222
12854
|
try {
|
|
@@ -12311,11 +12943,11 @@ function extractObjectLiteralBlock(source, property) {
|
|
|
12311
12943
|
}
|
|
12312
12944
|
function readFallbackIssueAnalysisConfig(projectRoot) {
|
|
12313
12945
|
for (const fileName of ["rig.config.ts", "rig.config.json"]) {
|
|
12314
|
-
const path =
|
|
12315
|
-
if (!
|
|
12946
|
+
const path = resolve24(projectRoot, fileName);
|
|
12947
|
+
if (!existsSync19(path))
|
|
12316
12948
|
continue;
|
|
12317
12949
|
try {
|
|
12318
|
-
const source =
|
|
12950
|
+
const source = readFileSync14(path, "utf8");
|
|
12319
12951
|
if (fileName.endsWith(".json"))
|
|
12320
12952
|
return JSON.parse(source);
|
|
12321
12953
|
const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
|
|
@@ -12448,8 +13080,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
|
|
|
12448
13080
|
async function withServerPathEnv(projectRoot, fn) {
|
|
12449
13081
|
const waitForTurn = serverPathEnvQueue;
|
|
12450
13082
|
let releaseTurn;
|
|
12451
|
-
serverPathEnvQueue = new Promise((
|
|
12452
|
-
releaseTurn =
|
|
13083
|
+
serverPathEnvQueue = new Promise((resolve25) => {
|
|
13084
|
+
releaseTurn = resolve25;
|
|
12453
13085
|
});
|
|
12454
13086
|
await waitForTurn;
|
|
12455
13087
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -12485,9 +13117,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
12485
13117
|
return withServerPathEnv(projectRoot, fn);
|
|
12486
13118
|
}
|
|
12487
13119
|
async function readWorkspaceTasks(projectRoot) {
|
|
12488
|
-
const issuesPath =
|
|
13120
|
+
const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
|
|
12489
13121
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
12490
|
-
if (!
|
|
13122
|
+
if (!existsSync19(issuesPath)) {
|
|
12491
13123
|
return [];
|
|
12492
13124
|
}
|
|
12493
13125
|
const latestById = new Map;
|
|
@@ -12506,7 +13138,7 @@ async function readWorkspaceTasks(projectRoot) {
|
|
|
12506
13138
|
description: normalizeString(entry.description),
|
|
12507
13139
|
acceptanceCriteria: normalizeString(entry.acceptance_criteria),
|
|
12508
13140
|
status: normalizedStatus ?? "unknown",
|
|
12509
|
-
sourceStatus: normalizedStatus ? null : rawStatus,
|
|
13141
|
+
sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
|
|
12510
13142
|
priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
|
|
12511
13143
|
issueType: normalizeString(entry.issue_type),
|
|
12512
13144
|
role: normalizeString(config.role) ?? null,
|
|
@@ -12561,11 +13193,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
|
|
|
12561
13193
|
continue;
|
|
12562
13194
|
add(run.artifactRoot);
|
|
12563
13195
|
if (run.worktreePath) {
|
|
12564
|
-
add(
|
|
13196
|
+
add(resolve24(run.worktreePath, "artifacts", taskId));
|
|
12565
13197
|
}
|
|
12566
13198
|
}
|
|
12567
13199
|
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
12568
|
-
add(
|
|
13200
|
+
add(resolve24(artifactsRoot, taskId));
|
|
12569
13201
|
}
|
|
12570
13202
|
return candidates;
|
|
12571
13203
|
}
|
|
@@ -12579,7 +13211,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12579
13211
|
}
|
|
12580
13212
|
}
|
|
12581
13213
|
return taskIds.flatMap((currentTaskId) => {
|
|
12582
|
-
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) =>
|
|
13214
|
+
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
|
|
12583
13215
|
if (!currentRoot) {
|
|
12584
13216
|
return [];
|
|
12585
13217
|
}
|
|
@@ -12591,7 +13223,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12591
13223
|
taskId: currentTaskId,
|
|
12592
13224
|
kind: "file",
|
|
12593
13225
|
label: fileName,
|
|
12594
|
-
path:
|
|
13226
|
+
path: resolve24(currentRoot, fileName),
|
|
12595
13227
|
url: null,
|
|
12596
13228
|
metadata: {
|
|
12597
13229
|
fileName
|
|
@@ -12634,11 +13266,11 @@ function buildInspectorStreamPayload(state, sequence) {
|
|
|
12634
13266
|
}
|
|
12635
13267
|
function listRemoteRunArtifacts(projectRoot, runId) {
|
|
12636
13268
|
const root = remoteArtifactsRoot(projectRoot, runId);
|
|
12637
|
-
if (!
|
|
13269
|
+
if (!existsSync19(root)) {
|
|
12638
13270
|
return [];
|
|
12639
13271
|
}
|
|
12640
13272
|
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
|
|
12641
|
-
const artifactPath =
|
|
13273
|
+
const artifactPath = resolve24(root, entry.name);
|
|
12642
13274
|
const stat = statSync6(artifactPath);
|
|
12643
13275
|
const meta = readJsonFile4(`${artifactPath}.json`, null);
|
|
12644
13276
|
return {
|
|
@@ -12880,8 +13512,8 @@ function fileStats(path) {
|
|
|
12880
13512
|
}
|
|
12881
13513
|
}
|
|
12882
13514
|
function runFileCursor(projectRoot, run) {
|
|
12883
|
-
const runDir =
|
|
12884
|
-
const runJson = fileStats(
|
|
13515
|
+
const runDir = dirname20(runLogsPath(projectRoot, run.runId));
|
|
13516
|
+
const runJson = fileStats(resolve24(runDir, "run.json"));
|
|
12885
13517
|
const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
|
|
12886
13518
|
const logs = fileStats(runLogsPath(projectRoot, run.runId));
|
|
12887
13519
|
return {
|
|
@@ -12931,10 +13563,10 @@ function startRunFileWatcher(state, pollMs) {
|
|
|
12931
13563
|
}, Math.max(250, Math.min(pollMs, 1000)));
|
|
12932
13564
|
}
|
|
12933
13565
|
function startPoller(state, pollMs) {
|
|
12934
|
-
let offset =
|
|
13566
|
+
let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
|
|
12935
13567
|
return setInterval(async () => {
|
|
12936
13568
|
try {
|
|
12937
|
-
if (!
|
|
13569
|
+
if (!existsSync19(state.eventsFile)) {
|
|
12938
13570
|
return;
|
|
12939
13571
|
}
|
|
12940
13572
|
const file = await open(state.eventsFile, "r");
|
|
@@ -12986,6 +13618,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
12986
13618
|
const server = Bun.serve({
|
|
12987
13619
|
hostname: options.host,
|
|
12988
13620
|
port: options.port,
|
|
13621
|
+
idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
|
|
12989
13622
|
fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
|
|
12990
13623
|
websocket: {
|
|
12991
13624
|
open(ws) {
|
|
@@ -13061,7 +13694,7 @@ function resolveProjectRoot() {
|
|
|
13061
13694
|
return resolveRigProjectRoot({
|
|
13062
13695
|
envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
|
|
13063
13696
|
cwd: process.cwd(),
|
|
13064
|
-
fallbackRoot:
|
|
13697
|
+
fallbackRoot: resolve24(import.meta.dir, "../..")
|
|
13065
13698
|
});
|
|
13066
13699
|
}
|
|
13067
13700
|
var __testOnly = {
|