@h-rig/server 0.0.6-alpha.1 → 0.0.6-alpha.10
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/dist/src/index.js +331 -77
- package/dist/src/server-helpers/github-auth-store.js +52 -0
- package/dist/src/server-helpers/http-router.js +492 -24
- package/dist/src/server-helpers/issue-analysis.js +30 -11
- package/dist/src/server-helpers/run-mutations.js +188 -85
- package/dist/src/server-helpers/snapshot-orchestrator.js +1 -1
- package/dist/src/server-helpers/snapshot-service.js +1 -1
- package/dist/src/server-helpers/ws-router.js +4 -4
- package/dist/src/server.js +332 -77
- package/package.json +4 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/server/src/server-helpers/issue-analysis.ts
|
|
3
|
-
import { execFile } from "child_process";
|
|
4
3
|
import { createHash } from "crypto";
|
|
5
4
|
function stableIssueHash(issue) {
|
|
6
5
|
const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
|
|
@@ -134,16 +133,33 @@ function parseIssueAnalysisResult(raw) {
|
|
|
134
133
|
return result;
|
|
135
134
|
}
|
|
136
135
|
function createDefaultPiIssueAnalysisCommandRunner() {
|
|
137
|
-
return (command, args, options) =>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
|
|
144
|
-
resolve({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
|
|
136
|
+
return async (command, args, options) => {
|
|
137
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
138
|
+
const proc = Bun.spawn([command, ...args], {
|
|
139
|
+
stdout: "pipe",
|
|
140
|
+
stderr: "pipe",
|
|
141
|
+
env
|
|
145
142
|
});
|
|
146
|
-
|
|
143
|
+
let timedOut = false;
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
timedOut = true;
|
|
146
|
+
proc.kill();
|
|
147
|
+
}, options.timeoutMs);
|
|
148
|
+
try {
|
|
149
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
150
|
+
new Response(proc.stdout).text(),
|
|
151
|
+
new Response(proc.stderr).text(),
|
|
152
|
+
proc.exited
|
|
153
|
+
]);
|
|
154
|
+
return {
|
|
155
|
+
exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
|
|
156
|
+
stdout,
|
|
157
|
+
stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
|
|
158
|
+
};
|
|
159
|
+
} finally {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
147
163
|
}
|
|
148
164
|
function createPiIssueAnalyzer(input = {}) {
|
|
149
165
|
const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
|
|
@@ -151,7 +167,10 @@ function createPiIssueAnalyzer(input = {}) {
|
|
|
151
167
|
const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
|
|
152
168
|
return async ({ prompt }) => {
|
|
153
169
|
const args = ["--print", "--mode", "json", "--no-session"];
|
|
154
|
-
const
|
|
170
|
+
const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
|
|
171
|
+
const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
|
|
172
|
+
if (provider)
|
|
173
|
+
args.push("--provider", provider);
|
|
155
174
|
if (model)
|
|
156
175
|
args.push("--model", model);
|
|
157
176
|
args.push(prompt);
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// packages/server/src/server-helpers/run-mutations.ts
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import { loadConfig } from "@rig/core/load-config";
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { dirname as dirname5, relative as relative2, resolve as
|
|
5
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
6
|
+
import { dirname as dirname5, relative as relative2, resolve as resolve10 } from "path";
|
|
7
7
|
import {
|
|
8
8
|
listAuthorityRuns as listAuthorityRuns7,
|
|
9
9
|
readAuthorityRun as readAuthorityRun8,
|
|
@@ -98,8 +98,8 @@ function normalizeStatus(value) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// packages/server/src/server.ts
|
|
101
|
-
import { existsSync as
|
|
102
|
-
import { dirname as dirname4, resolve as
|
|
101
|
+
import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
102
|
+
import { dirname as dirname4, resolve as resolve8 } from "path";
|
|
103
103
|
import {
|
|
104
104
|
listAuthorityArtifactRoots,
|
|
105
105
|
listAuthorityRuns as listAuthorityRuns6,
|
|
@@ -295,6 +295,24 @@ var snapshotCache = new Map;
|
|
|
295
295
|
var contextCache = new Map;
|
|
296
296
|
var taskListCache = new Map;
|
|
297
297
|
|
|
298
|
+
// packages/server/src/server-helpers/task-projection.ts
|
|
299
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
|
|
300
|
+
import { resolve as resolve5 } from "path";
|
|
301
|
+
function projectionPath(projectRoot) {
|
|
302
|
+
return resolve5(projectRoot, ".rig", "state", "task-projection.json");
|
|
303
|
+
}
|
|
304
|
+
function readTaskProjection(projectRoot) {
|
|
305
|
+
const file = projectionPath(projectRoot);
|
|
306
|
+
if (!existsSync3(file))
|
|
307
|
+
return null;
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
310
|
+
return parsed && parsed.version === 1 && Array.isArray(parsed.tasks) ? parsed : null;
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
298
316
|
// packages/server/src/server-helpers/terminal-runtime.ts
|
|
299
317
|
import { WS_CHANNELS as WS_CHANNELS2 } from "@rig/contracts";
|
|
300
318
|
|
|
@@ -302,7 +320,7 @@ import { WS_CHANNELS as WS_CHANNELS2 } from "@rig/contracts";
|
|
|
302
320
|
import { RIG_WS_CHANNELS } from "@rig/contracts";
|
|
303
321
|
|
|
304
322
|
// packages/server/src/server-helpers/run-writers.ts
|
|
305
|
-
import { resolve as
|
|
323
|
+
import { resolve as resolve6 } from "path";
|
|
306
324
|
import {
|
|
307
325
|
appendJsonlRecord,
|
|
308
326
|
readAuthorityRun as readAuthorityRun3,
|
|
@@ -327,7 +345,7 @@ function patchRunRecord(projectRoot, runId, patch) {
|
|
|
327
345
|
...patch,
|
|
328
346
|
updatedAt: normalizeString(patch.updatedAt) ?? new Date().toISOString()
|
|
329
347
|
};
|
|
330
|
-
writeJsonFile2(
|
|
348
|
+
writeJsonFile2(resolve6(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
|
|
331
349
|
return next;
|
|
332
350
|
}
|
|
333
351
|
function buildRunStartPatch(startedAt) {
|
|
@@ -459,8 +477,9 @@ import {
|
|
|
459
477
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
460
478
|
|
|
461
479
|
// packages/server/src/server-helpers/github-auth-store.ts
|
|
462
|
-
import {
|
|
463
|
-
import {
|
|
480
|
+
import { randomBytes } from "crypto";
|
|
481
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
482
|
+
import { resolve as resolve7 } from "path";
|
|
464
483
|
function cleanString(value) {
|
|
465
484
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
466
485
|
}
|
|
@@ -472,11 +491,29 @@ function cleanScopes(value) {
|
|
|
472
491
|
return clean ? [clean] : [];
|
|
473
492
|
});
|
|
474
493
|
}
|
|
494
|
+
function parseApiSessions(value) {
|
|
495
|
+
if (!Array.isArray(value))
|
|
496
|
+
return [];
|
|
497
|
+
return value.flatMap((entry) => {
|
|
498
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
499
|
+
return [];
|
|
500
|
+
const record = entry;
|
|
501
|
+
const token = cleanString(record.token);
|
|
502
|
+
if (!token)
|
|
503
|
+
return [];
|
|
504
|
+
return [{
|
|
505
|
+
token,
|
|
506
|
+
login: cleanString(record.login),
|
|
507
|
+
userId: cleanString(record.userId),
|
|
508
|
+
createdAt: cleanString(record.createdAt) ?? undefined
|
|
509
|
+
}];
|
|
510
|
+
});
|
|
511
|
+
}
|
|
475
512
|
function readStoredAuth(stateFile) {
|
|
476
|
-
if (!
|
|
513
|
+
if (!existsSync4(stateFile))
|
|
477
514
|
return {};
|
|
478
515
|
try {
|
|
479
|
-
const parsed = JSON.parse(
|
|
516
|
+
const parsed = JSON.parse(readFileSync2(stateFile, "utf8"));
|
|
480
517
|
return {
|
|
481
518
|
...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
|
|
482
519
|
login: cleanString(parsed.login),
|
|
@@ -485,6 +522,7 @@ function readStoredAuth(stateFile) {
|
|
|
485
522
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
486
523
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
487
524
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
525
|
+
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
488
526
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
489
527
|
};
|
|
490
528
|
} catch {
|
|
@@ -503,16 +541,19 @@ function parsePendingDevice(value) {
|
|
|
503
541
|
return null;
|
|
504
542
|
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
505
543
|
}
|
|
544
|
+
function newApiSessionToken() {
|
|
545
|
+
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
546
|
+
}
|
|
506
547
|
function writeStoredAuth(stateFile, payload) {
|
|
507
|
-
|
|
508
|
-
|
|
548
|
+
mkdirSync4(resolve7(stateFile, ".."), { recursive: true });
|
|
549
|
+
writeFileSync4(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
509
550
|
`, { encoding: "utf8", mode: 384 });
|
|
510
551
|
try {
|
|
511
552
|
chmodSync(stateFile, 384);
|
|
512
553
|
} catch {}
|
|
513
554
|
}
|
|
514
555
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
515
|
-
return
|
|
556
|
+
return resolve7(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
516
557
|
}
|
|
517
558
|
function createGitHubAuthStore(projectRoot) {
|
|
518
559
|
const stateFile = resolveGitHubAuthStateFile(projectRoot);
|
|
@@ -545,9 +586,38 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
545
586
|
scopes: input.scopes ?? [],
|
|
546
587
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
547
588
|
pendingDevice: null,
|
|
589
|
+
apiSessions: previous.apiSessions ?? [],
|
|
548
590
|
updatedAt: new Date().toISOString()
|
|
549
591
|
});
|
|
550
592
|
},
|
|
593
|
+
createApiSession() {
|
|
594
|
+
const previous = readStoredAuth(stateFile);
|
|
595
|
+
const token = newApiSessionToken();
|
|
596
|
+
const session = {
|
|
597
|
+
token,
|
|
598
|
+
login: cleanString(previous.login),
|
|
599
|
+
userId: cleanString(previous.userId),
|
|
600
|
+
createdAt: new Date().toISOString()
|
|
601
|
+
};
|
|
602
|
+
writeStoredAuth(stateFile, {
|
|
603
|
+
...previous,
|
|
604
|
+
apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
|
|
605
|
+
updatedAt: new Date().toISOString()
|
|
606
|
+
});
|
|
607
|
+
return { token, login: session.login ?? null, userId: session.userId ?? null };
|
|
608
|
+
},
|
|
609
|
+
readApiSession(token) {
|
|
610
|
+
const clean = cleanString(token);
|
|
611
|
+
if (!clean)
|
|
612
|
+
return null;
|
|
613
|
+
const previous = readStoredAuth(stateFile);
|
|
614
|
+
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
615
|
+
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
616
|
+
},
|
|
617
|
+
copyToProjectRoot(projectRoot2) {
|
|
618
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot2);
|
|
619
|
+
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
620
|
+
},
|
|
551
621
|
savePendingDevice(input) {
|
|
552
622
|
const previous = readStoredAuth(stateFile);
|
|
553
623
|
writeStoredAuth(stateFile, {
|
|
@@ -775,10 +845,10 @@ function extractGitHubIssueNodeId(task) {
|
|
|
775
845
|
}
|
|
776
846
|
|
|
777
847
|
// packages/server/src/server-helpers/http-router.ts
|
|
778
|
-
var
|
|
848
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
779
849
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
780
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
781
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
850
|
+
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
851
|
+
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
782
852
|
};
|
|
783
853
|
|
|
784
854
|
// packages/server/src/server-helpers/ws-router.ts
|
|
@@ -917,10 +987,10 @@ var CLUSTERS = {
|
|
|
917
987
|
};
|
|
918
988
|
|
|
919
989
|
// packages/server/src/server-helpers/task-config.ts
|
|
920
|
-
import { existsSync as
|
|
990
|
+
import { existsSync as existsSync5 } from "fs";
|
|
921
991
|
async function readTaskConfig(projectRoot) {
|
|
922
992
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
923
|
-
if (!
|
|
993
|
+
if (!existsSync5(taskConfigPath)) {
|
|
924
994
|
return {};
|
|
925
995
|
}
|
|
926
996
|
try {
|
|
@@ -937,8 +1007,8 @@ var serverPathEnvQueue = Promise.resolve();
|
|
|
937
1007
|
async function withServerPathEnv(projectRoot, fn) {
|
|
938
1008
|
const waitForTurn = serverPathEnvQueue;
|
|
939
1009
|
let releaseTurn;
|
|
940
|
-
serverPathEnvQueue = new Promise((
|
|
941
|
-
releaseTurn =
|
|
1010
|
+
serverPathEnvQueue = new Promise((resolve9) => {
|
|
1011
|
+
releaseTurn = resolve9;
|
|
942
1012
|
});
|
|
943
1013
|
await waitForTurn;
|
|
944
1014
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -974,9 +1044,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
974
1044
|
return withServerPathEnv(projectRoot, fn);
|
|
975
1045
|
}
|
|
976
1046
|
async function readWorkspaceTasks(projectRoot) {
|
|
977
|
-
const issuesPath =
|
|
1047
|
+
const issuesPath = resolve8(resolveMonorepoRoot5(projectRoot), ".beads", "issues.jsonl");
|
|
978
1048
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
979
|
-
if (!
|
|
1049
|
+
if (!existsSync6(issuesPath)) {
|
|
980
1050
|
return [];
|
|
981
1051
|
}
|
|
982
1052
|
const latestById = new Map;
|
|
@@ -995,7 +1065,7 @@ async function readWorkspaceTasks(projectRoot) {
|
|
|
995
1065
|
description: normalizeString(entry.description),
|
|
996
1066
|
acceptanceCriteria: normalizeString(entry.acceptance_criteria),
|
|
997
1067
|
status: normalizedStatus ?? "unknown",
|
|
998
|
-
sourceStatus: normalizedStatus ? null : rawStatus,
|
|
1068
|
+
sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
|
|
999
1069
|
priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
|
|
1000
1070
|
issueType: normalizeString(entry.issue_type),
|
|
1001
1071
|
role: normalizeString(config.role) ?? null,
|
|
@@ -1022,7 +1092,7 @@ async function readWorkspaceTasks(projectRoot) {
|
|
|
1022
1092
|
if (false) {}
|
|
1023
1093
|
|
|
1024
1094
|
// packages/server/src/server-helpers/validation-failure.ts
|
|
1025
|
-
import { resolve as
|
|
1095
|
+
import { resolve as resolve9 } from "path";
|
|
1026
1096
|
import {
|
|
1027
1097
|
readJsonFile as readJsonFile4,
|
|
1028
1098
|
resolveTaskArtifactDirs
|
|
@@ -1036,7 +1106,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
1036
1106
|
continue;
|
|
1037
1107
|
}
|
|
1038
1108
|
seen.add(artifactRoot);
|
|
1039
|
-
const summary = readJsonFile4(
|
|
1109
|
+
const summary = readJsonFile4(resolve9(artifactRoot, "validation-summary.json"), null);
|
|
1040
1110
|
if (!summary || summary.status !== "fail") {
|
|
1041
1111
|
continue;
|
|
1042
1112
|
}
|
|
@@ -1120,15 +1190,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
|
|
|
1120
1190
|
if (!run.taskId)
|
|
1121
1191
|
return;
|
|
1122
1192
|
const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1193
|
+
try {
|
|
1194
|
+
const result = await syncGitHubProjectStatusForTaskUpdate({
|
|
1195
|
+
taskId: run.taskId,
|
|
1196
|
+
status,
|
|
1197
|
+
issueNodeId,
|
|
1198
|
+
token: createGitHubAuthStore(projectRoot).readToken(),
|
|
1199
|
+
config
|
|
1200
|
+
});
|
|
1201
|
+
if (!result.synced && result.reason !== "project-sync-disabled") {
|
|
1202
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
1203
|
+
id: `log:${run.runId}:github-project-sync:${status}`,
|
|
1204
|
+
title: "GitHub Project sync skipped",
|
|
1205
|
+
detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
|
|
1206
|
+
tone: "warn",
|
|
1207
|
+
status: "running",
|
|
1208
|
+
createdAt: new Date().toISOString(),
|
|
1209
|
+
payload: { reason: result.reason, issueNodeId }
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
1214
|
+
id: `log:${run.runId}:github-project-sync-error:${status}`,
|
|
1215
|
+
title: "GitHub Project sync failed",
|
|
1216
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1217
|
+
tone: "error",
|
|
1218
|
+
status: "running",
|
|
1219
|
+
createdAt: new Date().toISOString(),
|
|
1220
|
+
payload: { issueNodeId }
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1132
1223
|
}
|
|
1133
1224
|
async function autoAssignRunIssue(projectRoot, run) {
|
|
1134
1225
|
if (!run.taskId)
|
|
@@ -1230,11 +1321,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
|
|
|
1230
1321
|
return;
|
|
1231
1322
|
throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
|
|
1232
1323
|
}
|
|
1324
|
+
async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
|
|
1325
|
+
const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
1326
|
+
if (fromReader)
|
|
1327
|
+
return fromReader;
|
|
1328
|
+
const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
|
|
1329
|
+
if (projected)
|
|
1330
|
+
return projected;
|
|
1331
|
+
if (readTasks !== readWorkspaceTasks) {
|
|
1332
|
+
return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
1333
|
+
}
|
|
1334
|
+
return null;
|
|
1335
|
+
}
|
|
1233
1336
|
async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
|
|
1234
1337
|
if ("taskId" in input && input.taskId) {
|
|
1235
1338
|
assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
|
|
1236
1339
|
}
|
|
1237
|
-
const sourceTask = "taskId" in input && input.taskId ?
|
|
1340
|
+
const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
|
|
1238
1341
|
const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
|
|
1239
1342
|
const runDir = resolveAuthorityRunDir4(projectRoot, input.runId);
|
|
1240
1343
|
const runRecord = {
|
|
@@ -1268,11 +1371,11 @@ async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTask
|
|
|
1268
1371
|
initiatedBy: input.initiatedBy ?? null,
|
|
1269
1372
|
...sourceTask ? { sourceTask: sourceTaskContract(sourceTask) } : {}
|
|
1270
1373
|
};
|
|
1271
|
-
|
|
1272
|
-
|
|
1374
|
+
mkdirSync6(runDir, { recursive: true });
|
|
1375
|
+
writeFileSync6(resolve10(runDir, "run.json"), `${JSON.stringify(runRecord, null, 2)}
|
|
1273
1376
|
`, "utf8");
|
|
1274
1377
|
if ("initialPrompt" in input && input.initialPrompt && input.initialPrompt.trim().length > 0) {
|
|
1275
|
-
|
|
1378
|
+
writeFileSync6(resolve10(runDir, "timeline.jsonl"), `${JSON.stringify({
|
|
1276
1379
|
id: `message-${Date.now()}`,
|
|
1277
1380
|
type: "user_message",
|
|
1278
1381
|
text: input.initialPrompt,
|
|
@@ -1306,6 +1409,7 @@ async function startLocalRun(state, runId, options) {
|
|
|
1306
1409
|
throw new Error(`Run not found: ${runId}`);
|
|
1307
1410
|
}
|
|
1308
1411
|
const startedAt = new Date().toISOString();
|
|
1412
|
+
const resumeMode = options?.resume === true;
|
|
1309
1413
|
state.runProcesses.set(runId, {
|
|
1310
1414
|
runId,
|
|
1311
1415
|
child: null,
|
|
@@ -1322,9 +1426,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
1322
1426
|
summary: run.title
|
|
1323
1427
|
});
|
|
1324
1428
|
appendRunLogEntry(state.projectRoot, runId, {
|
|
1325
|
-
id: `log:${runId}:prepare`,
|
|
1326
|
-
title: "Rig task run starting",
|
|
1327
|
-
detail: run.taskId ?? run.title,
|
|
1429
|
+
id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
|
|
1430
|
+
title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
|
|
1431
|
+
detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
|
|
1328
1432
|
tone: "info",
|
|
1329
1433
|
status: "preparing",
|
|
1330
1434
|
createdAt: startedAt
|
|
@@ -1332,8 +1436,8 @@ async function startLocalRun(state, runId, options) {
|
|
|
1332
1436
|
broadcastRunLogAppended(state, runId, readLatestRawRunLog(state.projectRoot, runId));
|
|
1333
1437
|
broadcastSnapshotInvalidation(state);
|
|
1334
1438
|
const cliProjectRoot = resolveLocalRunCliProjectRoot(state.projectRoot);
|
|
1335
|
-
const cliEntryPoint =
|
|
1336
|
-
if (!
|
|
1439
|
+
const cliEntryPoint = resolve10(cliProjectRoot, "packages/cli/bin/rig.ts");
|
|
1440
|
+
if (!existsSync7(cliEntryPoint)) {
|
|
1337
1441
|
const completedAt = new Date().toISOString();
|
|
1338
1442
|
const failureSummary = `Rig task-run entrypoint missing at ${relative2(state.projectRoot, cliEntryPoint)}`;
|
|
1339
1443
|
patchRunRecord(state.projectRoot, runId, {
|
|
@@ -1402,7 +1506,15 @@ async function startLocalRun(state, runId, options) {
|
|
|
1402
1506
|
RIG_SERVER_INTERNAL_EXEC: "1",
|
|
1403
1507
|
...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
|
|
1404
1508
|
...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
|
|
1405
|
-
...bridgeGitHubToken ? {
|
|
1509
|
+
...bridgeGitHubToken ? {
|
|
1510
|
+
RIG_GITHUB_TOKEN: bridgeGitHubToken,
|
|
1511
|
+
GITHUB_TOKEN: bridgeGitHubToken,
|
|
1512
|
+
GH_TOKEN: bridgeGitHubToken
|
|
1513
|
+
} : {},
|
|
1514
|
+
...resumeMode ? {
|
|
1515
|
+
RIG_RUN_RESUME: "1",
|
|
1516
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
1517
|
+
} : {}
|
|
1406
1518
|
},
|
|
1407
1519
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1408
1520
|
});
|
|
@@ -1442,9 +1554,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
1442
1554
|
handleRunProcessOutput(Buffer.isBuffer(data) ? data.toString("utf8") : String(data), "error", "Rig task run stderr");
|
|
1443
1555
|
});
|
|
1444
1556
|
try {
|
|
1445
|
-
const exit = await new Promise((
|
|
1446
|
-
child.once("error", (error) =>
|
|
1447
|
-
child.once("close", (code, signal) =>
|
|
1557
|
+
const exit = await new Promise((resolve11) => {
|
|
1558
|
+
child.once("error", (error) => resolve11({ code: 1, signal: null, error }));
|
|
1559
|
+
child.once("close", (code, signal) => resolve11({ code, signal }));
|
|
1448
1560
|
});
|
|
1449
1561
|
if (exit.error) {
|
|
1450
1562
|
throw new Error(`Failed to start task run: ${exit.error.message}`);
|
|
@@ -1544,17 +1656,17 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
1544
1656
|
process.env.PROJECT_RIG_ROOT?.trim()
|
|
1545
1657
|
].filter((value) => !!value);
|
|
1546
1658
|
for (const candidate of envCandidates) {
|
|
1547
|
-
if (
|
|
1548
|
-
return
|
|
1659
|
+
if (existsSync7(resolve10(candidate, "packages/cli/bin/rig.ts"))) {
|
|
1660
|
+
return resolve10(candidate);
|
|
1549
1661
|
}
|
|
1550
1662
|
}
|
|
1551
|
-
if (
|
|
1663
|
+
if (existsSync7(resolve10(projectRoot, "packages/cli/bin/rig.ts"))) {
|
|
1552
1664
|
return projectRoot;
|
|
1553
1665
|
}
|
|
1554
1666
|
try {
|
|
1555
1667
|
const monorepoRoot = resolveMonorepoRoot6(projectRoot);
|
|
1556
1668
|
const outerProjectRoot = dirname5(dirname5(monorepoRoot));
|
|
1557
|
-
if (
|
|
1669
|
+
if (existsSync7(resolve10(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
1558
1670
|
return outerProjectRoot;
|
|
1559
1671
|
}
|
|
1560
1672
|
} catch {}
|
|
@@ -1574,15 +1686,15 @@ async function resumeRunRecord(state, input) {
|
|
|
1574
1686
|
if (run.status === "completed") {
|
|
1575
1687
|
throw new Error("Completed runs cannot be resumed.");
|
|
1576
1688
|
}
|
|
1577
|
-
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
|
|
1689
|
+
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
1578
1690
|
}
|
|
1579
1691
|
function appendRunMessage(projectRoot, input) {
|
|
1580
1692
|
const run = readAuthorityRun8(projectRoot, input.runId);
|
|
1581
1693
|
if (!run) {
|
|
1582
1694
|
throw new Error(`Run not found: ${input.runId}`);
|
|
1583
1695
|
}
|
|
1584
|
-
const timelinePath =
|
|
1585
|
-
const existingLines = fileExists(timelinePath) ?
|
|
1696
|
+
const timelinePath = resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "timeline.jsonl");
|
|
1697
|
+
const existingLines = fileExists(timelinePath) ? readFileSync4(timelinePath, "utf8").trim() : "";
|
|
1586
1698
|
const nextLine = JSON.stringify({
|
|
1587
1699
|
id: input.messageId,
|
|
1588
1700
|
type: "user_message",
|
|
@@ -1590,11 +1702,11 @@ function appendRunMessage(projectRoot, input) {
|
|
|
1590
1702
|
attachments: input.attachments ?? [],
|
|
1591
1703
|
createdAt: input.createdAt
|
|
1592
1704
|
});
|
|
1593
|
-
|
|
1705
|
+
writeFileSync6(timelinePath, existingLines.length > 0 ? `${existingLines}
|
|
1594
1706
|
${nextLine}
|
|
1595
1707
|
` : `${nextLine}
|
|
1596
1708
|
`, "utf8");
|
|
1597
|
-
writeJsonFile4(
|
|
1709
|
+
writeJsonFile4(resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), {
|
|
1598
1710
|
...run,
|
|
1599
1711
|
updatedAt: input.createdAt
|
|
1600
1712
|
});
|
|
@@ -1620,7 +1732,7 @@ async function stopRunRecord(stateOrProjectRoot, input) {
|
|
|
1620
1732
|
completedAt: run.completedAt ?? input.createdAt,
|
|
1621
1733
|
updatedAt: input.createdAt
|
|
1622
1734
|
};
|
|
1623
|
-
writeJsonFile4(
|
|
1735
|
+
writeJsonFile4(resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), nextRun);
|
|
1624
1736
|
if (run.status !== "completed" && run.taskId) {
|
|
1625
1737
|
const taskId = run.taskId;
|
|
1626
1738
|
(async () => {
|
|
@@ -1658,34 +1770,12 @@ function removeTaskIdsFromQueueState2(projectRoot, taskIds) {
|
|
|
1658
1770
|
writeQueueState(projectRoot, next);
|
|
1659
1771
|
return next;
|
|
1660
1772
|
}
|
|
1661
|
-
var
|
|
1662
|
-
function
|
|
1663
|
-
|
|
1664
|
-
for (const run of runs) {
|
|
1773
|
+
var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
1774
|
+
function collectResumableLocalRuns(state, runs) {
|
|
1775
|
+
return runs.filter((run) => {
|
|
1665
1776
|
const status = normalizeString(run.status)?.toLowerCase() ?? "";
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
if (run.mode !== "local" || !wasStartedByRigServer || !ORPHANABLE_LOCAL_RUN_STATUSES.has(status) || state.runProcesses.has(run.runId)) {
|
|
1669
|
-
continue;
|
|
1670
|
-
}
|
|
1671
|
-
const detail = "Recovered stale local run after Rig server restart; no live child process was attached to this server instance.";
|
|
1672
|
-
patchRunRecord(state.projectRoot, run.runId, {
|
|
1673
|
-
status: "failed",
|
|
1674
|
-
completedAt: run.completedAt ?? nowIso,
|
|
1675
|
-
updatedAt: nowIso,
|
|
1676
|
-
errorText: detail
|
|
1677
|
-
});
|
|
1678
|
-
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
1679
|
-
id: `log:${run.runId}:stale-local-run`,
|
|
1680
|
-
title: "Run marked stale after server restart",
|
|
1681
|
-
detail,
|
|
1682
|
-
tone: "error",
|
|
1683
|
-
status: "failed",
|
|
1684
|
-
createdAt: nowIso
|
|
1685
|
-
});
|
|
1686
|
-
changed = true;
|
|
1687
|
-
}
|
|
1688
|
-
return changed;
|
|
1777
|
+
return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
|
|
1778
|
+
});
|
|
1689
1779
|
}
|
|
1690
1780
|
async function reconcileScheduler(state, reason) {
|
|
1691
1781
|
if (state.scheduler.reconciling) {
|
|
@@ -1700,7 +1790,20 @@ async function reconcileScheduler(state, reason) {
|
|
|
1700
1790
|
const queue = readQueueState(state.projectRoot);
|
|
1701
1791
|
const tasks = await state.snapshotService.getWorkspaceTasks();
|
|
1702
1792
|
let runs = listAuthorityRuns7(state.projectRoot);
|
|
1703
|
-
let changed =
|
|
1793
|
+
let changed = false;
|
|
1794
|
+
const resumableRuns = collectResumableLocalRuns(state, runs);
|
|
1795
|
+
for (const run of resumableRuns) {
|
|
1796
|
+
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
1797
|
+
id: `log:${run.runId}:auto-resume:${Date.now()}`,
|
|
1798
|
+
title: "Run auto-resume scheduled",
|
|
1799
|
+
detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
|
|
1800
|
+
tone: "info",
|
|
1801
|
+
status: "preparing",
|
|
1802
|
+
createdAt: new Date().toISOString()
|
|
1803
|
+
});
|
|
1804
|
+
await startLocalRun(state, run.runId, { resume: true });
|
|
1805
|
+
changed = true;
|
|
1806
|
+
}
|
|
1704
1807
|
if (changed) {
|
|
1705
1808
|
runs = listAuthorityRuns7(state.projectRoot);
|
|
1706
1809
|
}
|
|
@@ -699,7 +699,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
|
|
|
699
699
|
const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
|
|
700
700
|
...toTaskSummary(workspace.id, {
|
|
701
701
|
...task,
|
|
702
|
-
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
|
|
702
|
+
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
|
|
703
703
|
}),
|
|
704
704
|
graphId: graph.id
|
|
705
705
|
}));
|
|
@@ -711,7 +711,7 @@ async function buildRigSnapshotPayload(projectRoot, readers) {
|
|
|
711
711
|
const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
|
|
712
712
|
...toTaskSummary(workspace.id, {
|
|
713
713
|
...task,
|
|
714
|
-
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
|
|
714
|
+
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft" || task.status === "failed" || task.sourceStatus === "failed") ? "queued" : task.status
|
|
715
715
|
}),
|
|
716
716
|
graphId: graph.id
|
|
717
717
|
}));
|
|
@@ -403,7 +403,7 @@ var TERMINAL_RUN_STATUSES2 = new Set([
|
|
|
403
403
|
"needs-attention",
|
|
404
404
|
"stopped"
|
|
405
405
|
]);
|
|
406
|
-
var
|
|
406
|
+
var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
407
407
|
|
|
408
408
|
// packages/server/src/server-helpers/http-router.ts
|
|
409
409
|
import {
|
|
@@ -437,10 +437,10 @@ import {
|
|
|
437
437
|
buildTaskRunLifecycleComment as buildTaskRunLifecycleComment2,
|
|
438
438
|
updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
|
|
439
439
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
440
|
-
var
|
|
440
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
441
441
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
442
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
443
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
442
|
+
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
443
|
+
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
444
444
|
};
|
|
445
445
|
|
|
446
446
|
// packages/server/src/server-helpers/inspector-jobs.ts
|