@h-rig/server 0.0.6-alpha.2 → 0.0.6-alpha.21
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 +1513 -331
- package/dist/src/server-helpers/github-api-session-index.js +107 -0
- package/dist/src/server-helpers/github-auth-store.js +68 -24
- package/dist/src/server-helpers/github-project-status-sync.js +3 -0
- package/dist/src/server-helpers/github-user-namespace.js +102 -0
- package/dist/src/server-helpers/http-router.js +1207 -185
- 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-io.js +13 -0
- package/dist/src/server-helpers/run-mutations.js +599 -114
- package/dist/src/server-helpers/run-writers.js +1 -2
- package/dist/src/server-helpers/ws-router.js +7 -1
- package/dist/src/server.js +1512 -331
- 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,
|
|
@@ -1580,6 +1580,18 @@ function readJsonlFileTail(path, options) {
|
|
|
1580
1580
|
const completeLines = start > 0 ? lines.slice(1) : lines;
|
|
1581
1581
|
return parseJsonlRecords(completeLines.filter(Boolean).slice(-limit));
|
|
1582
1582
|
}
|
|
1583
|
+
async function readRunTimelinePage(projectRoot, runId, options = {}) {
|
|
1584
|
+
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 200), 500));
|
|
1585
|
+
const cursor = options.cursor == null ? 0 : Number.parseInt(options.cursor, 10);
|
|
1586
|
+
const entries = readJsonlFile(runTimelinePath(projectRoot, runId)).filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
1587
|
+
const startInclusive = Number.isFinite(cursor) ? Math.max(0, Math.min(cursor, entries.length)) : 0;
|
|
1588
|
+
const endExclusive = Math.min(entries.length, startInclusive + limit);
|
|
1589
|
+
return {
|
|
1590
|
+
entries: entries.slice(startInclusive, endExclusive).map((entry, offset) => ({ ...entry, cursor: startInclusive + offset + 1 })),
|
|
1591
|
+
nextCursor: String(endExclusive),
|
|
1592
|
+
hasMore: endExclusive < entries.length
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1583
1595
|
var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
|
|
1584
1596
|
async function readRunLogsPage(projectRoot, runId, options = {}) {
|
|
1585
1597
|
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 200), 500));
|
|
@@ -2819,7 +2831,6 @@ function createGitHubTaskReconciler(input) {
|
|
|
2819
2831
|
}
|
|
2820
2832
|
|
|
2821
2833
|
// packages/server/src/server-helpers/issue-analysis.ts
|
|
2822
|
-
import { execFile } from "child_process";
|
|
2823
2834
|
import { createHash } from "crypto";
|
|
2824
2835
|
function stableIssueHash(issue) {
|
|
2825
2836
|
const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
|
|
@@ -2953,16 +2964,33 @@ function parseIssueAnalysisResult(raw) {
|
|
|
2953
2964
|
return result;
|
|
2954
2965
|
}
|
|
2955
2966
|
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 ?? "") });
|
|
2967
|
+
return async (command, args, options) => {
|
|
2968
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
2969
|
+
const proc = Bun.spawn([command, ...args], {
|
|
2970
|
+
stdout: "pipe",
|
|
2971
|
+
stderr: "pipe",
|
|
2972
|
+
env
|
|
2964
2973
|
});
|
|
2965
|
-
|
|
2974
|
+
let timedOut = false;
|
|
2975
|
+
const timer = setTimeout(() => {
|
|
2976
|
+
timedOut = true;
|
|
2977
|
+
proc.kill();
|
|
2978
|
+
}, options.timeoutMs);
|
|
2979
|
+
try {
|
|
2980
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
2981
|
+
new Response(proc.stdout).text(),
|
|
2982
|
+
new Response(proc.stderr).text(),
|
|
2983
|
+
proc.exited
|
|
2984
|
+
]);
|
|
2985
|
+
return {
|
|
2986
|
+
exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
|
|
2987
|
+
stdout,
|
|
2988
|
+
stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
|
|
2989
|
+
};
|
|
2990
|
+
} finally {
|
|
2991
|
+
clearTimeout(timer);
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2966
2994
|
}
|
|
2967
2995
|
function createPiIssueAnalyzer(input = {}) {
|
|
2968
2996
|
const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
|
|
@@ -2970,7 +2998,10 @@ function createPiIssueAnalyzer(input = {}) {
|
|
|
2970
2998
|
const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
|
|
2971
2999
|
return async ({ prompt }) => {
|
|
2972
3000
|
const args = ["--print", "--mode", "json", "--no-session"];
|
|
2973
|
-
const
|
|
3001
|
+
const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
|
|
3002
|
+
const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
|
|
3003
|
+
if (provider)
|
|
3004
|
+
args.push("--provider", provider);
|
|
2974
3005
|
if (model)
|
|
2975
3006
|
args.push("--model", model);
|
|
2976
3007
|
args.push(prompt);
|
|
@@ -3218,8 +3249,7 @@ function buildRunStartPatch(startedAt) {
|
|
|
3218
3249
|
status: "preparing",
|
|
3219
3250
|
startedAt,
|
|
3220
3251
|
completedAt: null,
|
|
3221
|
-
errorText: null
|
|
3222
|
-
serverPid: process.pid
|
|
3252
|
+
errorText: null
|
|
3223
3253
|
};
|
|
3224
3254
|
}
|
|
3225
3255
|
|
|
@@ -3688,7 +3718,7 @@ function applyOrchestrationCommand2(state, command) {
|
|
|
3688
3718
|
import { spawn as spawn3 } from "child_process";
|
|
3689
3719
|
import { loadConfig } from "@rig/core/load-config";
|
|
3690
3720
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3691
|
-
import { dirname as
|
|
3721
|
+
import { dirname as dirname9, relative as relative2, resolve as resolve14 } from "path";
|
|
3692
3722
|
import {
|
|
3693
3723
|
listAuthorityRuns as listAuthorityRuns4,
|
|
3694
3724
|
readAuthorityRun as readAuthorityRun4,
|
|
@@ -3701,6 +3731,11 @@ import {
|
|
|
3701
3731
|
buildTaskRunLifecycleComment,
|
|
3702
3732
|
updateConfiguredTaskSourceTask
|
|
3703
3733
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
3734
|
+
import {
|
|
3735
|
+
closeIssueAfterMergedPr,
|
|
3736
|
+
commitRunChanges,
|
|
3737
|
+
runPrAutomation
|
|
3738
|
+
} from "@rig/runtime/control-plane/native/pr-automation";
|
|
3704
3739
|
|
|
3705
3740
|
// packages/server/src/scheduler.ts
|
|
3706
3741
|
import { normalizeTaskLifecycleStatus } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -3812,8 +3847,8 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
3812
3847
|
|
|
3813
3848
|
// packages/server/src/server-helpers/github-auth-store.ts
|
|
3814
3849
|
import { randomBytes } from "crypto";
|
|
3815
|
-
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
3816
|
-
import { resolve as resolve13 } from "path";
|
|
3850
|
+
import { chmodSync, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
3851
|
+
import { dirname as dirname8, resolve as resolve13 } from "path";
|
|
3817
3852
|
function cleanString(value) {
|
|
3818
3853
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3819
3854
|
}
|
|
@@ -3843,6 +3878,26 @@ function parseApiSessions(value) {
|
|
|
3843
3878
|
}];
|
|
3844
3879
|
});
|
|
3845
3880
|
}
|
|
3881
|
+
function parsePendingDevice(value) {
|
|
3882
|
+
if (!value || typeof value !== "object")
|
|
3883
|
+
return null;
|
|
3884
|
+
const record = value;
|
|
3885
|
+
const pollId = cleanString(record.pollId);
|
|
3886
|
+
const deviceCode = cleanString(record.deviceCode);
|
|
3887
|
+
const expiresAt = cleanString(record.expiresAt);
|
|
3888
|
+
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3889
|
+
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3890
|
+
return null;
|
|
3891
|
+
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3892
|
+
}
|
|
3893
|
+
function parsePendingDevices(value) {
|
|
3894
|
+
if (!Array.isArray(value))
|
|
3895
|
+
return [];
|
|
3896
|
+
return value.flatMap((entry) => {
|
|
3897
|
+
const pending = parsePendingDevice(entry);
|
|
3898
|
+
return pending ? [pending] : [];
|
|
3899
|
+
});
|
|
3900
|
+
}
|
|
3846
3901
|
function readStoredAuth(stateFile) {
|
|
3847
3902
|
if (!existsSync6(stateFile))
|
|
3848
3903
|
return {};
|
|
@@ -3856,6 +3911,7 @@ function readStoredAuth(stateFile) {
|
|
|
3856
3911
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
3857
3912
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
3858
3913
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
3914
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
3859
3915
|
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
3860
3916
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
3861
3917
|
};
|
|
@@ -3863,34 +3919,36 @@ function readStoredAuth(stateFile) {
|
|
|
3863
3919
|
return {};
|
|
3864
3920
|
}
|
|
3865
3921
|
}
|
|
3866
|
-
function parsePendingDevice(value) {
|
|
3867
|
-
if (!value || typeof value !== "object")
|
|
3868
|
-
return null;
|
|
3869
|
-
const record = value;
|
|
3870
|
-
const pollId = cleanString(record.pollId);
|
|
3871
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
3872
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
3873
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
3874
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
3875
|
-
return null;
|
|
3876
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
3877
|
-
}
|
|
3878
3922
|
function newApiSessionToken() {
|
|
3879
3923
|
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
3880
3924
|
}
|
|
3881
3925
|
function writeStoredAuth(stateFile, payload) {
|
|
3882
|
-
mkdirSync6(
|
|
3926
|
+
mkdirSync6(dirname8(stateFile), { recursive: true });
|
|
3883
3927
|
writeFileSync5(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
3884
3928
|
`, { encoding: "utf8", mode: 384 });
|
|
3885
3929
|
try {
|
|
3886
3930
|
chmodSync(stateFile, 384);
|
|
3887
3931
|
} catch {}
|
|
3888
3932
|
}
|
|
3933
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
3934
|
+
return resolve13(projectRoot, ".rig", "state", "github-auth.json");
|
|
3935
|
+
}
|
|
3889
3936
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
3890
3937
|
return resolve13(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
3891
3938
|
}
|
|
3892
|
-
function
|
|
3893
|
-
const
|
|
3939
|
+
function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
|
|
3940
|
+
const targetFile = localProjectAuthStateFile(projectRoot);
|
|
3941
|
+
mkdirSync6(dirname8(targetFile), { recursive: true });
|
|
3942
|
+
if (existsSync6(stateFile)) {
|
|
3943
|
+
copyFileSync(stateFile, targetFile);
|
|
3944
|
+
try {
|
|
3945
|
+
chmodSync(targetFile, 384);
|
|
3946
|
+
} catch {}
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
writeStoredAuth(targetFile, {});
|
|
3950
|
+
}
|
|
3951
|
+
function createGitHubAuthStoreFromStateFile(stateFile) {
|
|
3894
3952
|
return {
|
|
3895
3953
|
stateFile,
|
|
3896
3954
|
status(options) {
|
|
@@ -3920,6 +3978,7 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3920
3978
|
scopes: input.scopes ?? [],
|
|
3921
3979
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
3922
3980
|
pendingDevice: null,
|
|
3981
|
+
pendingDevices: [],
|
|
3923
3982
|
apiSessions: previous.apiSessions ?? [],
|
|
3924
3983
|
updatedAt: new Date().toISOString()
|
|
3925
3984
|
});
|
|
@@ -3948,15 +4007,24 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3948
4007
|
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
3949
4008
|
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
3950
4009
|
},
|
|
3951
|
-
copyToProjectRoot(
|
|
3952
|
-
const targetFile = resolveGitHubAuthStateFile(
|
|
4010
|
+
copyToProjectRoot(projectRoot) {
|
|
4011
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
3953
4012
|
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
3954
4013
|
},
|
|
4014
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
4015
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
4016
|
+
},
|
|
3955
4017
|
savePendingDevice(input) {
|
|
3956
4018
|
const previous = readStoredAuth(stateFile);
|
|
4019
|
+
const pendingDevices = [
|
|
4020
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
4021
|
+
...previous.pendingDevices ?? [],
|
|
4022
|
+
input
|
|
4023
|
+
].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
|
|
3957
4024
|
writeStoredAuth(stateFile, {
|
|
3958
4025
|
...previous,
|
|
3959
|
-
pendingDevice:
|
|
4026
|
+
pendingDevice: null,
|
|
4027
|
+
pendingDevices,
|
|
3960
4028
|
updatedAt: new Date().toISOString()
|
|
3961
4029
|
});
|
|
3962
4030
|
},
|
|
@@ -3969,23 +4037,32 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
3969
4037
|
});
|
|
3970
4038
|
},
|
|
3971
4039
|
readPendingDevice(pollId) {
|
|
3972
|
-
const
|
|
3973
|
-
|
|
4040
|
+
const previous = readStoredAuth(stateFile);
|
|
4041
|
+
const pending = [
|
|
4042
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
4043
|
+
...previous.pendingDevices ?? []
|
|
4044
|
+
].find((entry) => entry.pollId === pollId) ?? null;
|
|
4045
|
+
if (!pending)
|
|
3974
4046
|
return null;
|
|
3975
4047
|
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
3976
4048
|
return null;
|
|
3977
4049
|
return pending;
|
|
3978
4050
|
},
|
|
3979
|
-
clearPendingDevice() {
|
|
4051
|
+
clearPendingDevice(pollId) {
|
|
3980
4052
|
const previous = readStoredAuth(stateFile);
|
|
4053
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
3981
4054
|
writeStoredAuth(stateFile, {
|
|
3982
4055
|
...previous,
|
|
3983
4056
|
pendingDevice: null,
|
|
4057
|
+
pendingDevices: remaining,
|
|
3984
4058
|
updatedAt: new Date().toISOString()
|
|
3985
4059
|
});
|
|
3986
4060
|
}
|
|
3987
4061
|
};
|
|
3988
4062
|
}
|
|
4063
|
+
function createGitHubAuthStore(projectRoot) {
|
|
4064
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
4065
|
+
}
|
|
3989
4066
|
|
|
3990
4067
|
// packages/server/src/server-helpers/github-projects.ts
|
|
3991
4068
|
function asRecord(value) {
|
|
@@ -3994,6 +4071,9 @@ function asRecord(value) {
|
|
|
3994
4071
|
function asString(value) {
|
|
3995
4072
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
3996
4073
|
}
|
|
4074
|
+
function asNumber(value) {
|
|
4075
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
4076
|
+
}
|
|
3997
4077
|
async function defaultGraphQLFetch(query, variables, token) {
|
|
3998
4078
|
const response = await fetch("https://api.github.com/graphql", {
|
|
3999
4079
|
method: "POST",
|
|
@@ -4010,6 +4090,32 @@ async function defaultGraphQLFetch(query, variables, token) {
|
|
|
4010
4090
|
}
|
|
4011
4091
|
return json.data;
|
|
4012
4092
|
}
|
|
4093
|
+
function projectNodesFrom(data) {
|
|
4094
|
+
const root = asRecord(data);
|
|
4095
|
+
const owner = asRecord(root?.organization) ?? asRecord(root?.user);
|
|
4096
|
+
const projects = asRecord(owner?.projectsV2);
|
|
4097
|
+
const nodes = projects?.nodes;
|
|
4098
|
+
return Array.isArray(nodes) ? nodes : [];
|
|
4099
|
+
}
|
|
4100
|
+
async function listGitHubProjects(input) {
|
|
4101
|
+
const query = `
|
|
4102
|
+
query RigListProjects($owner: String!, $first: Int!) {
|
|
4103
|
+
organization(login: $owner) { projectsV2(first: $first, orderBy: { field: UPDATED_AT, direction: DESC }) { nodes { id number title url } } }
|
|
4104
|
+
user(login: $owner) { projectsV2(first: $first, orderBy: { field: UPDATED_AT, direction: DESC }) { nodes { id number title url } } }
|
|
4105
|
+
}
|
|
4106
|
+
`;
|
|
4107
|
+
const fetchGraphQL = input.fetchGraphQL ?? defaultGraphQLFetch;
|
|
4108
|
+
const data = await fetchGraphQL(query, { owner: input.owner, first: input.first ?? 20 }, input.token);
|
|
4109
|
+
return projectNodesFrom(data).flatMap((node) => {
|
|
4110
|
+
const record = asRecord(node);
|
|
4111
|
+
const id = asString(record?.id);
|
|
4112
|
+
const number = asNumber(record?.number);
|
|
4113
|
+
const title = asString(record?.title);
|
|
4114
|
+
if (!id || number === undefined || !title)
|
|
4115
|
+
return [];
|
|
4116
|
+
return [{ id, number, title, ...asString(record?.url) ? { url: asString(record?.url) } : {} }];
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4013
4119
|
async function resolveProjectStatusField(input) {
|
|
4014
4120
|
const query = `
|
|
4015
4121
|
query RigProjectStatusField($projectId: ID!) {
|
|
@@ -4104,6 +4210,7 @@ var DEFAULT_PROJECT_STATUSES = {
|
|
|
4104
4210
|
running: "In Progress",
|
|
4105
4211
|
prOpen: "In Review",
|
|
4106
4212
|
ciFixing: "In Review",
|
|
4213
|
+
merging: "Merging",
|
|
4107
4214
|
done: "Done",
|
|
4108
4215
|
needsAttention: "Needs Attention"
|
|
4109
4216
|
};
|
|
@@ -4117,6 +4224,8 @@ function lifecycleStatusForTaskStatus(status) {
|
|
|
4117
4224
|
return "prOpen";
|
|
4118
4225
|
if (normalized === "ci_fixing" || normalized === "fixing")
|
|
4119
4226
|
return "ciFixing";
|
|
4227
|
+
if (normalized === "merging" || normalized === "merge")
|
|
4228
|
+
return "merging";
|
|
4120
4229
|
if (normalized === "failed" || normalized === "needs_attention" || normalized === "blocked")
|
|
4121
4230
|
return "needsAttention";
|
|
4122
4231
|
if (normalized === "in_progress" || normalized === "running" || normalized === "ready" || normalized === "open")
|
|
@@ -4245,9 +4354,14 @@ function parseIssueRef(sourceTask, fallbackTaskId) {
|
|
|
4245
4354
|
return null;
|
|
4246
4355
|
return null;
|
|
4247
4356
|
}
|
|
4357
|
+
function githubProjectsEnabled(config) {
|
|
4358
|
+
const github = config?.github && typeof config.github === "object" && !Array.isArray(config.github) ? config.github : null;
|
|
4359
|
+
const projects = github?.projects && typeof github.projects === "object" && !Array.isArray(github.projects) ? github.projects : null;
|
|
4360
|
+
return projects?.enabled === true;
|
|
4361
|
+
}
|
|
4248
4362
|
async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config) {
|
|
4249
4363
|
if (!run.taskId)
|
|
4250
|
-
return;
|
|
4364
|
+
return false;
|
|
4251
4365
|
const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
|
|
4252
4366
|
try {
|
|
4253
4367
|
const result = await syncGitHubProjectStatusForTaskUpdate({
|
|
@@ -4258,28 +4372,86 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
|
|
|
4258
4372
|
config
|
|
4259
4373
|
});
|
|
4260
4374
|
if (!result.synced && result.reason !== "project-sync-disabled") {
|
|
4375
|
+
const detail = `Project status sync for ${run.taskId} could not run: ${result.reason}.`;
|
|
4261
4376
|
appendRunLogEntry(projectRoot, run.runId, {
|
|
4262
4377
|
id: `log:${run.runId}:github-project-sync:${status}`,
|
|
4263
4378
|
title: "GitHub Project sync skipped",
|
|
4264
|
-
detail
|
|
4379
|
+
detail,
|
|
4265
4380
|
tone: "warn",
|
|
4266
4381
|
status: "running",
|
|
4267
4382
|
createdAt: new Date().toISOString(),
|
|
4268
4383
|
payload: { reason: result.reason, issueNodeId }
|
|
4269
4384
|
});
|
|
4385
|
+
if (githubProjectsEnabled(config)) {
|
|
4386
|
+
throw new Error(detail);
|
|
4387
|
+
}
|
|
4388
|
+
return false;
|
|
4270
4389
|
}
|
|
4390
|
+
return result.synced === true;
|
|
4271
4391
|
} catch (error) {
|
|
4392
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4272
4393
|
appendRunLogEntry(projectRoot, run.runId, {
|
|
4273
4394
|
id: `log:${run.runId}:github-project-sync-error:${status}`,
|
|
4274
4395
|
title: "GitHub Project sync failed",
|
|
4275
|
-
detail
|
|
4396
|
+
detail,
|
|
4276
4397
|
tone: "error",
|
|
4277
4398
|
status: "running",
|
|
4278
4399
|
createdAt: new Date().toISOString(),
|
|
4279
4400
|
payload: { issueNodeId }
|
|
4280
4401
|
});
|
|
4402
|
+
if (githubProjectsEnabled(config)) {
|
|
4403
|
+
throw new Error(detail);
|
|
4404
|
+
}
|
|
4405
|
+
return false;
|
|
4281
4406
|
}
|
|
4282
4407
|
}
|
|
4408
|
+
function createCommandRunner(binary, extraEnv = {}) {
|
|
4409
|
+
return async (args, options) => {
|
|
4410
|
+
const child = spawn3(binary, [...args], {
|
|
4411
|
+
cwd: options?.cwd,
|
|
4412
|
+
env: { ...process.env, ...extraEnv },
|
|
4413
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4414
|
+
});
|
|
4415
|
+
const stdoutChunks = [];
|
|
4416
|
+
const stderrChunks = [];
|
|
4417
|
+
child.stdout?.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
4418
|
+
child.stderr?.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
4419
|
+
const exitCode = await new Promise((resolve15) => {
|
|
4420
|
+
child.once("error", () => resolve15(1));
|
|
4421
|
+
child.once("close", (code) => resolve15(code ?? 1));
|
|
4422
|
+
});
|
|
4423
|
+
return {
|
|
4424
|
+
exitCode,
|
|
4425
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
4426
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
4427
|
+
};
|
|
4428
|
+
};
|
|
4429
|
+
}
|
|
4430
|
+
function closeoutRecord(run) {
|
|
4431
|
+
const value = run.serverCloseout;
|
|
4432
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4433
|
+
}
|
|
4434
|
+
function closeoutPhasePatch(phase, status, extra = {}) {
|
|
4435
|
+
const updatedAt = new Date().toISOString();
|
|
4436
|
+
return {
|
|
4437
|
+
serverCloseout: {
|
|
4438
|
+
phase,
|
|
4439
|
+
status,
|
|
4440
|
+
updatedAt,
|
|
4441
|
+
...extra
|
|
4442
|
+
}
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
function appendCloseoutStage(state, runId, phase, detail, status = "reviewing", tone = "info") {
|
|
4446
|
+
appendRunLogEntryAndBroadcast(state, runId, {
|
|
4447
|
+
id: `log:${runId}:server-closeout:${phase}:${Date.now()}`,
|
|
4448
|
+
title: `Server closeout: ${phase}`,
|
|
4449
|
+
detail,
|
|
4450
|
+
tone,
|
|
4451
|
+
status,
|
|
4452
|
+
createdAt: new Date().toISOString()
|
|
4453
|
+
}, `server-closeout-${phase}`);
|
|
4454
|
+
}
|
|
4283
4455
|
async function autoAssignRunIssue(projectRoot, run) {
|
|
4284
4456
|
if (!run.taskId)
|
|
4285
4457
|
return;
|
|
@@ -4309,7 +4481,7 @@ async function updateRunTaskSourceLifecycle(projectRoot, run, status, summary, o
|
|
|
4309
4481
|
return;
|
|
4310
4482
|
}
|
|
4311
4483
|
const config = await loadRigLifecycleConfig(projectRoot);
|
|
4312
|
-
await syncProjectStatusForRunLifecycle(projectRoot, run, status, config);
|
|
4484
|
+
const projectSynced = await syncProjectStatusForRunLifecycle(projectRoot, run, status, config);
|
|
4313
4485
|
if (status === "in_progress") {
|
|
4314
4486
|
await autoAssignRunIssue(projectRoot, run);
|
|
4315
4487
|
}
|
|
@@ -4325,24 +4497,53 @@ async function updateRunTaskSourceLifecycle(projectRoot, run, status, summary, o
|
|
|
4325
4497
|
});
|
|
4326
4498
|
return;
|
|
4327
4499
|
}
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4500
|
+
const sourceTask = runSourceTaskIdentity(run);
|
|
4501
|
+
const previousStatus = normalizeString(sourceTask?.status) ?? normalizeString(sourceTask?.sourceStatus);
|
|
4502
|
+
const rollbackProjectSync = async () => {
|
|
4503
|
+
if (!projectSynced || !previousStatus || !run.taskId || !githubProjectsEnabled(config))
|
|
4504
|
+
return;
|
|
4505
|
+
await syncGitHubProjectStatusForTaskUpdate({
|
|
4506
|
+
taskId: run.taskId,
|
|
4507
|
+
status: previousStatus,
|
|
4508
|
+
issueNodeId: extractGitHubIssueNodeId(sourceTask),
|
|
4509
|
+
token: createGitHubAuthStore(projectRoot).readToken(),
|
|
4510
|
+
config
|
|
4511
|
+
}).catch((rollbackError) => {
|
|
4512
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
4513
|
+
id: `log:${run.runId}:github-project-sync-rollback:${status}`,
|
|
4514
|
+
title: "GitHub Project sync rollback failed",
|
|
4515
|
+
detail: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
|
|
4516
|
+
tone: "error",
|
|
4517
|
+
status: "running",
|
|
4518
|
+
createdAt: new Date().toISOString()
|
|
4519
|
+
});
|
|
4520
|
+
});
|
|
4521
|
+
};
|
|
4522
|
+
let result;
|
|
4523
|
+
try {
|
|
4524
|
+
result = await updateConfiguredTaskSourceTask(projectRoot, {
|
|
4525
|
+
taskId: run.taskId,
|
|
4526
|
+
sourceTask,
|
|
4527
|
+
update: {
|
|
4335
4528
|
status,
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4529
|
+
comment: buildTaskRunLifecycleComment({
|
|
4530
|
+
runId: run.runId,
|
|
4531
|
+
status,
|
|
4532
|
+
summary,
|
|
4533
|
+
runtimeWorkspace: normalizeString(run.worktreePath),
|
|
4534
|
+
logsDir: normalizeString(run.logRoot),
|
|
4535
|
+
sessionDir: normalizeString(run.sessionPath),
|
|
4536
|
+
errorText: options.errorText ?? normalizeString(run.errorText)
|
|
4537
|
+
})
|
|
4538
|
+
}
|
|
4539
|
+
});
|
|
4540
|
+
} catch (error) {
|
|
4541
|
+
await rollbackProjectSync();
|
|
4542
|
+
throw error;
|
|
4543
|
+
}
|
|
4344
4544
|
if (!result.updated) {
|
|
4345
4545
|
if (result.source === "plugin" || result.sourceKind) {
|
|
4546
|
+
await rollbackProjectSync();
|
|
4346
4547
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
4347
4548
|
}
|
|
4348
4549
|
appendRunLogEntry(projectRoot, run.runId, {
|
|
@@ -4355,6 +4556,219 @@ async function updateRunTaskSourceLifecycle(projectRoot, run, status, summary, o
|
|
|
4355
4556
|
});
|
|
4356
4557
|
}
|
|
4357
4558
|
}
|
|
4559
|
+
async function runServerOwnedPrCloseout(state, runId) {
|
|
4560
|
+
const run = readAuthorityRun4(state.projectRoot, runId);
|
|
4561
|
+
if (!run)
|
|
4562
|
+
throw new Error(`Run not found: ${runId}`);
|
|
4563
|
+
const closeout = closeoutRecord(run);
|
|
4564
|
+
if (!closeout)
|
|
4565
|
+
return;
|
|
4566
|
+
const taskId = normalizeString(closeout.taskId) ?? normalizeString(run.taskId);
|
|
4567
|
+
if (!taskId)
|
|
4568
|
+
throw new Error("Server-owned closeout requires a task id.");
|
|
4569
|
+
const workspace = normalizeString(closeout.runtimeWorkspace) ?? normalizeString(run.worktreePath) ?? state.projectRoot;
|
|
4570
|
+
const branch = normalizeString(closeout.branch) ?? `rig/${taskId}-${runId}`;
|
|
4571
|
+
const config = await loadRigLifecycleConfig(state.projectRoot);
|
|
4572
|
+
const runPrMode = normalizeString(run.prMode);
|
|
4573
|
+
const prMode = runPrMode === "auto" || runPrMode === "ask" || runPrMode === "off" ? runPrMode : config?.pr?.mode ?? "off";
|
|
4574
|
+
const effectiveConfig = {
|
|
4575
|
+
...config ?? {},
|
|
4576
|
+
pr: {
|
|
4577
|
+
...config?.pr ?? {},
|
|
4578
|
+
mode: prMode,
|
|
4579
|
+
autoFixChecks: false,
|
|
4580
|
+
autoFixReview: false
|
|
4581
|
+
}
|
|
4582
|
+
};
|
|
4583
|
+
const readCurrentRun = () => readAuthorityRun4(state.projectRoot, runId) ?? run;
|
|
4584
|
+
const sourceTask = runSourceTaskIdentity(run);
|
|
4585
|
+
const closeoutPhase = normalizeString(closeout.phase)?.toLowerCase() ?? "";
|
|
4586
|
+
const closeoutStatus = normalizeString(closeout.status)?.toLowerCase() ?? "";
|
|
4587
|
+
const closeoutPrUrl = normalizeString(closeout.prUrl);
|
|
4588
|
+
if (closeoutPhase === "completed" || closeoutStatus === "completed") {
|
|
4589
|
+
return;
|
|
4590
|
+
}
|
|
4591
|
+
if (closeoutPhase === "close-source" && closeoutPrUrl) {
|
|
4592
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4593
|
+
status: "reviewing",
|
|
4594
|
+
...closeoutPhasePatch("close-source", "running", { ...closeout, prUrl: closeoutPrUrl, taskId, runtimeWorkspace: workspace, branch })
|
|
4595
|
+
});
|
|
4596
|
+
await closeIssueAfterMergedPr({
|
|
4597
|
+
projectRoot: state.projectRoot,
|
|
4598
|
+
taskId,
|
|
4599
|
+
runId,
|
|
4600
|
+
prUrl: closeoutPrUrl,
|
|
4601
|
+
sourceTask,
|
|
4602
|
+
updateTaskSource: async (projectRoot, input) => {
|
|
4603
|
+
await updateRunTaskSourceLifecycle(projectRoot, readCurrentRun(), "closed", "Rig merged the pull request and closed this task source.");
|
|
4604
|
+
return { updated: true, taskId: input.taskId, status: input.update.status, source: "server", sourceKind: "server" };
|
|
4605
|
+
}
|
|
4606
|
+
});
|
|
4607
|
+
const completedAt = new Date().toISOString();
|
|
4608
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4609
|
+
status: "completed",
|
|
4610
|
+
completedAt,
|
|
4611
|
+
errorText: null,
|
|
4612
|
+
...closeoutPhasePatch("completed", "completed", { ...closeout, prUrl: closeoutPrUrl, iterations: closeout.iterations, completedAt })
|
|
4613
|
+
});
|
|
4614
|
+
appendCloseoutStage(state, runId, "completed", `PR merged and issue closed: ${closeoutPrUrl}`, "completed", "info");
|
|
4615
|
+
emitRigEvent(state, {
|
|
4616
|
+
type: "rig.run.completed",
|
|
4617
|
+
aggregateId: runId,
|
|
4618
|
+
payload: { runId, taskId, prUrl: closeoutPrUrl, closeout: "merged" },
|
|
4619
|
+
createdAt: completedAt
|
|
4620
|
+
});
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
if (prMode === "off" || prMode === "ask") {
|
|
4624
|
+
const completedAt = new Date().toISOString();
|
|
4625
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4626
|
+
status: "completed",
|
|
4627
|
+
completedAt,
|
|
4628
|
+
errorText: null,
|
|
4629
|
+
...closeoutPhasePatch("completed", "completed", { taskId, runtimeWorkspace: workspace, branch, reason: prMode === "ask" ? "pr-mode-ask" : "pr-mode-off" })
|
|
4630
|
+
});
|
|
4631
|
+
appendCloseoutStage(state, runId, "completed", prMode === "ask" ? "Validation completed; PR creation awaits operator approval." : "Validation completed; PR automation disabled.", "completed", "info");
|
|
4632
|
+
emitRigEvent(state, {
|
|
4633
|
+
type: "rig.run.completed",
|
|
4634
|
+
aggregateId: runId,
|
|
4635
|
+
payload: { runId, taskId, closeout: "skipped", reason: prMode === "ask" ? "pr-mode-ask" : "pr-mode-off" },
|
|
4636
|
+
createdAt: completedAt
|
|
4637
|
+
});
|
|
4638
|
+
return;
|
|
4639
|
+
}
|
|
4640
|
+
const githubToken = createGitHubAuthStore(state.projectRoot).readToken();
|
|
4641
|
+
const githubEnv = githubToken ? { RIG_GITHUB_TOKEN: githubToken, GITHUB_TOKEN: githubToken, GH_TOKEN: githubToken } : {};
|
|
4642
|
+
const gitCommand = createCommandRunner("git", githubEnv);
|
|
4643
|
+
const ghCommand = createCommandRunner("gh", githubEnv);
|
|
4644
|
+
const setCloseout = (phase, status, extra = {}) => {
|
|
4645
|
+
const previous = closeoutRecord(readCurrentRun()) ?? closeout;
|
|
4646
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4647
|
+
status: status === "failed" ? "failed" : status === "needs_attention" ? "needs_attention" : "reviewing",
|
|
4648
|
+
...closeoutPhasePatch(phase, status, { ...previous, ...extra })
|
|
4649
|
+
});
|
|
4650
|
+
};
|
|
4651
|
+
setCloseout("commit", "running", { runtimeWorkspace: workspace, branch, taskId });
|
|
4652
|
+
appendCloseoutStage(state, runId, "commit", `Committing changes in ${workspace}.`, "reviewing", "tool");
|
|
4653
|
+
const commit = await commitRunChanges({ cwd: workspace, message: `rig: complete task ${taskId}`, command: gitCommand });
|
|
4654
|
+
appendCloseoutStage(state, runId, "commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "reviewing", "tool");
|
|
4655
|
+
setCloseout("push", "running", { runtimeWorkspace: workspace, branch, taskId });
|
|
4656
|
+
const push = await gitCommand(["push", "--set-upstream", "origin", branch], { cwd: workspace });
|
|
4657
|
+
if (push.exitCode !== 0) {
|
|
4658
|
+
throw new Error(`git push --set-upstream origin ${branch} failed (${push.exitCode}): ${push.stderr ?? push.stdout ?? ""}`.trim());
|
|
4659
|
+
}
|
|
4660
|
+
const sourceTaskForPr = {
|
|
4661
|
+
title: normalizeString(sourceTask?.title) ?? normalizeString(run.title)
|
|
4662
|
+
};
|
|
4663
|
+
const artifactRoot = resolve14(state.projectRoot, "artifacts", taskId);
|
|
4664
|
+
setCloseout("pr-review-merge", "running", { runtimeWorkspace: workspace, branch, taskId, artifactRoot });
|
|
4665
|
+
const pr = await runPrAutomation({
|
|
4666
|
+
projectRoot: workspace,
|
|
4667
|
+
taskId,
|
|
4668
|
+
runId,
|
|
4669
|
+
branch,
|
|
4670
|
+
config: effectiveConfig,
|
|
4671
|
+
sourceTask: sourceTaskForPr,
|
|
4672
|
+
artifactRoot,
|
|
4673
|
+
command: ghCommand,
|
|
4674
|
+
gitCommand,
|
|
4675
|
+
steerPi: async (message) => {
|
|
4676
|
+
appendCloseoutStage(state, runId, "feedback", message, "reviewing", "info");
|
|
4677
|
+
appendRunTimelineEntry(state.projectRoot, runId, {
|
|
4678
|
+
id: `message:${runId}:server-closeout-feedback:${Date.now()}`,
|
|
4679
|
+
type: "user_message",
|
|
4680
|
+
text: message,
|
|
4681
|
+
createdAt: new Date().toISOString(),
|
|
4682
|
+
state: "completed"
|
|
4683
|
+
});
|
|
4684
|
+
},
|
|
4685
|
+
lifecycle: {
|
|
4686
|
+
onPrOpened: async ({ prUrl }) => {
|
|
4687
|
+
setCloseout("pr-opened", "running", { prUrl, runtimeWorkspace: workspace, branch, taskId, artifactRoot });
|
|
4688
|
+
appendCloseoutStage(state, runId, "open-pr", prUrl, "reviewing", "tool");
|
|
4689
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, readCurrentRun(), "under_review", "Rig opened a pull request for this task.");
|
|
4690
|
+
},
|
|
4691
|
+
onReviewCiStarted: ({ prUrl, iteration }) => appendCloseoutStage(state, runId, "review-ci", `${prUrl} (iteration ${iteration})`, "reviewing", "info"),
|
|
4692
|
+
onFeedback: async ({ feedback }) => {
|
|
4693
|
+
appendCloseoutStage(state, runId, "feedback", feedback.join(`
|
|
4694
|
+
`), "reviewing", "error");
|
|
4695
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, readCurrentRun(), "ci_fixing", "Rig is fixing CI/review feedback for this task.");
|
|
4696
|
+
},
|
|
4697
|
+
onMergeStarted: async ({ prUrl }) => {
|
|
4698
|
+
setCloseout("merge", "running", { prUrl, runtimeWorkspace: workspace, branch, taskId, artifactRoot });
|
|
4699
|
+
appendCloseoutStage(state, runId, "merge", prUrl, "reviewing", "tool");
|
|
4700
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, readCurrentRun(), "merging", "Rig is merging the pull request for this task.");
|
|
4701
|
+
},
|
|
4702
|
+
onMerged: ({ prUrl }) => {
|
|
4703
|
+
setCloseout("close-source", "running", { prUrl, runtimeWorkspace: workspace, branch, taskId, artifactRoot, merged: true });
|
|
4704
|
+
appendCloseoutStage(state, runId, "merge", prUrl, "reviewing", "tool");
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
});
|
|
4708
|
+
if (pr.status === "merged" && pr.prUrl) {
|
|
4709
|
+
setCloseout("close-source", "running", { prUrl: pr.prUrl, iterations: pr.iterations });
|
|
4710
|
+
await closeIssueAfterMergedPr({
|
|
4711
|
+
projectRoot: state.projectRoot,
|
|
4712
|
+
taskId,
|
|
4713
|
+
runId,
|
|
4714
|
+
prUrl: pr.prUrl,
|
|
4715
|
+
sourceTask,
|
|
4716
|
+
updateTaskSource: async (projectRoot, input) => {
|
|
4717
|
+
await updateRunTaskSourceLifecycle(projectRoot, readCurrentRun(), "closed", "Rig merged the pull request and closed this task source.");
|
|
4718
|
+
return { updated: true, taskId: input.taskId, status: input.update.status, source: "server", sourceKind: "server" };
|
|
4719
|
+
}
|
|
4720
|
+
});
|
|
4721
|
+
const completedAt = new Date().toISOString();
|
|
4722
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4723
|
+
status: "completed",
|
|
4724
|
+
completedAt,
|
|
4725
|
+
errorText: null,
|
|
4726
|
+
...closeoutPhasePatch("completed", "completed", { prUrl: pr.prUrl, iterations: pr.iterations, completedAt })
|
|
4727
|
+
});
|
|
4728
|
+
appendCloseoutStage(state, runId, "completed", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
4729
|
+
emitRigEvent(state, {
|
|
4730
|
+
type: "rig.run.completed",
|
|
4731
|
+
aggregateId: runId,
|
|
4732
|
+
payload: { runId, taskId, prUrl: pr.prUrl, closeout: "merged" },
|
|
4733
|
+
createdAt: completedAt
|
|
4734
|
+
});
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
if (pr.status === "opened" && pr.prUrl) {
|
|
4738
|
+
const completedAt = new Date().toISOString();
|
|
4739
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4740
|
+
status: "completed",
|
|
4741
|
+
completedAt,
|
|
4742
|
+
errorText: null,
|
|
4743
|
+
...closeoutPhasePatch("completed", "completed", { prUrl: pr.prUrl, iterations: pr.iterations })
|
|
4744
|
+
});
|
|
4745
|
+
appendCloseoutStage(state, runId, "completed", `PR ready without merge: ${pr.prUrl}`, "completed", "info");
|
|
4746
|
+
emitRigEvent(state, {
|
|
4747
|
+
type: "rig.run.completed",
|
|
4748
|
+
aggregateId: runId,
|
|
4749
|
+
payload: { runId, taskId, prUrl: pr.prUrl, closeout: "pr-ready" },
|
|
4750
|
+
createdAt: completedAt
|
|
4751
|
+
});
|
|
4752
|
+
return;
|
|
4753
|
+
}
|
|
4754
|
+
const detail = pr.actionableFeedback.join(`
|
|
4755
|
+
`) || "PR automation did not merge the PR.";
|
|
4756
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, readCurrentRun(), "needs_attention", "Rig needs operator attention before this task can proceed.", { errorText: detail }).catch((error) => {
|
|
4757
|
+
appendCloseoutStage(state, runId, "needs-attention-update", error instanceof Error ? error.message : String(error), "needs_attention", "error");
|
|
4758
|
+
});
|
|
4759
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
4760
|
+
status: "needs_attention",
|
|
4761
|
+
completedAt: new Date().toISOString(),
|
|
4762
|
+
errorText: detail,
|
|
4763
|
+
...closeoutPhasePatch("needs_attention", "needs_attention", { feedback: pr.actionableFeedback, prUrl: pr.prUrl ?? null, iterations: pr.iterations })
|
|
4764
|
+
});
|
|
4765
|
+
appendCloseoutStage(state, runId, "needs-attention", detail, "needs_attention", "error");
|
|
4766
|
+
emitRigEvent(state, {
|
|
4767
|
+
type: "rig.run.needs-attention",
|
|
4768
|
+
aggregateId: runId,
|
|
4769
|
+
payload: { runId, taskId, error: detail, prUrl: pr.prUrl ?? null }
|
|
4770
|
+
});
|
|
4771
|
+
}
|
|
4358
4772
|
var TERMINAL_RUN_STATUSES2 = new Set([
|
|
4359
4773
|
"completed",
|
|
4360
4774
|
"complete",
|
|
@@ -4380,11 +4794,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
|
|
|
4380
4794
|
return;
|
|
4381
4795
|
throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
|
|
4382
4796
|
}
|
|
4797
|
+
async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
|
|
4798
|
+
const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
4799
|
+
if (fromReader)
|
|
4800
|
+
return fromReader;
|
|
4801
|
+
const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
|
|
4802
|
+
if (projected)
|
|
4803
|
+
return projected;
|
|
4804
|
+
if (readTasks !== readWorkspaceTasks) {
|
|
4805
|
+
return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
4806
|
+
}
|
|
4807
|
+
return null;
|
|
4808
|
+
}
|
|
4383
4809
|
async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
|
|
4384
4810
|
if ("taskId" in input && input.taskId) {
|
|
4385
4811
|
assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
|
|
4386
4812
|
}
|
|
4387
|
-
const sourceTask = "taskId" in input && input.taskId ?
|
|
4813
|
+
const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
|
|
4388
4814
|
const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
|
|
4389
4815
|
const runDir = resolveAuthorityRunDir3(projectRoot, input.runId);
|
|
4390
4816
|
const runRecord = {
|
|
@@ -4456,6 +4882,7 @@ async function startLocalRun(state, runId, options) {
|
|
|
4456
4882
|
throw new Error(`Run not found: ${runId}`);
|
|
4457
4883
|
}
|
|
4458
4884
|
const startedAt = new Date().toISOString();
|
|
4885
|
+
const resumeMode = options?.resume === true;
|
|
4459
4886
|
state.runProcesses.set(runId, {
|
|
4460
4887
|
runId,
|
|
4461
4888
|
child: null,
|
|
@@ -4472,9 +4899,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
4472
4899
|
summary: run.title
|
|
4473
4900
|
});
|
|
4474
4901
|
appendRunLogEntry(state.projectRoot, runId, {
|
|
4475
|
-
id: `log:${runId}:prepare`,
|
|
4476
|
-
title: "Rig task run starting",
|
|
4477
|
-
detail: run.taskId ?? run.title,
|
|
4902
|
+
id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
|
|
4903
|
+
title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
|
|
4904
|
+
detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
|
|
4478
4905
|
tone: "info",
|
|
4479
4906
|
status: "preparing",
|
|
4480
4907
|
createdAt: startedAt
|
|
@@ -4550,9 +4977,18 @@ async function startLocalRun(state, runId, options) {
|
|
|
4550
4977
|
RIG_HOST_PROJECT_ROOT: cliProjectRoot,
|
|
4551
4978
|
RIG_RUNTIME_BASE_REF: process.env.RIG_RUNTIME_BASE_REF ?? "HEAD",
|
|
4552
4979
|
RIG_SERVER_INTERNAL_EXEC: "1",
|
|
4980
|
+
RIG_SERVER_OWNS_CLOSEOUT: "1",
|
|
4553
4981
|
...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
|
|
4554
4982
|
...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
|
|
4555
|
-
...bridgeGitHubToken ? {
|
|
4983
|
+
...bridgeGitHubToken ? {
|
|
4984
|
+
RIG_GITHUB_TOKEN: bridgeGitHubToken,
|
|
4985
|
+
GITHUB_TOKEN: bridgeGitHubToken,
|
|
4986
|
+
GH_TOKEN: bridgeGitHubToken
|
|
4987
|
+
} : {},
|
|
4988
|
+
...resumeMode ? {
|
|
4989
|
+
RIG_RUN_RESUME: "1",
|
|
4990
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
4991
|
+
} : {}
|
|
4556
4992
|
},
|
|
4557
4993
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4558
4994
|
});
|
|
@@ -4638,6 +5074,38 @@ ${sourceFailure}` });
|
|
|
4638
5074
|
agent: current.runtimeAdapter,
|
|
4639
5075
|
summary: failureSummary
|
|
4640
5076
|
});
|
|
5077
|
+
} else if (closeoutRecord(current)?.status === "pending") {
|
|
5078
|
+
try {
|
|
5079
|
+
await runServerOwnedPrCloseout(state, runId);
|
|
5080
|
+
} catch (closeoutError) {
|
|
5081
|
+
const closeoutFailure = closeoutError instanceof Error ? closeoutError.message : String(closeoutError);
|
|
5082
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
5083
|
+
status: "failed",
|
|
5084
|
+
completedAt: new Date().toISOString(),
|
|
5085
|
+
errorText: closeoutFailure,
|
|
5086
|
+
...closeoutPhasePatch("failed", "failed", { error: closeoutFailure })
|
|
5087
|
+
});
|
|
5088
|
+
appendRunLogEntryAndBroadcast(state, runId, {
|
|
5089
|
+
id: `log:${runId}:server-closeout-failed`,
|
|
5090
|
+
title: "Server-owned closeout failed",
|
|
5091
|
+
detail: closeoutFailure,
|
|
5092
|
+
tone: "error",
|
|
5093
|
+
status: "failed",
|
|
5094
|
+
createdAt: new Date().toISOString()
|
|
5095
|
+
}, "server-closeout-failed");
|
|
5096
|
+
if (current.taskId) {
|
|
5097
|
+
await updateRunTaskSourceLifecycle(state.projectRoot, { ...current, status: "failed", errorText: closeoutFailure }, "failed", "Rig server-owned closeout failed.", { errorText: closeoutFailure }).catch((error) => {
|
|
5098
|
+
appendRunLogEntry(state.projectRoot, runId, {
|
|
5099
|
+
id: `log:${runId}:task-source-closeout-failed-update`,
|
|
5100
|
+
title: "Task source closeout failure update failed",
|
|
5101
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
5102
|
+
tone: "error",
|
|
5103
|
+
status: "failed",
|
|
5104
|
+
createdAt: new Date().toISOString()
|
|
5105
|
+
});
|
|
5106
|
+
});
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
4641
5109
|
}
|
|
4642
5110
|
broadcastSnapshotInvalidation(state);
|
|
4643
5111
|
} catch (error) {
|
|
@@ -4703,7 +5171,7 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
4703
5171
|
}
|
|
4704
5172
|
try {
|
|
4705
5173
|
const monorepoRoot = resolveMonorepoRoot3(projectRoot);
|
|
4706
|
-
const outerProjectRoot =
|
|
5174
|
+
const outerProjectRoot = dirname9(dirname9(monorepoRoot));
|
|
4707
5175
|
if (existsSync7(resolve14(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
4708
5176
|
return outerProjectRoot;
|
|
4709
5177
|
}
|
|
@@ -4724,7 +5192,13 @@ async function resumeRunRecord(state, input) {
|
|
|
4724
5192
|
if (run.status === "completed") {
|
|
4725
5193
|
throw new Error("Completed runs cannot be resumed.");
|
|
4726
5194
|
}
|
|
4727
|
-
|
|
5195
|
+
const closeout = closeoutRecord(run);
|
|
5196
|
+
const closeoutStatus = normalizeString(closeout?.status)?.toLowerCase() ?? "";
|
|
5197
|
+
if (RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus)) {
|
|
5198
|
+
await runServerOwnedPrCloseout(state, input.runId);
|
|
5199
|
+
return;
|
|
5200
|
+
}
|
|
5201
|
+
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
4728
5202
|
}
|
|
4729
5203
|
function appendRunMessage(projectRoot, input) {
|
|
4730
5204
|
const run = readAuthorityRun4(projectRoot, input.runId);
|
|
@@ -4808,34 +5282,46 @@ function removeTaskIdsFromQueueState(projectRoot, taskIds) {
|
|
|
4808
5282
|
writeQueueState(projectRoot, next);
|
|
4809
5283
|
return next;
|
|
4810
5284
|
}
|
|
4811
|
-
var
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
|
|
4822
|
-
patchRunRecord(state.projectRoot, run.runId, {
|
|
4823
|
-
status: "failed",
|
|
4824
|
-
completedAt: run.completedAt ?? nowIso2,
|
|
4825
|
-
updatedAt: nowIso2,
|
|
4826
|
-
errorText: detail
|
|
4827
|
-
});
|
|
4828
|
-
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
4829
|
-
id: `log:${run.runId}:stale-local-run`,
|
|
4830
|
-
title: "Run marked stale after server restart",
|
|
4831
|
-
detail,
|
|
4832
|
-
tone: "error",
|
|
4833
|
-
status: "failed",
|
|
4834
|
-
createdAt: nowIso2
|
|
4835
|
-
});
|
|
4836
|
-
changed = true;
|
|
5285
|
+
var RESUMABLE_SERVER_CLOSEOUT_STATUSES = new Set(["pending", "running"]);
|
|
5286
|
+
var ACTIVE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
5287
|
+
function processExists(pid) {
|
|
5288
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
5289
|
+
return false;
|
|
5290
|
+
try {
|
|
5291
|
+
process.kill(pid, 0);
|
|
5292
|
+
return true;
|
|
5293
|
+
} catch {
|
|
5294
|
+
return false;
|
|
4837
5295
|
}
|
|
4838
|
-
|
|
5296
|
+
}
|
|
5297
|
+
function recoverStaleLocalRun(projectRoot, run) {
|
|
5298
|
+
const record = run;
|
|
5299
|
+
if (run.mode !== "local")
|
|
5300
|
+
return false;
|
|
5301
|
+
const status = normalizeString(record.status)?.toLowerCase() ?? "";
|
|
5302
|
+
if (!ACTIVE_LOCAL_RUN_STATUSES.has(status))
|
|
5303
|
+
return false;
|
|
5304
|
+
const serverPid = typeof record.serverPid === "number" ? record.serverPid : null;
|
|
5305
|
+
const childPid = typeof record.pid === "number" ? record.pid : null;
|
|
5306
|
+
if (serverPid === null && childPid === null)
|
|
5307
|
+
return false;
|
|
5308
|
+
const hasLiveRecordedProcess = [serverPid, childPid].some((pid) => typeof pid === "number" && processExists(pid));
|
|
5309
|
+
if (hasLiveRecordedProcess && serverPid === process.pid)
|
|
5310
|
+
return false;
|
|
5311
|
+
const completedAt = new Date().toISOString();
|
|
5312
|
+
patchRunRecord(projectRoot, run.runId, {
|
|
5313
|
+
status: "failed",
|
|
5314
|
+
completedAt,
|
|
5315
|
+
errorText: `Recovered stale local run ${run.runId} after server startup; no active server-owned process was tracking it.`
|
|
5316
|
+
});
|
|
5317
|
+
return true;
|
|
5318
|
+
}
|
|
5319
|
+
function collectResumableServerCloseouts(state, runs) {
|
|
5320
|
+
return runs.filter((run) => {
|
|
5321
|
+
const closeout = closeoutRecord(run);
|
|
5322
|
+
const closeoutStatus = normalizeString(closeout?.status)?.toLowerCase() ?? "";
|
|
5323
|
+
return run.mode === "local" && RESUMABLE_SERVER_CLOSEOUT_STATUSES.has(closeoutStatus) && !state.runProcesses.has(run.runId);
|
|
5324
|
+
});
|
|
4839
5325
|
}
|
|
4840
5326
|
async function reconcileScheduler(state, reason) {
|
|
4841
5327
|
if (state.scheduler.reconciling) {
|
|
@@ -4850,7 +5336,36 @@ async function reconcileScheduler(state, reason) {
|
|
|
4850
5336
|
const queue = readQueueState(state.projectRoot);
|
|
4851
5337
|
const tasks = await state.snapshotService.getWorkspaceTasks();
|
|
4852
5338
|
let runs = listAuthorityRuns4(state.projectRoot);
|
|
4853
|
-
let changed =
|
|
5339
|
+
let changed = false;
|
|
5340
|
+
for (const run of runs) {
|
|
5341
|
+
if (!state.runProcesses.has(run.runId) && recoverStaleLocalRun(state.projectRoot, run)) {
|
|
5342
|
+
changed = true;
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
if (changed) {
|
|
5346
|
+
runs = listAuthorityRuns4(state.projectRoot);
|
|
5347
|
+
}
|
|
5348
|
+
const resumableCloseouts = collectResumableServerCloseouts(state, runs);
|
|
5349
|
+
for (const run of resumableCloseouts) {
|
|
5350
|
+
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
5351
|
+
id: `log:${run.runId}:server-closeout-auto-resume:${Date.now()}`,
|
|
5352
|
+
title: "Server-owned closeout auto-resume scheduled",
|
|
5353
|
+
detail: `Rig server recovered closeout checkpoint ${run.runId} after ${reason}; resuming the server-owned lifecycle phase.`,
|
|
5354
|
+
tone: "info",
|
|
5355
|
+
status: "reviewing",
|
|
5356
|
+
createdAt: new Date().toISOString()
|
|
5357
|
+
});
|
|
5358
|
+
await runServerOwnedPrCloseout(state, run.runId).catch((error) => {
|
|
5359
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
5360
|
+
patchRunRecord(state.projectRoot, run.runId, {
|
|
5361
|
+
status: "failed",
|
|
5362
|
+
completedAt: new Date().toISOString(),
|
|
5363
|
+
errorText: detail,
|
|
5364
|
+
...closeoutPhasePatch("failed", "failed", { error: detail })
|
|
5365
|
+
});
|
|
5366
|
+
});
|
|
5367
|
+
changed = true;
|
|
5368
|
+
}
|
|
4854
5369
|
if (changed) {
|
|
4855
5370
|
runs = listAuthorityRuns4(state.projectRoot);
|
|
4856
5371
|
}
|
|
@@ -4926,8 +5441,8 @@ async function reconcileScheduler(state, reason) {
|
|
|
4926
5441
|
// packages/server/src/server-helpers/http-router.ts
|
|
4927
5442
|
import { randomUUID } from "crypto";
|
|
4928
5443
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4929
|
-
import { basename, dirname as
|
|
4930
|
-
import { copyFileSync, existsSync as
|
|
5444
|
+
import { basename, dirname as dirname15, isAbsolute as isAbsolute4, resolve as resolve20 } from "path";
|
|
5445
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
4931
5446
|
import {
|
|
4932
5447
|
listAuthorityRuns as listAuthorityRuns5,
|
|
4933
5448
|
readAuthorityRun as readAuthorityRun6,
|
|
@@ -4951,7 +5466,7 @@ import {
|
|
|
4951
5466
|
} from "@rig/runtime/control-plane/remote";
|
|
4952
5467
|
|
|
4953
5468
|
// packages/server/src/server-helpers/run-steering.ts
|
|
4954
|
-
import { dirname as
|
|
5469
|
+
import { dirname as dirname10, resolve as resolve15 } from "path";
|
|
4955
5470
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5 } from "fs";
|
|
4956
5471
|
import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
|
|
4957
5472
|
var steeringSequence = 0;
|
|
@@ -5038,7 +5553,7 @@ function queueRunSteeringMessage(projectRoot, runId, input) {
|
|
|
5038
5553
|
delivered: false
|
|
5039
5554
|
};
|
|
5040
5555
|
const path = runSteeringPath(projectRoot, runId);
|
|
5041
|
-
mkdirSync8(
|
|
5556
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
5042
5557
|
appendJsonlRecord2(path, entry);
|
|
5043
5558
|
appendRunTimelineEntry(projectRoot, runId, {
|
|
5044
5559
|
id: entry.id,
|
|
@@ -5075,6 +5590,187 @@ import {
|
|
|
5075
5590
|
updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
|
|
5076
5591
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
5077
5592
|
|
|
5593
|
+
// packages/server/src/server-helpers/github-api-session-index.ts
|
|
5594
|
+
import { chmodSync as chmodSync3, existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
5595
|
+
import { dirname as dirname12, resolve as resolve17 } from "path";
|
|
5596
|
+
|
|
5597
|
+
// packages/server/src/server-helpers/github-user-namespace.ts
|
|
5598
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
5599
|
+
import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve16 } from "path";
|
|
5600
|
+
function cleanString3(value) {
|
|
5601
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
5602
|
+
}
|
|
5603
|
+
function sanitizePathSegment(value) {
|
|
5604
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
5605
|
+
}
|
|
5606
|
+
function deriveGitHubUserNamespaceKey(identity) {
|
|
5607
|
+
const userId = cleanString3(identity.userId);
|
|
5608
|
+
if (userId) {
|
|
5609
|
+
const safeId = sanitizePathSegment(userId);
|
|
5610
|
+
if (safeId)
|
|
5611
|
+
return `ghu-${safeId}`;
|
|
5612
|
+
}
|
|
5613
|
+
const login = cleanString3(identity.login);
|
|
5614
|
+
if (login) {
|
|
5615
|
+
const safeLogin = sanitizePathSegment(login);
|
|
5616
|
+
if (safeLogin)
|
|
5617
|
+
return `ghu-login-${safeLogin}`;
|
|
5618
|
+
}
|
|
5619
|
+
throw new Error("GitHub user namespace requires a user id or login");
|
|
5620
|
+
}
|
|
5621
|
+
function resolveRemoteUserNamespacesRoot(projectRoot) {
|
|
5622
|
+
const explicitRoot = cleanString3(process.env.RIG_REMOTE_USER_NAMESPACE_ROOT);
|
|
5623
|
+
if (explicitRoot)
|
|
5624
|
+
return resolve16(explicitRoot);
|
|
5625
|
+
const stateDir2 = cleanString3(process.env.RIG_STATE_DIR);
|
|
5626
|
+
if (stateDir2)
|
|
5627
|
+
return resolve16(dirname11(resolve16(stateDir2)), "users");
|
|
5628
|
+
return resolve16(projectRoot, ".rig", "users");
|
|
5629
|
+
}
|
|
5630
|
+
function resolveRemoteUserNamespace(projectRoot, identity) {
|
|
5631
|
+
const key = deriveGitHubUserNamespaceKey(identity);
|
|
5632
|
+
const root = resolve16(resolveRemoteUserNamespacesRoot(projectRoot), key);
|
|
5633
|
+
const stateDir2 = resolve16(root, ".rig", "state");
|
|
5634
|
+
return {
|
|
5635
|
+
key,
|
|
5636
|
+
userId: cleanString3(identity.userId),
|
|
5637
|
+
login: cleanString3(identity.login),
|
|
5638
|
+
root,
|
|
5639
|
+
stateDir: stateDir2,
|
|
5640
|
+
authStateFile: resolve16(stateDir2, "github-auth.json"),
|
|
5641
|
+
metadataFile: resolve16(stateDir2, "user-namespace.json"),
|
|
5642
|
+
checkoutBaseDir: resolve16(root, "remote-checkouts"),
|
|
5643
|
+
snapshotBaseDir: resolve16(root, "remote-snapshots")
|
|
5644
|
+
};
|
|
5645
|
+
}
|
|
5646
|
+
function serializeRemoteUserNamespace(namespace) {
|
|
5647
|
+
return {
|
|
5648
|
+
key: namespace.key,
|
|
5649
|
+
userId: namespace.userId,
|
|
5650
|
+
login: namespace.login,
|
|
5651
|
+
root: namespace.root,
|
|
5652
|
+
checkoutBaseDir: namespace.checkoutBaseDir,
|
|
5653
|
+
snapshotBaseDir: namespace.snapshotBaseDir
|
|
5654
|
+
};
|
|
5655
|
+
}
|
|
5656
|
+
function isPathInsideNamespace(namespaceRoot, candidatePath) {
|
|
5657
|
+
const root = resolve16(namespaceRoot);
|
|
5658
|
+
const candidate = resolve16(candidatePath);
|
|
5659
|
+
const rel = relative3(root, candidate);
|
|
5660
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
5661
|
+
}
|
|
5662
|
+
function writeRemoteUserNamespaceMetadata(namespace) {
|
|
5663
|
+
mkdirSync9(namespace.stateDir, { recursive: true });
|
|
5664
|
+
const previous = (() => {
|
|
5665
|
+
if (!existsSync9(namespace.metadataFile))
|
|
5666
|
+
return null;
|
|
5667
|
+
try {
|
|
5668
|
+
const parsed = JSON.parse(readFileSync6(namespace.metadataFile, "utf8"));
|
|
5669
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5670
|
+
} catch {
|
|
5671
|
+
return null;
|
|
5672
|
+
}
|
|
5673
|
+
})();
|
|
5674
|
+
const now = new Date().toISOString();
|
|
5675
|
+
writeFileSync8(namespace.metadataFile, `${JSON.stringify({
|
|
5676
|
+
key: namespace.key,
|
|
5677
|
+
userId: namespace.userId,
|
|
5678
|
+
login: namespace.login,
|
|
5679
|
+
root: namespace.root,
|
|
5680
|
+
checkoutBaseDir: namespace.checkoutBaseDir,
|
|
5681
|
+
snapshotBaseDir: namespace.snapshotBaseDir,
|
|
5682
|
+
createdAt: typeof previous?.createdAt === "string" ? previous.createdAt : now,
|
|
5683
|
+
updatedAt: now
|
|
5684
|
+
}, null, 2)}
|
|
5685
|
+
`, { encoding: "utf8", mode: 384 });
|
|
5686
|
+
try {
|
|
5687
|
+
chmodSync2(namespace.metadataFile, 384);
|
|
5688
|
+
} catch {}
|
|
5689
|
+
}
|
|
5690
|
+
|
|
5691
|
+
// packages/server/src/server-helpers/github-api-session-index.ts
|
|
5692
|
+
function cleanString4(value) {
|
|
5693
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
5694
|
+
}
|
|
5695
|
+
function resolveGitHubApiSessionIndexFile(projectRoot) {
|
|
5696
|
+
return resolve17(resolveRemoteUserNamespacesRoot(projectRoot), ".api-sessions.json");
|
|
5697
|
+
}
|
|
5698
|
+
function parseEntry(value) {
|
|
5699
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5700
|
+
return null;
|
|
5701
|
+
const record = value;
|
|
5702
|
+
const token = cleanString4(record.token);
|
|
5703
|
+
const namespaceKey = cleanString4(record.namespaceKey);
|
|
5704
|
+
const namespaceRoot = cleanString4(record.namespaceRoot);
|
|
5705
|
+
const authStateFile = cleanString4(record.authStateFile);
|
|
5706
|
+
const checkoutBaseDir = cleanString4(record.checkoutBaseDir);
|
|
5707
|
+
const snapshotBaseDir = cleanString4(record.snapshotBaseDir);
|
|
5708
|
+
const createdAt = cleanString4(record.createdAt);
|
|
5709
|
+
if (!token || !namespaceKey || !namespaceRoot || !authStateFile || !checkoutBaseDir || !snapshotBaseDir || !createdAt)
|
|
5710
|
+
return null;
|
|
5711
|
+
return {
|
|
5712
|
+
token,
|
|
5713
|
+
namespaceKey,
|
|
5714
|
+
namespaceRoot,
|
|
5715
|
+
authStateFile,
|
|
5716
|
+
checkoutBaseDir,
|
|
5717
|
+
snapshotBaseDir,
|
|
5718
|
+
createdAt,
|
|
5719
|
+
login: cleanString4(record.login),
|
|
5720
|
+
userId: cleanString4(record.userId),
|
|
5721
|
+
selectedRepo: cleanString4(record.selectedRepo)
|
|
5722
|
+
};
|
|
5723
|
+
}
|
|
5724
|
+
function readIndex(indexFile) {
|
|
5725
|
+
if (!existsSync10(indexFile))
|
|
5726
|
+
return [];
|
|
5727
|
+
try {
|
|
5728
|
+
const parsed = JSON.parse(readFileSync7(indexFile, "utf8"));
|
|
5729
|
+
return Array.isArray(parsed.sessions) ? parsed.sessions.flatMap((entry) => {
|
|
5730
|
+
const parsedEntry = parseEntry(entry);
|
|
5731
|
+
return parsedEntry ? [parsedEntry] : [];
|
|
5732
|
+
}) : [];
|
|
5733
|
+
} catch {
|
|
5734
|
+
return [];
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
function writeIndex(indexFile, sessions) {
|
|
5738
|
+
mkdirSync10(dirname12(indexFile), { recursive: true });
|
|
5739
|
+
writeFileSync9(indexFile, `${JSON.stringify({ sessions }, null, 2)}
|
|
5740
|
+
`, { encoding: "utf8", mode: 384 });
|
|
5741
|
+
try {
|
|
5742
|
+
chmodSync3(indexFile, 384);
|
|
5743
|
+
} catch {}
|
|
5744
|
+
}
|
|
5745
|
+
function registerGitHubApiSession(input) {
|
|
5746
|
+
const cleanToken = cleanString4(input.token);
|
|
5747
|
+
if (!cleanToken)
|
|
5748
|
+
throw new Error("GitHub API session token is required");
|
|
5749
|
+
const indexFile = resolveGitHubApiSessionIndexFile(input.projectRoot);
|
|
5750
|
+
const createdAt = new Date().toISOString();
|
|
5751
|
+
const entry = {
|
|
5752
|
+
token: cleanToken,
|
|
5753
|
+
login: input.namespace.login,
|
|
5754
|
+
userId: input.namespace.userId,
|
|
5755
|
+
namespaceKey: input.namespace.key,
|
|
5756
|
+
namespaceRoot: input.namespace.root,
|
|
5757
|
+
authStateFile: input.namespace.authStateFile,
|
|
5758
|
+
checkoutBaseDir: input.namespace.checkoutBaseDir,
|
|
5759
|
+
snapshotBaseDir: input.namespace.snapshotBaseDir,
|
|
5760
|
+
selectedRepo: cleanString4(input.selectedRepo),
|
|
5761
|
+
createdAt
|
|
5762
|
+
};
|
|
5763
|
+
const previous = readIndex(indexFile).filter((session) => session.token !== cleanToken);
|
|
5764
|
+
writeIndex(indexFile, [...previous.slice(-199), entry]);
|
|
5765
|
+
return entry;
|
|
5766
|
+
}
|
|
5767
|
+
function readGitHubApiSession(input) {
|
|
5768
|
+
const cleanToken = cleanString4(input.token);
|
|
5769
|
+
if (!cleanToken)
|
|
5770
|
+
return null;
|
|
5771
|
+
return readIndex(resolveGitHubApiSessionIndexFile(input.projectRoot)).find((entry) => entry.token === cleanToken) ?? null;
|
|
5772
|
+
}
|
|
5773
|
+
|
|
5078
5774
|
// packages/server/src/server-helpers/inspector-agent-lifecycle.ts
|
|
5079
5775
|
function createInspectorAgentLifecycleController(options) {
|
|
5080
5776
|
const initialDelayMs = Math.max(1, options.initialDelayMs ?? 5000);
|
|
@@ -5178,21 +5874,21 @@ function inspectorAgentLifecycleSnapshot(input) {
|
|
|
5178
5874
|
// packages/server/src/server-helpers/project-registry.ts
|
|
5179
5875
|
import { createHash as createHash2 } from "crypto";
|
|
5180
5876
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5181
|
-
import { existsSync as
|
|
5182
|
-
import { dirname as
|
|
5877
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync8, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
5878
|
+
import { dirname as dirname13, resolve as resolve18 } from "path";
|
|
5183
5879
|
function normalizeRepoSlug(value) {
|
|
5184
5880
|
const trimmed = value.trim();
|
|
5185
5881
|
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : null;
|
|
5186
5882
|
}
|
|
5187
5883
|
function registryPath(projectRoot) {
|
|
5188
|
-
return
|
|
5884
|
+
return resolve18(projectRoot, ".rig", "state", "projects.json");
|
|
5189
5885
|
}
|
|
5190
5886
|
function readRegistry(projectRoot) {
|
|
5191
5887
|
const path = registryPath(projectRoot);
|
|
5192
|
-
if (!
|
|
5888
|
+
if (!existsSync11(path))
|
|
5193
5889
|
return {};
|
|
5194
5890
|
try {
|
|
5195
|
-
const payload = JSON.parse(
|
|
5891
|
+
const payload = JSON.parse(readFileSync8(path, "utf8"));
|
|
5196
5892
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
5197
5893
|
return {};
|
|
5198
5894
|
const projects = payload.projects;
|
|
@@ -5203,14 +5899,14 @@ function readRegistry(projectRoot) {
|
|
|
5203
5899
|
}
|
|
5204
5900
|
function writeRegistry(projectRoot, projects) {
|
|
5205
5901
|
const path = registryPath(projectRoot);
|
|
5206
|
-
|
|
5207
|
-
|
|
5902
|
+
mkdirSync11(dirname13(path), { recursive: true });
|
|
5903
|
+
writeFileSync10(path, `${JSON.stringify({ projects }, null, 2)}
|
|
5208
5904
|
`, "utf8");
|
|
5209
5905
|
}
|
|
5210
5906
|
function resolveConfigPath(projectRoot) {
|
|
5211
5907
|
for (const name of ["rig.config.ts", "rig.config.mts", "rig.config.json"]) {
|
|
5212
|
-
const path =
|
|
5213
|
-
if (
|
|
5908
|
+
const path = resolve18(projectRoot, name);
|
|
5909
|
+
if (existsSync11(path))
|
|
5214
5910
|
return path;
|
|
5215
5911
|
}
|
|
5216
5912
|
return null;
|
|
@@ -5219,7 +5915,7 @@ function hashFile(path) {
|
|
|
5219
5915
|
if (!path)
|
|
5220
5916
|
return null;
|
|
5221
5917
|
try {
|
|
5222
|
-
return createHash2("sha256").update(
|
|
5918
|
+
return createHash2("sha256").update(readFileSync8(path)).digest("hex");
|
|
5223
5919
|
} catch {
|
|
5224
5920
|
return null;
|
|
5225
5921
|
}
|
|
@@ -5235,11 +5931,11 @@ function readDefaultBranch(projectRoot) {
|
|
|
5235
5931
|
return head.status === 0 && head.stdout.trim() && head.stdout.trim() !== "HEAD" ? head.stdout.trim() : null;
|
|
5236
5932
|
}
|
|
5237
5933
|
function buildRunSummary(projectRoot) {
|
|
5238
|
-
const runsDir =
|
|
5934
|
+
const runsDir = resolve18(projectRoot, ".rig", "runs");
|
|
5239
5935
|
try {
|
|
5240
5936
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
5241
5937
|
try {
|
|
5242
|
-
const run = JSON.parse(
|
|
5938
|
+
const run = JSON.parse(readFileSync8(resolve18(runsDir, entry.name, "run.json"), "utf8"));
|
|
5243
5939
|
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 : "" }];
|
|
5244
5940
|
} catch {
|
|
5245
5941
|
return [];
|
|
@@ -5291,10 +5987,14 @@ function upsertProjectRecord(projectRoot, input) {
|
|
|
5291
5987
|
function linkProjectCheckout(projectRoot, repoSlug, checkout) {
|
|
5292
5988
|
return upsertProjectRecord(projectRoot, { repoSlug, checkout });
|
|
5293
5989
|
}
|
|
5990
|
+
function projectRegistryContainsCheckout(projectRoot, checkoutPath) {
|
|
5991
|
+
const target = resolve18(checkoutPath);
|
|
5992
|
+
return Object.values(readRegistry(projectRoot)).some((project) => project.checkouts.some((checkout) => checkout.path ? resolve18(checkout.path) === target : false));
|
|
5993
|
+
}
|
|
5294
5994
|
|
|
5295
5995
|
// packages/server/src/server-helpers/remote-checkout.ts
|
|
5296
|
-
import { existsSync as
|
|
5297
|
-
import { dirname as
|
|
5996
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
5997
|
+
import { dirname as dirname14, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve19 } from "path";
|
|
5298
5998
|
function safeSlugSegments(repoSlug) {
|
|
5299
5999
|
const segments = repoSlug.split("/").map((part) => part.trim()).filter(Boolean);
|
|
5300
6000
|
if (segments.length !== 2 || segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\"))) {
|
|
@@ -5311,7 +6011,7 @@ function safeCheckoutKey(value) {
|
|
|
5311
6011
|
}
|
|
5312
6012
|
function repoSlugPath(baseDir, repoSlug, checkoutKey) {
|
|
5313
6013
|
const key = safeCheckoutKey(checkoutKey);
|
|
5314
|
-
return
|
|
6014
|
+
return resolve19(baseDir, ...key ? [key] : [], ...safeSlugSegments(repoSlug));
|
|
5315
6015
|
}
|
|
5316
6016
|
function sanitizeSnapshotId(value, fallback) {
|
|
5317
6017
|
const raw = (value ?? fallback).trim();
|
|
@@ -5319,7 +6019,7 @@ function sanitizeSnapshotId(value, fallback) {
|
|
|
5319
6019
|
return safe || fallback;
|
|
5320
6020
|
}
|
|
5321
6021
|
function assertWithinRoot(root, relativePath) {
|
|
5322
|
-
if (!relativePath ||
|
|
6022
|
+
if (!relativePath || isAbsolute3(relativePath) || relativePath.includes("\x00")) {
|
|
5323
6023
|
throw new Error(`Invalid snapshot file path: ${relativePath}`);
|
|
5324
6024
|
}
|
|
5325
6025
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
@@ -5327,9 +6027,9 @@ function assertWithinRoot(root, relativePath) {
|
|
|
5327
6027
|
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
5328
6028
|
throw new Error(`Unsafe snapshot file path: ${relativePath}`);
|
|
5329
6029
|
}
|
|
5330
|
-
const target =
|
|
5331
|
-
const rel =
|
|
5332
|
-
if (rel === ".." || rel.split(/[\\/]/)[0] === ".." ||
|
|
6030
|
+
const target = resolve19(root, ...segments);
|
|
6031
|
+
const rel = relative4(root, target);
|
|
6032
|
+
if (rel === ".." || rel.split(/[\\/]/)[0] === ".." || isAbsolute3(rel)) {
|
|
5333
6033
|
throw new Error(`Snapshot file path escapes checkout root: ${relativePath}`);
|
|
5334
6034
|
}
|
|
5335
6035
|
return target;
|
|
@@ -5347,17 +6047,17 @@ function parseSnapshotArchiveContentBase64(contentBase64) {
|
|
|
5347
6047
|
function extractUploadedSnapshotArchive(input) {
|
|
5348
6048
|
const archive = decodeSnapshotArchive(input.archive);
|
|
5349
6049
|
const snapshotId = sanitizeSnapshotId(input.snapshotId, `snapshot-${(input.now?.() ?? new Date).toISOString().replace(/[:.]/g, "-")}`);
|
|
5350
|
-
const checkoutPath =
|
|
5351
|
-
|
|
6050
|
+
const checkoutPath = resolve19(repoSlugPath(input.baseDir, input.repoSlug, input.checkoutKey), snapshotId);
|
|
6051
|
+
mkdirSync12(checkoutPath, { recursive: true });
|
|
5352
6052
|
for (const file of archive.files) {
|
|
5353
6053
|
if (!file || typeof file.path !== "string" || typeof file.contentBase64 !== "string") {
|
|
5354
6054
|
throw new Error("Invalid snapshot archive file entry");
|
|
5355
6055
|
}
|
|
5356
6056
|
const target = assertWithinRoot(checkoutPath, file.path);
|
|
5357
|
-
|
|
5358
|
-
|
|
6057
|
+
mkdirSync12(dirname14(target), { recursive: true });
|
|
6058
|
+
writeFileSync11(target, Buffer.from(file.contentBase64, "base64"));
|
|
5359
6059
|
}
|
|
5360
|
-
|
|
6060
|
+
writeFileSync11(resolve19(checkoutPath, ".rig-uploaded-snapshot.json"), `${JSON.stringify({
|
|
5361
6061
|
repoSlug: input.repoSlug,
|
|
5362
6062
|
snapshotId,
|
|
5363
6063
|
fileCount: archive.files.length,
|
|
@@ -5392,7 +6092,7 @@ function gitCredentialConfig(token) {
|
|
|
5392
6092
|
};
|
|
5393
6093
|
}
|
|
5394
6094
|
async function prepareRemoteCheckout(input) {
|
|
5395
|
-
const exists = input.exists ??
|
|
6095
|
+
const exists = input.exists ?? existsSync12;
|
|
5396
6096
|
const strategy = input.strategy;
|
|
5397
6097
|
if (strategy.kind === "uploaded-snapshot") {
|
|
5398
6098
|
return extractUploadedSnapshotArchive({
|
|
@@ -5404,7 +6104,7 @@ async function prepareRemoteCheckout(input) {
|
|
|
5404
6104
|
});
|
|
5405
6105
|
}
|
|
5406
6106
|
if (strategy.kind === "existing-path") {
|
|
5407
|
-
const checkoutPath2 =
|
|
6107
|
+
const checkoutPath2 = resolve19(strategy.path);
|
|
5408
6108
|
if (!exists(checkoutPath2)) {
|
|
5409
6109
|
throw new Error(`Existing remote checkout path does not exist: ${checkoutPath2}`);
|
|
5410
6110
|
}
|
|
@@ -5440,9 +6140,9 @@ function buildServerControlStatus() {
|
|
|
5440
6140
|
};
|
|
5441
6141
|
}
|
|
5442
6142
|
function buildProjectConfigStatus(root) {
|
|
5443
|
-
const hasConfigTs =
|
|
5444
|
-
const hasConfigJson =
|
|
5445
|
-
const hasLegacyTaskConfig =
|
|
6143
|
+
const hasConfigTs = existsSync13(resolve20(root, "rig.config.ts"));
|
|
6144
|
+
const hasConfigJson = existsSync13(resolve20(root, "rig.config.json"));
|
|
6145
|
+
const hasLegacyTaskConfig = existsSync13(resolve20(root, ".rig", "task-config.json"));
|
|
5446
6146
|
let kind = "missing";
|
|
5447
6147
|
if (hasConfigTs)
|
|
5448
6148
|
kind = "rig-config-ts";
|
|
@@ -5459,6 +6159,75 @@ function buildProjectConfigStatus(root) {
|
|
|
5459
6159
|
suggestion: kind === "missing" ? "Run `rig init` in the project root to scaffold rig.config.ts." : null
|
|
5460
6160
|
};
|
|
5461
6161
|
}
|
|
6162
|
+
var RIG_GITHUB_LIFECYCLE_LABELS = [
|
|
6163
|
+
"ready",
|
|
6164
|
+
"blocked",
|
|
6165
|
+
"in-progress",
|
|
6166
|
+
"under-review",
|
|
6167
|
+
"failed",
|
|
6168
|
+
"cancelled",
|
|
6169
|
+
"rig:running",
|
|
6170
|
+
"rig:pr-open",
|
|
6171
|
+
"rig:ci-fixing",
|
|
6172
|
+
"rig:merging",
|
|
6173
|
+
"rig:done",
|
|
6174
|
+
"rig:needs-attention"
|
|
6175
|
+
];
|
|
6176
|
+
function githubProjectsEnabled2(config) {
|
|
6177
|
+
if (!config || typeof config !== "object" || Array.isArray(config))
|
|
6178
|
+
return false;
|
|
6179
|
+
const root = config;
|
|
6180
|
+
const github = root.github && typeof root.github === "object" && !Array.isArray(root.github) ? root.github : null;
|
|
6181
|
+
const projects = github?.projects && typeof github.projects === "object" && !Array.isArray(github.projects) ? github.projects : null;
|
|
6182
|
+
return projects?.enabled === true;
|
|
6183
|
+
}
|
|
6184
|
+
function githubIssueSourceRepo(config) {
|
|
6185
|
+
if (!config || typeof config !== "object" || Array.isArray(config))
|
|
6186
|
+
return null;
|
|
6187
|
+
const root = config;
|
|
6188
|
+
const taskSource = root.taskSource && typeof root.taskSource === "object" && !Array.isArray(root.taskSource) ? root.taskSource : null;
|
|
6189
|
+
const owner = normalizeString(taskSource?.owner);
|
|
6190
|
+
const repo = normalizeString(taskSource?.repo);
|
|
6191
|
+
if (taskSource?.kind === "github-issues" && owner && repo)
|
|
6192
|
+
return { owner, repo };
|
|
6193
|
+
const project = root.project && typeof root.project === "object" && !Array.isArray(root.project) ? root.project : null;
|
|
6194
|
+
const slug = normalizeString(project?.repo) ?? normalizeString(project?.name);
|
|
6195
|
+
const match = slug?.match(/^([^/]+)\/([^/]+)$/);
|
|
6196
|
+
return match ? { owner: match[1], repo: match[2] } : null;
|
|
6197
|
+
}
|
|
6198
|
+
async function ensureGitHubLifecycleLabels(projectRoot, config) {
|
|
6199
|
+
const repo = githubIssueSourceRepo(config);
|
|
6200
|
+
if (!repo)
|
|
6201
|
+
return { ok: false, ready: false, labelsReady: false, reason: "not-github-issues-source", labels: RIG_GITHUB_LIFECYCLE_LABELS };
|
|
6202
|
+
const token = createGitHubAuthStore(projectRoot).readToken();
|
|
6203
|
+
if (!token)
|
|
6204
|
+
return { ok: false, ready: false, labelsReady: false, reason: "missing-token", repo, labels: RIG_GITHUB_LIFECYCLE_LABELS };
|
|
6205
|
+
const existingResponse = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.repo}/labels?per_page=100`, {
|
|
6206
|
+
headers: { accept: "application/vnd.github+json", authorization: `Bearer ${token}`, "user-agent": "rig-server" }
|
|
6207
|
+
});
|
|
6208
|
+
const existingJson = await existingResponse.json().catch(() => []);
|
|
6209
|
+
const existing = new Set(Array.isArray(existingJson) ? existingJson.flatMap((entry) => entry && typeof entry === "object" && typeof entry.name === "string" ? [entry.name] : []) : []);
|
|
6210
|
+
const created = [];
|
|
6211
|
+
const alreadyPresent = [];
|
|
6212
|
+
const failed = [];
|
|
6213
|
+
for (const label of RIG_GITHUB_LIFECYCLE_LABELS) {
|
|
6214
|
+
if (existing.has(label)) {
|
|
6215
|
+
alreadyPresent.push(label);
|
|
6216
|
+
continue;
|
|
6217
|
+
}
|
|
6218
|
+
const response = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.repo}/labels`, {
|
|
6219
|
+
method: "POST",
|
|
6220
|
+
headers: { accept: "application/vnd.github+json", authorization: `Bearer ${token}`, "content-type": "application/json", "user-agent": "rig-server" },
|
|
6221
|
+
body: JSON.stringify({ name: label, color: label.startsWith("rig:") ? "6f42c1" : "ededed", description: label.startsWith("rig:") ? "Task status managed by Rig" : "Task lifecycle status managed by Rig" })
|
|
6222
|
+
});
|
|
6223
|
+
if (response.ok || response.status === 422) {
|
|
6224
|
+
(response.status === 422 ? alreadyPresent : created).push(label);
|
|
6225
|
+
} else {
|
|
6226
|
+
failed.push({ label, error: await response.text().catch(() => response.statusText) });
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
return { ok: failed.length === 0, ready: failed.length === 0, labelsReady: failed.length === 0, repo, labels: RIG_GITHUB_LIFECYCLE_LABELS, created, existing: alreadyPresent, failed };
|
|
6230
|
+
}
|
|
5462
6231
|
function normalizeCommit(value) {
|
|
5463
6232
|
const raw = normalizeString(value);
|
|
5464
6233
|
return raw && /^[0-9a-f]{7,40}$/i.test(raw) ? raw : null;
|
|
@@ -5478,24 +6247,24 @@ function repoParts(repoSlug) {
|
|
|
5478
6247
|
return { owner, repo, slug: `${owner}/${repo}` };
|
|
5479
6248
|
}
|
|
5480
6249
|
function repairDir(checkoutPath) {
|
|
5481
|
-
const dir =
|
|
5482
|
-
|
|
6250
|
+
const dir = resolve20(checkoutPath, ".rig", "state", "repairs", new Date().toISOString().replace(/[:.]/g, "-"));
|
|
6251
|
+
mkdirSync13(dir, { recursive: true });
|
|
5483
6252
|
return dir;
|
|
5484
6253
|
}
|
|
5485
6254
|
function backupCheckoutFile(checkoutPath, relativePath) {
|
|
5486
|
-
const source =
|
|
5487
|
-
const backupPath =
|
|
5488
|
-
|
|
5489
|
-
|
|
6255
|
+
const source = resolve20(checkoutPath, relativePath);
|
|
6256
|
+
const backupPath = resolve20(repairDir(checkoutPath), relativePath.replace(/[\\/]/g, "__"));
|
|
6257
|
+
mkdirSync13(dirname15(backupPath), { recursive: true });
|
|
6258
|
+
copyFileSync2(source, backupPath);
|
|
5490
6259
|
return backupPath;
|
|
5491
6260
|
}
|
|
5492
6261
|
function parsePackageJsonLosslessly(checkoutPath) {
|
|
5493
|
-
const packagePath =
|
|
5494
|
-
if (!
|
|
6262
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
6263
|
+
if (!existsSync13(packagePath)) {
|
|
5495
6264
|
return { existed: false, packageJson: { name: basename(checkoutPath) || "rig-project", private: true } };
|
|
5496
6265
|
}
|
|
5497
6266
|
try {
|
|
5498
|
-
const parsed = JSON.parse(
|
|
6267
|
+
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
5499
6268
|
return {
|
|
5500
6269
|
existed: true,
|
|
5501
6270
|
packageJson: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { name: basename(checkoutPath) || "rig-project", private: true }
|
|
@@ -5509,9 +6278,9 @@ function parsePackageJsonLosslessly(checkoutPath) {
|
|
|
5509
6278
|
}
|
|
5510
6279
|
}
|
|
5511
6280
|
function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
5512
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5513
|
-
const packagePath =
|
|
5514
|
-
if (!hasConfig && !
|
|
6281
|
+
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
6282
|
+
const packagePath = resolve20(checkoutPath, "package.json");
|
|
6283
|
+
if (!hasConfig && !existsSync13(packagePath)) {
|
|
5515
6284
|
return { skipped: true, reason: "package.json and rig.config missing" };
|
|
5516
6285
|
}
|
|
5517
6286
|
const parsed = parsePackageJsonLosslessly(checkoutPath);
|
|
@@ -5530,7 +6299,7 @@ function ensureRemoteCheckoutRigPackageDeps(checkoutPath) {
|
|
|
5530
6299
|
}
|
|
5531
6300
|
const changed = !parsed.existed || Boolean(parsed.backupPath) || added.length > 0 || updated.length > 0 || existingDevDependencies !== devDependencies && (!existingDevDependencies || typeof existingDevDependencies !== "object" || Array.isArray(existingDevDependencies));
|
|
5532
6301
|
if (changed) {
|
|
5533
|
-
|
|
6302
|
+
writeFileSync12(packagePath, `${JSON.stringify({ ...parsed.packageJson, devDependencies }, null, 2)}
|
|
5534
6303
|
`, "utf8");
|
|
5535
6304
|
}
|
|
5536
6305
|
return {
|
|
@@ -5556,11 +6325,11 @@ function configLooksStructurallyUsable(source) {
|
|
|
5556
6325
|
return /taskSource\s*:/.test(source) && /workspace\s*:/.test(source) && /project\s*:/.test(source);
|
|
5557
6326
|
}
|
|
5558
6327
|
function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing or incomplete rig config") {
|
|
5559
|
-
const configPath =
|
|
5560
|
-
const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) =>
|
|
6328
|
+
const configPath = resolve20(checkoutPath, "rig.config.ts");
|
|
6329
|
+
const existingConfigName = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5561
6330
|
if (existingConfigName) {
|
|
5562
|
-
const existingPath =
|
|
5563
|
-
const source =
|
|
6331
|
+
const existingPath = resolve20(checkoutPath, existingConfigName);
|
|
6332
|
+
const source = readFileSync9(existingPath, "utf8");
|
|
5564
6333
|
if (existingConfigName !== "rig.config.json" && configLooksStructurallyUsable(source)) {
|
|
5565
6334
|
return { path: existingPath, changed: false, reason: "config structurally complete" };
|
|
5566
6335
|
}
|
|
@@ -5574,7 +6343,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5574
6343
|
}
|
|
5575
6344
|
}
|
|
5576
6345
|
const backupPath = existingConfigName ? backupCheckoutFile(checkoutPath, existingConfigName) : undefined;
|
|
5577
|
-
|
|
6346
|
+
writeFileSync12(configPath, generatedRigConfigSource(repoSlug), "utf8");
|
|
5578
6347
|
return {
|
|
5579
6348
|
path: configPath,
|
|
5580
6349
|
changed: true,
|
|
@@ -5583,7 +6352,7 @@ function ensureRemoteCheckoutRigConfig(checkoutPath, repoSlug, reason = "missing
|
|
|
5583
6352
|
};
|
|
5584
6353
|
}
|
|
5585
6354
|
function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
5586
|
-
const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) =>
|
|
6355
|
+
const configFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].find((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
5587
6356
|
if (!configFile)
|
|
5588
6357
|
return { ok: false, error: "missing rig config" };
|
|
5589
6358
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5605,7 +6374,7 @@ function validateRemoteCheckoutRigConfig(checkoutPath) {
|
|
|
5605
6374
|
return { ok: true, configFile };
|
|
5606
6375
|
}
|
|
5607
6376
|
function installRemoteCheckoutPackages(checkoutPath) {
|
|
5608
|
-
if (!
|
|
6377
|
+
if (!existsSync13(resolve20(checkoutPath, "package.json"))) {
|
|
5609
6378
|
return { skipped: true, reason: "package.json missing" };
|
|
5610
6379
|
}
|
|
5611
6380
|
if (process.env.RIG_TEST_SKIP_REMOTE_CHECKOUT_INSTALL === "1") {
|
|
@@ -5618,8 +6387,8 @@ function installRemoteCheckoutPackages(checkoutPath) {
|
|
|
5618
6387
|
return { ok: true, command: "bun install", stdout: result.stdout?.trim() || undefined };
|
|
5619
6388
|
}
|
|
5620
6389
|
function repairRemoteCheckoutForRig(checkoutPath, repoSlug) {
|
|
5621
|
-
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
5622
|
-
const hasPackage =
|
|
6390
|
+
const hasConfig = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync13(resolve20(checkoutPath, name)));
|
|
6391
|
+
const hasPackage = existsSync13(resolve20(checkoutPath, "package.json"));
|
|
5623
6392
|
if (!hasConfig && !hasPackage) {
|
|
5624
6393
|
return {
|
|
5625
6394
|
packageJson: { skipped: true, reason: "package.json and rig.config missing" },
|
|
@@ -5717,26 +6486,26 @@ function buildRemoteRunLogEntry(body, identifiers) {
|
|
|
5717
6486
|
}
|
|
5718
6487
|
function readGitHeadCommit(projectRoot) {
|
|
5719
6488
|
try {
|
|
5720
|
-
let gitDir =
|
|
6489
|
+
let gitDir = resolve20(projectRoot, ".git");
|
|
5721
6490
|
try {
|
|
5722
|
-
const dotGit =
|
|
6491
|
+
const dotGit = readFileSync9(gitDir, "utf8").trim();
|
|
5723
6492
|
const gitDirPrefix = "gitdir:";
|
|
5724
6493
|
if (dotGit.startsWith(gitDirPrefix)) {
|
|
5725
|
-
gitDir =
|
|
6494
|
+
gitDir = resolve20(projectRoot, dotGit.slice(gitDirPrefix.length).trim());
|
|
5726
6495
|
}
|
|
5727
6496
|
} catch {}
|
|
5728
|
-
const head =
|
|
6497
|
+
const head = readFileSync9(resolve20(gitDir, "HEAD"), "utf8").trim();
|
|
5729
6498
|
const refPrefix = "ref:";
|
|
5730
6499
|
if (!head.startsWith(refPrefix)) {
|
|
5731
6500
|
return normalizeCommit(head);
|
|
5732
6501
|
}
|
|
5733
6502
|
const ref = head.slice(refPrefix.length).trim();
|
|
5734
|
-
const refPath =
|
|
5735
|
-
if (
|
|
5736
|
-
return normalizeCommit(
|
|
6503
|
+
const refPath = resolve20(gitDir, ref);
|
|
6504
|
+
if (existsSync13(refPath)) {
|
|
6505
|
+
return normalizeCommit(readFileSync9(refPath, "utf8").trim());
|
|
5737
6506
|
}
|
|
5738
|
-
const commonDir = normalizeString(
|
|
5739
|
-
return commonDir ? normalizeCommit(
|
|
6507
|
+
const commonDir = normalizeString(readFileSync9(resolve20(gitDir, "commondir"), "utf8"));
|
|
6508
|
+
return commonDir ? normalizeCommit(readFileSync9(resolve20(gitDir, commonDir, ref), "utf8").trim()) : null;
|
|
5740
6509
|
} catch {
|
|
5741
6510
|
return null;
|
|
5742
6511
|
}
|
|
@@ -5774,9 +6543,9 @@ function configuredRepoFromTaskSource(taskSource) {
|
|
|
5774
6543
|
const repo = normalizeString(taskSource?.repo);
|
|
5775
6544
|
return owner && repo ? `${owner}/${repo}` : null;
|
|
5776
6545
|
}
|
|
5777
|
-
async function buildTaskSourceStatus(state, config) {
|
|
6546
|
+
async function buildTaskSourceStatus(state, config, requestAuth) {
|
|
5778
6547
|
const diagnostics = state.snapshotService.getTaskSourceErrors();
|
|
5779
|
-
const selectedRepo =
|
|
6548
|
+
const selectedRepo = requestScopedAuthStore(state.projectRoot, requestAuth).status({
|
|
5780
6549
|
oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim())
|
|
5781
6550
|
}).selectedRepo;
|
|
5782
6551
|
try {
|
|
@@ -5829,37 +6598,146 @@ function isLoopbackRequest(req) {
|
|
|
5829
6598
|
}
|
|
5830
6599
|
}
|
|
5831
6600
|
function isPublicRigAuthBootstrapRoute(pathname) {
|
|
5832
|
-
return pathname === "/" || 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";
|
|
6601
|
+
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";
|
|
6602
|
+
}
|
|
6603
|
+
function buildRigInstallScript() {
|
|
6604
|
+
return `#!/usr/bin/env bash
|
|
6605
|
+
set -euo pipefail
|
|
6606
|
+
|
|
6607
|
+
say() {
|
|
6608
|
+
printf 'rig-install: %s
|
|
6609
|
+
' "$*"
|
|
6610
|
+
}
|
|
6611
|
+
|
|
6612
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
6613
|
+
say "Bun not found; installing Bun first"
|
|
6614
|
+
curl -fsSL https://bun.sh/install | bash
|
|
6615
|
+
export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
|
|
6616
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
6617
|
+
fi
|
|
6618
|
+
|
|
6619
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
6620
|
+
printf 'rig-install: bun install completed, but bun is still not on PATH. Add ~/.bun/bin to PATH and retry.
|
|
6621
|
+
' >&2
|
|
6622
|
+
exit 1
|
|
6623
|
+
fi
|
|
6624
|
+
|
|
6625
|
+
say "Installing @h-rig/cli@latest"
|
|
6626
|
+
bun add -g --force @h-rig/cli@latest
|
|
6627
|
+
|
|
6628
|
+
export BUN_INSTALL="\${BUN_INSTALL:-$HOME/.bun}"
|
|
6629
|
+
BUN_RIG="$BUN_INSTALL/bin/rig"
|
|
6630
|
+
if [ ! -x "$BUN_RIG" ]; then
|
|
6631
|
+
printf 'rig-install: expected Bun global rig at %s but it was not executable.
|
|
6632
|
+
' "$BUN_RIG" >&2
|
|
6633
|
+
exit 1
|
|
6634
|
+
fi
|
|
6635
|
+
|
|
6636
|
+
USER_BIN="$HOME/.local/bin"
|
|
6637
|
+
mkdir -p "$USER_BIN"
|
|
6638
|
+
cat > "$USER_BIN/rig" <<'RIG_SHIM'
|
|
6639
|
+
#!/usr/bin/env bash
|
|
6640
|
+
set -euo pipefail
|
|
6641
|
+
exec "\${BUN_INSTALL:-$HOME/.bun}/bin/rig" "$@"
|
|
6642
|
+
RIG_SHIM
|
|
6643
|
+
chmod +x "$USER_BIN/rig"
|
|
6644
|
+
|
|
6645
|
+
export PATH="$USER_BIN:$BUN_INSTALL/bin:$PATH"
|
|
6646
|
+
if command -v hash >/dev/null 2>&1; then hash -r; fi
|
|
6647
|
+
|
|
6648
|
+
if ! command -v rig >/dev/null 2>&1; then
|
|
6649
|
+
printf 'rig-install: rig installed, but rig is not on PATH. Add %s and %s/bin to PATH and retry.
|
|
6650
|
+
' "$USER_BIN" "$BUN_INSTALL" >&2
|
|
6651
|
+
exit 1
|
|
6652
|
+
fi
|
|
6653
|
+
|
|
6654
|
+
say "Verifying rig"
|
|
6655
|
+
"$BUN_RIG" --help >/dev/null
|
|
6656
|
+
rig --help >/dev/null
|
|
6657
|
+
say "Done. Run: rig --help"
|
|
6658
|
+
`;
|
|
5833
6659
|
}
|
|
5834
6660
|
function normalizePrMode(value) {
|
|
5835
6661
|
const mode = normalizeString(value);
|
|
5836
6662
|
return mode === "auto" || mode === "ask" || mode === "off" ? mode : undefined;
|
|
5837
6663
|
}
|
|
6664
|
+
function requestAuthResult(input) {
|
|
6665
|
+
return {
|
|
6666
|
+
authorized: input.authorized,
|
|
6667
|
+
actor: input.actor ?? null,
|
|
6668
|
+
reason: input.reason,
|
|
6669
|
+
login: input.login ?? null,
|
|
6670
|
+
userId: input.userId ?? null,
|
|
6671
|
+
userNamespace: input.userNamespace ?? null,
|
|
6672
|
+
authStateFile: input.authStateFile ?? null
|
|
6673
|
+
};
|
|
6674
|
+
}
|
|
6675
|
+
function namespaceFromSessionIndex(entry) {
|
|
6676
|
+
const stateDir2 = dirname15(entry.authStateFile);
|
|
6677
|
+
return {
|
|
6678
|
+
key: entry.namespaceKey,
|
|
6679
|
+
userId: entry.userId,
|
|
6680
|
+
login: entry.login,
|
|
6681
|
+
root: entry.namespaceRoot,
|
|
6682
|
+
stateDir: stateDir2,
|
|
6683
|
+
authStateFile: entry.authStateFile,
|
|
6684
|
+
metadataFile: resolve20(stateDir2, "user-namespace.json"),
|
|
6685
|
+
checkoutBaseDir: entry.checkoutBaseDir,
|
|
6686
|
+
snapshotBaseDir: entry.snapshotBaseDir
|
|
6687
|
+
};
|
|
6688
|
+
}
|
|
5838
6689
|
function authorizeRigHttpRequest(input) {
|
|
5839
6690
|
if (input.legacyAuthorized) {
|
|
5840
|
-
return { authorized: true, actor: "rig-local-server", reason: "server-token" };
|
|
6691
|
+
return requestAuthResult({ authorized: true, actor: "rig-local-server", reason: "server-token" });
|
|
5841
6692
|
}
|
|
5842
6693
|
const bearer = bearerTokenFromRequest(input.req);
|
|
5843
6694
|
const store = createGitHubAuthStore(input.projectRoot);
|
|
5844
6695
|
const storedToken = store.readToken();
|
|
5845
6696
|
const session = bearer ? store.readApiSession(bearer) : null;
|
|
5846
6697
|
if (session) {
|
|
5847
|
-
return {
|
|
6698
|
+
return requestAuthResult({
|
|
6699
|
+
authorized: true,
|
|
6700
|
+
actor: session.login ?? "github-operator",
|
|
6701
|
+
reason: "github-session",
|
|
6702
|
+
login: session.login,
|
|
6703
|
+
userId: session.userId,
|
|
6704
|
+
authStateFile: store.stateFile
|
|
6705
|
+
});
|
|
5848
6706
|
}
|
|
5849
6707
|
if (bearer && storedToken && bearer === storedToken) {
|
|
5850
6708
|
const status = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
5851
|
-
return {
|
|
6709
|
+
return requestAuthResult({
|
|
6710
|
+
authorized: true,
|
|
6711
|
+
actor: status.login ?? "github-operator",
|
|
6712
|
+
reason: "github-token",
|
|
6713
|
+
login: status.login,
|
|
6714
|
+
userId: status.userId,
|
|
6715
|
+
authStateFile: store.stateFile
|
|
6716
|
+
});
|
|
6717
|
+
}
|
|
6718
|
+
const indexedSession = readGitHubApiSession({ projectRoot: input.projectRoot, token: bearer });
|
|
6719
|
+
if (indexedSession) {
|
|
6720
|
+
const userNamespace = namespaceFromSessionIndex(indexedSession);
|
|
6721
|
+
return requestAuthResult({
|
|
6722
|
+
authorized: true,
|
|
6723
|
+
actor: indexedSession.login ?? "github-operator",
|
|
6724
|
+
reason: "github-user-session",
|
|
6725
|
+
login: indexedSession.login,
|
|
6726
|
+
userId: indexedSession.userId,
|
|
6727
|
+
userNamespace,
|
|
6728
|
+
authStateFile: indexedSession.authStateFile
|
|
6729
|
+
});
|
|
5852
6730
|
}
|
|
5853
6731
|
if (isPublicRigAuthBootstrapRoute(input.pathname)) {
|
|
5854
|
-
return { authorized: true, actor: null, reason: "public-bootstrap" };
|
|
6732
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "public-bootstrap" });
|
|
5855
6733
|
}
|
|
5856
6734
|
if (!input.serverAuthToken && !storedToken) {
|
|
5857
6735
|
if (isLoopbackRequest(input.req)) {
|
|
5858
|
-
return { authorized: true, actor: null, reason: "loopback-dev-no-auth" };
|
|
6736
|
+
return requestAuthResult({ authorized: true, actor: null, reason: "loopback-dev-no-auth" });
|
|
5859
6737
|
}
|
|
5860
|
-
return { authorized: false, actor: null, reason: "auth-required" };
|
|
6738
|
+
return requestAuthResult({ authorized: false, actor: null, reason: "auth-required" });
|
|
5861
6739
|
}
|
|
5862
|
-
return { authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" };
|
|
6740
|
+
return requestAuthResult({ authorized: false, actor: null, reason: storedToken ? "github-token-required" : "auth-required" });
|
|
5863
6741
|
}
|
|
5864
6742
|
async function fetchGitHubUserInfo(token) {
|
|
5865
6743
|
const response = await fetch("https://api.github.com/user", {
|
|
@@ -5879,6 +6757,67 @@ async function fetchGitHubUserInfo(token) {
|
|
|
5879
6757
|
scopes: cleanHeaderScopes(response.headers.get("x-oauth-scopes"))
|
|
5880
6758
|
};
|
|
5881
6759
|
}
|
|
6760
|
+
function shouldWriteRootAuthCompat(projectRoot) {
|
|
6761
|
+
if (process.env.RIG_REMOTE_USER_NAMESPACE_ROOT?.trim())
|
|
6762
|
+
return false;
|
|
6763
|
+
const stateDir2 = normalizeString(process.env.RIG_STATE_DIR);
|
|
6764
|
+
if (!stateDir2)
|
|
6765
|
+
return true;
|
|
6766
|
+
return resolve20(stateDir2) === resolve20(projectRoot, ".rig", "state");
|
|
6767
|
+
}
|
|
6768
|
+
function requestScopedRegistryRoot(stateProjectRoot, requestAuth) {
|
|
6769
|
+
return requestAuth.userNamespace?.root ?? stateProjectRoot;
|
|
6770
|
+
}
|
|
6771
|
+
function requestScopedAuthStore(stateProjectRoot, requestAuth) {
|
|
6772
|
+
return requestAuth.authStateFile ? createGitHubAuthStoreFromStateFile(requestAuth.authStateFile) : createGitHubAuthStore(stateProjectRoot);
|
|
6773
|
+
}
|
|
6774
|
+
function userNamespaceResponse(namespace) {
|
|
6775
|
+
return namespace ? serializeRemoteUserNamespace(namespace) : undefined;
|
|
6776
|
+
}
|
|
6777
|
+
function resolveNamespacedBaseDir(input) {
|
|
6778
|
+
if (input.explicitBaseDir)
|
|
6779
|
+
return input.explicitBaseDir;
|
|
6780
|
+
const envBase = normalizeString(process.env[input.envName]);
|
|
6781
|
+
if (input.userNamespace) {
|
|
6782
|
+
return envBase ? resolve20(envBase, input.userNamespace.key) : input.userNamespace[input.legacySubdir === "remote-checkouts" ? "checkoutBaseDir" : "snapshotBaseDir"];
|
|
6783
|
+
}
|
|
6784
|
+
return envBase ?? (normalizeString(process.env.RIG_STATE_DIR) ? resolve20(normalizeString(process.env.RIG_STATE_DIR), input.legacySubdir) : resolve20(input.legacyProjectRoot, ".rig", input.legacySubdir));
|
|
6785
|
+
}
|
|
6786
|
+
function explicitCheckoutKey(body, checkoutInput, requestAuth) {
|
|
6787
|
+
return normalizeString(body.checkoutKey) ?? normalizeString(checkoutInput.checkoutKey) ?? normalizeString(checkoutInput.key) ?? (requestAuth.userNamespace ? undefined : "default");
|
|
6788
|
+
}
|
|
6789
|
+
function saveGitHubTokenForRemoteUser(input) {
|
|
6790
|
+
const namespace = resolveRemoteUserNamespace(input.projectRoot, { userId: input.user.userId, login: input.user.login });
|
|
6791
|
+
writeRemoteUserNamespaceMetadata(namespace);
|
|
6792
|
+
const store = createGitHubAuthStoreFromStateFile(namespace.authStateFile);
|
|
6793
|
+
store.saveToken({
|
|
6794
|
+
token: input.token,
|
|
6795
|
+
tokenSource: input.tokenSource,
|
|
6796
|
+
login: input.user.login,
|
|
6797
|
+
userId: input.user.userId,
|
|
6798
|
+
scopes: input.user.scopes,
|
|
6799
|
+
selectedRepo: input.selectedRepo
|
|
6800
|
+
});
|
|
6801
|
+
const apiSession = store.createApiSession();
|
|
6802
|
+
registerGitHubApiSession({ projectRoot: input.projectRoot, token: apiSession.token, namespace, selectedRepo: input.selectedRepo });
|
|
6803
|
+
const requestedRoot = normalizeString(input.requestedProjectRoot);
|
|
6804
|
+
if (requestedRoot && isAbsolute4(requestedRoot) && existsSync13(resolve20(requestedRoot))) {
|
|
6805
|
+
copyGitHubAuthStateToLocalProjectRoot(namespace.authStateFile, resolve20(requestedRoot));
|
|
6806
|
+
}
|
|
6807
|
+
if (shouldWriteRootAuthCompat(input.projectRoot)) {
|
|
6808
|
+
const rootStore = createGitHubAuthStore(input.projectRoot);
|
|
6809
|
+
rootStore.saveToken({
|
|
6810
|
+
token: input.token,
|
|
6811
|
+
tokenSource: input.tokenSource,
|
|
6812
|
+
login: input.user.login,
|
|
6813
|
+
userId: input.user.userId,
|
|
6814
|
+
scopes: input.user.scopes,
|
|
6815
|
+
selectedRepo: input.selectedRepo
|
|
6816
|
+
});
|
|
6817
|
+
rootStore.createApiSession();
|
|
6818
|
+
}
|
|
6819
|
+
return { store, namespace, apiSessionToken: apiSession.token };
|
|
6820
|
+
}
|
|
5882
6821
|
async function postGitHubForm(endpoint, body) {
|
|
5883
6822
|
const response = await fetch(endpoint, {
|
|
5884
6823
|
method: "POST",
|
|
@@ -5896,11 +6835,11 @@ function resolveRequestedProjectRoot(currentRoot, rawRoot) {
|
|
|
5896
6835
|
const requestedRoot = normalizeString(rawRoot);
|
|
5897
6836
|
if (!requestedRoot)
|
|
5898
6837
|
return currentRoot;
|
|
5899
|
-
if (!
|
|
6838
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
5900
6839
|
throw new Error("projectRoot must be an absolute path on the Rig server host");
|
|
5901
6840
|
}
|
|
5902
|
-
const normalizedRoot =
|
|
5903
|
-
if (!
|
|
6841
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
6842
|
+
if (!existsSync13(normalizedRoot)) {
|
|
5904
6843
|
throw new Error("projectRoot does not exist on the Rig server host");
|
|
5905
6844
|
}
|
|
5906
6845
|
return normalizedRoot;
|
|
@@ -6128,6 +7067,27 @@ function filterWorkspaceTasks(projectRoot, tasks, searchParams) {
|
|
|
6128
7067
|
}
|
|
6129
7068
|
return filtered;
|
|
6130
7069
|
}
|
|
7070
|
+
function issueAnalysisTargetFor(source) {
|
|
7071
|
+
if (!source)
|
|
7072
|
+
return null;
|
|
7073
|
+
const candidate = source;
|
|
7074
|
+
if (typeof candidate.updateTask !== "function")
|
|
7075
|
+
return null;
|
|
7076
|
+
return {
|
|
7077
|
+
...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
|
|
7078
|
+
updateTask: candidate.updateTask.bind(candidate),
|
|
7079
|
+
...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
|
|
7080
|
+
...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
|
|
7081
|
+
...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
|
|
7082
|
+
};
|
|
7083
|
+
}
|
|
7084
|
+
function uniqueStringList(value) {
|
|
7085
|
+
const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
7086
|
+
return [...new Set(raw.map((entry) => String(entry).trim()).filter(Boolean))];
|
|
7087
|
+
}
|
|
7088
|
+
function taskRecordId(task) {
|
|
7089
|
+
return String(task.id ?? "");
|
|
7090
|
+
}
|
|
6131
7091
|
function redactRemoteEndpoint(endpoint) {
|
|
6132
7092
|
const { token, ...rest } = endpoint;
|
|
6133
7093
|
return {
|
|
@@ -6212,6 +7172,13 @@ function createRigServerFetch(state, deps) {
|
|
|
6212
7172
|
notifications: state.targets.length
|
|
6213
7173
|
});
|
|
6214
7174
|
}
|
|
7175
|
+
if (url.pathname === "/install" && req.method === "GET") {
|
|
7176
|
+
return new Response(buildRigInstallScript(), {
|
|
7177
|
+
headers: {
|
|
7178
|
+
"Content-Type": "text/x-shellscript; charset=utf-8"
|
|
7179
|
+
}
|
|
7180
|
+
});
|
|
7181
|
+
}
|
|
6215
7182
|
const isLinearWebhook = url.pathname === "/api/linear/webhook" && req.method === "POST";
|
|
6216
7183
|
const isInspectorStream = url.pathname === "/api/inspector/stream" && req.method === "GET";
|
|
6217
7184
|
const legacyAuthorizedHttpRequest = Boolean(state.authToken) && (isInspectorStream && isAuthorizedInspectorStreamRequest(req, state.authToken) ? true : deps.isAuthorizedHttpRequest(req, state.authToken));
|
|
@@ -6424,16 +7391,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6424
7391
|
if (!source) {
|
|
6425
7392
|
return deps.badRequest("No task source is configured");
|
|
6426
7393
|
}
|
|
7394
|
+
if (!source.updateTask && !(update.status && source.updateStatus)) {
|
|
7395
|
+
return deps.badRequest("Configured task source does not support updates");
|
|
7396
|
+
}
|
|
6427
7397
|
const taskBeforeUpdate = source.get ? await source.get(id).catch(() => {
|
|
6428
7398
|
return;
|
|
6429
7399
|
}) : (await deps.snapshotService.getWorkspaceTasks().catch(() => [])).find((task) => task.id === id);
|
|
6430
|
-
if (source.updateTask) {
|
|
6431
|
-
await source.updateTask(id, update);
|
|
6432
|
-
} else if (update.status && source.updateStatus) {
|
|
6433
|
-
await source.updateStatus(id, update.status);
|
|
6434
|
-
} else {
|
|
6435
|
-
return deps.badRequest("Configured task source does not support updates");
|
|
6436
|
-
}
|
|
6437
7400
|
const issueNodeId = normalizeString(body.issueNodeId) ?? extractGitHubIssueNodeId(taskBeforeUpdate);
|
|
6438
7401
|
const projectSync = update.status ? await syncGitHubProjectStatusForTaskUpdate({
|
|
6439
7402
|
taskId: id,
|
|
@@ -6442,6 +7405,35 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6442
7405
|
token: createGitHubAuthStore(state.projectRoot).readToken(),
|
|
6443
7406
|
config: ctx?.config
|
|
6444
7407
|
}).catch((error) => ({ synced: false, reason: `error:${error instanceof Error ? error.message : String(error)}` })) : { synced: false, reason: "missing-status" };
|
|
7408
|
+
if (update.status && githubProjectsEnabled2(ctx?.config) && projectSync.synced === false) {
|
|
7409
|
+
return deps.jsonResponse({ ok: false, id, projectSync, error: `GitHub Project status sync failed: ${String(projectSync.reason)}` }, 502);
|
|
7410
|
+
}
|
|
7411
|
+
try {
|
|
7412
|
+
if (source.updateTask) {
|
|
7413
|
+
await source.updateTask(id, update);
|
|
7414
|
+
} else if (update.status && source.updateStatus) {
|
|
7415
|
+
await source.updateStatus(id, update.status);
|
|
7416
|
+
}
|
|
7417
|
+
} catch (error) {
|
|
7418
|
+
let rollback = null;
|
|
7419
|
+
const previousStatus = normalizeString(taskBeforeUpdate?.status) ?? normalizeString(taskBeforeUpdate?.sourceStatus);
|
|
7420
|
+
if (update.status && previousStatus && githubProjectsEnabled2(ctx?.config) && projectSync.synced !== false) {
|
|
7421
|
+
rollback = await syncGitHubProjectStatusForTaskUpdate({
|
|
7422
|
+
taskId: id,
|
|
7423
|
+
status: previousStatus,
|
|
7424
|
+
issueNodeId,
|
|
7425
|
+
token: createGitHubAuthStore(state.projectRoot).readToken(),
|
|
7426
|
+
config: ctx?.config
|
|
7427
|
+
}).catch((rollbackError) => ({ synced: false, reason: `rollback-error:${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}` }));
|
|
7428
|
+
}
|
|
7429
|
+
return deps.jsonResponse({
|
|
7430
|
+
ok: false,
|
|
7431
|
+
id,
|
|
7432
|
+
projectSync,
|
|
7433
|
+
rollback,
|
|
7434
|
+
error: `Task source update failed: ${error instanceof Error ? error.message : String(error)}`
|
|
7435
|
+
}, 502);
|
|
7436
|
+
}
|
|
6445
7437
|
deps.snapshotService.invalidate("github-issue-updated");
|
|
6446
7438
|
await state.taskProjectionReconciler?.tick("github-issue-updated").catch(() => {
|
|
6447
7439
|
return;
|
|
@@ -6450,29 +7442,105 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6450
7442
|
return deps.jsonResponse({ ok: true, id, projectSync });
|
|
6451
7443
|
}
|
|
6452
7444
|
if (url.pathname === "/api/workspace/task-labels") {
|
|
7445
|
+
const ctx = await getCachedPluginHostContext(state.projectRoot).catch(() => null);
|
|
7446
|
+
if (url.searchParams.get("ensure") === "1" || req.method === "POST") {
|
|
7447
|
+
return deps.jsonResponse(await ensureGitHubLifecycleLabels(state.projectRoot, ctx?.config));
|
|
7448
|
+
}
|
|
6453
7449
|
return deps.jsonResponse({
|
|
6454
7450
|
ok: true,
|
|
6455
7451
|
ready: true,
|
|
6456
7452
|
labelsReady: true,
|
|
6457
|
-
labels: [
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
7453
|
+
labels: [...RIG_GITHUB_LIFECYCLE_LABELS],
|
|
7454
|
+
note: "Lifecycle labels are required during init; call POST /api/workspace/task-labels or ?ensure=1 to proactively create them."
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
7457
|
+
if (url.pathname === "/api/github/projects" && req.method === "GET") {
|
|
7458
|
+
const owner = normalizeString(url.searchParams.get("owner"));
|
|
7459
|
+
if (!owner)
|
|
7460
|
+
return deps.badRequest("owner is required");
|
|
7461
|
+
const token = createGitHubAuthStore(state.projectRoot).readToken();
|
|
7462
|
+
if (!token)
|
|
7463
|
+
return deps.jsonResponse({ ok: false, error: "missing-token", projects: [] }, 401);
|
|
7464
|
+
const projects = await listGitHubProjects({ owner, token }).catch((error) => {
|
|
7465
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
7466
|
+
});
|
|
7467
|
+
return deps.jsonResponse({ ok: true, projects });
|
|
7468
|
+
}
|
|
7469
|
+
const projectStatusMatch = url.pathname.match(/^\/api\/github\/projects\/([^/]+)\/status-field$/);
|
|
7470
|
+
if (projectStatusMatch && req.method === "GET") {
|
|
7471
|
+
const projectId = decodeURIComponent(projectStatusMatch[1]);
|
|
7472
|
+
const token = createGitHubAuthStore(state.projectRoot).readToken();
|
|
7473
|
+
if (!token)
|
|
7474
|
+
return deps.jsonResponse({ ok: false, error: "missing-token" }, 401);
|
|
7475
|
+
const field = await resolveProjectStatusField({ projectId, token }).catch((error) => {
|
|
7476
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
7477
|
+
});
|
|
7478
|
+
return deps.jsonResponse({ ok: true, field });
|
|
7479
|
+
}
|
|
7480
|
+
if (url.pathname === "/api/workspace/issue-analysis/run" && req.method === "POST") {
|
|
7481
|
+
const body = await deps.readJsonBody(req);
|
|
7482
|
+
const ids = uniqueStringList(body.ids ?? body.id);
|
|
7483
|
+
const analyzeAll = deps.isTruthyQuery(String(body.all ?? ""));
|
|
7484
|
+
if (ids.length === 0 && !analyzeAll) {
|
|
7485
|
+
return deps.badRequest("ids is required unless all=true");
|
|
7486
|
+
}
|
|
7487
|
+
const ctx = await getCachedPluginHostContext(state.projectRoot);
|
|
7488
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
7489
|
+
const target = issueAnalysisTargetFor(source);
|
|
7490
|
+
if (!source || !target) {
|
|
7491
|
+
return deps.badRequest("Configured task source does not support issue-analysis writeback");
|
|
7492
|
+
}
|
|
7493
|
+
const allTasks = [...await source.list()];
|
|
7494
|
+
const issues = analyzeAll ? allTasks.slice(0, Math.max(1, Math.min(25, Number(body.limit ?? 25) || 25))) : (await Promise.all(ids.map(async (id) => {
|
|
7495
|
+
const cached = allTasks.find((task) => taskRecordId(task) === id);
|
|
7496
|
+
if (cached)
|
|
7497
|
+
return cached;
|
|
7498
|
+
return typeof source.get === "function" ? await source.get(id) : undefined;
|
|
7499
|
+
}))).filter((task) => Boolean(task));
|
|
7500
|
+
if (issues.length === 0) {
|
|
7501
|
+
return deps.jsonResponse({ ok: false, error: "No matching issues found for issue analysis", ids }, 404);
|
|
7502
|
+
}
|
|
7503
|
+
const config = ctx?.config && typeof ctx.config === "object" ? ctx.config : {};
|
|
7504
|
+
const issueAnalysis = config.issueAnalysis && typeof config.issueAnalysis === "object" ? config.issueAnalysis : {};
|
|
7505
|
+
const runtime = config.runtime && typeof config.runtime === "object" ? config.runtime : {};
|
|
7506
|
+
const model = normalizeString(issueAnalysis.model) ?? normalizeString(runtime.model);
|
|
7507
|
+
const service = createIssueAnalysisService({
|
|
7508
|
+
analyzer: createPiIssueAnalyzer({
|
|
7509
|
+
...model ? { model } : {},
|
|
7510
|
+
env: { RIG_PROJECT_ROOT: state.projectRoot }
|
|
7511
|
+
}),
|
|
7512
|
+
writeBack: createIssueAnalysisWriteBack({ target })
|
|
7513
|
+
});
|
|
7514
|
+
const reason = normalizeString(body.reason) ?? "http-issue-analysis";
|
|
7515
|
+
let results;
|
|
7516
|
+
try {
|
|
7517
|
+
results = await service.analyze(issues, { reason, neighbors: ids.length > 0 ? issues : allTasks });
|
|
7518
|
+
} catch (error) {
|
|
7519
|
+
return deps.jsonResponse({
|
|
7520
|
+
ok: false,
|
|
7521
|
+
error: `Issue analysis failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7522
|
+
reason,
|
|
7523
|
+
ids: issues.map((issue) => issue.id)
|
|
7524
|
+
}, 502);
|
|
7525
|
+
}
|
|
7526
|
+
deps.snapshotService.invalidate("issue-analysis-http-run");
|
|
7527
|
+
await state.taskProjectionReconciler?.tick("issue-analysis-http-run").catch(() => {
|
|
7528
|
+
return;
|
|
7529
|
+
});
|
|
7530
|
+
deps.broadcastSnapshotInvalidation(state, "issue-analysis-http-run");
|
|
7531
|
+
return deps.jsonResponse({
|
|
7532
|
+
ok: true,
|
|
7533
|
+
reason,
|
|
7534
|
+
analyzed: results.map((entry) => ({
|
|
7535
|
+
id: entry.issue.id,
|
|
7536
|
+
title: entry.issue.title ?? null,
|
|
7537
|
+
result: entry.result
|
|
7538
|
+
}))
|
|
6471
7539
|
});
|
|
6472
7540
|
}
|
|
6473
7541
|
if (url.pathname === "/api/server/status") {
|
|
6474
7542
|
const config = buildProjectConfigStatus(state.projectRoot);
|
|
6475
|
-
const taskSource = await buildTaskSourceStatus(state, config);
|
|
7543
|
+
const taskSource = await buildTaskSourceStatus(state, config, requestAuth);
|
|
6476
7544
|
return deps.jsonResponse({
|
|
6477
7545
|
ok: true,
|
|
6478
7546
|
projectRoot: state.projectRoot,
|
|
@@ -6496,8 +7564,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6496
7564
|
path: normalizeString(rawCheckout?.path) ?? state.projectRoot,
|
|
6497
7565
|
ref: normalizeString(rawCheckout?.ref) ?? undefined
|
|
6498
7566
|
} : undefined;
|
|
6499
|
-
const
|
|
6500
|
-
|
|
7567
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7568
|
+
const record = upsertProjectRecord(registryRoot, { repoSlug, checkout });
|
|
7569
|
+
return deps.jsonResponse({ ok: true, project: record, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6501
7570
|
}
|
|
6502
7571
|
const snapshotUploadMatch = url.pathname.match(/^\/api\/projects\/(.+?)\/upload-snapshot$/);
|
|
6503
7572
|
if (snapshotUploadMatch && req.method === "POST") {
|
|
@@ -6510,8 +7579,15 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6510
7579
|
if (!archiveContentBase64) {
|
|
6511
7580
|
return deps.badRequest("archiveContentBase64 is required");
|
|
6512
7581
|
}
|
|
6513
|
-
const
|
|
6514
|
-
const
|
|
7582
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7583
|
+
const baseDir = resolveNamespacedBaseDir({
|
|
7584
|
+
explicitBaseDir: normalizeString(body.baseDir),
|
|
7585
|
+
envName: "RIG_REMOTE_SNAPSHOT_BASE_DIR",
|
|
7586
|
+
userNamespace: requestAuth.userNamespace,
|
|
7587
|
+
legacyProjectRoot: state.projectRoot,
|
|
7588
|
+
legacySubdir: "remote-snapshots"
|
|
7589
|
+
});
|
|
7590
|
+
const checkoutKey = explicitCheckoutKey(body, body, requestAuth);
|
|
6515
7591
|
try {
|
|
6516
7592
|
const archive = parseSnapshotArchiveContentBase64(archiveContentBase64);
|
|
6517
7593
|
const checkout = extractUploadedSnapshotArchive({
|
|
@@ -6524,14 +7600,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6524
7600
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6525
7601
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6526
7602
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6527
|
-
const project = linkProjectCheckout(
|
|
7603
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6528
7604
|
kind: "uploaded-snapshot",
|
|
6529
7605
|
path: checkout.path,
|
|
6530
7606
|
ref: checkout.snapshotId
|
|
6531
7607
|
});
|
|
6532
7608
|
deps.snapshotService.invalidate("uploaded-snapshot-checkout");
|
|
6533
7609
|
deps.broadcastSnapshotInvalidation(state, "uploaded-snapshot-checkout");
|
|
6534
|
-
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
|
|
7610
|
+
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6535
7611
|
} catch (error) {
|
|
6536
7612
|
return deps.jsonResponse({
|
|
6537
7613
|
ok: false,
|
|
@@ -6551,10 +7627,17 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6551
7627
|
if (kind !== "managed-clone" && kind !== "current-ref" && kind !== "existing-path") {
|
|
6552
7628
|
return deps.jsonResponse({ ok: false, error: "checkout kind must be managed-clone, current-ref, or existing-path" }, 400);
|
|
6553
7629
|
}
|
|
6554
|
-
const
|
|
6555
|
-
const
|
|
7630
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7631
|
+
const baseDir = resolveNamespacedBaseDir({
|
|
7632
|
+
explicitBaseDir: normalizeString(body.baseDir) ?? normalizeString(checkoutInput.baseDir),
|
|
7633
|
+
envName: "RIG_REMOTE_CHECKOUT_BASE_DIR",
|
|
7634
|
+
userNamespace: requestAuth.userNamespace,
|
|
7635
|
+
legacyProjectRoot: state.projectRoot,
|
|
7636
|
+
legacySubdir: "remote-checkouts"
|
|
7637
|
+
});
|
|
7638
|
+
const checkoutKey = explicitCheckoutKey(body, checkoutInput, requestAuth);
|
|
6556
7639
|
const repoUrl = normalizeString(body.repoUrl) ?? normalizeString(checkoutInput.repoUrl) ?? `https://github.com/${repoSlug}.git`;
|
|
6557
|
-
const credentialToken =
|
|
7640
|
+
const credentialToken = requestScopedAuthStore(state.projectRoot, requestAuth).readToken();
|
|
6558
7641
|
try {
|
|
6559
7642
|
const checkout = await prepareRemoteCheckout({
|
|
6560
7643
|
command: runRemoteCheckoutCommand,
|
|
@@ -6563,14 +7646,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6563
7646
|
const checkoutRepair = repairRemoteCheckoutForRig(checkout.path, repoSlug);
|
|
6564
7647
|
const packageInstall = installRemoteCheckoutPackages(checkout.path);
|
|
6565
7648
|
const postInstallConfigValidation = validateRemoteCheckoutRigConfig(checkout.path);
|
|
6566
|
-
const project = linkProjectCheckout(
|
|
7649
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6567
7650
|
kind: checkout.kind,
|
|
6568
7651
|
path: checkout.path,
|
|
6569
7652
|
ref: checkout.ref ?? checkout.snapshotId ?? undefined
|
|
6570
7653
|
});
|
|
6571
7654
|
deps.snapshotService.invalidate("remote-checkout-prepared");
|
|
6572
7655
|
deps.broadcastSnapshotInvalidation(state, "remote-checkout-prepared");
|
|
6573
|
-
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation });
|
|
7656
|
+
return deps.jsonResponse({ ok: true, checkout, project, checkoutRepair, packageInstall, postInstallConfigValidation, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6574
7657
|
} catch (error) {
|
|
6575
7658
|
return deps.jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
|
|
6576
7659
|
}
|
|
@@ -6587,16 +7670,18 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6587
7670
|
if (kind !== "local" && kind !== "managed-clone" && kind !== "current-ref" && kind !== "uploaded-snapshot" && kind !== "existing-path") {
|
|
6588
7671
|
return deps.jsonResponse({ ok: false, error: "checkout kind is required" }, 400);
|
|
6589
7672
|
}
|
|
6590
|
-
const
|
|
7673
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7674
|
+
const project = linkProjectCheckout(registryRoot, repoSlug, {
|
|
6591
7675
|
kind,
|
|
6592
7676
|
path: normalizeString(body.path) ?? state.projectRoot,
|
|
6593
7677
|
ref: normalizeString(body.ref) ?? undefined
|
|
6594
7678
|
});
|
|
6595
|
-
return deps.jsonResponse({ ok: true, project });
|
|
7679
|
+
return deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6596
7680
|
}
|
|
6597
7681
|
if (req.method === "GET") {
|
|
6598
|
-
const
|
|
6599
|
-
|
|
7682
|
+
const registryRoot = requestScopedRegistryRoot(state.projectRoot, requestAuth);
|
|
7683
|
+
const project = getProjectRecord(registryRoot, repoSlug);
|
|
7684
|
+
return project ? deps.jsonResponse({ ok: true, project, userNamespace: userNamespaceResponse(requestAuth.userNamespace) }) : deps.notFound();
|
|
6600
7685
|
}
|
|
6601
7686
|
}
|
|
6602
7687
|
if (url.pathname === "/api/server/project-root" && req.method === "POST") {
|
|
@@ -6605,13 +7690,26 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6605
7690
|
if (!requestedRoot) {
|
|
6606
7691
|
return deps.badRequest("projectRoot is required");
|
|
6607
7692
|
}
|
|
6608
|
-
if (!
|
|
7693
|
+
if (!isAbsolute4(requestedRoot)) {
|
|
6609
7694
|
return deps.badRequest("projectRoot must be an absolute path on the Rig server host");
|
|
6610
7695
|
}
|
|
6611
|
-
const normalizedRoot =
|
|
6612
|
-
const exists =
|
|
6613
|
-
if (exists) {
|
|
6614
|
-
|
|
7696
|
+
const normalizedRoot = resolve20(requestedRoot);
|
|
7697
|
+
const exists = existsSync13(normalizedRoot);
|
|
7698
|
+
if (exists && requestAuth.userNamespace) {
|
|
7699
|
+
const allowedByNamespace = isPathInsideNamespace(requestAuth.userNamespace.root, normalizedRoot);
|
|
7700
|
+
const allowedByRegistry = projectRegistryContainsCheckout(requestAuth.userNamespace.root, normalizedRoot);
|
|
7701
|
+
if (!allowedByNamespace && !allowedByRegistry) {
|
|
7702
|
+
return deps.jsonResponse({
|
|
7703
|
+
ok: false,
|
|
7704
|
+
error: "Requested project root is outside the authenticated GitHub user namespace.",
|
|
7705
|
+
projectRoot: state.projectRoot,
|
|
7706
|
+
requestedProjectRoot: normalizedRoot,
|
|
7707
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace)
|
|
7708
|
+
}, 403);
|
|
7709
|
+
}
|
|
7710
|
+
copyGitHubAuthStateToLocalProjectRoot(requestAuth.userNamespace.authStateFile, normalizedRoot);
|
|
7711
|
+
} else if (exists) {
|
|
7712
|
+
createGitHubAuthStore(state.projectRoot).copyToLocalProjectRoot(normalizedRoot);
|
|
6615
7713
|
}
|
|
6616
7714
|
const control = buildServerControlStatus();
|
|
6617
7715
|
const switchCommand = process.env.RIG_PROJECT_ROOT_SWITCH_COMMAND?.trim();
|
|
@@ -6626,7 +7724,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6626
7724
|
message: "Requested project root does not exist on the Rig server host."
|
|
6627
7725
|
}, 404);
|
|
6628
7726
|
}
|
|
6629
|
-
if (!
|
|
7727
|
+
if (!existsSync13(resolve20(normalizedRoot, "rig.config.ts")) && !existsSync13(resolve20(normalizedRoot, "rig.config.json"))) {
|
|
6630
7728
|
return deps.jsonResponse({
|
|
6631
7729
|
ok: false,
|
|
6632
7730
|
projectRoot: state.projectRoot,
|
|
@@ -6661,6 +7759,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6661
7759
|
exists,
|
|
6662
7760
|
control,
|
|
6663
7761
|
requiresRestart: false,
|
|
7762
|
+
userNamespace: userNamespaceResponse(requestAuth.userNamespace),
|
|
6664
7763
|
message: "Project-root switch accepted. Rig server restart has been scheduled."
|
|
6665
7764
|
}, 202);
|
|
6666
7765
|
}
|
|
@@ -6675,11 +7774,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6675
7774
|
}, 409);
|
|
6676
7775
|
}
|
|
6677
7776
|
if (url.pathname === "/api/github/auth/status") {
|
|
6678
|
-
const store =
|
|
6679
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }) });
|
|
7777
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
7778
|
+
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), userNamespace: userNamespaceResponse(requestAuth.userNamespace) });
|
|
6680
7779
|
}
|
|
6681
7780
|
if (url.pathname === "/api/github/repo/permissions") {
|
|
6682
|
-
const store =
|
|
7781
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6683
7782
|
const auth = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6684
7783
|
if (!auth.signedIn) {
|
|
6685
7784
|
return deps.jsonResponse({ ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" }, 401);
|
|
@@ -6707,24 +7806,20 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6707
7806
|
}
|
|
6708
7807
|
try {
|
|
6709
7808
|
const user = await fetchGitHubUserInfo(token);
|
|
6710
|
-
const
|
|
6711
|
-
state.projectRoot,
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
}
|
|
6725
|
-
const store = stores[stores.length - 1] ?? createGitHubAuthStore(state.projectRoot);
|
|
6726
|
-
const apiSession = store.createApiSession();
|
|
6727
|
-
return deps.jsonResponse({ ok: true, ...store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }), apiSessionToken: apiSession.token });
|
|
7809
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
7810
|
+
projectRoot: state.projectRoot,
|
|
7811
|
+
token,
|
|
7812
|
+
tokenSource: "manual-token",
|
|
7813
|
+
user,
|
|
7814
|
+
selectedRepo,
|
|
7815
|
+
requestedProjectRoot
|
|
7816
|
+
});
|
|
7817
|
+
return deps.jsonResponse({
|
|
7818
|
+
ok: true,
|
|
7819
|
+
...saved.store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) }),
|
|
7820
|
+
apiSessionToken: saved.apiSessionToken,
|
|
7821
|
+
userNamespace: userNamespaceResponse(saved.namespace)
|
|
7822
|
+
});
|
|
6728
7823
|
} catch (error) {
|
|
6729
7824
|
const message = error instanceof Error ? error.message : String(error);
|
|
6730
7825
|
return deps.jsonResponse({ ok: false, error: message }, 400);
|
|
@@ -6791,9 +7886,21 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6791
7886
|
}
|
|
6792
7887
|
const token = result.payload.access_token;
|
|
6793
7888
|
const user = await fetchGitHubUserInfo(token);
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
7889
|
+
const saved = saveGitHubTokenForRemoteUser({
|
|
7890
|
+
projectRoot: state.projectRoot,
|
|
7891
|
+
token,
|
|
7892
|
+
tokenSource: "oauth-device",
|
|
7893
|
+
user,
|
|
7894
|
+
selectedRepo: null
|
|
7895
|
+
});
|
|
7896
|
+
store.clearPendingDevice(pollId);
|
|
7897
|
+
return deps.jsonResponse({
|
|
7898
|
+
ok: true,
|
|
7899
|
+
status: "signed-in",
|
|
7900
|
+
...saved.store.status({ oauthConfigured: true }),
|
|
7901
|
+
apiSessionToken: saved.apiSessionToken,
|
|
7902
|
+
userNamespace: userNamespaceResponse(saved.namespace)
|
|
7903
|
+
});
|
|
6797
7904
|
}
|
|
6798
7905
|
if (url.pathname === "/api/github/repo/probe" && req.method === "POST") {
|
|
6799
7906
|
const body = await deps.readJsonBody(req);
|
|
@@ -6802,7 +7909,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6802
7909
|
if (!owner || !repo) {
|
|
6803
7910
|
return deps.badRequest("owner and repo are required");
|
|
6804
7911
|
}
|
|
6805
|
-
const store =
|
|
7912
|
+
const store = requestScopedAuthStore(state.projectRoot, requestAuth);
|
|
6806
7913
|
const authStatus = store.status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6807
7914
|
const probe = await probeGitHubRepository({ owner, repo, token: store.readToken(), scopes: authStatus.scopes });
|
|
6808
7915
|
return deps.jsonResponse({ ok: probe.ok, probe }, probe.ok ? 200 : 400);
|
|
@@ -6823,7 +7930,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6823
7930
|
return deps.badRequest(error instanceof Error ? error.message : String(error));
|
|
6824
7931
|
}
|
|
6825
7932
|
const authStatus = createGitHubAuthStore(state.projectRoot).status({ oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
6826
|
-
const configPath =
|
|
7933
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
6827
7934
|
const source = buildGitHubProjectConfigSource({
|
|
6828
7935
|
projectName: rawProjectName,
|
|
6829
7936
|
owner,
|
|
@@ -6835,8 +7942,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6835
7942
|
ok: true,
|
|
6836
7943
|
projectRoot: targetRoot,
|
|
6837
7944
|
configPath,
|
|
6838
|
-
exists:
|
|
6839
|
-
requiresOverwrite:
|
|
7945
|
+
exists: existsSync13(configPath),
|
|
7946
|
+
requiresOverwrite: existsSync13(configPath),
|
|
6840
7947
|
source,
|
|
6841
7948
|
owner,
|
|
6842
7949
|
repo,
|
|
@@ -6872,8 +7979,8 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6872
7979
|
assignee,
|
|
6873
7980
|
githubUserId: authStatus.userId ?? authStatus.login
|
|
6874
7981
|
});
|
|
6875
|
-
const configPath =
|
|
6876
|
-
if (
|
|
7982
|
+
const configPath = resolve20(targetRoot, "rig.config.ts");
|
|
7983
|
+
if (existsSync13(configPath) && !overwrite) {
|
|
6877
7984
|
return deps.jsonResponse({
|
|
6878
7985
|
ok: false,
|
|
6879
7986
|
error: "rig.config.ts already exists. Confirm overwrite to replace it; Rig will create a backup first.",
|
|
@@ -6889,11 +7996,11 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
6889
7996
|
return deps.jsonResponse({ ok: false, error: repoProbe.message, repoProbe }, 400);
|
|
6890
7997
|
}
|
|
6891
7998
|
let backupPath = null;
|
|
6892
|
-
if (
|
|
7999
|
+
if (existsSync13(configPath)) {
|
|
6893
8000
|
backupPath = backupConfigPath(configPath);
|
|
6894
|
-
|
|
8001
|
+
copyFileSync2(configPath, backupPath);
|
|
6895
8002
|
}
|
|
6896
|
-
|
|
8003
|
+
writeFileSync12(configPath, source, "utf8");
|
|
6897
8004
|
const selectedRepo = `${owner}/${repo}`;
|
|
6898
8005
|
store.saveSelectedRepo(selectedRepo);
|
|
6899
8006
|
const targetStore = createGitHubAuthStore(targetRoot);
|
|
@@ -7165,11 +8272,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7165
8272
|
const runId = normalizeString(body.runId);
|
|
7166
8273
|
const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
|
|
7167
8274
|
const promptOverride = normalizeString(body.promptOverride);
|
|
8275
|
+
const restart = body.restart === true;
|
|
7168
8276
|
if (!runId) {
|
|
7169
8277
|
return deps.badRequest("runId is required");
|
|
7170
8278
|
}
|
|
7171
8279
|
try {
|
|
7172
|
-
await deps.resumeRunRecord(state, { runId, createdAt, promptOverride });
|
|
8280
|
+
await deps.resumeRunRecord(state, { runId, createdAt, promptOverride, restart });
|
|
7173
8281
|
deps.broadcastSnapshotInvalidation(state);
|
|
7174
8282
|
return deps.jsonResponse({ ok: true, runId, createdAt });
|
|
7175
8283
|
} catch (error) {
|
|
@@ -7178,7 +8286,7 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7178
8286
|
}
|
|
7179
8287
|
if (url.pathname === "/api/pi-rig/install" && req.method === "POST") {
|
|
7180
8288
|
const configuredPackageSource = normalizeString(process.env.RIG_PI_RIG_PACKAGE_SOURCE);
|
|
7181
|
-
const packageSource = configuredPackageSource ?? [process.env.RIG_HOST_PROJECT_ROOT, process.cwd(), state.projectRoot].map((root) => normalizeString(root)).filter((root) => Boolean(root)).map((root) =>
|
|
8289
|
+
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:@h-rig/pi-rig";
|
|
7182
8290
|
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
7183
8291
|
return deps.jsonResponse({ ok: true, installed: true, piOk: true, piRigOk: true, extensionPath: "remote:~/.pi/agent/extensions/pi-rig", packageSource });
|
|
7184
8292
|
}
|
|
@@ -7494,9 +8602,9 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7494
8602
|
} catch {
|
|
7495
8603
|
return deps.badRequest("Invalid artifact path");
|
|
7496
8604
|
}
|
|
7497
|
-
|
|
8605
|
+
mkdirSync13(dirname15(artifactPath), { recursive: true });
|
|
7498
8606
|
const bytes = Buffer.from(contentBase64, "base64");
|
|
7499
|
-
|
|
8607
|
+
writeFileSync12(artifactPath, bytes);
|
|
7500
8608
|
writeJsonFile4(`${artifactPath}.json`, {
|
|
7501
8609
|
workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
|
|
7502
8610
|
runId,
|
|
@@ -7533,13 +8641,75 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7533
8641
|
}
|
|
7534
8642
|
const run = leaseValidation.run;
|
|
7535
8643
|
const completedAt = new Date().toISOString();
|
|
8644
|
+
const workspaceDir = normalizeString(body.workspaceDir) ?? normalizeString(body.runtimeWorkspace) ?? normalizeString(run.worktreePath);
|
|
8645
|
+
if (run.taskId && workspaceDir) {
|
|
8646
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
8647
|
+
status: "reviewing",
|
|
8648
|
+
completedAt: null,
|
|
8649
|
+
hostId,
|
|
8650
|
+
endpointId: leaseId,
|
|
8651
|
+
worktreePath: workspaceDir,
|
|
8652
|
+
serverCloseout: {
|
|
8653
|
+
status: "pending",
|
|
8654
|
+
phase: "queued",
|
|
8655
|
+
requestedAt: completedAt,
|
|
8656
|
+
updatedAt: completedAt,
|
|
8657
|
+
runtimeWorkspace: workspaceDir,
|
|
8658
|
+
branch: normalizeString(body.branch) ?? normalizeString(run.branch) ?? `rig/${run.taskId}-${runId}`,
|
|
8659
|
+
taskId: run.taskId,
|
|
8660
|
+
source: "remote-complete"
|
|
8661
|
+
}
|
|
8662
|
+
});
|
|
8663
|
+
deps.appendRunLogEntryAndBroadcast(state, runId, {
|
|
8664
|
+
id: `log:${runId}:remote-server-closeout-requested`,
|
|
8665
|
+
title: "Server-owned closeout requested",
|
|
8666
|
+
detail: "Remote run completed provider work and handed commit/PR/review/merge closeout to the Rig server.",
|
|
8667
|
+
tone: "info",
|
|
8668
|
+
status: "reviewing",
|
|
8669
|
+
createdAt: completedAt,
|
|
8670
|
+
payload: { workspaceDir, hostId, leaseId }
|
|
8671
|
+
}, "remote-server-closeout-requested");
|
|
8672
|
+
deps.runServerOwnedPrCloseout(state, runId).catch((error) => {
|
|
8673
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8674
|
+
patchRunRecord(state.projectRoot, runId, {
|
|
8675
|
+
status: "failed",
|
|
8676
|
+
completedAt: new Date().toISOString(),
|
|
8677
|
+
errorText: detail,
|
|
8678
|
+
serverCloseout: {
|
|
8679
|
+
status: "failed",
|
|
8680
|
+
phase: "failed",
|
|
8681
|
+
updatedAt: new Date().toISOString(),
|
|
8682
|
+
error: detail
|
|
8683
|
+
}
|
|
8684
|
+
});
|
|
8685
|
+
deps.appendRunLogEntryAndBroadcast(state, runId, {
|
|
8686
|
+
id: `log:${runId}:remote-server-closeout-failed`,
|
|
8687
|
+
title: "Server-owned closeout failed",
|
|
8688
|
+
detail,
|
|
8689
|
+
tone: "error",
|
|
8690
|
+
status: "failed",
|
|
8691
|
+
createdAt: new Date().toISOString()
|
|
8692
|
+
}, "remote-server-closeout-failed");
|
|
8693
|
+
}).finally(() => {
|
|
8694
|
+
deps.reconcileScheduler(state, "remote-server-closeout-terminal");
|
|
8695
|
+
});
|
|
8696
|
+
deps.broadcastSnapshotInvalidation(state);
|
|
8697
|
+
return deps.jsonResponse({
|
|
8698
|
+
ok: true,
|
|
8699
|
+
workspaceId: normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID,
|
|
8700
|
+
hostId,
|
|
8701
|
+
runId,
|
|
8702
|
+
leaseId,
|
|
8703
|
+
closeout: "server-owned",
|
|
8704
|
+
acceptedAt: new Date().toISOString()
|
|
8705
|
+
});
|
|
8706
|
+
}
|
|
7536
8707
|
patchRunRecord(state.projectRoot, runId, {
|
|
7537
8708
|
status: "completed",
|
|
7538
8709
|
completedAt,
|
|
7539
8710
|
hostId,
|
|
7540
8711
|
endpointId: leaseId
|
|
7541
8712
|
});
|
|
7542
|
-
await updateRemoteRunTaskSourceLifecycle(state.projectRoot, { ...run, status: "completed", completedAt, hostId, endpointId: leaseId }, "closed", "Remote Rig task run completed and closed this task.");
|
|
7543
8713
|
await deps.enqueueRunLinearEvent(state.projectRoot, {
|
|
7544
8714
|
type: "run.completed",
|
|
7545
8715
|
runId,
|
|
@@ -7658,12 +8828,12 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7658
8828
|
try {
|
|
7659
8829
|
const runsRoot = resolveAuthorityPaths(state.projectRoot).runsDir;
|
|
7660
8830
|
const runRoot = deps.normalizeRelativePath(runsRoot, runId);
|
|
7661
|
-
const artifactsRoot =
|
|
8831
|
+
const artifactsRoot = resolve20(runRoot, "remote-artifacts");
|
|
7662
8832
|
artifactPath = deps.normalizeRelativePath(artifactsRoot, fileName);
|
|
7663
8833
|
} catch {
|
|
7664
8834
|
return deps.badRequest("Invalid artifact path");
|
|
7665
8835
|
}
|
|
7666
|
-
if (!
|
|
8836
|
+
if (!existsSync13(artifactPath)) {
|
|
7667
8837
|
return deps.notFound();
|
|
7668
8838
|
}
|
|
7669
8839
|
return new Response(Bun.file(artifactPath));
|
|
@@ -7676,6 +8846,14 @@ data: ${JSON.stringify({ connectedAt: new Date().toISOString() })}
|
|
|
7676
8846
|
const page = await readRunLogsPage(state.projectRoot, runId, { limit, cursor });
|
|
7677
8847
|
return deps.jsonResponse(page);
|
|
7678
8848
|
}
|
|
8849
|
+
const runTimelineMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/timeline$/);
|
|
8850
|
+
if (runTimelineMatch) {
|
|
8851
|
+
const runId = decodeURIComponent(runTimelineMatch[1]);
|
|
8852
|
+
const limit = Number.parseInt(url.searchParams.get("limit") || "500", 10);
|
|
8853
|
+
const cursor = normalizeString(url.searchParams.get("cursor"));
|
|
8854
|
+
const page = await readRunTimelinePage(state.projectRoot, runId, { limit, cursor });
|
|
8855
|
+
return deps.jsonResponse(page);
|
|
8856
|
+
}
|
|
7679
8857
|
const runSteerMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/steer$/);
|
|
7680
8858
|
if (runSteerMatch && req.method === "POST") {
|
|
7681
8859
|
const runId = decodeURIComponent(runSteerMatch[1]);
|
|
@@ -8556,8 +9734,8 @@ async function routeWebSocketRequest(state, deps, request) {
|
|
|
8556
9734
|
}
|
|
8557
9735
|
|
|
8558
9736
|
// packages/server/src/server-helpers/inspector-jobs.ts
|
|
8559
|
-
import { existsSync as
|
|
8560
|
-
import { dirname as
|
|
9737
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
9738
|
+
import { dirname as dirname18, resolve as resolve23 } from "path";
|
|
8561
9739
|
import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
|
|
8562
9740
|
import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
|
|
8563
9741
|
import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
|
|
@@ -8665,8 +9843,8 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8665
9843
|
|
|
8666
9844
|
// packages/server/src/inspector/mission.ts
|
|
8667
9845
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8668
|
-
import { appendFileSync, existsSync as
|
|
8669
|
-
import { dirname as
|
|
9846
|
+
import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync13 } from "fs";
|
|
9847
|
+
import { dirname as dirname16, join, resolve as resolve21 } from "path";
|
|
8670
9848
|
function isJsonValue(value) {
|
|
8671
9849
|
if (value === null)
|
|
8672
9850
|
return true;
|
|
@@ -8706,7 +9884,7 @@ function isRecord2(value) {
|
|
|
8706
9884
|
}
|
|
8707
9885
|
function readJsonRecord(path) {
|
|
8708
9886
|
try {
|
|
8709
|
-
const parsed = JSON.parse(
|
|
9887
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
8710
9888
|
if (!isRecord2(parsed)) {
|
|
8711
9889
|
return { ok: false, error: `Mission file ${path} does not contain an object` };
|
|
8712
9890
|
}
|
|
@@ -8786,14 +9964,14 @@ function missionActionDetails(mission) {
|
|
|
8786
9964
|
};
|
|
8787
9965
|
}
|
|
8788
9966
|
function writeJsonFile5(path, value) {
|
|
8789
|
-
|
|
9967
|
+
mkdirSync14(dirname16(path), { recursive: true });
|
|
8790
9968
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
8791
|
-
|
|
9969
|
+
writeFileSync13(tempPath, `${JSON.stringify(value, null, 2)}
|
|
8792
9970
|
`, "utf8");
|
|
8793
9971
|
renameSync(tempPath, path);
|
|
8794
9972
|
}
|
|
8795
9973
|
function resolveInspectorMissionPaths(projectRoot) {
|
|
8796
|
-
const inspectorDir =
|
|
9974
|
+
const inspectorDir = resolve21(resolveRigServerPaths(projectRoot).stateDir, "inspector");
|
|
8797
9975
|
return {
|
|
8798
9976
|
inspectorDir,
|
|
8799
9977
|
missionsDir: join(inspectorDir, "missions"),
|
|
@@ -8802,8 +9980,8 @@ function resolveInspectorMissionPaths(projectRoot) {
|
|
|
8802
9980
|
}
|
|
8803
9981
|
function createInspectorMissionController(options) {
|
|
8804
9982
|
const paths = resolveInspectorMissionPaths(options.projectRoot);
|
|
8805
|
-
|
|
8806
|
-
|
|
9983
|
+
mkdirSync14(paths.missionsDir, { recursive: true });
|
|
9984
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8807
9985
|
const now = options.now ?? (() => new Date().toISOString());
|
|
8808
9986
|
const nextId = options.idGenerator ?? (() => `mission:${randomUUID2()}`);
|
|
8809
9987
|
function missionPath(missionId) {
|
|
@@ -8813,15 +9991,15 @@ function createInspectorMissionController(options) {
|
|
|
8813
9991
|
return join(paths.journalsDir, `${missionId}.jsonl`);
|
|
8814
9992
|
}
|
|
8815
9993
|
function appendMissionJournal(entry) {
|
|
8816
|
-
|
|
9994
|
+
mkdirSync14(paths.journalsDir, { recursive: true });
|
|
8817
9995
|
appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
|
|
8818
9996
|
`, "utf8");
|
|
8819
9997
|
}
|
|
8820
9998
|
function listMissionJournal(missionId) {
|
|
8821
9999
|
const path = journalPath(missionId);
|
|
8822
|
-
if (!
|
|
10000
|
+
if (!existsSync14(path))
|
|
8823
10001
|
return [];
|
|
8824
|
-
return
|
|
10002
|
+
return readFileSync10(path, "utf8").split(`
|
|
8825
10003
|
`).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord2).map((entry) => ({
|
|
8826
10004
|
id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID2()}`,
|
|
8827
10005
|
missionId,
|
|
@@ -8837,7 +10015,7 @@ function createInspectorMissionController(options) {
|
|
|
8837
10015
|
}
|
|
8838
10016
|
function readMissionOnly(missionId) {
|
|
8839
10017
|
const path = missionPath(missionId);
|
|
8840
|
-
if (!
|
|
10018
|
+
if (!existsSync14(path)) {
|
|
8841
10019
|
return { ok: false, error: `Mission ${missionId} was not found` };
|
|
8842
10020
|
}
|
|
8843
10021
|
const read = readJsonRecord(path);
|
|
@@ -8888,7 +10066,7 @@ function createInspectorMissionController(options) {
|
|
|
8888
10066
|
const source = cloneJsonRecord(input.sourceTask);
|
|
8889
10067
|
const missionId = nextId();
|
|
8890
10068
|
const path = missionPath(missionId);
|
|
8891
|
-
if (
|
|
10069
|
+
if (existsSync14(path)) {
|
|
8892
10070
|
const existing = readMissionOnly(missionId);
|
|
8893
10071
|
if (!existing.ok)
|
|
8894
10072
|
return existing;
|
|
@@ -10488,8 +11666,8 @@ function createCodexInspectorTransport(options) {
|
|
|
10488
11666
|
const sendRequest = async (method, params) => {
|
|
10489
11667
|
const id = nextRequestId;
|
|
10490
11668
|
nextRequestId += 1;
|
|
10491
|
-
const response = new Promise((
|
|
10492
|
-
pendingResponses.set(id, { resolve:
|
|
11669
|
+
const response = new Promise((resolve22, reject) => {
|
|
11670
|
+
pendingResponses.set(id, { resolve: resolve22, reject });
|
|
10493
11671
|
});
|
|
10494
11672
|
response.catch(() => {});
|
|
10495
11673
|
try {
|
|
@@ -10799,9 +11977,9 @@ function createCodexInspectorTransport(options) {
|
|
|
10799
11977
|
}
|
|
10800
11978
|
lastAssistantMessage = null;
|
|
10801
11979
|
lastError = null;
|
|
10802
|
-
const turnResult = new Promise((
|
|
11980
|
+
const turnResult = new Promise((resolve22, reject) => {
|
|
10803
11981
|
currentTurn = {
|
|
10804
|
-
resolve:
|
|
11982
|
+
resolve: resolve22,
|
|
10805
11983
|
reject,
|
|
10806
11984
|
events: []
|
|
10807
11985
|
};
|
|
@@ -10861,13 +12039,13 @@ function createCodexInspectorTransport(options) {
|
|
|
10861
12039
|
};
|
|
10862
12040
|
}
|
|
10863
12041
|
function writeChildLine(child, line) {
|
|
10864
|
-
return new Promise((
|
|
12042
|
+
return new Promise((resolve22, reject) => {
|
|
10865
12043
|
child.stdin.write(line, (error) => {
|
|
10866
12044
|
if (error) {
|
|
10867
12045
|
reject(error);
|
|
10868
12046
|
return;
|
|
10869
12047
|
}
|
|
10870
|
-
|
|
12048
|
+
resolve22();
|
|
10871
12049
|
});
|
|
10872
12050
|
});
|
|
10873
12051
|
}
|
|
@@ -10880,10 +12058,10 @@ function terminateChild(child) {
|
|
|
10880
12058
|
} catch {}
|
|
10881
12059
|
}
|
|
10882
12060
|
async function waitForChildSpawn(child) {
|
|
10883
|
-
await new Promise((
|
|
12061
|
+
await new Promise((resolve22, reject) => {
|
|
10884
12062
|
const onSpawn = () => {
|
|
10885
12063
|
cleanup();
|
|
10886
|
-
|
|
12064
|
+
resolve22();
|
|
10887
12065
|
};
|
|
10888
12066
|
const onError = (error) => {
|
|
10889
12067
|
cleanup();
|
|
@@ -11395,8 +12573,8 @@ function createGlobalInspectorService(options) {
|
|
|
11395
12573
|
|
|
11396
12574
|
// packages/server/src/inspector/upstream-sync.ts
|
|
11397
12575
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11398
|
-
import { existsSync as
|
|
11399
|
-
import { dirname as
|
|
12576
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
12577
|
+
import { dirname as dirname17, resolve as resolve22 } from "path";
|
|
11400
12578
|
import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
|
|
11401
12579
|
var UPSTREAM_VALIDATION_DESCRIPTIONS = {
|
|
11402
12580
|
"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.",
|
|
@@ -11534,34 +12712,34 @@ function defaultGitRunner(repoRoot, args) {
|
|
|
11534
12712
|
}
|
|
11535
12713
|
function upstreamStatePath(projectRoot, override) {
|
|
11536
12714
|
if (override) {
|
|
11537
|
-
return
|
|
12715
|
+
return resolve22(override);
|
|
11538
12716
|
}
|
|
11539
|
-
return
|
|
12717
|
+
return resolve22(resolveRigServerPaths(projectRoot).stateDir, "inspector", "upstream-sync.json");
|
|
11540
12718
|
}
|
|
11541
12719
|
function readUpstreamState(projectRoot, statePath) {
|
|
11542
12720
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11543
|
-
if (!
|
|
12721
|
+
if (!existsSync15(path)) {
|
|
11544
12722
|
return null;
|
|
11545
12723
|
}
|
|
11546
12724
|
try {
|
|
11547
|
-
return JSON.parse(
|
|
12725
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
11548
12726
|
} catch {
|
|
11549
12727
|
return null;
|
|
11550
12728
|
}
|
|
11551
12729
|
}
|
|
11552
12730
|
function writeUpstreamState(projectRoot, state, statePath) {
|
|
11553
12731
|
const path = upstreamStatePath(projectRoot, statePath);
|
|
11554
|
-
|
|
11555
|
-
|
|
12732
|
+
mkdirSync15(dirname17(path), { recursive: true });
|
|
12733
|
+
writeFileSync14(path, `${JSON.stringify(state, null, 2)}
|
|
11556
12734
|
`, "utf8");
|
|
11557
12735
|
}
|
|
11558
12736
|
function readImportedRevision(projectRoot, upstreamsDocPath) {
|
|
11559
12737
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11560
|
-
const docPath = upstreamsDocPath ?
|
|
11561
|
-
if (!
|
|
12738
|
+
const docPath = upstreamsDocPath ? resolve22(upstreamsDocPath) : resolve22(monorepoRoot, "docs", "UPSTREAMS.md");
|
|
12739
|
+
if (!existsSync15(docPath)) {
|
|
11562
12740
|
throw new Error(`UPSTREAMS.md not found at ${docPath}`);
|
|
11563
12741
|
}
|
|
11564
|
-
const docContent =
|
|
12742
|
+
const docContent = readFileSync11(docPath, "utf-8");
|
|
11565
12743
|
const revision = parseImportedUpstreamRevision(docContent, "upstream") ?? parseImportedUpstreamRevision(docContent, "humoongate");
|
|
11566
12744
|
if (!revision) {
|
|
11567
12745
|
throw new Error(`Failed to parse upstream imported revision from ${docPath}`);
|
|
@@ -11583,7 +12761,7 @@ function resolveRemoteBranch(repoRoot, remote, gitRunner) {
|
|
|
11583
12761
|
return null;
|
|
11584
12762
|
}
|
|
11585
12763
|
function isGitCheckout(path, gitRunner) {
|
|
11586
|
-
if (!
|
|
12764
|
+
if (!existsSync15(resolve22(path, ".git"))) {
|
|
11587
12765
|
return false;
|
|
11588
12766
|
}
|
|
11589
12767
|
const result = gitRunner(path, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -11592,12 +12770,12 @@ function isGitCheckout(path, gitRunner) {
|
|
|
11592
12770
|
function resolveUpstreamCheckout(projectRoot, explicitCheckout, gitRunner) {
|
|
11593
12771
|
const monorepoRoot = resolveMonorepoRoot4(projectRoot);
|
|
11594
12772
|
const candidates = [
|
|
11595
|
-
explicitCheckout ?
|
|
11596
|
-
process.env.UPSTREAM_CHECKOUT?.trim() ?
|
|
11597
|
-
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ?
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
12773
|
+
explicitCheckout ? resolve22(explicitCheckout) : "",
|
|
12774
|
+
process.env.UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.UPSTREAM_CHECKOUT.trim()) : "",
|
|
12775
|
+
process.env.HUMOONGATE_UPSTREAM_CHECKOUT?.trim() ? resolve22(process.env.HUMOONGATE_UPSTREAM_CHECKOUT.trim()) : "",
|
|
12776
|
+
resolve22(projectRoot, "..", "humoongate"),
|
|
12777
|
+
resolve22(monorepoRoot, "..", "humoongate"),
|
|
12778
|
+
resolve22(monorepoRoot, "humoongate")
|
|
11601
12779
|
].filter(Boolean);
|
|
11602
12780
|
for (const candidate of candidates) {
|
|
11603
12781
|
if (isGitCheckout(candidate, gitRunner)) {
|
|
@@ -11833,10 +13011,10 @@ async function runUpstreamSyncScan(options) {
|
|
|
11833
13011
|
}
|
|
11834
13012
|
|
|
11835
13013
|
// packages/server/src/server-helpers/task-config.ts
|
|
11836
|
-
import { existsSync as
|
|
13014
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11837
13015
|
async function readTaskConfig(projectRoot) {
|
|
11838
13016
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
11839
|
-
if (!
|
|
13017
|
+
if (!existsSync16(taskConfigPath)) {
|
|
11840
13018
|
return {};
|
|
11841
13019
|
}
|
|
11842
13020
|
try {
|
|
@@ -11872,11 +13050,11 @@ function resolveFollowupSourceCommit(input) {
|
|
|
11872
13050
|
}
|
|
11873
13051
|
async function createInspectorFollowupTask(projectRoot, input) {
|
|
11874
13052
|
const monorepoRoot = resolveMonorepoRoot5(projectRoot);
|
|
11875
|
-
const issuesPath =
|
|
11876
|
-
const taskStatePath =
|
|
11877
|
-
const taskConfigPath =
|
|
11878
|
-
|
|
11879
|
-
|
|
13053
|
+
const issuesPath = resolve23(monorepoRoot, ".beads", "issues.jsonl");
|
|
13054
|
+
const taskStatePath = resolve23(monorepoRoot, ".beads", "task-state.json");
|
|
13055
|
+
const taskConfigPath = resolve23(monorepoRoot, ".rig", "task-config.json");
|
|
13056
|
+
mkdirSync16(dirname18(issuesPath), { recursive: true });
|
|
13057
|
+
mkdirSync16(dirname18(taskConfigPath), { recursive: true });
|
|
11880
13058
|
const summary = normalizeString(input.summary) ?? "Inspector follow-up";
|
|
11881
13059
|
const description = normalizeString(input.description) ?? normalizeString(input.details?.description) ?? `Created by the global inspector: ${summary}`;
|
|
11882
13060
|
const acceptanceCriteria = normalizeString(input.acceptanceCriteria) ?? "Investigate the detected drift and port the relevant changes into Rig.";
|
|
@@ -11895,7 +13073,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11895
13073
|
const sourceKey = normalizeString(input.sourceKey) ?? normalizeString(input.details?.sourceKey);
|
|
11896
13074
|
const createdAt = normalizeString(input.createdAt) ?? new Date().toISOString();
|
|
11897
13075
|
const status = normalizeTaskLifecycleStatus2(normalizeString(input.status) ?? "open") ?? "open";
|
|
11898
|
-
const existingIssueLines =
|
|
13076
|
+
const existingIssueLines = existsSync17(issuesPath) ? readFileSync12(issuesPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
11899
13077
|
const existingIssues = existingIssueLines.map((line) => {
|
|
11900
13078
|
try {
|
|
11901
13079
|
return JSON.parse(line);
|
|
@@ -11904,7 +13082,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11904
13082
|
}
|
|
11905
13083
|
}).filter((value) => value !== null);
|
|
11906
13084
|
const existingIds = new Set(existingIssues.map((issue) => typeof issue.id === "string" ? issue.id : null).filter((value) => value !== null));
|
|
11907
|
-
const rawTaskState =
|
|
13085
|
+
const rawTaskState = existsSync17(taskStatePath) ? readJsonFile3(taskStatePath, {}) : {};
|
|
11908
13086
|
const tasks = rawTaskState.tasks && typeof rawTaskState.tasks === "object" && !Array.isArray(rawTaskState.tasks) ? rawTaskState.tasks : {};
|
|
11909
13087
|
const existingTaskIdFromSourceKey = sourceKey == null ? null : Object.entries(tasks).find(([, metadata]) => {
|
|
11910
13088
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
@@ -11930,7 +13108,7 @@ async function createInspectorFollowupTask(projectRoot, input) {
|
|
|
11930
13108
|
updated_at: createdAt,
|
|
11931
13109
|
labels: mergedLabels
|
|
11932
13110
|
};
|
|
11933
|
-
|
|
13111
|
+
writeFileSync15(issuesPath, existingIssueLines.length > 0 ? `${existingIssueLines.join(`
|
|
11934
13112
|
`)}
|
|
11935
13113
|
${JSON.stringify(issueRecord)}
|
|
11936
13114
|
` : `${JSON.stringify(issueRecord)}
|
|
@@ -11948,7 +13126,7 @@ ${JSON.stringify(issueRecord)}
|
|
|
11948
13126
|
labels: mergedLabels
|
|
11949
13127
|
};
|
|
11950
13128
|
});
|
|
11951
|
-
|
|
13129
|
+
writeFileSync15(issuesPath, `${updatedIssues.map((issue) => JSON.stringify(issue)).join(`
|
|
11952
13130
|
`)}
|
|
11953
13131
|
`, "utf8");
|
|
11954
13132
|
}
|
|
@@ -11971,14 +13149,14 @@ ${JSON.stringify(issueRecord)}
|
|
|
11971
13149
|
}
|
|
11972
13150
|
};
|
|
11973
13151
|
}
|
|
11974
|
-
|
|
13152
|
+
writeFileSync15(taskConfigPath, `${JSON.stringify(taskConfig, null, 2)}
|
|
11975
13153
|
`, "utf8");
|
|
11976
13154
|
tasks[taskId] = {
|
|
11977
13155
|
status,
|
|
11978
13156
|
sourceCommit: resolveFollowupSourceCommit(input),
|
|
11979
13157
|
...sourceKey ? { sourceKey } : {}
|
|
11980
13158
|
};
|
|
11981
|
-
|
|
13159
|
+
writeFileSync15(taskStatePath, `${JSON.stringify({
|
|
11982
13160
|
schemaVersion: 1,
|
|
11983
13161
|
baseTrackerCommit: typeof rawTaskState.baseTrackerCommit === "string" ? rawTaskState.baseTrackerCommit : null,
|
|
11984
13162
|
tasks
|
|
@@ -12286,12 +13464,12 @@ function isAuthorizedLinearWebhookRequest(req) {
|
|
|
12286
13464
|
}
|
|
12287
13465
|
|
|
12288
13466
|
// packages/server/src/server-helpers/notifications.ts
|
|
12289
|
-
import { existsSync as
|
|
12290
|
-
import { dirname as
|
|
13467
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync13 } from "fs";
|
|
13468
|
+
import { dirname as dirname19 } from "path";
|
|
12291
13469
|
async function loadNotificationConfig(path) {
|
|
12292
|
-
if (!
|
|
13470
|
+
if (!existsSync18(path)) {
|
|
12293
13471
|
const defaultConfig = { targets: [] };
|
|
12294
|
-
|
|
13472
|
+
mkdirSync17(dirname19(path), { recursive: true });
|
|
12295
13473
|
await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
|
|
12296
13474
|
`);
|
|
12297
13475
|
return defaultConfig;
|
|
@@ -12306,10 +13484,10 @@ async function loadNotificationConfig(path) {
|
|
|
12306
13484
|
}
|
|
12307
13485
|
}
|
|
12308
13486
|
function readRecentEvents(file, limit) {
|
|
12309
|
-
if (!
|
|
13487
|
+
if (!existsSync18(file)) {
|
|
12310
13488
|
return [];
|
|
12311
13489
|
}
|
|
12312
|
-
const lines =
|
|
13490
|
+
const lines = readFileSync13(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
|
|
12313
13491
|
const events = [];
|
|
12314
13492
|
for (const line of lines) {
|
|
12315
13493
|
try {
|
|
@@ -12404,11 +13582,11 @@ function extractObjectLiteralBlock(source, property) {
|
|
|
12404
13582
|
}
|
|
12405
13583
|
function readFallbackIssueAnalysisConfig(projectRoot) {
|
|
12406
13584
|
for (const fileName of ["rig.config.ts", "rig.config.json"]) {
|
|
12407
|
-
const path =
|
|
12408
|
-
if (!
|
|
13585
|
+
const path = resolve24(projectRoot, fileName);
|
|
13586
|
+
if (!existsSync19(path))
|
|
12409
13587
|
continue;
|
|
12410
13588
|
try {
|
|
12411
|
-
const source =
|
|
13589
|
+
const source = readFileSync14(path, "utf8");
|
|
12412
13590
|
if (fileName.endsWith(".json"))
|
|
12413
13591
|
return JSON.parse(source);
|
|
12414
13592
|
const issueBlock = extractObjectLiteralBlock(source, "issueAnalysis");
|
|
@@ -12541,8 +13719,8 @@ async function createIssueAnalysisRunnerForServerState(state, input) {
|
|
|
12541
13719
|
async function withServerPathEnv(projectRoot, fn) {
|
|
12542
13720
|
const waitForTurn = serverPathEnvQueue;
|
|
12543
13721
|
let releaseTurn;
|
|
12544
|
-
serverPathEnvQueue = new Promise((
|
|
12545
|
-
releaseTurn =
|
|
13722
|
+
serverPathEnvQueue = new Promise((resolve25) => {
|
|
13723
|
+
releaseTurn = resolve25;
|
|
12546
13724
|
});
|
|
12547
13725
|
await waitForTurn;
|
|
12548
13726
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -12578,9 +13756,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
12578
13756
|
return withServerPathEnv(projectRoot, fn);
|
|
12579
13757
|
}
|
|
12580
13758
|
async function readWorkspaceTasks(projectRoot) {
|
|
12581
|
-
const issuesPath =
|
|
13759
|
+
const issuesPath = resolve24(resolveMonorepoRoot6(projectRoot), ".beads", "issues.jsonl");
|
|
12582
13760
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
12583
|
-
if (!
|
|
13761
|
+
if (!existsSync19(issuesPath)) {
|
|
12584
13762
|
return [];
|
|
12585
13763
|
}
|
|
12586
13764
|
const latestById = new Map;
|
|
@@ -12654,11 +13832,11 @@ function resolveTaskArtifactDirsFromRuns(projectRoot, taskId, knownRuns) {
|
|
|
12654
13832
|
continue;
|
|
12655
13833
|
add(run.artifactRoot);
|
|
12656
13834
|
if (run.worktreePath) {
|
|
12657
|
-
add(
|
|
13835
|
+
add(resolve24(run.worktreePath, "artifacts", taskId));
|
|
12658
13836
|
}
|
|
12659
13837
|
}
|
|
12660
13838
|
for (const artifactsRoot of listAuthorityArtifactRoots(projectRoot)) {
|
|
12661
|
-
add(
|
|
13839
|
+
add(resolve24(artifactsRoot, taskId));
|
|
12662
13840
|
}
|
|
12663
13841
|
return candidates;
|
|
12664
13842
|
}
|
|
@@ -12672,7 +13850,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12672
13850
|
}
|
|
12673
13851
|
}
|
|
12674
13852
|
return taskIds.flatMap((currentTaskId) => {
|
|
12675
|
-
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) =>
|
|
13853
|
+
const currentRoot = resolveTaskArtifactDirsFromRuns(projectRoot, currentTaskId, runs).find((path) => existsSync19(path));
|
|
12676
13854
|
if (!currentRoot) {
|
|
12677
13855
|
return [];
|
|
12678
13856
|
}
|
|
@@ -12684,7 +13862,7 @@ async function listArtifactSummaries(projectRoot, taskId, knownTaskIds, knownRun
|
|
|
12684
13862
|
taskId: currentTaskId,
|
|
12685
13863
|
kind: "file",
|
|
12686
13864
|
label: fileName,
|
|
12687
|
-
path:
|
|
13865
|
+
path: resolve24(currentRoot, fileName),
|
|
12688
13866
|
url: null,
|
|
12689
13867
|
metadata: {
|
|
12690
13868
|
fileName
|
|
@@ -12727,11 +13905,11 @@ function buildInspectorStreamPayload(state, sequence) {
|
|
|
12727
13905
|
}
|
|
12728
13906
|
function listRemoteRunArtifacts(projectRoot, runId) {
|
|
12729
13907
|
const root = remoteArtifactsRoot(projectRoot, runId);
|
|
12730
|
-
if (!
|
|
13908
|
+
if (!existsSync19(root)) {
|
|
12731
13909
|
return [];
|
|
12732
13910
|
}
|
|
12733
13911
|
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isFile()).filter((entry) => !entry.name.endsWith(".json")).map((entry) => {
|
|
12734
|
-
const artifactPath =
|
|
13912
|
+
const artifactPath = resolve24(root, entry.name);
|
|
12735
13913
|
const stat = statSync6(artifactPath);
|
|
12736
13914
|
const meta = readJsonFile4(`${artifactPath}.json`, null);
|
|
12737
13915
|
return {
|
|
@@ -12892,6 +14070,7 @@ function buildHttpRouterDeps(state) {
|
|
|
12892
14070
|
startLocalRun,
|
|
12893
14071
|
stopRunRecord,
|
|
12894
14072
|
resumeRunRecord,
|
|
14073
|
+
runServerOwnedPrCloseout,
|
|
12895
14074
|
claimRemoteRun,
|
|
12896
14075
|
listRemoteRunArtifacts,
|
|
12897
14076
|
broadcastSnapshotInvalidation,
|
|
@@ -12973,8 +14152,8 @@ function fileStats(path) {
|
|
|
12973
14152
|
}
|
|
12974
14153
|
}
|
|
12975
14154
|
function runFileCursor(projectRoot, run) {
|
|
12976
|
-
const runDir =
|
|
12977
|
-
const runJson = fileStats(
|
|
14155
|
+
const runDir = dirname20(runLogsPath(projectRoot, run.runId));
|
|
14156
|
+
const runJson = fileStats(resolve24(runDir, "run.json"));
|
|
12978
14157
|
const timeline = fileStats(runTimelinePath(projectRoot, run.runId));
|
|
12979
14158
|
const logs = fileStats(runLogsPath(projectRoot, run.runId));
|
|
12980
14159
|
return {
|
|
@@ -13024,10 +14203,10 @@ function startRunFileWatcher(state, pollMs) {
|
|
|
13024
14203
|
}, Math.max(250, Math.min(pollMs, 1000)));
|
|
13025
14204
|
}
|
|
13026
14205
|
function startPoller(state, pollMs) {
|
|
13027
|
-
let offset =
|
|
14206
|
+
let offset = existsSync19(state.eventsFile) ? statSync6(state.eventsFile).size : 0;
|
|
13028
14207
|
return setInterval(async () => {
|
|
13029
14208
|
try {
|
|
13030
|
-
if (!
|
|
14209
|
+
if (!existsSync19(state.eventsFile)) {
|
|
13031
14210
|
return;
|
|
13032
14211
|
}
|
|
13033
14212
|
const file = await open(state.eventsFile, "r");
|
|
@@ -13079,6 +14258,7 @@ async function createRigServer(options, projectRoot = resolveProjectRoot()) {
|
|
|
13079
14258
|
const server = Bun.serve({
|
|
13080
14259
|
hostname: options.host,
|
|
13081
14260
|
port: options.port,
|
|
14261
|
+
idleTimeout: Math.max(10, Math.min(255, Number.parseInt(process.env.RIG_SERVER_IDLE_TIMEOUT_SECONDS || "255", 10) || 255)),
|
|
13082
14262
|
fetch: (req, server2) => createRigServerFetch2(state)(req, server2),
|
|
13083
14263
|
websocket: {
|
|
13084
14264
|
open(ws) {
|
|
@@ -13154,7 +14334,7 @@ function resolveProjectRoot() {
|
|
|
13154
14334
|
return resolveRigProjectRoot({
|
|
13155
14335
|
envProjectRoot: process.env.PROJECT_RIG_ROOT ?? null,
|
|
13156
14336
|
cwd: process.cwd(),
|
|
13157
|
-
fallbackRoot:
|
|
14337
|
+
fallbackRoot: resolve24(import.meta.dir, "../..")
|
|
13158
14338
|
});
|
|
13159
14339
|
}
|
|
13160
14340
|
var __testOnly = {
|
|
@@ -13215,6 +14395,7 @@ export {
|
|
|
13215
14395
|
resolveRigServerPaths,
|
|
13216
14396
|
resolveRigProjectRoot,
|
|
13217
14397
|
resolvePublishedRigServerStatePath,
|
|
14398
|
+
resolveProjectStatusField,
|
|
13218
14399
|
resolveProjectRoot,
|
|
13219
14400
|
registerRemoteHost,
|
|
13220
14401
|
readWorkspaceTasks,
|
|
@@ -13223,6 +14404,7 @@ export {
|
|
|
13223
14404
|
parseRigServerArgs,
|
|
13224
14405
|
parseArgs,
|
|
13225
14406
|
main,
|
|
14407
|
+
listGitHubProjects,
|
|
13226
14408
|
heartbeatRemoteHost,
|
|
13227
14409
|
handleWebSocketUpgrade,
|
|
13228
14410
|
encodeWebSocketPayload,
|