@h-rig/server 0.0.6-alpha.1 → 0.0.6-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/src/index.js +920 -287
- package/dist/src/server-helpers/github-api-session-index.js +107 -0
- package/dist/src/server-helpers/github-auth-store.js +117 -21
- package/dist/src/server-helpers/github-user-namespace.js +102 -0
- package/dist/src/server-helpers/http-router.js +998 -151
- package/dist/src/server-helpers/issue-analysis.js +30 -11
- package/dist/src/server-helpers/project-registry.js +5 -0
- package/dist/src/server-helpers/run-mutations.js +248 -103
- package/dist/src/server-helpers/snapshot-orchestrator.js +1 -1
- package/dist/src/server-helpers/snapshot-service.js +1 -1
- package/dist/src/server-helpers/ws-router.js +4 -4
- package/dist/src/server.js +921 -287
- package/package.json +4 -4
|
@@ -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
|
|
5
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
6
|
+
import { dirname as dirname6, 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
|
|
101
|
+
import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
102
|
+
import { dirname as dirname5, 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, copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
482
|
+
import { dirname as dirname4, 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,49 @@ 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
|
+
}
|
|
512
|
+
function parsePendingDevice(value) {
|
|
513
|
+
if (!value || typeof value !== "object")
|
|
514
|
+
return null;
|
|
515
|
+
const record = value;
|
|
516
|
+
const pollId = cleanString(record.pollId);
|
|
517
|
+
const deviceCode = cleanString(record.deviceCode);
|
|
518
|
+
const expiresAt = cleanString(record.expiresAt);
|
|
519
|
+
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
520
|
+
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
521
|
+
return null;
|
|
522
|
+
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
523
|
+
}
|
|
524
|
+
function parsePendingDevices(value) {
|
|
525
|
+
if (!Array.isArray(value))
|
|
526
|
+
return [];
|
|
527
|
+
return value.flatMap((entry) => {
|
|
528
|
+
const pending = parsePendingDevice(entry);
|
|
529
|
+
return pending ? [pending] : [];
|
|
530
|
+
});
|
|
531
|
+
}
|
|
475
532
|
function readStoredAuth(stateFile) {
|
|
476
|
-
if (!
|
|
533
|
+
if (!existsSync4(stateFile))
|
|
477
534
|
return {};
|
|
478
535
|
try {
|
|
479
|
-
const parsed = JSON.parse(
|
|
536
|
+
const parsed = JSON.parse(readFileSync2(stateFile, "utf8"));
|
|
480
537
|
return {
|
|
481
538
|
...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
|
|
482
539
|
login: cleanString(parsed.login),
|
|
@@ -485,37 +542,44 @@ function readStoredAuth(stateFile) {
|
|
|
485
542
|
selectedRepo: cleanString(parsed.selectedRepo),
|
|
486
543
|
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
487
544
|
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
545
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
546
|
+
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
488
547
|
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
489
548
|
};
|
|
490
549
|
} catch {
|
|
491
550
|
return {};
|
|
492
551
|
}
|
|
493
552
|
}
|
|
494
|
-
function
|
|
495
|
-
|
|
496
|
-
return null;
|
|
497
|
-
const record = value;
|
|
498
|
-
const pollId = cleanString(record.pollId);
|
|
499
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
500
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
501
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
502
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
503
|
-
return null;
|
|
504
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
553
|
+
function newApiSessionToken() {
|
|
554
|
+
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
505
555
|
}
|
|
506
556
|
function writeStoredAuth(stateFile, payload) {
|
|
507
|
-
|
|
508
|
-
|
|
557
|
+
mkdirSync4(dirname4(stateFile), { recursive: true });
|
|
558
|
+
writeFileSync4(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
509
559
|
`, { encoding: "utf8", mode: 384 });
|
|
510
560
|
try {
|
|
511
561
|
chmodSync(stateFile, 384);
|
|
512
562
|
} catch {}
|
|
513
563
|
}
|
|
564
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
565
|
+
return resolve7(projectRoot, ".rig", "state", "github-auth.json");
|
|
566
|
+
}
|
|
514
567
|
function resolveGitHubAuthStateFile(projectRoot) {
|
|
515
|
-
return
|
|
568
|
+
return resolve7(resolveServerAuthorityPaths(projectRoot).stateDir, "github-auth.json");
|
|
516
569
|
}
|
|
517
|
-
function
|
|
518
|
-
const
|
|
570
|
+
function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
|
|
571
|
+
const targetFile = localProjectAuthStateFile(projectRoot);
|
|
572
|
+
mkdirSync4(dirname4(targetFile), { recursive: true });
|
|
573
|
+
if (existsSync4(stateFile)) {
|
|
574
|
+
copyFileSync(stateFile, targetFile);
|
|
575
|
+
try {
|
|
576
|
+
chmodSync(targetFile, 384);
|
|
577
|
+
} catch {}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
writeStoredAuth(targetFile, {});
|
|
581
|
+
}
|
|
582
|
+
function createGitHubAuthStoreFromStateFile(stateFile) {
|
|
519
583
|
return {
|
|
520
584
|
stateFile,
|
|
521
585
|
status(options) {
|
|
@@ -545,14 +609,53 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
545
609
|
scopes: input.scopes ?? [],
|
|
546
610
|
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
547
611
|
pendingDevice: null,
|
|
612
|
+
pendingDevices: [],
|
|
613
|
+
apiSessions: previous.apiSessions ?? [],
|
|
548
614
|
updatedAt: new Date().toISOString()
|
|
549
615
|
});
|
|
550
616
|
},
|
|
617
|
+
createApiSession() {
|
|
618
|
+
const previous = readStoredAuth(stateFile);
|
|
619
|
+
const token = newApiSessionToken();
|
|
620
|
+
const session = {
|
|
621
|
+
token,
|
|
622
|
+
login: cleanString(previous.login),
|
|
623
|
+
userId: cleanString(previous.userId),
|
|
624
|
+
createdAt: new Date().toISOString()
|
|
625
|
+
};
|
|
626
|
+
writeStoredAuth(stateFile, {
|
|
627
|
+
...previous,
|
|
628
|
+
apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
|
|
629
|
+
updatedAt: new Date().toISOString()
|
|
630
|
+
});
|
|
631
|
+
return { token, login: session.login ?? null, userId: session.userId ?? null };
|
|
632
|
+
},
|
|
633
|
+
readApiSession(token) {
|
|
634
|
+
const clean = cleanString(token);
|
|
635
|
+
if (!clean)
|
|
636
|
+
return null;
|
|
637
|
+
const previous = readStoredAuth(stateFile);
|
|
638
|
+
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
639
|
+
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
640
|
+
},
|
|
641
|
+
copyToProjectRoot(projectRoot) {
|
|
642
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
643
|
+
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
644
|
+
},
|
|
645
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
646
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
647
|
+
},
|
|
551
648
|
savePendingDevice(input) {
|
|
552
649
|
const previous = readStoredAuth(stateFile);
|
|
650
|
+
const pendingDevices = [
|
|
651
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
652
|
+
...previous.pendingDevices ?? [],
|
|
653
|
+
input
|
|
654
|
+
].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
|
|
553
655
|
writeStoredAuth(stateFile, {
|
|
554
656
|
...previous,
|
|
555
|
-
pendingDevice:
|
|
657
|
+
pendingDevice: null,
|
|
658
|
+
pendingDevices,
|
|
556
659
|
updatedAt: new Date().toISOString()
|
|
557
660
|
});
|
|
558
661
|
},
|
|
@@ -565,23 +668,32 @@ function createGitHubAuthStore(projectRoot) {
|
|
|
565
668
|
});
|
|
566
669
|
},
|
|
567
670
|
readPendingDevice(pollId) {
|
|
568
|
-
const
|
|
569
|
-
|
|
671
|
+
const previous = readStoredAuth(stateFile);
|
|
672
|
+
const pending = [
|
|
673
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
674
|
+
...previous.pendingDevices ?? []
|
|
675
|
+
].find((entry) => entry.pollId === pollId) ?? null;
|
|
676
|
+
if (!pending)
|
|
570
677
|
return null;
|
|
571
678
|
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
572
679
|
return null;
|
|
573
680
|
return pending;
|
|
574
681
|
},
|
|
575
|
-
clearPendingDevice() {
|
|
682
|
+
clearPendingDevice(pollId) {
|
|
576
683
|
const previous = readStoredAuth(stateFile);
|
|
684
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
577
685
|
writeStoredAuth(stateFile, {
|
|
578
686
|
...previous,
|
|
579
687
|
pendingDevice: null,
|
|
688
|
+
pendingDevices: remaining,
|
|
580
689
|
updatedAt: new Date().toISOString()
|
|
581
690
|
});
|
|
582
691
|
}
|
|
583
692
|
};
|
|
584
693
|
}
|
|
694
|
+
function createGitHubAuthStore(projectRoot) {
|
|
695
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
696
|
+
}
|
|
585
697
|
|
|
586
698
|
// packages/server/src/server-helpers/github-projects.ts
|
|
587
699
|
function asRecord(value) {
|
|
@@ -775,10 +887,10 @@ function extractGitHubIssueNodeId(task) {
|
|
|
775
887
|
}
|
|
776
888
|
|
|
777
889
|
// packages/server/src/server-helpers/http-router.ts
|
|
778
|
-
var
|
|
890
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
779
891
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
780
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
781
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
892
|
+
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
893
|
+
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
782
894
|
};
|
|
783
895
|
|
|
784
896
|
// packages/server/src/server-helpers/ws-router.ts
|
|
@@ -917,10 +1029,10 @@ var CLUSTERS = {
|
|
|
917
1029
|
};
|
|
918
1030
|
|
|
919
1031
|
// packages/server/src/server-helpers/task-config.ts
|
|
920
|
-
import { existsSync as
|
|
1032
|
+
import { existsSync as existsSync5 } from "fs";
|
|
921
1033
|
async function readTaskConfig(projectRoot) {
|
|
922
1034
|
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
923
|
-
if (!
|
|
1035
|
+
if (!existsSync5(taskConfigPath)) {
|
|
924
1036
|
return {};
|
|
925
1037
|
}
|
|
926
1038
|
try {
|
|
@@ -937,8 +1049,8 @@ var serverPathEnvQueue = Promise.resolve();
|
|
|
937
1049
|
async function withServerPathEnv(projectRoot, fn) {
|
|
938
1050
|
const waitForTurn = serverPathEnvQueue;
|
|
939
1051
|
let releaseTurn;
|
|
940
|
-
serverPathEnvQueue = new Promise((
|
|
941
|
-
releaseTurn =
|
|
1052
|
+
serverPathEnvQueue = new Promise((resolve9) => {
|
|
1053
|
+
releaseTurn = resolve9;
|
|
942
1054
|
});
|
|
943
1055
|
await waitForTurn;
|
|
944
1056
|
const paths = resolveServerAuthorityPaths(projectRoot);
|
|
@@ -974,9 +1086,9 @@ async function withServerAuthorityEnvIfNeeded(projectRoot, fn) {
|
|
|
974
1086
|
return withServerPathEnv(projectRoot, fn);
|
|
975
1087
|
}
|
|
976
1088
|
async function readWorkspaceTasks(projectRoot) {
|
|
977
|
-
const issuesPath =
|
|
1089
|
+
const issuesPath = resolve8(resolveMonorepoRoot5(projectRoot), ".beads", "issues.jsonl");
|
|
978
1090
|
const taskConfig = await readTaskConfig(projectRoot);
|
|
979
|
-
if (!
|
|
1091
|
+
if (!existsSync6(issuesPath)) {
|
|
980
1092
|
return [];
|
|
981
1093
|
}
|
|
982
1094
|
const latestById = new Map;
|
|
@@ -995,7 +1107,7 @@ async function readWorkspaceTasks(projectRoot) {
|
|
|
995
1107
|
description: normalizeString(entry.description),
|
|
996
1108
|
acceptanceCriteria: normalizeString(entry.acceptance_criteria),
|
|
997
1109
|
status: normalizedStatus ?? "unknown",
|
|
998
|
-
sourceStatus: normalizedStatus ? null : rawStatus,
|
|
1110
|
+
sourceStatus: normalizedStatus && rawStatus !== normalizedStatus ? rawStatus : normalizedStatus ? null : rawStatus,
|
|
999
1111
|
priority: typeof entry.priority === "number" ? entry.priority : typeof entry.priority === "string" ? Number(entry.priority) : null,
|
|
1000
1112
|
issueType: normalizeString(entry.issue_type),
|
|
1001
1113
|
role: normalizeString(config.role) ?? null,
|
|
@@ -1022,7 +1134,7 @@ async function readWorkspaceTasks(projectRoot) {
|
|
|
1022
1134
|
if (false) {}
|
|
1023
1135
|
|
|
1024
1136
|
// packages/server/src/server-helpers/validation-failure.ts
|
|
1025
|
-
import { resolve as
|
|
1137
|
+
import { resolve as resolve9 } from "path";
|
|
1026
1138
|
import {
|
|
1027
1139
|
readJsonFile as readJsonFile4,
|
|
1028
1140
|
resolveTaskArtifactDirs
|
|
@@ -1036,7 +1148,7 @@ function summarizeRunValidationFailure(projectRoot, run) {
|
|
|
1036
1148
|
continue;
|
|
1037
1149
|
}
|
|
1038
1150
|
seen.add(artifactRoot);
|
|
1039
|
-
const summary = readJsonFile4(
|
|
1151
|
+
const summary = readJsonFile4(resolve9(artifactRoot, "validation-summary.json"), null);
|
|
1040
1152
|
if (!summary || summary.status !== "fail") {
|
|
1041
1153
|
continue;
|
|
1042
1154
|
}
|
|
@@ -1120,15 +1232,36 @@ async function syncProjectStatusForRunLifecycle(projectRoot, run, status, config
|
|
|
1120
1232
|
if (!run.taskId)
|
|
1121
1233
|
return;
|
|
1122
1234
|
const issueNodeId = extractGitHubIssueNodeId(runSourceTaskIdentity(run));
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1235
|
+
try {
|
|
1236
|
+
const result = await syncGitHubProjectStatusForTaskUpdate({
|
|
1237
|
+
taskId: run.taskId,
|
|
1238
|
+
status,
|
|
1239
|
+
issueNodeId,
|
|
1240
|
+
token: createGitHubAuthStore(projectRoot).readToken(),
|
|
1241
|
+
config
|
|
1242
|
+
});
|
|
1243
|
+
if (!result.synced && result.reason !== "project-sync-disabled") {
|
|
1244
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
1245
|
+
id: `log:${run.runId}:github-project-sync:${status}`,
|
|
1246
|
+
title: "GitHub Project sync skipped",
|
|
1247
|
+
detail: `Project status sync for ${run.taskId} could not run: ${result.reason}.`,
|
|
1248
|
+
tone: "warn",
|
|
1249
|
+
status: "running",
|
|
1250
|
+
createdAt: new Date().toISOString(),
|
|
1251
|
+
payload: { reason: result.reason, issueNodeId }
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
appendRunLogEntry(projectRoot, run.runId, {
|
|
1256
|
+
id: `log:${run.runId}:github-project-sync-error:${status}`,
|
|
1257
|
+
title: "GitHub Project sync failed",
|
|
1258
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1259
|
+
tone: "error",
|
|
1260
|
+
status: "running",
|
|
1261
|
+
createdAt: new Date().toISOString(),
|
|
1262
|
+
payload: { issueNodeId }
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1132
1265
|
}
|
|
1133
1266
|
async function autoAssignRunIssue(projectRoot, run) {
|
|
1134
1267
|
if (!run.taskId)
|
|
@@ -1230,11 +1363,23 @@ function assertNoActiveRunForTask(projectRoot, taskId, newRunId) {
|
|
|
1230
1363
|
return;
|
|
1231
1364
|
throw new Error(`Task ${taskId} already has an active Rig run: ${existing.runId}`);
|
|
1232
1365
|
}
|
|
1366
|
+
async function resolveSourceTaskForRun(projectRoot, taskId, readTasks) {
|
|
1367
|
+
const fromReader = (await readTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
1368
|
+
if (fromReader)
|
|
1369
|
+
return fromReader;
|
|
1370
|
+
const projected = readTaskProjection(projectRoot)?.tasks.find((task) => String(task.id) === taskId) ?? null;
|
|
1371
|
+
if (projected)
|
|
1372
|
+
return projected;
|
|
1373
|
+
if (readTasks !== readWorkspaceTasks) {
|
|
1374
|
+
return (await readWorkspaceTasks(projectRoot)).find((task) => task.id === taskId) ?? null;
|
|
1375
|
+
}
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1233
1378
|
async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTasks) {
|
|
1234
1379
|
if ("taskId" in input && input.taskId) {
|
|
1235
1380
|
assertNoActiveRunForTask(projectRoot, input.taskId, input.runId);
|
|
1236
1381
|
}
|
|
1237
|
-
const sourceTask = "taskId" in input && input.taskId ?
|
|
1382
|
+
const sourceTask = "taskId" in input && input.taskId ? await resolveSourceTaskForRun(projectRoot, input.taskId, readTasks) : null;
|
|
1238
1383
|
const taskTitle = sourceTask?.title ?? ("taskId" in input && input.taskId ? input.taskId : null);
|
|
1239
1384
|
const runDir = resolveAuthorityRunDir4(projectRoot, input.runId);
|
|
1240
1385
|
const runRecord = {
|
|
@@ -1268,11 +1413,11 @@ async function createRunRecord(projectRoot, input, readTasks = readWorkspaceTask
|
|
|
1268
1413
|
initiatedBy: input.initiatedBy ?? null,
|
|
1269
1414
|
...sourceTask ? { sourceTask: sourceTaskContract(sourceTask) } : {}
|
|
1270
1415
|
};
|
|
1271
|
-
|
|
1272
|
-
|
|
1416
|
+
mkdirSync6(runDir, { recursive: true });
|
|
1417
|
+
writeFileSync6(resolve10(runDir, "run.json"), `${JSON.stringify(runRecord, null, 2)}
|
|
1273
1418
|
`, "utf8");
|
|
1274
1419
|
if ("initialPrompt" in input && input.initialPrompt && input.initialPrompt.trim().length > 0) {
|
|
1275
|
-
|
|
1420
|
+
writeFileSync6(resolve10(runDir, "timeline.jsonl"), `${JSON.stringify({
|
|
1276
1421
|
id: `message-${Date.now()}`,
|
|
1277
1422
|
type: "user_message",
|
|
1278
1423
|
text: input.initialPrompt,
|
|
@@ -1306,6 +1451,7 @@ async function startLocalRun(state, runId, options) {
|
|
|
1306
1451
|
throw new Error(`Run not found: ${runId}`);
|
|
1307
1452
|
}
|
|
1308
1453
|
const startedAt = new Date().toISOString();
|
|
1454
|
+
const resumeMode = options?.resume === true;
|
|
1309
1455
|
state.runProcesses.set(runId, {
|
|
1310
1456
|
runId,
|
|
1311
1457
|
child: null,
|
|
@@ -1322,9 +1468,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
1322
1468
|
summary: run.title
|
|
1323
1469
|
});
|
|
1324
1470
|
appendRunLogEntry(state.projectRoot, runId, {
|
|
1325
|
-
id: `log:${runId}:prepare`,
|
|
1326
|
-
title: "Rig task run starting",
|
|
1327
|
-
detail: run.taskId ?? run.title,
|
|
1471
|
+
id: `log:${runId}:${resumeMode ? "resume" : "prepare"}`,
|
|
1472
|
+
title: resumeMode ? "Rig task run resuming" : "Rig task run starting",
|
|
1473
|
+
detail: resumeMode ? `Resuming ${run.taskId ?? run.title ?? runId} after server restart or operator resume.` : run.taskId ?? run.title,
|
|
1328
1474
|
tone: "info",
|
|
1329
1475
|
status: "preparing",
|
|
1330
1476
|
createdAt: startedAt
|
|
@@ -1332,8 +1478,8 @@ async function startLocalRun(state, runId, options) {
|
|
|
1332
1478
|
broadcastRunLogAppended(state, runId, readLatestRawRunLog(state.projectRoot, runId));
|
|
1333
1479
|
broadcastSnapshotInvalidation(state);
|
|
1334
1480
|
const cliProjectRoot = resolveLocalRunCliProjectRoot(state.projectRoot);
|
|
1335
|
-
const cliEntryPoint =
|
|
1336
|
-
if (!
|
|
1481
|
+
const cliEntryPoint = resolve10(cliProjectRoot, "packages/cli/bin/rig.ts");
|
|
1482
|
+
if (!existsSync7(cliEntryPoint)) {
|
|
1337
1483
|
const completedAt = new Date().toISOString();
|
|
1338
1484
|
const failureSummary = `Rig task-run entrypoint missing at ${relative2(state.projectRoot, cliEntryPoint)}`;
|
|
1339
1485
|
patchRunRecord(state.projectRoot, runId, {
|
|
@@ -1402,7 +1548,15 @@ async function startLocalRun(state, runId, options) {
|
|
|
1402
1548
|
RIG_SERVER_INTERNAL_EXEC: "1",
|
|
1403
1549
|
...serverUrl ? { RIG_SERVER_URL: serverUrl } : {},
|
|
1404
1550
|
...bridgeAuthToken ? { RIG_AUTH_TOKEN: bridgeAuthToken } : {},
|
|
1405
|
-
...bridgeGitHubToken ? {
|
|
1551
|
+
...bridgeGitHubToken ? {
|
|
1552
|
+
RIG_GITHUB_TOKEN: bridgeGitHubToken,
|
|
1553
|
+
GITHUB_TOKEN: bridgeGitHubToken,
|
|
1554
|
+
GH_TOKEN: bridgeGitHubToken
|
|
1555
|
+
} : {},
|
|
1556
|
+
...resumeMode ? {
|
|
1557
|
+
RIG_RUN_RESUME: "1",
|
|
1558
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
1559
|
+
} : {}
|
|
1406
1560
|
},
|
|
1407
1561
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1408
1562
|
});
|
|
@@ -1442,9 +1596,9 @@ async function startLocalRun(state, runId, options) {
|
|
|
1442
1596
|
handleRunProcessOutput(Buffer.isBuffer(data) ? data.toString("utf8") : String(data), "error", "Rig task run stderr");
|
|
1443
1597
|
});
|
|
1444
1598
|
try {
|
|
1445
|
-
const exit = await new Promise((
|
|
1446
|
-
child.once("error", (error) =>
|
|
1447
|
-
child.once("close", (code, signal) =>
|
|
1599
|
+
const exit = await new Promise((resolve11) => {
|
|
1600
|
+
child.once("error", (error) => resolve11({ code: 1, signal: null, error }));
|
|
1601
|
+
child.once("close", (code, signal) => resolve11({ code, signal }));
|
|
1448
1602
|
});
|
|
1449
1603
|
if (exit.error) {
|
|
1450
1604
|
throw new Error(`Failed to start task run: ${exit.error.message}`);
|
|
@@ -1544,17 +1698,17 @@ function resolveLocalRunCliProjectRoot(projectRoot) {
|
|
|
1544
1698
|
process.env.PROJECT_RIG_ROOT?.trim()
|
|
1545
1699
|
].filter((value) => !!value);
|
|
1546
1700
|
for (const candidate of envCandidates) {
|
|
1547
|
-
if (
|
|
1548
|
-
return
|
|
1701
|
+
if (existsSync7(resolve10(candidate, "packages/cli/bin/rig.ts"))) {
|
|
1702
|
+
return resolve10(candidate);
|
|
1549
1703
|
}
|
|
1550
1704
|
}
|
|
1551
|
-
if (
|
|
1705
|
+
if (existsSync7(resolve10(projectRoot, "packages/cli/bin/rig.ts"))) {
|
|
1552
1706
|
return projectRoot;
|
|
1553
1707
|
}
|
|
1554
1708
|
try {
|
|
1555
1709
|
const monorepoRoot = resolveMonorepoRoot6(projectRoot);
|
|
1556
|
-
const outerProjectRoot =
|
|
1557
|
-
if (
|
|
1710
|
+
const outerProjectRoot = dirname6(dirname6(monorepoRoot));
|
|
1711
|
+
if (existsSync7(resolve10(outerProjectRoot, "packages/cli/bin/rig.ts"))) {
|
|
1558
1712
|
return outerProjectRoot;
|
|
1559
1713
|
}
|
|
1560
1714
|
} catch {}
|
|
@@ -1574,15 +1728,15 @@ async function resumeRunRecord(state, input) {
|
|
|
1574
1728
|
if (run.status === "completed") {
|
|
1575
1729
|
throw new Error("Completed runs cannot be resumed.");
|
|
1576
1730
|
}
|
|
1577
|
-
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null });
|
|
1731
|
+
await startLocalRun(state, input.runId, { promptOverride: input.promptOverride ?? null, resume: input.restart !== true });
|
|
1578
1732
|
}
|
|
1579
1733
|
function appendRunMessage(projectRoot, input) {
|
|
1580
1734
|
const run = readAuthorityRun8(projectRoot, input.runId);
|
|
1581
1735
|
if (!run) {
|
|
1582
1736
|
throw new Error(`Run not found: ${input.runId}`);
|
|
1583
1737
|
}
|
|
1584
|
-
const timelinePath =
|
|
1585
|
-
const existingLines = fileExists(timelinePath) ?
|
|
1738
|
+
const timelinePath = resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "timeline.jsonl");
|
|
1739
|
+
const existingLines = fileExists(timelinePath) ? readFileSync4(timelinePath, "utf8").trim() : "";
|
|
1586
1740
|
const nextLine = JSON.stringify({
|
|
1587
1741
|
id: input.messageId,
|
|
1588
1742
|
type: "user_message",
|
|
@@ -1590,11 +1744,11 @@ function appendRunMessage(projectRoot, input) {
|
|
|
1590
1744
|
attachments: input.attachments ?? [],
|
|
1591
1745
|
createdAt: input.createdAt
|
|
1592
1746
|
});
|
|
1593
|
-
|
|
1747
|
+
writeFileSync6(timelinePath, existingLines.length > 0 ? `${existingLines}
|
|
1594
1748
|
${nextLine}
|
|
1595
1749
|
` : `${nextLine}
|
|
1596
1750
|
`, "utf8");
|
|
1597
|
-
writeJsonFile4(
|
|
1751
|
+
writeJsonFile4(resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), {
|
|
1598
1752
|
...run,
|
|
1599
1753
|
updatedAt: input.createdAt
|
|
1600
1754
|
});
|
|
@@ -1620,7 +1774,7 @@ async function stopRunRecord(stateOrProjectRoot, input) {
|
|
|
1620
1774
|
completedAt: run.completedAt ?? input.createdAt,
|
|
1621
1775
|
updatedAt: input.createdAt
|
|
1622
1776
|
};
|
|
1623
|
-
writeJsonFile4(
|
|
1777
|
+
writeJsonFile4(resolve10(resolveAuthorityRunDir4(projectRoot, input.runId), "run.json"), nextRun);
|
|
1624
1778
|
if (run.status !== "completed" && run.taskId) {
|
|
1625
1779
|
const taskId = run.taskId;
|
|
1626
1780
|
(async () => {
|
|
@@ -1658,34 +1812,12 @@ function removeTaskIdsFromQueueState2(projectRoot, taskIds) {
|
|
|
1658
1812
|
writeQueueState(projectRoot, next);
|
|
1659
1813
|
return next;
|
|
1660
1814
|
}
|
|
1661
|
-
var
|
|
1662
|
-
function
|
|
1663
|
-
|
|
1664
|
-
for (const run of runs) {
|
|
1815
|
+
var RESUMABLE_LOCAL_RUN_STATUSES = new Set(["created", "preparing", "running", "validating", "reviewing"]);
|
|
1816
|
+
function collectResumableLocalRuns(state, runs) {
|
|
1817
|
+
return runs.filter((run) => {
|
|
1665
1818
|
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;
|
|
1819
|
+
return run.mode === "local" && RESUMABLE_LOCAL_RUN_STATUSES.has(status) && !state.runProcesses.has(run.runId);
|
|
1820
|
+
});
|
|
1689
1821
|
}
|
|
1690
1822
|
async function reconcileScheduler(state, reason) {
|
|
1691
1823
|
if (state.scheduler.reconciling) {
|
|
@@ -1700,7 +1832,20 @@ async function reconcileScheduler(state, reason) {
|
|
|
1700
1832
|
const queue = readQueueState(state.projectRoot);
|
|
1701
1833
|
const tasks = await state.snapshotService.getWorkspaceTasks();
|
|
1702
1834
|
let runs = listAuthorityRuns7(state.projectRoot);
|
|
1703
|
-
let changed =
|
|
1835
|
+
let changed = false;
|
|
1836
|
+
const resumableRuns = collectResumableLocalRuns(state, runs);
|
|
1837
|
+
for (const run of resumableRuns) {
|
|
1838
|
+
appendRunLogEntry(state.projectRoot, run.runId, {
|
|
1839
|
+
id: `log:${run.runId}:auto-resume:${Date.now()}`,
|
|
1840
|
+
title: "Run auto-resume scheduled",
|
|
1841
|
+
detail: `Rig server recovered nonterminal run ${run.runId} after ${reason}; resuming the same lifecycle instead of restarting it.`,
|
|
1842
|
+
tone: "info",
|
|
1843
|
+
status: "preparing",
|
|
1844
|
+
createdAt: new Date().toISOString()
|
|
1845
|
+
});
|
|
1846
|
+
await startLocalRun(state, run.runId, { resume: true });
|
|
1847
|
+
changed = true;
|
|
1848
|
+
}
|
|
1704
1849
|
if (changed) {
|
|
1705
1850
|
runs = listAuthorityRuns7(state.projectRoot);
|
|
1706
1851
|
}
|
|
@@ -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
|