@h-rig/runtime 0.0.6-alpha.31 → 0.0.6-alpha.33
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/bin/rig-agent-dispatch.js +12 -0
- package/dist/bin/rig-agent.js +12 -14
- package/dist/src/control-plane/agent-wrapper.js +12 -0
- package/dist/src/control-plane/harness-main.js +1353 -1398
- package/dist/src/control-plane/hooks/completion-verification.js +510 -511
- package/dist/src/control-plane/hooks/inject-context.js +333 -333
- package/dist/src/control-plane/hooks/submodule-branch.js +1600 -1600
- package/dist/src/control-plane/hooks/task-runtime-start.js +1600 -1600
- package/dist/src/control-plane/native/git-ops.js +78 -151
- package/dist/src/control-plane/native/harness-cli.js +1353 -1398
- package/dist/src/control-plane/native/repo-ops.js +50 -50
- package/dist/src/control-plane/native/task-ops.js +1467 -1503
- package/dist/src/control-plane/native/verifier.js +34 -34
- package/dist/src/control-plane/pi-sessiond/bin.js +3 -2
- package/dist/src/control-plane/pi-sessiond/launcher.js +12 -0
- package/dist/src/control-plane/pi-sessiond/server.js +3 -2
- package/dist/src/control-plane/pi-sessiond/session-service.js +3 -2
- package/package.json +8 -8
|
@@ -457,121 +457,47 @@ function readBuildConfig() {
|
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
// packages/runtime/src/control-plane/
|
|
461
|
-
import { existsSync as
|
|
462
|
-
import { resolve as resolve7 } from "path";
|
|
463
|
-
|
|
464
|
-
// packages/runtime/src/control-plane/runtime/tooling/shell.ts
|
|
465
|
-
import { chmodSync, copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
460
|
+
// packages/runtime/src/control-plane/native/git-native.ts
|
|
461
|
+
import { chmodSync, copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
466
462
|
import { tmpdir } from "os";
|
|
467
|
-
import {
|
|
468
|
-
|
|
469
|
-
var
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (!sourcePath) {
|
|
476
|
-
const bundledBinary = resolveBundledRigShellBinaryPath();
|
|
477
|
-
if (bundledBinary) {
|
|
478
|
-
return bundledBinary;
|
|
479
|
-
}
|
|
480
|
-
throw new Error("rig-shell.zig source file not found.");
|
|
481
|
-
}
|
|
482
|
-
const zigBinary = Bun.which("zig");
|
|
483
|
-
if (!zigBinary) {
|
|
484
|
-
throw new Error("zig is required to build the native Rig shell.");
|
|
485
|
-
}
|
|
486
|
-
mkdirSync2(dirname3(outputPath), { recursive: true });
|
|
487
|
-
const sourceDigest = await sha256File(sourcePath);
|
|
488
|
-
const buildKey = JSON.stringify({
|
|
489
|
-
version: 1,
|
|
490
|
-
zigBinary,
|
|
491
|
-
platform: process.platform,
|
|
492
|
-
arch: process.arch,
|
|
493
|
-
sourcePath,
|
|
494
|
-
sourceDigest
|
|
495
|
-
});
|
|
496
|
-
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
497
|
-
const needsBuild = !existsSync4(outputPath) || !await hasMatchingNativeBuildManifest(manifestPath, buildKey);
|
|
498
|
-
if (!needsBuild) {
|
|
499
|
-
return outputPath;
|
|
500
|
-
}
|
|
501
|
-
const build = Bun.spawn([
|
|
502
|
-
zigBinary,
|
|
503
|
-
"build-exe",
|
|
504
|
-
sourcePath,
|
|
505
|
-
"-O",
|
|
506
|
-
"ReleaseFast",
|
|
507
|
-
`-femit-bin=${outputPath}`
|
|
508
|
-
], {
|
|
509
|
-
cwd: dirname3(sourcePath),
|
|
510
|
-
stdout: "pipe",
|
|
511
|
-
stderr: "pipe"
|
|
512
|
-
});
|
|
513
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
514
|
-
build.exited,
|
|
515
|
-
new Response(build.stdout).text(),
|
|
516
|
-
new Response(build.stderr).text()
|
|
517
|
-
]);
|
|
518
|
-
if (exitCode !== 0 || !existsSync4(outputPath)) {
|
|
519
|
-
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join(`
|
|
520
|
-
`);
|
|
521
|
-
throw new Error(`Failed to build native Rig shell: ${details || `zig exited with code ${exitCode}`}`);
|
|
522
|
-
}
|
|
523
|
-
await Bun.write(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
524
|
-
`);
|
|
525
|
-
return outputPath;
|
|
526
|
-
}
|
|
527
|
-
async function materializeRigShellBinary(targetDir) {
|
|
528
|
-
const sourcePath = await ensureRigShellBinaryPath();
|
|
529
|
-
const targetPath = resolve4(targetDir, runtimeRigShellFileName());
|
|
530
|
-
mkdirSync2(targetDir, { recursive: true });
|
|
531
|
-
const sourceDigest = await sha256File(sourcePath);
|
|
532
|
-
const buildKey = JSON.stringify({
|
|
533
|
-
version: 1,
|
|
534
|
-
sourcePath,
|
|
535
|
-
sourceDigest
|
|
536
|
-
});
|
|
537
|
-
const needsCopy = !existsSync4(targetPath) || !await hasMatchingNativeBuildManifest(nativeBuildManifestPath(targetPath), buildKey);
|
|
538
|
-
if (needsCopy) {
|
|
539
|
-
copyFileSync(sourcePath, targetPath);
|
|
540
|
-
chmodSync(targetPath, 493);
|
|
541
|
-
await Bun.write(nativeBuildManifestPath(targetPath), `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
542
|
-
`);
|
|
543
|
-
}
|
|
544
|
-
return targetPath;
|
|
463
|
+
import { dirname as dirname3, isAbsolute, resolve as resolve4 } from "path";
|
|
464
|
+
import { createHash } from "crypto";
|
|
465
|
+
var sharedGitNativeOutputDir = resolve4(tmpdir(), "rig-native");
|
|
466
|
+
var sharedGitNativeOutputPath = resolve4(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
467
|
+
var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
468
|
+
function temporaryGitBinaryOutputPath(outputPath) {
|
|
469
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
470
|
+
return resolve4(dirname3(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
|
|
545
471
|
}
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
472
|
+
function publishGitBinary(tempOutputPath, outputPath) {
|
|
473
|
+
try {
|
|
474
|
+
renameSync(tempOutputPath, outputPath);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (process.platform === "win32" && existsSync4(outputPath)) {
|
|
477
|
+
rmSync(outputPath, { force: true });
|
|
478
|
+
renameSync(tempOutputPath, outputPath);
|
|
479
|
+
return;
|
|
550
480
|
}
|
|
481
|
+
throw error;
|
|
551
482
|
}
|
|
552
|
-
return null;
|
|
553
483
|
}
|
|
554
|
-
function
|
|
555
|
-
|
|
556
|
-
if (candidate && existsSync4(candidate)) {
|
|
557
|
-
return candidate;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return null;
|
|
484
|
+
function runtimeRigGitFileName() {
|
|
485
|
+
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
561
486
|
}
|
|
562
|
-
function
|
|
487
|
+
function rigGitSourceCandidates() {
|
|
563
488
|
const execDir = process.execPath?.trim() ? dirname3(process.execPath.trim()) : "";
|
|
564
489
|
const cwd = process.cwd()?.trim() || "";
|
|
565
490
|
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
566
491
|
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
492
|
+
const moduleRelativeSource = resolve4(import.meta.dir, "../../../native/rig-git.zig");
|
|
567
493
|
return [...new Set([
|
|
568
|
-
process.env.
|
|
569
|
-
|
|
570
|
-
projectRoot ? resolve4(projectRoot, "packages/runtime/native/rig-
|
|
571
|
-
hostProjectRoot ? resolve4(hostProjectRoot, "packages/runtime/native/rig-
|
|
572
|
-
|
|
573
|
-
execDir ? resolve4(execDir, "..", "
|
|
574
|
-
resolve4(
|
|
494
|
+
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
495
|
+
moduleRelativeSource,
|
|
496
|
+
projectRoot ? resolve4(projectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
497
|
+
hostProjectRoot ? resolve4(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
498
|
+
cwd ? resolve4(cwd, "packages/runtime/native/rig-git.zig") : "",
|
|
499
|
+
execDir ? resolve4(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
|
|
500
|
+
execDir ? resolve4(execDir, "..", "native", "rig-git.zig") : ""
|
|
575
501
|
].filter(Boolean))];
|
|
576
502
|
}
|
|
577
503
|
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
@@ -586,55 +512,57 @@ function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
|
586
512
|
}
|
|
587
513
|
return candidates;
|
|
588
514
|
}
|
|
589
|
-
function
|
|
515
|
+
function rigGitBinaryCandidates() {
|
|
590
516
|
const execDir = process.execPath?.trim() ? dirname3(process.execPath.trim()) : "";
|
|
591
|
-
const fileName =
|
|
517
|
+
const fileName = runtimeRigGitFileName();
|
|
518
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
592
519
|
return [...new Set([
|
|
593
|
-
|
|
520
|
+
explicit,
|
|
594
521
|
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
595
522
|
execDir ? resolve4(execDir, fileName) : "",
|
|
596
523
|
execDir ? resolve4(execDir, "..", fileName) : "",
|
|
597
|
-
execDir ? resolve4(execDir, "..", "bin", fileName) : ""
|
|
524
|
+
execDir ? resolve4(execDir, "..", "bin", fileName) : "",
|
|
525
|
+
sharedGitNativeOutputPath
|
|
598
526
|
].filter(Boolean))];
|
|
599
527
|
}
|
|
600
|
-
function
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
"
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
"
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
528
|
+
function resolveGitSourcePath() {
|
|
529
|
+
for (const candidate of rigGitSourceCandidates()) {
|
|
530
|
+
if (candidate && existsSync4(candidate)) {
|
|
531
|
+
return candidate;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
function resolveGitBinaryPath() {
|
|
537
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
for (const candidate of rigGitBinaryCandidates()) {
|
|
541
|
+
if (candidate && existsSync4(candidate)) {
|
|
542
|
+
return candidate;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
function preferredGitBinaryOutputPath() {
|
|
548
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
549
|
+
return explicit || sharedGitNativeOutputPath;
|
|
550
|
+
}
|
|
551
|
+
function binarySupportsTrackerCommandsSync(binaryPath) {
|
|
552
|
+
try {
|
|
553
|
+
const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
|
|
554
|
+
stdout: "pipe",
|
|
555
|
+
stderr: "pipe"
|
|
556
|
+
});
|
|
557
|
+
const stdout = probe.stdout.toString().trim();
|
|
558
|
+
const stderr = probe.stderr.toString().trim();
|
|
559
|
+
if (stdout.includes('"error":"unknown command"')) {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
563
|
+
} catch {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
638
566
|
}
|
|
639
567
|
function nativeBuildManifestPath(outputPath) {
|
|
640
568
|
return `${outputPath}.build-manifest.json`;
|
|
@@ -650,71 +578,43 @@ async function hasMatchingNativeBuildManifest(manifestPath, buildKey) {
|
|
|
650
578
|
return false;
|
|
651
579
|
}
|
|
652
580
|
}
|
|
581
|
+
function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
|
|
582
|
+
if (!existsSync4(manifestPath)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
587
|
+
return manifest.version === 1 && manifest.buildKey === buildKey;
|
|
588
|
+
} catch {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
653
592
|
async function sha256File(path) {
|
|
654
593
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
655
594
|
hasher.update(await Bun.file(path).arrayBuffer());
|
|
656
595
|
return hasher.digest("hex");
|
|
657
596
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
import { existsSync as existsSync5, rmSync, symlinkSync } from "fs";
|
|
661
|
-
import { resolve as resolve5 } from "path";
|
|
662
|
-
function runtimeBrowserToolBinaryName() {
|
|
663
|
-
return `rig-browser-tool${process.platform === "win32" ? ".exe" : ""}`;
|
|
664
|
-
}
|
|
665
|
-
function runtimeBrowserToolNames() {
|
|
666
|
-
return [
|
|
667
|
-
"rig-browser-launch",
|
|
668
|
-
"rig-browser-check",
|
|
669
|
-
"rig-browser-attach-info",
|
|
670
|
-
"rig-browser-e2e",
|
|
671
|
-
"rig-browser-reset-profile"
|
|
672
|
-
];
|
|
597
|
+
function sha256FileSync(path) {
|
|
598
|
+
return createHash("sha256").update(readFileSync3(path)).digest("hex");
|
|
673
599
|
}
|
|
674
|
-
async function
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const toolPath = resolve5(targetDir, tool);
|
|
678
|
-
if (existsSync5(toolPath)) {
|
|
679
|
-
rmSync(toolPath, { force: true, recursive: true });
|
|
680
|
-
}
|
|
681
|
-
symlinkSync(binaryPath, toolPath);
|
|
600
|
+
async function ensureRigGitBinaryPath(outputPath = sharedGitNativeOutputPath) {
|
|
601
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
602
|
+
throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
|
|
682
603
|
}
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
// packages/runtime/src/control-plane/runtime/tooling/file-tools.ts
|
|
686
|
-
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync2, symlinkSync as symlinkSync2 } from "fs";
|
|
687
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
688
|
-
import { basename as basename3, dirname as dirname4, resolve as resolve6 } from "path";
|
|
689
|
-
var sharedNativeToolsOutputDir = resolve6(tmpdir2(), "rig-native");
|
|
690
|
-
var sharedNativeToolsOutputPath = resolve6(sharedNativeToolsOutputDir, `rig-tools-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
691
|
-
function runtimeRigToolsFileName() {
|
|
692
|
-
return `rig-tools${process.platform === "win32" ? ".exe" : ""}`;
|
|
693
|
-
}
|
|
694
|
-
function runtimeFileToolNames() {
|
|
695
|
-
return [
|
|
696
|
-
"rig-read",
|
|
697
|
-
"rig-write",
|
|
698
|
-
"rig-edit",
|
|
699
|
-
"rig-glob",
|
|
700
|
-
"rig-grep"
|
|
701
|
-
];
|
|
702
|
-
}
|
|
703
|
-
async function ensureRigToolsBinaryPath(outputPath = sharedNativeToolsOutputPath) {
|
|
704
|
-
const sourcePath = resolveRigToolsSourcePath();
|
|
604
|
+
const sourcePath = resolveGitSourcePath();
|
|
705
605
|
if (!sourcePath) {
|
|
706
|
-
const
|
|
707
|
-
if (
|
|
708
|
-
return
|
|
606
|
+
const binaryPath = resolveGitBinaryPath();
|
|
607
|
+
if (binaryPath) {
|
|
608
|
+
return binaryPath;
|
|
709
609
|
}
|
|
710
|
-
throw new Error("rig-
|
|
610
|
+
throw new Error("rig-git.zig source file not found.");
|
|
711
611
|
}
|
|
712
612
|
const zigBinary = Bun.which("zig");
|
|
713
613
|
if (!zigBinary) {
|
|
714
|
-
throw new Error("zig is required to build native Rig
|
|
614
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
715
615
|
}
|
|
716
|
-
|
|
717
|
-
const sourceDigest = await
|
|
616
|
+
mkdirSync2(dirname3(outputPath), { recursive: true });
|
|
617
|
+
const sourceDigest = await sha256File(sourcePath);
|
|
718
618
|
const buildKey = JSON.stringify({
|
|
719
619
|
version: 1,
|
|
720
620
|
zigBinary,
|
|
@@ -723,20 +623,22 @@ async function ensureRigToolsBinaryPath(outputPath = sharedNativeToolsOutputPath
|
|
|
723
623
|
sourcePath,
|
|
724
624
|
sourceDigest
|
|
725
625
|
});
|
|
726
|
-
const manifestPath =
|
|
727
|
-
const needsBuild = !
|
|
626
|
+
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
627
|
+
const needsBuild = !existsSync4(outputPath) || !await hasMatchingNativeBuildManifest(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
728
628
|
if (!needsBuild) {
|
|
629
|
+
chmodSync(outputPath, 493);
|
|
729
630
|
return outputPath;
|
|
730
631
|
}
|
|
632
|
+
const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
|
|
731
633
|
const build = Bun.spawn([
|
|
732
634
|
zigBinary,
|
|
733
635
|
"build-exe",
|
|
734
636
|
sourcePath,
|
|
735
637
|
"-O",
|
|
736
638
|
"ReleaseFast",
|
|
737
|
-
`-femit-bin=${
|
|
639
|
+
`-femit-bin=${tempOutputPath}`
|
|
738
640
|
], {
|
|
739
|
-
cwd:
|
|
641
|
+
cwd: dirname3(sourcePath),
|
|
740
642
|
stdout: "pipe",
|
|
741
643
|
stderr: "pipe"
|
|
742
644
|
});
|
|
@@ -745,1609 +647,1707 @@ async function ensureRigToolsBinaryPath(outputPath = sharedNativeToolsOutputPath
|
|
|
745
647
|
new Response(build.stdout).text(),
|
|
746
648
|
new Response(build.stderr).text()
|
|
747
649
|
]);
|
|
748
|
-
if (exitCode !== 0 || !
|
|
650
|
+
if (exitCode !== 0 || !existsSync4(tempOutputPath)) {
|
|
749
651
|
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join(`
|
|
750
652
|
`);
|
|
751
|
-
throw new Error(`Failed to build native Rig
|
|
653
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${exitCode}`}`);
|
|
654
|
+
}
|
|
655
|
+
chmodSync(tempOutputPath, 493);
|
|
656
|
+
if (existsSync4(outputPath) && await hasMatchingNativeBuildManifest(manifestPath, buildKey)) {
|
|
657
|
+
rmSync(tempOutputPath, { force: true });
|
|
658
|
+
chmodSync(outputPath, 493);
|
|
659
|
+
return outputPath;
|
|
660
|
+
}
|
|
661
|
+
publishGitBinary(tempOutputPath, outputPath);
|
|
662
|
+
if (!binarySupportsTrackerCommandsSync(outputPath)) {
|
|
663
|
+
rmSync(outputPath, { force: true });
|
|
664
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
752
665
|
}
|
|
753
666
|
await Bun.write(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
754
667
|
`);
|
|
755
668
|
return outputPath;
|
|
756
669
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const
|
|
670
|
+
function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
|
|
671
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
672
|
+
throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
|
|
673
|
+
}
|
|
674
|
+
const sourcePath = resolveGitSourcePath();
|
|
675
|
+
if (!sourcePath) {
|
|
676
|
+
const binaryPath = resolveGitBinaryPath();
|
|
677
|
+
if (binaryPath) {
|
|
678
|
+
return binaryPath;
|
|
679
|
+
}
|
|
680
|
+
throw new Error("rig-git.zig source file not found.");
|
|
681
|
+
}
|
|
682
|
+
const zigBinary = Bun.which("zig");
|
|
683
|
+
if (!zigBinary) {
|
|
684
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
685
|
+
}
|
|
686
|
+
mkdirSync2(dirname3(outputPath), { recursive: true });
|
|
687
|
+
const sourceDigest = sha256FileSync(sourcePath);
|
|
762
688
|
const buildKey = JSON.stringify({
|
|
763
689
|
version: 1,
|
|
690
|
+
zigBinary,
|
|
691
|
+
platform: process.platform,
|
|
692
|
+
arch: process.arch,
|
|
764
693
|
sourcePath,
|
|
765
694
|
sourceDigest
|
|
766
695
|
});
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
696
|
+
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
697
|
+
const needsBuild = !existsSync4(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
698
|
+
if (!needsBuild) {
|
|
699
|
+
chmodSync(outputPath, 493);
|
|
700
|
+
return outputPath;
|
|
701
|
+
}
|
|
702
|
+
const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
|
|
703
|
+
const build = Bun.spawnSync([
|
|
704
|
+
zigBinary,
|
|
705
|
+
"build-exe",
|
|
706
|
+
sourcePath,
|
|
707
|
+
"-O",
|
|
708
|
+
"ReleaseFast",
|
|
709
|
+
`-femit-bin=${tempOutputPath}`
|
|
710
|
+
], {
|
|
711
|
+
cwd: dirname3(sourcePath),
|
|
712
|
+
stdout: "pipe",
|
|
713
|
+
stderr: "pipe"
|
|
714
|
+
});
|
|
715
|
+
if (build.exitCode !== 0 || !existsSync4(tempOutputPath)) {
|
|
716
|
+
const stderr = build.stderr.toString().trim();
|
|
717
|
+
const stdout = build.stdout.toString().trim();
|
|
718
|
+
const details = [stderr, stdout].filter(Boolean).join(`
|
|
772
719
|
`);
|
|
720
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
773
721
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
722
|
+
chmodSync(tempOutputPath, 493);
|
|
723
|
+
if (existsSync4(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
|
|
724
|
+
rmSync(tempOutputPath, { force: true });
|
|
725
|
+
chmodSync(outputPath, 493);
|
|
726
|
+
return outputPath;
|
|
727
|
+
}
|
|
728
|
+
publishGitBinary(tempOutputPath, outputPath);
|
|
729
|
+
if (!binarySupportsTrackerCommandsSync(outputPath)) {
|
|
730
|
+
rmSync(outputPath, { force: true });
|
|
731
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
732
|
+
}
|
|
733
|
+
writeFileSync2(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
734
|
+
`, "utf8");
|
|
735
|
+
return outputPath;
|
|
736
|
+
}
|
|
737
|
+
async function materializeRigGitBinary(targetDir) {
|
|
738
|
+
const sourcePath = await ensureRigGitBinaryPath();
|
|
739
|
+
const targetPath = resolve4(targetDir, runtimeRigGitFileName());
|
|
740
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
741
|
+
const sourceDigest = await sha256File(sourcePath);
|
|
742
|
+
const buildKey = JSON.stringify({
|
|
743
|
+
version: 1,
|
|
744
|
+
sourcePath,
|
|
745
|
+
sourceDigest
|
|
746
|
+
});
|
|
747
|
+
const needsCopy = !existsSync4(targetPath) || !await hasMatchingNativeBuildManifest(nativeBuildManifestPath(targetPath), buildKey);
|
|
748
|
+
if (needsCopy) {
|
|
749
|
+
copyFileSync(sourcePath, targetPath);
|
|
750
|
+
chmodSync(targetPath, 493);
|
|
751
|
+
await Bun.write(nativeBuildManifestPath(targetPath), `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
752
|
+
`);
|
|
780
753
|
}
|
|
781
754
|
return targetPath;
|
|
782
755
|
}
|
|
783
|
-
function
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
756
|
+
function runGitNative(command, args) {
|
|
757
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
758
|
+
return { ok: false, error: "rig-git native disabled" };
|
|
759
|
+
}
|
|
760
|
+
const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
|
|
761
|
+
let binaryPath = null;
|
|
762
|
+
if (trackerCommand) {
|
|
763
|
+
try {
|
|
764
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
765
|
+
} catch (error) {
|
|
766
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
767
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
768
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
769
|
+
}
|
|
770
|
+
return { ok: false, error: message };
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
774
|
+
binaryPath = explicitBinaryPath && existsSync4(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
|
|
775
|
+
if (!binaryPath) {
|
|
776
|
+
try {
|
|
777
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
778
|
+
} catch (error) {
|
|
779
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
780
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
781
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
782
|
+
}
|
|
783
|
+
return { ok: false, error: message };
|
|
784
|
+
}
|
|
787
785
|
}
|
|
788
786
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
787
|
+
try {
|
|
788
|
+
const proc = Bun.spawnSync([binaryPath, command, ...args], {
|
|
789
|
+
stdout: "pipe",
|
|
790
|
+
stderr: "pipe",
|
|
791
|
+
env: process.env
|
|
792
|
+
});
|
|
793
|
+
if (proc.exitCode !== 0) {
|
|
794
|
+
const stdoutText = proc.stdout.toString().trim();
|
|
795
|
+
if (stdoutText) {
|
|
796
|
+
try {
|
|
797
|
+
const parsed = JSON.parse(stdoutText);
|
|
798
|
+
if (!parsed.ok) {
|
|
799
|
+
return parsed;
|
|
800
|
+
}
|
|
801
|
+
} catch {}
|
|
802
|
+
}
|
|
803
|
+
const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
804
|
+
return { ok: false, error: errText };
|
|
795
805
|
}
|
|
806
|
+
const output = proc.stdout.toString().trim();
|
|
807
|
+
return JSON.parse(output);
|
|
808
|
+
} catch (err) {
|
|
809
|
+
return { ok: false, error: String(err) };
|
|
796
810
|
}
|
|
797
|
-
return null;
|
|
798
811
|
}
|
|
799
|
-
function
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
return
|
|
805
|
-
process.env.RIG_NATIVE_TOOLS_SOURCE?.trim() || "",
|
|
806
|
-
cwd ? resolve6(cwd, "packages/runtime/native/rig-tools.zig") : "",
|
|
807
|
-
projectRoot ? resolve6(projectRoot, "packages/runtime/native/rig-tools.zig") : "",
|
|
808
|
-
hostProjectRoot ? resolve6(hostProjectRoot, "packages/runtime/native/rig-tools.zig") : "",
|
|
809
|
-
execDir ? resolve6(execDir, "..", "..", "packages/runtime/native/rig-tools.zig") : "",
|
|
810
|
-
execDir ? resolve6(execDir, "..", "native", "rig-tools.zig") : "",
|
|
811
|
-
resolve6(import.meta.dir, "../../../../native/rig-tools.zig")
|
|
812
|
-
].filter(Boolean))];
|
|
812
|
+
function requireGitNative(command, args) {
|
|
813
|
+
const result = runGitNative(command, args);
|
|
814
|
+
if (!result.ok) {
|
|
815
|
+
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
816
|
+
}
|
|
817
|
+
return result;
|
|
813
818
|
}
|
|
814
|
-
function
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
candidates.push(resolve6(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve6(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve6(cursor, "native", fileName), resolve6(cursor, "native", "bin", fileName));
|
|
819
|
-
const parent = dirname4(cursor);
|
|
820
|
-
if (parent === cursor)
|
|
821
|
-
break;
|
|
822
|
-
cursor = parent;
|
|
819
|
+
function requireGitNativeString(command, args) {
|
|
820
|
+
const result = requireGitNative(command, args);
|
|
821
|
+
if ("value" in result && typeof result.value === "string") {
|
|
822
|
+
return result.value;
|
|
823
823
|
}
|
|
824
|
-
|
|
824
|
+
throw new Error(`rig-git ${command} returned an unexpected result payload`);
|
|
825
825
|
}
|
|
826
|
-
function
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
execDir ? resolve6(execDir, "..", fileName) : "",
|
|
834
|
-
execDir ? resolve6(execDir, "..", "bin", fileName) : ""
|
|
835
|
-
].filter(Boolean))];
|
|
826
|
+
function nativeBranchName(repoPath) {
|
|
827
|
+
const result = runGitNative("branch-name", [repoPath]);
|
|
828
|
+
if (!result.ok)
|
|
829
|
+
return null;
|
|
830
|
+
if ("value" in result && typeof result.value === "string")
|
|
831
|
+
return result.value;
|
|
832
|
+
return null;
|
|
836
833
|
}
|
|
837
|
-
function
|
|
838
|
-
return
|
|
834
|
+
function nativeFetchRef(repoPath, remote, branch) {
|
|
835
|
+
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
839
836
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
837
|
+
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
838
|
+
const requestDir = resolve4(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
839
|
+
mkdirSync2(requestDir, { recursive: true });
|
|
840
|
+
const outputPath = resolve4(requestDir, "blob.txt");
|
|
844
841
|
try {
|
|
845
|
-
|
|
846
|
-
return
|
|
847
|
-
}
|
|
848
|
-
|
|
842
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
843
|
+
return readFileSync3(outputPath, "utf8");
|
|
844
|
+
} finally {
|
|
845
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
849
846
|
}
|
|
850
847
|
}
|
|
851
|
-
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
848
|
+
function nativeReadBlobBytesAtRef(repoPath, ref, path) {
|
|
849
|
+
const requestDir = resolve4(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
850
|
+
mkdirSync2(requestDir, { recursive: true });
|
|
851
|
+
const outputPath = resolve4(requestDir, "blob.bin");
|
|
852
|
+
try {
|
|
853
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
854
|
+
return readFileSync3(outputPath);
|
|
855
|
+
} finally {
|
|
856
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
857
|
+
}
|
|
855
858
|
}
|
|
856
859
|
|
|
857
860
|
// packages/runtime/src/control-plane/runtime/tooling/gateway.ts
|
|
858
|
-
|
|
859
|
-
|
|
861
|
+
import { existsSync as existsSync8, rmSync as rmSync4, symlinkSync as symlinkSync3 } from "fs";
|
|
862
|
+
import { resolve as resolve8 } from "path";
|
|
863
|
+
|
|
864
|
+
// packages/runtime/src/control-plane/runtime/tooling/shell.ts
|
|
865
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
866
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
867
|
+
import { basename as basename2, dirname as dirname4, resolve as resolve5 } from "path";
|
|
868
|
+
var sharedNativeShellOutputDir = resolve5(tmpdir2(), "rig-native");
|
|
869
|
+
var sharedNativeShellOutputPath = resolve5(sharedNativeShellOutputDir, `rig-shell-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
870
|
+
function runtimeRigShellFileName() {
|
|
871
|
+
return `rig-shell${process.platform === "win32" ? ".exe" : ""}`;
|
|
860
872
|
}
|
|
861
|
-
async function
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
const
|
|
865
|
-
if (
|
|
866
|
-
|
|
873
|
+
async function ensureRigShellBinaryPath(outputPath = sharedNativeShellOutputPath) {
|
|
874
|
+
const sourcePath = resolveRigShellSourcePath();
|
|
875
|
+
if (!sourcePath) {
|
|
876
|
+
const bundledBinary = resolveBundledRigShellBinaryPath();
|
|
877
|
+
if (bundledBinary) {
|
|
878
|
+
return bundledBinary;
|
|
867
879
|
}
|
|
868
|
-
|
|
880
|
+
throw new Error("rig-shell.zig source file not found.");
|
|
869
881
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
// packages/runtime/src/control-plane/browser-contract.ts
|
|
874
|
-
import { resolve as resolve8 } from "path";
|
|
875
|
-
var DEFAULT_BROWSER_ATTACH_URL = "http://127.0.0.1:9333";
|
|
876
|
-
var DEFAULT_BROWSER_MODE = "persistent";
|
|
877
|
-
var RUNTIME_BROWSER_HELPERS = {
|
|
878
|
-
launch: "rig-browser-launch",
|
|
879
|
-
check: "rig-browser-check",
|
|
880
|
-
attachInfo: "rig-browser-attach-info",
|
|
881
|
-
e2e: "rig-browser-e2e",
|
|
882
|
-
resetProfile: "rig-browser-reset-profile"
|
|
883
|
-
};
|
|
884
|
-
var BASE_REMOTE_DEBUGGING_PORT = 9222;
|
|
885
|
-
var REMOTE_DEBUGGING_PORT_SPREAD = 4000;
|
|
886
|
-
function hashString(input) {
|
|
887
|
-
let hash = 0;
|
|
888
|
-
for (let index = 0;index < input.length; index += 1) {
|
|
889
|
-
hash = (hash << 5) - hash + input.charCodeAt(index) | 0;
|
|
890
|
-
}
|
|
891
|
-
return Math.abs(hash);
|
|
892
|
-
}
|
|
893
|
-
function derivePortFromProfile(profileName) {
|
|
894
|
-
return BASE_REMOTE_DEBUGGING_PORT + hashString(profileName) % REMOTE_DEBUGGING_PORT_SPREAD;
|
|
895
|
-
}
|
|
896
|
-
function parseAttachUrl(attachUrl) {
|
|
897
|
-
try {
|
|
898
|
-
return new URL((attachUrl || DEFAULT_BROWSER_ATTACH_URL).trim() || DEFAULT_BROWSER_ATTACH_URL);
|
|
899
|
-
} catch {
|
|
900
|
-
return new URL(DEFAULT_BROWSER_ATTACH_URL);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
function sanitizeRuntimeSuffix(runtimeId) {
|
|
904
|
-
return runtimeId.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 16) || "runtime";
|
|
905
|
-
}
|
|
906
|
-
function resolveBrowserStateDir(projectRoot, configuredStateDir) {
|
|
907
|
-
const trimmed = configuredStateDir?.trim() || ".tmp/rig-browser";
|
|
908
|
-
if (trimmed.startsWith("/")) {
|
|
909
|
-
return resolve8(trimmed);
|
|
910
|
-
}
|
|
911
|
-
return resolve8(projectRoot || process.cwd(), trimmed);
|
|
912
|
-
}
|
|
913
|
-
function resolveTaskBrowserContext(browser, options = {}) {
|
|
914
|
-
if (!browser?.required) {
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
const defaultProfile = browser.profile?.trim() || "default";
|
|
918
|
-
const mode = browser.mode?.trim() || DEFAULT_BROWSER_MODE;
|
|
919
|
-
const defaultAttach = parseAttachUrl(browser.attach_url);
|
|
920
|
-
const shouldDeriveRuntimeProfile = Boolean(options.runtimeId?.trim()) && mode !== "shared";
|
|
921
|
-
const effectiveProfile = shouldDeriveRuntimeProfile ? `${defaultProfile}-${sanitizeRuntimeSuffix(options.runtimeId.trim())}` : defaultProfile;
|
|
922
|
-
const effectivePort = shouldDeriveRuntimeProfile ? derivePortFromProfile(effectiveProfile) : Number(defaultAttach.port || "80");
|
|
923
|
-
const effectiveAttach = new URL(defaultAttach.toString());
|
|
924
|
-
effectiveAttach.port = String(effectivePort);
|
|
925
|
-
return {
|
|
926
|
-
required: true,
|
|
927
|
-
preset: browser.preset?.trim() || "default",
|
|
928
|
-
mode,
|
|
929
|
-
stateDir: resolveBrowserStateDir(options.hostProjectRoot, browser.state_dir),
|
|
930
|
-
defaultProfile,
|
|
931
|
-
effectiveProfile,
|
|
932
|
-
defaultAttachUrl: defaultAttach.toString(),
|
|
933
|
-
effectiveAttachUrl: effectiveAttach.toString(),
|
|
934
|
-
devCommand: browser.dev_command?.trim() || undefined,
|
|
935
|
-
launchCommand: browser.launch_command?.trim() || undefined,
|
|
936
|
-
checkCommand: browser.check_command?.trim() || undefined,
|
|
937
|
-
e2eCommand: browser.e2e_command?.trim() || undefined,
|
|
938
|
-
launchHelper: RUNTIME_BROWSER_HELPERS.launch,
|
|
939
|
-
checkHelper: RUNTIME_BROWSER_HELPERS.check,
|
|
940
|
-
attachInfoHelper: RUNTIME_BROWSER_HELPERS.attachInfo,
|
|
941
|
-
e2eHelper: RUNTIME_BROWSER_HELPERS.e2e,
|
|
942
|
-
resetProfileHelper: RUNTIME_BROWSER_HELPERS.resetProfile
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
function buildBrowserGuidanceLines(browser) {
|
|
946
|
-
const lines = [
|
|
947
|
-
"This task requires Rig Browser.",
|
|
948
|
-
`Launch the browser: \`${browser.launchHelper}\`${browser.devCommand ? " or `rig-browser-launch --dev`" : ""}.`,
|
|
949
|
-
`Check the browser contract: \`${browser.checkHelper}\`.`,
|
|
950
|
-
`Show attach details: \`${browser.attachInfoHelper}\`.`,
|
|
951
|
-
`Attach Chrome DevTools MCP to ${browser.effectiveAttachUrl}.`,
|
|
952
|
-
`Preset: ${browser.preset}.`,
|
|
953
|
-
`Profile: ${browser.effectiveProfile}.`,
|
|
954
|
-
`State dir: ${browser.stateDir}.`,
|
|
955
|
-
`Reset the active profile with \`${browser.resetProfileHelper}\`.`
|
|
956
|
-
];
|
|
957
|
-
if (browser.e2eCommand) {
|
|
958
|
-
lines.push(`Run app-owned browser e2e with \`${browser.e2eHelper}\`.`);
|
|
882
|
+
const zigBinary = Bun.which("zig");
|
|
883
|
+
if (!zigBinary) {
|
|
884
|
+
throw new Error("zig is required to build the native Rig shell.");
|
|
959
885
|
}
|
|
960
|
-
|
|
961
|
-
|
|
886
|
+
mkdirSync3(dirname4(outputPath), { recursive: true });
|
|
887
|
+
const sourceDigest = await sha256File2(sourcePath);
|
|
888
|
+
const buildKey = JSON.stringify({
|
|
889
|
+
version: 1,
|
|
890
|
+
zigBinary,
|
|
891
|
+
platform: process.platform,
|
|
892
|
+
arch: process.arch,
|
|
893
|
+
sourcePath,
|
|
894
|
+
sourceDigest
|
|
895
|
+
});
|
|
896
|
+
const manifestPath = nativeBuildManifestPath2(outputPath);
|
|
897
|
+
const needsBuild = !existsSync5(outputPath) || !await hasMatchingNativeBuildManifest2(manifestPath, buildKey);
|
|
898
|
+
if (!needsBuild) {
|
|
899
|
+
return outputPath;
|
|
962
900
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
throw new Error(`task source not registered: ${id}`);
|
|
985
|
-
return s;
|
|
986
|
-
},
|
|
987
|
-
resolveByKind(kind) {
|
|
988
|
-
for (const s of order)
|
|
989
|
-
if (s.kind === kind)
|
|
990
|
-
return s;
|
|
991
|
-
throw new Error(`no task source registered for kind: ${kind}`);
|
|
992
|
-
},
|
|
993
|
-
list: () => order
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// packages/runtime/src/control-plane/task-source-bootstrap.ts
|
|
998
|
-
function formatRegisteredKinds(pluginHost) {
|
|
999
|
-
const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
|
|
1000
|
-
return kinds.length > 0 ? kinds.join(", ") : "none";
|
|
1001
|
-
}
|
|
1002
|
-
function buildTaskSourceRegistry(config, pluginHost) {
|
|
1003
|
-
const registry = createTaskSourceRegistry();
|
|
1004
|
-
const taskSourceConfig = config.taskSource;
|
|
1005
|
-
const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
|
|
1006
|
-
if (!factory) {
|
|
1007
|
-
throw new Error(`No task source factory registered for kind "${taskSourceConfig.kind}". ` + `Registered kinds: ${formatRegisteredKinds(pluginHost)}. ` + "Load a plugin that contributes an executable task source factory for this kind.");
|
|
901
|
+
const build = Bun.spawn([
|
|
902
|
+
zigBinary,
|
|
903
|
+
"build-exe",
|
|
904
|
+
sourcePath,
|
|
905
|
+
"-O",
|
|
906
|
+
"ReleaseFast",
|
|
907
|
+
`-femit-bin=${outputPath}`
|
|
908
|
+
], {
|
|
909
|
+
cwd: dirname4(sourcePath),
|
|
910
|
+
stdout: "pipe",
|
|
911
|
+
stderr: "pipe"
|
|
912
|
+
});
|
|
913
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
914
|
+
build.exited,
|
|
915
|
+
new Response(build.stdout).text(),
|
|
916
|
+
new Response(build.stderr).text()
|
|
917
|
+
]);
|
|
918
|
+
if (exitCode !== 0 || !existsSync5(outputPath)) {
|
|
919
|
+
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join(`
|
|
920
|
+
`);
|
|
921
|
+
throw new Error(`Failed to build native Rig shell: ${details || `zig exited with code ${exitCode}`}`);
|
|
1008
922
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
923
|
+
await Bun.write(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
924
|
+
`);
|
|
925
|
+
return outputPath;
|
|
1011
926
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
927
|
+
async function materializeRigShellBinary(targetDir) {
|
|
928
|
+
const sourcePath = await ensureRigShellBinaryPath();
|
|
929
|
+
const targetPath = resolve5(targetDir, runtimeRigShellFileName());
|
|
930
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
931
|
+
const sourceDigest = await sha256File2(sourcePath);
|
|
932
|
+
const buildKey = JSON.stringify({
|
|
933
|
+
version: 1,
|
|
934
|
+
sourcePath,
|
|
935
|
+
sourceDigest
|
|
936
|
+
});
|
|
937
|
+
const needsCopy = !existsSync5(targetPath) || !await hasMatchingNativeBuildManifest2(nativeBuildManifestPath2(targetPath), buildKey);
|
|
938
|
+
if (needsCopy) {
|
|
939
|
+
copyFileSync2(sourcePath, targetPath);
|
|
940
|
+
chmodSync2(targetPath, 493);
|
|
941
|
+
await Bun.write(nativeBuildManifestPath2(targetPath), `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
942
|
+
`);
|
|
1020
943
|
}
|
|
1021
|
-
|
|
1022
|
-
return {
|
|
1023
|
-
getById: (id) => map.get(id),
|
|
1024
|
-
list: () => ordered
|
|
1025
|
-
};
|
|
944
|
+
return targetPath;
|
|
1026
945
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (next.has(e.id)) {
|
|
1032
|
-
throw new Error(`managed repo already registered: ${e.id}`);
|
|
946
|
+
function resolveRigShellSourcePath() {
|
|
947
|
+
for (const candidate of rigShellSourceCandidates()) {
|
|
948
|
+
if (candidate && existsSync5(candidate)) {
|
|
949
|
+
return candidate;
|
|
1033
950
|
}
|
|
1034
|
-
next.set(e.id, e);
|
|
1035
951
|
}
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
function getManagedRepoEntry(repoId) {
|
|
1039
|
-
const entry = MANAGED_REPOS.get(repoId);
|
|
1040
|
-
if (!entry) {
|
|
1041
|
-
throw new Error(`managed repo not registered: ${repoId}. Plugins contribute repos via RigPlugin.contributes.repoSources; ` + `make sure a plugin declares this id and the plugin host has been initialized.`);
|
|
1042
|
-
}
|
|
1043
|
-
return entry;
|
|
1044
|
-
}
|
|
1045
|
-
function listManagedRepoEntries() {
|
|
1046
|
-
return Array.from(MANAGED_REPOS.values());
|
|
952
|
+
return null;
|
|
1047
953
|
}
|
|
1048
|
-
function
|
|
1049
|
-
for (const
|
|
1050
|
-
if (
|
|
1051
|
-
return
|
|
954
|
+
function resolveBundledRigShellBinaryPath() {
|
|
955
|
+
for (const candidate of rigShellBinaryCandidates()) {
|
|
956
|
+
if (candidate && existsSync5(candidate)) {
|
|
957
|
+
return candidate;
|
|
1052
958
|
}
|
|
1053
959
|
}
|
|
1054
960
|
return null;
|
|
1055
961
|
}
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
962
|
+
function rigShellSourceCandidates() {
|
|
963
|
+
const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
|
|
964
|
+
const cwd = process.cwd()?.trim() || "";
|
|
965
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
966
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
967
|
+
return [...new Set([
|
|
968
|
+
process.env.RIG_NATIVE_SHELL_SOURCE?.trim() || "",
|
|
969
|
+
cwd ? resolve5(cwd, "packages/runtime/native/rig-shell.zig") : "",
|
|
970
|
+
projectRoot ? resolve5(projectRoot, "packages/runtime/native/rig-shell.zig") : "",
|
|
971
|
+
hostProjectRoot ? resolve5(hostProjectRoot, "packages/runtime/native/rig-shell.zig") : "",
|
|
972
|
+
execDir ? resolve5(execDir, "..", "..", "packages/runtime/native/rig-shell.zig") : "",
|
|
973
|
+
execDir ? resolve5(execDir, "..", "native", "rig-shell.zig") : "",
|
|
974
|
+
resolve5(import.meta.dir, "../../../../native/rig-shell.zig")
|
|
975
|
+
].filter(Boolean))];
|
|
1068
976
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (!model) {
|
|
1079
|
-
throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
|
|
1080
|
-
}
|
|
1081
|
-
map.set(r.id, { ...r, model });
|
|
977
|
+
function nativePackageBinaryCandidates2(fromDir, fileName) {
|
|
978
|
+
const candidates = [];
|
|
979
|
+
let cursor = resolve5(fromDir);
|
|
980
|
+
for (let index = 0;index < 8; index += 1) {
|
|
981
|
+
candidates.push(resolve5(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve5(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve5(cursor, "native", fileName), resolve5(cursor, "native", "bin", fileName));
|
|
982
|
+
const parent = dirname4(cursor);
|
|
983
|
+
if (parent === cursor)
|
|
984
|
+
break;
|
|
985
|
+
cursor = parent;
|
|
1082
986
|
}
|
|
1083
|
-
return
|
|
1084
|
-
resolve(id) {
|
|
1085
|
-
const r = map.get(id);
|
|
1086
|
-
if (!r)
|
|
1087
|
-
throw new Error(`agent role not registered: ${id}`);
|
|
1088
|
-
return r;
|
|
1089
|
-
},
|
|
1090
|
-
list: () => Array.from(map.values())
|
|
1091
|
-
};
|
|
987
|
+
return candidates;
|
|
1092
988
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
get: (id) => byId.get(id),
|
|
1104
|
-
list: () => Array.from(byId.values()),
|
|
1105
|
-
fieldNames: () => Array.from(byId.values()).map((e) => e.fieldName),
|
|
1106
|
-
validateTaskFields(task) {
|
|
1107
|
-
const errors = [];
|
|
1108
|
-
for (const ext of byId.values()) {
|
|
1109
|
-
let schema;
|
|
1110
|
-
try {
|
|
1111
|
-
schema = JSON.parse(ext.schemaJson);
|
|
1112
|
-
} catch {
|
|
1113
|
-
errors.push(`task field "${ext.id}": schemaJson is not valid JSON`);
|
|
1114
|
-
continue;
|
|
1115
|
-
}
|
|
1116
|
-
const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
|
|
1117
|
-
if (!isRequired)
|
|
1118
|
-
continue;
|
|
1119
|
-
const value = task[ext.fieldName];
|
|
1120
|
-
if (value === undefined || value === null || value === "") {
|
|
1121
|
-
errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
1125
|
-
}
|
|
1126
|
-
};
|
|
989
|
+
function rigShellBinaryCandidates() {
|
|
990
|
+
const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
|
|
991
|
+
const fileName = runtimeRigShellFileName();
|
|
992
|
+
return [...new Set([
|
|
993
|
+
process.env.RIG_NATIVE_SHELL_BIN?.trim() || "",
|
|
994
|
+
...nativePackageBinaryCandidates2(import.meta.dir, fileName),
|
|
995
|
+
execDir ? resolve5(execDir, fileName) : "",
|
|
996
|
+
execDir ? resolve5(execDir, "..", fileName) : "",
|
|
997
|
+
execDir ? resolve5(execDir, "..", "bin", fileName) : ""
|
|
998
|
+
].filter(Boolean))];
|
|
1127
999
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1000
|
+
function runtimeToolGatewayNames() {
|
|
1001
|
+
return [
|
|
1002
|
+
"bash",
|
|
1003
|
+
"sh",
|
|
1004
|
+
"zsh",
|
|
1005
|
+
"git",
|
|
1006
|
+
"bun",
|
|
1007
|
+
"node",
|
|
1008
|
+
"python3",
|
|
1009
|
+
"rg",
|
|
1010
|
+
"grep",
|
|
1011
|
+
"sed",
|
|
1012
|
+
"cat",
|
|
1013
|
+
"ls",
|
|
1014
|
+
"find",
|
|
1015
|
+
"tsc",
|
|
1016
|
+
"gh",
|
|
1017
|
+
"mkdir",
|
|
1018
|
+
"rm",
|
|
1019
|
+
"mv",
|
|
1020
|
+
"cp",
|
|
1021
|
+
"touch",
|
|
1022
|
+
"pwd",
|
|
1023
|
+
"head",
|
|
1024
|
+
"tail",
|
|
1025
|
+
"wc",
|
|
1026
|
+
"sort",
|
|
1027
|
+
"uniq",
|
|
1028
|
+
"awk",
|
|
1029
|
+
"xargs",
|
|
1030
|
+
"dirname",
|
|
1031
|
+
"basename",
|
|
1032
|
+
"realpath",
|
|
1033
|
+
"env",
|
|
1034
|
+
"jq",
|
|
1035
|
+
"tee",
|
|
1036
|
+
"which"
|
|
1037
|
+
];
|
|
1152
1038
|
}
|
|
1153
|
-
function
|
|
1154
|
-
|
|
1155
|
-
id: "std:typecheck",
|
|
1156
|
-
category: "custom",
|
|
1157
|
-
description: "Runs the package typecheck script when present.",
|
|
1158
|
-
run: async (ctx) => runStdTypecheck(ctx)
|
|
1159
|
-
});
|
|
1039
|
+
function nativeBuildManifestPath2(outputPath) {
|
|
1040
|
+
return `${outputPath}.build-manifest.json`;
|
|
1160
1041
|
}
|
|
1161
|
-
async function
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1042
|
+
async function hasMatchingNativeBuildManifest2(manifestPath, buildKey) {
|
|
1043
|
+
if (!existsSync5(manifestPath)) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
try {
|
|
1047
|
+
const manifest = await Bun.file(manifestPath).json();
|
|
1048
|
+
return manifest.version === 1 && manifest.buildKey === buildKey;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return false;
|
|
1169
1051
|
}
|
|
1170
|
-
const proc = Bun.spawn(["bun", "run", "typecheck"], {
|
|
1171
|
-
cwd: ctx.workspaceRoot,
|
|
1172
|
-
env: process.env,
|
|
1173
|
-
stdout: "pipe",
|
|
1174
|
-
stderr: "pipe"
|
|
1175
|
-
});
|
|
1176
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
1177
|
-
proc.exited,
|
|
1178
|
-
new Response(proc.stdout).text(),
|
|
1179
|
-
new Response(proc.stderr).text()
|
|
1180
|
-
]);
|
|
1181
|
-
const output = `${stdout}${stderr}`.trim();
|
|
1182
|
-
return {
|
|
1183
|
-
id: "std:typecheck",
|
|
1184
|
-
passed: exitCode === 0,
|
|
1185
|
-
summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
|
|
1186
|
-
...output ? { details: output.slice(0, 4000) } : {}
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
// packages/runtime/src/control-plane/native/scope-rules.ts
|
|
1191
|
-
var activeRules = null;
|
|
1192
|
-
function setScopeRules(rules) {
|
|
1193
|
-
activeRules = rules ?? null;
|
|
1194
1052
|
}
|
|
1195
|
-
function
|
|
1196
|
-
|
|
1053
|
+
async function sha256File2(path) {
|
|
1054
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
1055
|
+
hasher.update(await Bun.file(path).arrayBuffer());
|
|
1056
|
+
return hasher.digest("hex");
|
|
1197
1057
|
}
|
|
1198
1058
|
|
|
1199
|
-
// packages/runtime/src/control-plane/
|
|
1200
|
-
import { existsSync as
|
|
1201
|
-
import {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
function matcherToString(matcher) {
|
|
1205
|
-
if (matcher.kind === "all")
|
|
1206
|
-
return;
|
|
1207
|
-
if (matcher.kind === "tool")
|
|
1208
|
-
return matcher.name;
|
|
1209
|
-
return matcher.pattern;
|
|
1059
|
+
// packages/runtime/src/control-plane/runtime/tooling/browser-tools.ts
|
|
1060
|
+
import { existsSync as existsSync6, rmSync as rmSync2, symlinkSync } from "fs";
|
|
1061
|
+
import { resolve as resolve6 } from "path";
|
|
1062
|
+
function runtimeBrowserToolBinaryName() {
|
|
1063
|
+
return `rig-browser-tool${process.platform === "win32" ? ".exe" : ""}`;
|
|
1210
1064
|
}
|
|
1211
|
-
function
|
|
1212
|
-
return
|
|
1065
|
+
function runtimeBrowserToolNames() {
|
|
1066
|
+
return [
|
|
1067
|
+
"rig-browser-launch",
|
|
1068
|
+
"rig-browser-check",
|
|
1069
|
+
"rig-browser-attach-info",
|
|
1070
|
+
"rig-browser-e2e",
|
|
1071
|
+
"rig-browser-reset-profile"
|
|
1072
|
+
];
|
|
1213
1073
|
}
|
|
1214
|
-
function
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
const cleaned = [];
|
|
1221
|
-
for (const group of groups) {
|
|
1222
|
-
const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
|
|
1223
|
-
if (operatorHooks.length > 0) {
|
|
1224
|
-
cleaned.push({ ...group, hooks: operatorHooks });
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
if (cleaned.length > 0) {
|
|
1228
|
-
hooks[event] = cleaned;
|
|
1229
|
-
} else {
|
|
1230
|
-
delete hooks[event];
|
|
1074
|
+
async function materializeRuntimeBrowserTools(targetDir) {
|
|
1075
|
+
const binaryPath = resolve6(targetDir, runtimeBrowserToolBinaryName());
|
|
1076
|
+
for (const tool of runtimeBrowserToolNames()) {
|
|
1077
|
+
const toolPath = resolve6(targetDir, tool);
|
|
1078
|
+
if (existsSync6(toolPath)) {
|
|
1079
|
+
rmSync2(toolPath, { force: true, recursive: true });
|
|
1231
1080
|
}
|
|
1081
|
+
symlinkSync(binaryPath, toolPath);
|
|
1232
1082
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1083
|
+
return binaryPath;
|
|
1084
|
+
}
|
|
1085
|
+
// packages/runtime/src/control-plane/runtime/tooling/file-tools.ts
|
|
1086
|
+
import { chmodSync as chmodSync3, copyFileSync as copyFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, rmSync as rmSync3, symlinkSync as symlinkSync2 } from "fs";
|
|
1087
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
1088
|
+
import { basename as basename3, dirname as dirname5, resolve as resolve7 } from "path";
|
|
1089
|
+
var sharedNativeToolsOutputDir = resolve7(tmpdir3(), "rig-native");
|
|
1090
|
+
var sharedNativeToolsOutputPath = resolve7(sharedNativeToolsOutputDir, `rig-tools-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
1091
|
+
function runtimeRigToolsFileName() {
|
|
1092
|
+
return `rig-tools${process.platform === "win32" ? ".exe" : ""}`;
|
|
1093
|
+
}
|
|
1094
|
+
function runtimeFileToolNames() {
|
|
1095
|
+
return [
|
|
1096
|
+
"rig-read",
|
|
1097
|
+
"rig-write",
|
|
1098
|
+
"rig-edit",
|
|
1099
|
+
"rig-glob",
|
|
1100
|
+
"rig-grep"
|
|
1101
|
+
];
|
|
1102
|
+
}
|
|
1103
|
+
async function ensureRigToolsBinaryPath(outputPath = sharedNativeToolsOutputPath) {
|
|
1104
|
+
const sourcePath = resolveRigToolsSourcePath();
|
|
1105
|
+
if (!sourcePath) {
|
|
1106
|
+
const bundledBinary = resolveBundledRigToolsBinaryPath();
|
|
1107
|
+
if (bundledBinary) {
|
|
1108
|
+
return bundledBinary;
|
|
1244
1109
|
}
|
|
1245
|
-
|
|
1246
|
-
type: "command",
|
|
1247
|
-
command: hook.command,
|
|
1248
|
-
[MARKER_PLUGIN]: pluginName,
|
|
1249
|
-
[MARKER_HOOK_ID]: hook.id
|
|
1250
|
-
});
|
|
1110
|
+
throw new Error("rig-tools.zig source file not found.");
|
|
1251
1111
|
}
|
|
1252
|
-
const
|
|
1253
|
-
if (
|
|
1254
|
-
|
|
1255
|
-
} else {
|
|
1256
|
-
delete next.hooks;
|
|
1112
|
+
const zigBinary = Bun.which("zig");
|
|
1113
|
+
if (!zigBinary) {
|
|
1114
|
+
throw new Error("zig is required to build native Rig file tools.");
|
|
1257
1115
|
}
|
|
1258
|
-
mkdirSync4(dirname5(
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1116
|
+
mkdirSync4(dirname5(outputPath), { recursive: true });
|
|
1117
|
+
const sourceDigest = await sha256File3(sourcePath);
|
|
1118
|
+
const buildKey = JSON.stringify({
|
|
1119
|
+
version: 1,
|
|
1120
|
+
zigBinary,
|
|
1121
|
+
platform: process.platform,
|
|
1122
|
+
arch: process.arch,
|
|
1123
|
+
sourcePath,
|
|
1124
|
+
sourceDigest
|
|
1125
|
+
});
|
|
1126
|
+
const manifestPath = nativeBuildManifestPath3(outputPath);
|
|
1127
|
+
const needsBuild = !existsSync7(outputPath) || !await hasMatchingNativeBuildManifest3(manifestPath, buildKey);
|
|
1128
|
+
if (!needsBuild) {
|
|
1129
|
+
return outputPath;
|
|
1268
1130
|
}
|
|
1131
|
+
const build = Bun.spawn([
|
|
1132
|
+
zigBinary,
|
|
1133
|
+
"build-exe",
|
|
1134
|
+
sourcePath,
|
|
1135
|
+
"-O",
|
|
1136
|
+
"ReleaseFast",
|
|
1137
|
+
`-femit-bin=${outputPath}`
|
|
1138
|
+
], {
|
|
1139
|
+
cwd: dirname5(sourcePath),
|
|
1140
|
+
stdout: "pipe",
|
|
1141
|
+
stderr: "pipe"
|
|
1142
|
+
});
|
|
1143
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
1144
|
+
build.exited,
|
|
1145
|
+
new Response(build.stdout).text(),
|
|
1146
|
+
new Response(build.stderr).text()
|
|
1147
|
+
]);
|
|
1148
|
+
if (exitCode !== 0 || !existsSync7(outputPath)) {
|
|
1149
|
+
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join(`
|
|
1150
|
+
`);
|
|
1151
|
+
throw new Error(`Failed to build native Rig file tools: ${details || `zig exited with code ${exitCode}`}`);
|
|
1152
|
+
}
|
|
1153
|
+
await Bun.write(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
1154
|
+
`);
|
|
1155
|
+
return outputPath;
|
|
1269
1156
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const
|
|
1281
|
-
if (
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1157
|
+
async function materializeRuntimeFileTools(targetDir) {
|
|
1158
|
+
const sourcePath = await ensureRigToolsBinaryPath();
|
|
1159
|
+
const targetPath = resolve7(targetDir, runtimeRigToolsFileName());
|
|
1160
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
1161
|
+
const sourceDigest = await sha256File3(sourcePath);
|
|
1162
|
+
const buildKey = JSON.stringify({
|
|
1163
|
+
version: 1,
|
|
1164
|
+
sourcePath,
|
|
1165
|
+
sourceDigest
|
|
1166
|
+
});
|
|
1167
|
+
const needsCopy = !existsSync7(targetPath) || !await hasMatchingNativeBuildManifest3(nativeBuildManifestPath3(targetPath), buildKey);
|
|
1168
|
+
if (needsCopy) {
|
|
1169
|
+
copyFileSync3(sourcePath, targetPath);
|
|
1170
|
+
chmodSync3(targetPath, 493);
|
|
1171
|
+
await Bun.write(nativeBuildManifestPath3(targetPath), `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
1172
|
+
`);
|
|
1288
1173
|
}
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
|
|
1294
|
-
continue;
|
|
1174
|
+
for (const tool of runtimeFileToolNames()) {
|
|
1175
|
+
const toolPath = resolve7(targetDir, tool);
|
|
1176
|
+
if (existsSync7(toolPath)) {
|
|
1177
|
+
rmSync3(toolPath, { force: true, recursive: true });
|
|
1295
1178
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1179
|
+
symlinkSync2(targetPath, toolPath);
|
|
1180
|
+
}
|
|
1181
|
+
return targetPath;
|
|
1182
|
+
}
|
|
1183
|
+
function resolveRigToolsSourcePath() {
|
|
1184
|
+
for (const candidate of rigToolsSourceCandidates()) {
|
|
1185
|
+
if (candidate && existsSync7(candidate)) {
|
|
1186
|
+
return candidate;
|
|
1303
1187
|
}
|
|
1304
|
-
const dir = resolve10(skillsRoot, skillDirName(skill.id));
|
|
1305
|
-
mkdirSync5(dir, { recursive: true });
|
|
1306
|
-
writeFileSync3(resolve10(dir, "SKILL.md"), body, "utf-8");
|
|
1307
|
-
writeFileSync3(resolve10(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
|
|
1308
|
-
`, "utf-8");
|
|
1309
|
-
written.push({ id: skill.id, pluginName, directory: dir });
|
|
1310
1188
|
}
|
|
1311
|
-
return
|
|
1189
|
+
return null;
|
|
1312
1190
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
try {
|
|
1318
|
-
config = await loadConfig(projectRoot);
|
|
1319
|
-
} catch (err) {
|
|
1320
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1321
|
-
if (msg.includes("no rig.config")) {
|
|
1322
|
-
return null;
|
|
1191
|
+
function resolveBundledRigToolsBinaryPath() {
|
|
1192
|
+
for (const candidate of rigToolsBinaryCandidates()) {
|
|
1193
|
+
if (candidate && existsSync7(candidate)) {
|
|
1194
|
+
return candidate;
|
|
1323
1195
|
}
|
|
1324
|
-
throw err;
|
|
1325
1196
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
function rigToolsSourceCandidates() {
|
|
1200
|
+
const execDir = process.execPath?.trim() ? dirname5(process.execPath.trim()) : "";
|
|
1201
|
+
const cwd = process.cwd()?.trim() || "";
|
|
1202
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
1203
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
1204
|
+
return [...new Set([
|
|
1205
|
+
process.env.RIG_NATIVE_TOOLS_SOURCE?.trim() || "",
|
|
1206
|
+
cwd ? resolve7(cwd, "packages/runtime/native/rig-tools.zig") : "",
|
|
1207
|
+
projectRoot ? resolve7(projectRoot, "packages/runtime/native/rig-tools.zig") : "",
|
|
1208
|
+
hostProjectRoot ? resolve7(hostProjectRoot, "packages/runtime/native/rig-tools.zig") : "",
|
|
1209
|
+
execDir ? resolve7(execDir, "..", "..", "packages/runtime/native/rig-tools.zig") : "",
|
|
1210
|
+
execDir ? resolve7(execDir, "..", "native", "rig-tools.zig") : "",
|
|
1211
|
+
resolve7(import.meta.dir, "../../../../native/rig-tools.zig")
|
|
1212
|
+
].filter(Boolean))];
|
|
1213
|
+
}
|
|
1214
|
+
function nativePackageBinaryCandidates3(fromDir, fileName) {
|
|
1215
|
+
const candidates = [];
|
|
1216
|
+
let cursor = resolve7(fromDir);
|
|
1217
|
+
for (let index = 0;index < 8; index += 1) {
|
|
1218
|
+
candidates.push(resolve7(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve7(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve7(cursor, "native", fileName), resolve7(cursor, "native", "bin", fileName));
|
|
1219
|
+
const parent = dirname5(cursor);
|
|
1220
|
+
if (parent === cursor)
|
|
1221
|
+
break;
|
|
1222
|
+
cursor = parent;
|
|
1331
1223
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1224
|
+
return candidates;
|
|
1225
|
+
}
|
|
1226
|
+
function rigToolsBinaryCandidates() {
|
|
1227
|
+
const execDir = process.execPath?.trim() ? dirname5(process.execPath.trim()) : "";
|
|
1228
|
+
const fileName = runtimeRigToolsFileName();
|
|
1229
|
+
return [...new Set([
|
|
1230
|
+
process.env.RIG_NATIVE_TOOLS_BIN?.trim() || "",
|
|
1231
|
+
...nativePackageBinaryCandidates3(import.meta.dir, fileName),
|
|
1232
|
+
execDir ? resolve7(execDir, fileName) : "",
|
|
1233
|
+
execDir ? resolve7(execDir, "..", fileName) : "",
|
|
1234
|
+
execDir ? resolve7(execDir, "..", "bin", fileName) : ""
|
|
1235
|
+
].filter(Boolean))];
|
|
1236
|
+
}
|
|
1237
|
+
function nativeBuildManifestPath3(outputPath) {
|
|
1238
|
+
return `${outputPath}.build-manifest.json`;
|
|
1239
|
+
}
|
|
1240
|
+
async function hasMatchingNativeBuildManifest3(manifestPath, buildKey) {
|
|
1241
|
+
if (!existsSync7(manifestPath)) {
|
|
1242
|
+
return false;
|
|
1349
1243
|
}
|
|
1350
1244
|
try {
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (skillEntries.length > 0) {
|
|
1356
|
-
await materializeSkills(projectRoot, skillEntries);
|
|
1357
|
-
}
|
|
1358
|
-
} catch (err) {
|
|
1359
|
-
console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
|
|
1245
|
+
const manifest = await Bun.file(manifestPath).json();
|
|
1246
|
+
return manifest.version === 1 && manifest.buildKey === buildKey;
|
|
1247
|
+
} catch {
|
|
1248
|
+
return false;
|
|
1360
1249
|
}
|
|
1361
|
-
return {
|
|
1362
|
-
config,
|
|
1363
|
-
pluginHost,
|
|
1364
|
-
validatorRegistry,
|
|
1365
|
-
taskSourceRegistry,
|
|
1366
|
-
repoRegistry,
|
|
1367
|
-
agentRoleRegistry,
|
|
1368
|
-
taskFieldRegistry
|
|
1369
|
-
};
|
|
1370
1250
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
import { basename as basename4, join as join2, resolve as resolve12 } from "path";
|
|
1376
|
-
|
|
1377
|
-
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
1378
|
-
import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
|
|
1379
|
-
import { resolve as resolve11 } from "path";
|
|
1380
|
-
|
|
1381
|
-
// packages/runtime/src/control-plane/tasks/task-record-reader.ts
|
|
1382
|
-
async function findTaskById(reader, id) {
|
|
1383
|
-
const tasks = await reader.listTasks();
|
|
1384
|
-
return tasks.find((task) => task.id === id) ?? null;
|
|
1251
|
+
async function sha256File3(path) {
|
|
1252
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
1253
|
+
hasher.update(await Bun.file(path).arrayBuffer());
|
|
1254
|
+
return hasher.digest("hex");
|
|
1385
1255
|
}
|
|
1386
1256
|
|
|
1387
|
-
// packages/runtime/src/control-plane/
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
projectRoot;
|
|
1391
|
-
configPath;
|
|
1392
|
-
cause;
|
|
1393
|
-
constructor(input) {
|
|
1394
|
-
super(input.message, { cause: input.cause });
|
|
1395
|
-
this.name = "LegacyTaskConfigReadError";
|
|
1396
|
-
this.projectRoot = input.projectRoot;
|
|
1397
|
-
this.configPath = input.configPath;
|
|
1398
|
-
this.cause = input.cause;
|
|
1399
|
-
}
|
|
1257
|
+
// packages/runtime/src/control-plane/runtime/tooling/gateway.ts
|
|
1258
|
+
function runtimeGatewayToolNames() {
|
|
1259
|
+
return runtimeToolGatewayNames();
|
|
1400
1260
|
}
|
|
1401
|
-
function
|
|
1402
|
-
const
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
async getTask(id) {
|
|
1408
|
-
return findTaskById(reader, id);
|
|
1261
|
+
async function materializeRuntimeToolGateway(binDir) {
|
|
1262
|
+
const shellPath = await materializeRigShellBinary(binDir);
|
|
1263
|
+
for (const tool of runtimeGatewayToolNames()) {
|
|
1264
|
+
const toolPath = resolve8(binDir, tool);
|
|
1265
|
+
if (existsSync8(toolPath)) {
|
|
1266
|
+
rmSync4(toolPath, { force: true, recursive: true });
|
|
1409
1267
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1268
|
+
symlinkSync3(shellPath, toolPath);
|
|
1269
|
+
}
|
|
1270
|
+
await materializeRuntimeBrowserTools(binDir);
|
|
1271
|
+
return resolve8(binDir, runtimeRigShellFileName());
|
|
1412
1272
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1273
|
+
// packages/runtime/src/control-plane/browser-contract.ts
|
|
1274
|
+
import { resolve as resolve9 } from "path";
|
|
1275
|
+
var DEFAULT_BROWSER_ATTACH_URL = "http://127.0.0.1:9333";
|
|
1276
|
+
var DEFAULT_BROWSER_MODE = "persistent";
|
|
1277
|
+
var RUNTIME_BROWSER_HELPERS = {
|
|
1278
|
+
launch: "rig-browser-launch",
|
|
1279
|
+
check: "rig-browser-check",
|
|
1280
|
+
attachInfo: "rig-browser-attach-info",
|
|
1281
|
+
e2e: "rig-browser-e2e",
|
|
1282
|
+
resetProfile: "rig-browser-reset-profile"
|
|
1283
|
+
};
|
|
1284
|
+
var BASE_REMOTE_DEBUGGING_PORT = 9222;
|
|
1285
|
+
var REMOTE_DEBUGGING_PORT_SPREAD = 4000;
|
|
1286
|
+
function hashString(input) {
|
|
1287
|
+
let hash = 0;
|
|
1288
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
1289
|
+
hash = (hash << 5) - hash + input.charCodeAt(index) | 0;
|
|
1416
1290
|
}
|
|
1417
|
-
|
|
1418
|
-
return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
|
|
1291
|
+
return Math.abs(hash);
|
|
1419
1292
|
}
|
|
1420
|
-
function
|
|
1293
|
+
function derivePortFromProfile(profileName) {
|
|
1294
|
+
return BASE_REMOTE_DEBUGGING_PORT + hashString(profileName) % REMOTE_DEBUGGING_PORT_SPREAD;
|
|
1295
|
+
}
|
|
1296
|
+
function parseAttachUrl(attachUrl) {
|
|
1421
1297
|
try {
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
}
|
|
1426
|
-
throw new Error("task config root must be a JSON object");
|
|
1427
|
-
} catch (cause) {
|
|
1428
|
-
throw new LegacyTaskConfigReadError({
|
|
1429
|
-
projectRoot,
|
|
1430
|
-
configPath,
|
|
1431
|
-
message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1432
|
-
cause
|
|
1433
|
-
});
|
|
1298
|
+
return new URL((attachUrl || DEFAULT_BROWSER_ATTACH_URL).trim() || DEFAULT_BROWSER_ATTACH_URL);
|
|
1299
|
+
} catch {
|
|
1300
|
+
return new URL(DEFAULT_BROWSER_ATTACH_URL);
|
|
1434
1301
|
}
|
|
1435
1302
|
}
|
|
1436
|
-
function
|
|
1437
|
-
|
|
1438
|
-
return tasks;
|
|
1303
|
+
function sanitizeRuntimeSuffix(runtimeId) {
|
|
1304
|
+
return runtimeId.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 16) || "runtime";
|
|
1439
1305
|
}
|
|
1440
|
-
function
|
|
1441
|
-
|
|
1442
|
-
|
|
1306
|
+
function resolveBrowserStateDir(projectRoot, configuredStateDir) {
|
|
1307
|
+
const trimmed = configuredStateDir?.trim() || ".tmp/rig-browser";
|
|
1308
|
+
if (trimmed.startsWith("/")) {
|
|
1309
|
+
return resolve9(trimmed);
|
|
1443
1310
|
}
|
|
1444
|
-
|
|
1445
|
-
const validation = readStringList(entry.validation);
|
|
1446
|
-
const validators = readStringList(entry.validators);
|
|
1447
|
-
const scope = readStringList(entry.scope);
|
|
1448
|
-
const status = typeof entry.status === "string" ? entry.status : "open";
|
|
1449
|
-
const title = typeof entry.title === "string" ? entry.title : undefined;
|
|
1450
|
-
const description = typeof entry.description === "string" ? entry.description : undefined;
|
|
1451
|
-
const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
|
|
1452
|
-
return {
|
|
1453
|
-
id,
|
|
1454
|
-
deps,
|
|
1455
|
-
status,
|
|
1456
|
-
source: "legacy-task-config",
|
|
1457
|
-
...title ? { title } : {},
|
|
1458
|
-
...description ? { description } : {},
|
|
1459
|
-
...acceptanceCriteria ? { acceptanceCriteria } : {},
|
|
1460
|
-
...scope.length > 0 ? { scope } : {},
|
|
1461
|
-
...validation.length > 0 ? { validation } : {},
|
|
1462
|
-
...validators.length > 0 ? { validators } : {},
|
|
1463
|
-
...preservedLegacyFields(entry)
|
|
1464
|
-
};
|
|
1311
|
+
return resolve9(projectRoot || process.cwd(), trimmed);
|
|
1465
1312
|
}
|
|
1466
|
-
function
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
"role",
|
|
1470
|
-
"browser",
|
|
1471
|
-
"repo_pins",
|
|
1472
|
-
"criticality",
|
|
1473
|
-
"queue_weight",
|
|
1474
|
-
"creates_repo",
|
|
1475
|
-
"auto_synced"
|
|
1476
|
-
]) {
|
|
1477
|
-
if (entry[key] !== undefined) {
|
|
1478
|
-
preserved[key] = entry[key];
|
|
1479
|
-
}
|
|
1313
|
+
function resolveTaskBrowserContext(browser, options = {}) {
|
|
1314
|
+
if (!browser?.required) {
|
|
1315
|
+
return;
|
|
1480
1316
|
}
|
|
1481
|
-
|
|
1317
|
+
const defaultProfile = browser.profile?.trim() || "default";
|
|
1318
|
+
const mode = browser.mode?.trim() || DEFAULT_BROWSER_MODE;
|
|
1319
|
+
const defaultAttach = parseAttachUrl(browser.attach_url);
|
|
1320
|
+
const shouldDeriveRuntimeProfile = Boolean(options.runtimeId?.trim()) && mode !== "shared";
|
|
1321
|
+
const effectiveProfile = shouldDeriveRuntimeProfile ? `${defaultProfile}-${sanitizeRuntimeSuffix(options.runtimeId.trim())}` : defaultProfile;
|
|
1322
|
+
const effectivePort = shouldDeriveRuntimeProfile ? derivePortFromProfile(effectiveProfile) : Number(defaultAttach.port || "80");
|
|
1323
|
+
const effectiveAttach = new URL(defaultAttach.toString());
|
|
1324
|
+
effectiveAttach.port = String(effectivePort);
|
|
1325
|
+
return {
|
|
1326
|
+
required: true,
|
|
1327
|
+
preset: browser.preset?.trim() || "default",
|
|
1328
|
+
mode,
|
|
1329
|
+
stateDir: resolveBrowserStateDir(options.hostProjectRoot, browser.state_dir),
|
|
1330
|
+
defaultProfile,
|
|
1331
|
+
effectiveProfile,
|
|
1332
|
+
defaultAttachUrl: defaultAttach.toString(),
|
|
1333
|
+
effectiveAttachUrl: effectiveAttach.toString(),
|
|
1334
|
+
devCommand: browser.dev_command?.trim() || undefined,
|
|
1335
|
+
launchCommand: browser.launch_command?.trim() || undefined,
|
|
1336
|
+
checkCommand: browser.check_command?.trim() || undefined,
|
|
1337
|
+
e2eCommand: browser.e2e_command?.trim() || undefined,
|
|
1338
|
+
launchHelper: RUNTIME_BROWSER_HELPERS.launch,
|
|
1339
|
+
checkHelper: RUNTIME_BROWSER_HELPERS.check,
|
|
1340
|
+
attachInfoHelper: RUNTIME_BROWSER_HELPERS.attachInfo,
|
|
1341
|
+
e2eHelper: RUNTIME_BROWSER_HELPERS.e2e,
|
|
1342
|
+
resetProfileHelper: RUNTIME_BROWSER_HELPERS.resetProfile
|
|
1343
|
+
};
|
|
1482
1344
|
}
|
|
1483
|
-
function
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1345
|
+
function buildBrowserGuidanceLines(browser) {
|
|
1346
|
+
const lines = [
|
|
1347
|
+
"This task requires Rig Browser.",
|
|
1348
|
+
`Launch the browser: \`${browser.launchHelper}\`${browser.devCommand ? " or `rig-browser-launch --dev`" : ""}.`,
|
|
1349
|
+
`Check the browser contract: \`${browser.checkHelper}\`.`,
|
|
1350
|
+
`Show attach details: \`${browser.attachInfoHelper}\`.`,
|
|
1351
|
+
`Attach Chrome DevTools MCP to ${browser.effectiveAttachUrl}.`,
|
|
1352
|
+
`Preset: ${browser.preset}.`,
|
|
1353
|
+
`Profile: ${browser.effectiveProfile}.`,
|
|
1354
|
+
`State dir: ${browser.stateDir}.`,
|
|
1355
|
+
`Reset the active profile with \`${browser.resetProfileHelper}\`.`
|
|
1356
|
+
];
|
|
1357
|
+
if (browser.e2eCommand) {
|
|
1358
|
+
lines.push(`Run app-owned browser e2e with \`${browser.e2eHelper}\`.`);
|
|
1489
1359
|
}
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
function readStringList(candidate) {
|
|
1493
|
-
if (!Array.isArray(candidate)) {
|
|
1494
|
-
return [];
|
|
1360
|
+
if (browser.defaultProfile !== browser.effectiveProfile) {
|
|
1361
|
+
lines.push(`Base profile: ${browser.defaultProfile}. Runtime launches derive an isolated effective profile.`);
|
|
1495
1362
|
}
|
|
1496
|
-
return
|
|
1497
|
-
}
|
|
1498
|
-
function isPlainRecord(candidate) {
|
|
1499
|
-
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
1363
|
+
return lines;
|
|
1500
1364
|
}
|
|
1501
1365
|
|
|
1502
|
-
// packages/runtime/src/control-plane/
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
const
|
|
1509
|
-
const
|
|
1510
|
-
const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
|
|
1366
|
+
// packages/runtime/src/control-plane/plugin-host-context.ts
|
|
1367
|
+
import { createPluginHost } from "@rig/core";
|
|
1368
|
+
import { loadConfig } from "@rig/core/load-config";
|
|
1369
|
+
|
|
1370
|
+
// packages/runtime/src/control-plane/task-source.ts
|
|
1371
|
+
function createTaskSourceRegistry() {
|
|
1372
|
+
const byId = new Map;
|
|
1373
|
+
const order = [];
|
|
1511
1374
|
return {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
}
|
|
1518
|
-
const tasks = [];
|
|
1519
|
-
const legacyTasks = await legacy.listTasks();
|
|
1520
|
-
const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
|
|
1521
|
-
for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
|
|
1522
|
-
if (!isPlainRecord2(rawEntry)) {
|
|
1523
|
-
continue;
|
|
1524
|
-
}
|
|
1525
|
-
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1526
|
-
if (metadata.taskSource?.kind === "github-issues") {
|
|
1527
|
-
tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
|
|
1528
|
-
continue;
|
|
1529
|
-
}
|
|
1530
|
-
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1531
|
-
const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1532
|
-
if (fileTask)
|
|
1533
|
-
tasks.push(fileTask);
|
|
1534
|
-
continue;
|
|
1535
|
-
}
|
|
1536
|
-
if (!allowLocalFallback) {
|
|
1537
|
-
continue;
|
|
1538
|
-
}
|
|
1539
|
-
const legacyTask = legacyById.get(id);
|
|
1540
|
-
if (legacyTask) {
|
|
1541
|
-
tasks.push(legacyTask);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
return tasks;
|
|
1375
|
+
register(s) {
|
|
1376
|
+
if (byId.has(s.id))
|
|
1377
|
+
throw new Error(`task source already registered: ${s.id}`);
|
|
1378
|
+
byId.set(s.id, s);
|
|
1379
|
+
order.push(s);
|
|
1545
1380
|
},
|
|
1546
|
-
|
|
1547
|
-
const
|
|
1548
|
-
if (!
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
return allowLocalFallback ? legacy.getTask(id) : null;
|
|
1560
|
-
}
|
|
1381
|
+
resolveById(id) {
|
|
1382
|
+
const s = byId.get(id);
|
|
1383
|
+
if (!s)
|
|
1384
|
+
throw new Error(`task source not registered: ${id}`);
|
|
1385
|
+
return s;
|
|
1386
|
+
},
|
|
1387
|
+
resolveByKind(kind) {
|
|
1388
|
+
for (const s of order)
|
|
1389
|
+
if (s.kind === kind)
|
|
1390
|
+
return s;
|
|
1391
|
+
throw new Error(`no task source registered for kind: ${kind}`);
|
|
1392
|
+
},
|
|
1393
|
+
list: () => order
|
|
1561
1394
|
};
|
|
1562
1395
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
const rawSource = rawRig.taskSource;
|
|
1569
|
-
const metadata = {};
|
|
1570
|
-
if (isPlainRecord2(rawSource)) {
|
|
1571
|
-
const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
|
|
1572
|
-
if (kind.length > 0) {
|
|
1573
|
-
metadata.taskSource = {
|
|
1574
|
-
kind,
|
|
1575
|
-
...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
|
|
1576
|
-
...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
|
|
1577
|
-
...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
|
|
1578
|
-
...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
|
|
1579
|
-
...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
if (typeof rawRig.sourceIssueId === "string") {
|
|
1584
|
-
metadata.sourceIssueId = rawRig.sourceIssueId;
|
|
1585
|
-
}
|
|
1586
|
-
return metadata;
|
|
1396
|
+
|
|
1397
|
+
// packages/runtime/src/control-plane/task-source-bootstrap.ts
|
|
1398
|
+
function formatRegisteredKinds(pluginHost) {
|
|
1399
|
+
const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
|
|
1400
|
+
return kinds.length > 0 ? kinds.join(", ") : "none";
|
|
1587
1401
|
}
|
|
1588
|
-
function
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const source = parsed.taskSource;
|
|
1595
|
-
return source.kind === "files" && typeof source.path === "string" ? source.path : null;
|
|
1596
|
-
}
|
|
1597
|
-
} catch {
|
|
1598
|
-
return null;
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
const tsPath = resolve12(projectRoot, "rig.config.ts");
|
|
1602
|
-
if (!existsSync12(tsPath)) {
|
|
1603
|
-
return null;
|
|
1402
|
+
function buildTaskSourceRegistry(config, pluginHost) {
|
|
1403
|
+
const registry = createTaskSourceRegistry();
|
|
1404
|
+
const taskSourceConfig = config.taskSource;
|
|
1405
|
+
const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
|
|
1406
|
+
if (!factory) {
|
|
1407
|
+
throw new Error(`No task source factory registered for kind "${taskSourceConfig.kind}". ` + `Registered kinds: ${formatRegisteredKinds(pluginHost)}. ` + "Load a plugin that contributes an executable task source factory for this kind.");
|
|
1604
1408
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1409
|
+
registry.register(factory.factory(taskSourceConfig));
|
|
1410
|
+
return registry;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// packages/runtime/src/control-plane/repos/registry.ts
|
|
1414
|
+
function createRepoRegistry(entries) {
|
|
1415
|
+
const map = new Map;
|
|
1416
|
+
for (const e of entries) {
|
|
1417
|
+
if (map.has(e.id))
|
|
1418
|
+
throw new Error(`repo already registered: ${e.id}`);
|
|
1419
|
+
map.set(e.id, { ...e });
|
|
1615
1420
|
}
|
|
1421
|
+
const ordered = Array.from(map.values());
|
|
1422
|
+
return {
|
|
1423
|
+
getById: (id) => map.get(id),
|
|
1424
|
+
list: () => ordered
|
|
1425
|
+
};
|
|
1616
1426
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1427
|
+
var MANAGED_REPOS = new Map;
|
|
1428
|
+
function setManagedRepos(entries) {
|
|
1429
|
+
const next = new Map;
|
|
1430
|
+
for (const e of entries) {
|
|
1431
|
+
if (next.has(e.id)) {
|
|
1432
|
+
throw new Error(`managed repo already registered: ${e.id}`);
|
|
1433
|
+
}
|
|
1434
|
+
next.set(e.id, e);
|
|
1621
1435
|
}
|
|
1622
|
-
|
|
1623
|
-
return isPlainRecord2(entry) ? entry : null;
|
|
1436
|
+
MANAGED_REPOS = next;
|
|
1624
1437
|
}
|
|
1625
|
-
function
|
|
1626
|
-
|
|
1627
|
-
|
|
1438
|
+
function getManagedRepoEntry(repoId) {
|
|
1439
|
+
const entry = MANAGED_REPOS.get(repoId);
|
|
1440
|
+
if (!entry) {
|
|
1441
|
+
throw new Error(`managed repo not registered: ${repoId}. Plugins contribute repos via RigPlugin.contributes.repoSources; ` + `make sure a plugin declares this id and the plugin host has been initialized.`);
|
|
1628
1442
|
}
|
|
1629
|
-
|
|
1630
|
-
return isPlainRecord2(parsed) ? parsed : null;
|
|
1443
|
+
return entry;
|
|
1631
1444
|
}
|
|
1632
|
-
function
|
|
1633
|
-
|
|
1634
|
-
return tasks;
|
|
1445
|
+
function listManagedRepoEntries() {
|
|
1446
|
+
return Array.from(MANAGED_REPOS.values());
|
|
1635
1447
|
}
|
|
1636
|
-
function
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
const tasks = [];
|
|
1642
|
-
for (const name of readdirSync2(directory)) {
|
|
1643
|
-
if (!FILE_TASK_PATTERN.test(name))
|
|
1644
|
-
continue;
|
|
1645
|
-
const inferredId = basename4(name).replace(FILE_TASK_PATTERN, "");
|
|
1646
|
-
const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
|
|
1647
|
-
if (task)
|
|
1648
|
-
tasks.push(task);
|
|
1448
|
+
function resolveManagedRepoIdByAlias(alias) {
|
|
1449
|
+
for (const entry of MANAGED_REPOS.values()) {
|
|
1450
|
+
if (entry.alias === alias) {
|
|
1451
|
+
return entry.id;
|
|
1452
|
+
}
|
|
1649
1453
|
}
|
|
1650
|
-
return
|
|
1454
|
+
return null;
|
|
1651
1455
|
}
|
|
1652
|
-
function
|
|
1653
|
-
|
|
1654
|
-
if (!file) {
|
|
1456
|
+
function repoRegistrationToManagedEntry(reg) {
|
|
1457
|
+
if (!reg.defaultBranch) {
|
|
1655
1458
|
return null;
|
|
1656
1459
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1460
|
+
return {
|
|
1461
|
+
id: reg.id,
|
|
1462
|
+
alias: reg.defaultPath ?? reg.id,
|
|
1463
|
+
defaultBranch: reg.defaultBranch,
|
|
1464
|
+
defaultRemoteUrl: reg.url,
|
|
1465
|
+
remoteEnvVar: reg.remoteEnvVar,
|
|
1466
|
+
checkoutEnvVar: reg.checkoutEnvVar
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// packages/runtime/src/control-plane/agent-roles.ts
|
|
1471
|
+
function createAgentRoleRegistry(pluginRoles, configOverrides) {
|
|
1472
|
+
const map = new Map;
|
|
1473
|
+
for (const r of pluginRoles) {
|
|
1474
|
+
if (map.has(r.id))
|
|
1475
|
+
throw new Error(`agent role already registered: ${r.id}`);
|
|
1476
|
+
const override = configOverrides?.[r.id];
|
|
1477
|
+
const model = override?.model ?? r.defaultModel;
|
|
1478
|
+
if (!model) {
|
|
1479
|
+
throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
|
|
1480
|
+
}
|
|
1481
|
+
map.set(r.id, { ...r, model });
|
|
1660
1482
|
}
|
|
1661
1483
|
return {
|
|
1662
|
-
id
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1484
|
+
resolve(id) {
|
|
1485
|
+
const r = map.get(id);
|
|
1486
|
+
if (!r)
|
|
1487
|
+
throw new Error(`agent role not registered: ${id}`);
|
|
1488
|
+
return r;
|
|
1489
|
+
},
|
|
1490
|
+
list: () => Array.from(map.values())
|
|
1667
1491
|
};
|
|
1668
1492
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1493
|
+
|
|
1494
|
+
// packages/runtime/src/control-plane/task-fields.ts
|
|
1495
|
+
function createTaskFieldRegistry(extensions) {
|
|
1496
|
+
const byId = new Map;
|
|
1497
|
+
for (const e of extensions) {
|
|
1498
|
+
if (byId.has(e.id))
|
|
1499
|
+
throw new Error(`task field extension already registered: ${e.id}`);
|
|
1500
|
+
byId.set(e.id, e);
|
|
1672
1501
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1502
|
+
return {
|
|
1503
|
+
get: (id) => byId.get(id),
|
|
1504
|
+
list: () => Array.from(byId.values()),
|
|
1505
|
+
fieldNames: () => Array.from(byId.values()).map((e) => e.fieldName),
|
|
1506
|
+
validateTaskFields(task) {
|
|
1507
|
+
const errors = [];
|
|
1508
|
+
for (const ext of byId.values()) {
|
|
1509
|
+
let schema;
|
|
1510
|
+
try {
|
|
1511
|
+
schema = JSON.parse(ext.schemaJson);
|
|
1512
|
+
} catch {
|
|
1513
|
+
errors.push(`task field "${ext.id}": schemaJson is not valid JSON`);
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
|
|
1517
|
+
if (!isRequired)
|
|
1518
|
+
continue;
|
|
1519
|
+
const value = task[ext.fieldName];
|
|
1520
|
+
if (value === undefined || value === null || value === "") {
|
|
1521
|
+
errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
|
|
1522
|
+
}
|
|
1685
1523
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1524
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1689
1527
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1528
|
+
|
|
1529
|
+
// packages/runtime/src/control-plane/validators/runtime-registration.ts
|
|
1530
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1531
|
+
import { join } from "path";
|
|
1532
|
+
function createValidatorRegistry() {
|
|
1533
|
+
const map = new Map;
|
|
1534
|
+
const order = [];
|
|
1535
|
+
const registry = {
|
|
1536
|
+
register(v) {
|
|
1537
|
+
if (map.has(v.id))
|
|
1538
|
+
throw new Error(`validator already registered: ${v.id}`);
|
|
1539
|
+
map.set(v.id, v);
|
|
1540
|
+
order.push(v);
|
|
1541
|
+
},
|
|
1542
|
+
resolve(id) {
|
|
1543
|
+
const v = map.get(id);
|
|
1544
|
+
if (!v)
|
|
1545
|
+
throw new Error(`validator not registered: ${id}`);
|
|
1546
|
+
return v;
|
|
1547
|
+
},
|
|
1548
|
+
list: () => order
|
|
1549
|
+
};
|
|
1550
|
+
registerBuiltInValidators(registry);
|
|
1551
|
+
return registry;
|
|
1702
1552
|
}
|
|
1703
|
-
function
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
return { owner: parsed[1], repo: parsed[2] };
|
|
1711
|
-
}
|
|
1712
|
-
throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
|
|
1553
|
+
function registerBuiltInValidators(registry) {
|
|
1554
|
+
registry.register({
|
|
1555
|
+
id: "std:typecheck",
|
|
1556
|
+
category: "custom",
|
|
1557
|
+
description: "Runs the package typecheck script when present.",
|
|
1558
|
+
run: async (ctx) => runStdTypecheck(ctx)
|
|
1559
|
+
});
|
|
1713
1560
|
}
|
|
1714
|
-
function
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1561
|
+
async function runStdTypecheck(ctx) {
|
|
1562
|
+
const packageJsonPath = join(ctx.workspaceRoot, "package.json");
|
|
1563
|
+
if (!existsSync9(packageJsonPath)) {
|
|
1564
|
+
return {
|
|
1565
|
+
id: "std:typecheck",
|
|
1566
|
+
passed: false,
|
|
1567
|
+
summary: `package.json not found at ${packageJsonPath}`
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
const proc = Bun.spawn(["bun", "run", "typecheck"], {
|
|
1571
|
+
cwd: ctx.workspaceRoot,
|
|
1572
|
+
env: process.env,
|
|
1573
|
+
stdout: "pipe",
|
|
1574
|
+
stderr: "pipe"
|
|
1575
|
+
});
|
|
1576
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
1577
|
+
proc.exited,
|
|
1578
|
+
new Response(proc.stdout).text(),
|
|
1579
|
+
new Response(proc.stderr).text()
|
|
1580
|
+
]);
|
|
1581
|
+
const output = `${stdout}${stderr}`.trim();
|
|
1721
1582
|
return {
|
|
1722
|
-
id:
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
body,
|
|
1727
|
-
...scope.length > 0 ? { scope } : {},
|
|
1728
|
-
...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
|
|
1729
|
-
...validators.length > 0 ? { validators } : {},
|
|
1730
|
-
...issue.url ? { url: issue.url } : {},
|
|
1731
|
-
issueType: issueTypeFor(labelNames),
|
|
1732
|
-
sourceIssueId: `${repo}#${issue.number}`,
|
|
1733
|
-
parentChildDeps: parseParents(body),
|
|
1734
|
-
labels: labelNames,
|
|
1735
|
-
raw: issue,
|
|
1736
|
-
source: "github-issues",
|
|
1737
|
-
_rig: {
|
|
1738
|
-
taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
|
|
1739
|
-
sourceIssueId: `${repo}#${issue.number}`
|
|
1740
|
-
}
|
|
1583
|
+
id: "std:typecheck",
|
|
1584
|
+
passed: exitCode === 0,
|
|
1585
|
+
summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
|
|
1586
|
+
...output ? { details: output.slice(0, 4000) } : {}
|
|
1741
1587
|
};
|
|
1742
1588
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
if (labelNames.includes("in-progress"))
|
|
1749
|
-
return "in_progress";
|
|
1750
|
-
if (labelNames.includes("blocked"))
|
|
1751
|
-
return "blocked";
|
|
1752
|
-
if (labelNames.includes("ready"))
|
|
1753
|
-
return "ready";
|
|
1754
|
-
if (labelNames.includes("under-review"))
|
|
1755
|
-
return "under_review";
|
|
1756
|
-
if (labelNames.includes("failed"))
|
|
1757
|
-
return "failed";
|
|
1758
|
-
if (labelNames.includes("cancelled"))
|
|
1759
|
-
return "cancelled";
|
|
1760
|
-
return "open";
|
|
1589
|
+
|
|
1590
|
+
// packages/runtime/src/control-plane/native/scope-rules.ts
|
|
1591
|
+
var activeRules = null;
|
|
1592
|
+
function setScopeRules(rules) {
|
|
1593
|
+
activeRules = rules ?? null;
|
|
1761
1594
|
}
|
|
1762
|
-
function
|
|
1763
|
-
|
|
1764
|
-
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
1595
|
+
function getScopeRules() {
|
|
1596
|
+
return activeRules;
|
|
1765
1597
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1598
|
+
|
|
1599
|
+
// packages/runtime/src/control-plane/hook-materializer.ts
|
|
1600
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1601
|
+
import { dirname as dirname6, resolve as resolve10 } from "path";
|
|
1602
|
+
var MARKER_PLUGIN = "_rigPlugin";
|
|
1603
|
+
var MARKER_HOOK_ID = "_rigHookId";
|
|
1604
|
+
function matcherToString(matcher) {
|
|
1605
|
+
if (matcher.kind === "all")
|
|
1606
|
+
return;
|
|
1607
|
+
if (matcher.kind === "tool")
|
|
1608
|
+
return matcher.name;
|
|
1609
|
+
return matcher.pattern;
|
|
1768
1610
|
}
|
|
1769
|
-
function
|
|
1770
|
-
|
|
1771
|
-
assertGhSuccess(args, res);
|
|
1772
|
-
if (!res.stdout || res.stdout.trim() === "") {
|
|
1773
|
-
throw new Error(`gh ${args.join(" ")} returned empty stdout`);
|
|
1774
|
-
}
|
|
1775
|
-
return JSON.parse(res.stdout);
|
|
1611
|
+
function isPluginOwned(cmd) {
|
|
1612
|
+
return typeof cmd[MARKER_PLUGIN] === "string";
|
|
1776
1613
|
}
|
|
1777
|
-
function
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1614
|
+
function materializeHooks(projectRoot, entries) {
|
|
1615
|
+
const settingsPath = resolve10(projectRoot, ".claude", "settings.json");
|
|
1616
|
+
const existing = existsSync10(settingsPath) ? safeReadJson(settingsPath) : {};
|
|
1617
|
+
const hooks = existing.hooks ?? {};
|
|
1618
|
+
for (const event of Object.keys(hooks)) {
|
|
1619
|
+
const groups = hooks[event] ?? [];
|
|
1620
|
+
const cleaned = [];
|
|
1621
|
+
for (const group of groups) {
|
|
1622
|
+
const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
|
|
1623
|
+
if (operatorHooks.length > 0) {
|
|
1624
|
+
cleaned.push({ ...group, hooks: operatorHooks });
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if (cleaned.length > 0) {
|
|
1628
|
+
hooks[event] = cleaned;
|
|
1629
|
+
} else {
|
|
1630
|
+
delete hooks[event];
|
|
1631
|
+
}
|
|
1781
1632
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1633
|
+
for (const { pluginName, hook } of entries) {
|
|
1634
|
+
if (!hook.command) {
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
const event = hook.event;
|
|
1638
|
+
const matcherString = matcherToString(hook.matcher);
|
|
1639
|
+
const groups = hooks[event] ??= [];
|
|
1640
|
+
let group = groups.find((g) => g.matcher === matcherString);
|
|
1641
|
+
if (!group) {
|
|
1642
|
+
group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
|
|
1643
|
+
groups.push(group);
|
|
1644
|
+
}
|
|
1645
|
+
group.hooks.push({
|
|
1646
|
+
type: "command",
|
|
1647
|
+
command: hook.command,
|
|
1648
|
+
[MARKER_PLUGIN]: pluginName,
|
|
1649
|
+
[MARKER_HOOK_ID]: hook.id
|
|
1650
|
+
});
|
|
1784
1651
|
}
|
|
1652
|
+
const next = { ...existing };
|
|
1653
|
+
if (Object.keys(hooks).length > 0) {
|
|
1654
|
+
next.hooks = hooks;
|
|
1655
|
+
} else {
|
|
1656
|
+
delete next.hooks;
|
|
1657
|
+
}
|
|
1658
|
+
mkdirSync5(dirname6(settingsPath), { recursive: true });
|
|
1659
|
+
writeFileSync3(settingsPath, `${JSON.stringify(next, null, 2)}
|
|
1660
|
+
`, "utf-8");
|
|
1661
|
+
return settingsPath;
|
|
1785
1662
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
}
|
|
1792
|
-
function parseIssueRefs(body, pattern) {
|
|
1793
|
-
const match = body.match(pattern);
|
|
1794
|
-
if (!match)
|
|
1795
|
-
return [];
|
|
1796
|
-
return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
|
|
1797
|
-
}
|
|
1798
|
-
function issueTypeFor(labels) {
|
|
1799
|
-
const typed = labels.find((label) => label.startsWith("type:"));
|
|
1800
|
-
if (typed)
|
|
1801
|
-
return typed.slice("type:".length);
|
|
1802
|
-
if (labels.includes("epic"))
|
|
1803
|
-
return "epic";
|
|
1804
|
-
return "task";
|
|
1805
|
-
}
|
|
1806
|
-
function isPlainRecord2(candidate) {
|
|
1807
|
-
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
1663
|
+
function safeReadJson(path) {
|
|
1664
|
+
try {
|
|
1665
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
1666
|
+
} catch {
|
|
1667
|
+
return {};
|
|
1668
|
+
}
|
|
1808
1669
|
}
|
|
1809
1670
|
|
|
1810
|
-
// packages/runtime/src/control-plane/
|
|
1811
|
-
|
|
1812
|
-
|
|
1671
|
+
// packages/runtime/src/control-plane/skill-materializer.ts
|
|
1672
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync5, readdirSync, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1673
|
+
import { resolve as resolve11 } from "path";
|
|
1674
|
+
import { loadSkill } from "@rig/skill-loader";
|
|
1675
|
+
var MARKER_FILENAME = ".rig-plugin";
|
|
1676
|
+
function skillDirName(id) {
|
|
1677
|
+
return id.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
1813
1678
|
}
|
|
1814
|
-
async function
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1679
|
+
async function materializeSkills(projectRoot, entries) {
|
|
1680
|
+
const skillsRoot = resolve11(projectRoot, ".pi", "skills");
|
|
1681
|
+
if (existsSync11(skillsRoot)) {
|
|
1682
|
+
for (const name of readdirSync(skillsRoot)) {
|
|
1683
|
+
const dir = resolve11(skillsRoot, name);
|
|
1684
|
+
if (existsSync11(resolve11(dir, MARKER_FILENAME))) {
|
|
1685
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1819
1688
|
}
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1689
|
+
const written = [];
|
|
1690
|
+
for (const { pluginName, skill } of entries) {
|
|
1691
|
+
const sourcePath = resolve11(projectRoot, skill.path);
|
|
1692
|
+
if (!existsSync11(sourcePath)) {
|
|
1693
|
+
console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
let body;
|
|
1697
|
+
try {
|
|
1698
|
+
await loadSkill(sourcePath);
|
|
1699
|
+
body = readFileSync5(sourcePath, "utf-8");
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${err instanceof Error ? err.message : err}`);
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
const dir = resolve11(skillsRoot, skillDirName(skill.id));
|
|
1705
|
+
mkdirSync6(dir, { recursive: true });
|
|
1706
|
+
writeFileSync4(resolve11(dir, "SKILL.md"), body, "utf-8");
|
|
1707
|
+
writeFileSync4(resolve11(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
|
|
1708
|
+
`, "utf-8");
|
|
1709
|
+
written.push({ id: skill.id, pluginName, directory: dir });
|
|
1710
|
+
}
|
|
1711
|
+
return written;
|
|
1837
1712
|
}
|
|
1838
1713
|
|
|
1839
|
-
// packages/runtime/src/control-plane/
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
"open",
|
|
1848
|
-
"ready",
|
|
1849
|
-
"queued",
|
|
1850
|
-
"in_progress",
|
|
1851
|
-
"under_review",
|
|
1852
|
-
"blocked",
|
|
1853
|
-
"completed",
|
|
1854
|
-
"cancelled"
|
|
1855
|
-
]);
|
|
1856
|
-
function normalizeTaskLifecycleStatus(status) {
|
|
1857
|
-
switch (status) {
|
|
1858
|
-
case "draft":
|
|
1859
|
-
case "open":
|
|
1860
|
-
case "ready":
|
|
1861
|
-
case "queued":
|
|
1862
|
-
case "in_progress":
|
|
1863
|
-
case "under_review":
|
|
1864
|
-
case "blocked":
|
|
1865
|
-
case "completed":
|
|
1866
|
-
case "cancelled":
|
|
1867
|
-
return status;
|
|
1868
|
-
case "closed":
|
|
1869
|
-
return "completed";
|
|
1870
|
-
case "running":
|
|
1871
|
-
return "in_progress";
|
|
1872
|
-
case "failed":
|
|
1873
|
-
return "ready";
|
|
1874
|
-
default:
|
|
1714
|
+
// packages/runtime/src/control-plane/plugin-host-context.ts
|
|
1715
|
+
async function buildPluginHostContext(projectRoot) {
|
|
1716
|
+
let config;
|
|
1717
|
+
try {
|
|
1718
|
+
config = await loadConfig(projectRoot);
|
|
1719
|
+
} catch (err) {
|
|
1720
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1721
|
+
if (msg.includes("no rig.config")) {
|
|
1875
1722
|
return null;
|
|
1723
|
+
}
|
|
1724
|
+
throw err;
|
|
1876
1725
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
switch (status) {
|
|
1883
|
-
case "closed":
|
|
1884
|
-
return "completed";
|
|
1885
|
-
case "running":
|
|
1886
|
-
return "in_progress";
|
|
1887
|
-
case "failed":
|
|
1888
|
-
return "ready";
|
|
1889
|
-
default:
|
|
1890
|
-
return;
|
|
1726
|
+
const pluginHost = createPluginHost(config.plugins);
|
|
1727
|
+
setScopeRules(config.workspace.scopeNormalization);
|
|
1728
|
+
const validatorRegistry = createValidatorRegistry();
|
|
1729
|
+
for (const impl of pluginHost.listExecutableValidators()) {
|
|
1730
|
+
validatorRegistry.register(impl);
|
|
1891
1731
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1732
|
+
const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost);
|
|
1733
|
+
const repoRegistry = createRepoRegistry(pluginHost.listRepoSources());
|
|
1734
|
+
const managedEntries = pluginHost.listRepoSources().map(repoRegistrationToManagedEntry).filter((e) => e !== null);
|
|
1735
|
+
setManagedRepos(managedEntries);
|
|
1736
|
+
const configRoleOverrides = config.runtime?.agentRoles;
|
|
1737
|
+
const agentRoleRegistry = createAgentRoleRegistry(pluginHost.listAgentRoles(), configRoleOverrides);
|
|
1738
|
+
const taskFieldRegistry = createTaskFieldRegistry(pluginHost.listTaskFieldExtensions());
|
|
1739
|
+
try {
|
|
1740
|
+
const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
|
|
1741
|
+
pluginName: plugin.name,
|
|
1742
|
+
hook
|
|
1743
|
+
})));
|
|
1744
|
+
if (hookEntries.length > 0) {
|
|
1745
|
+
materializeHooks(projectRoot, hookEntries);
|
|
1746
|
+
}
|
|
1747
|
+
} catch (err) {
|
|
1748
|
+
console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
|
|
1896
1749
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1750
|
+
try {
|
|
1751
|
+
const skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
|
|
1752
|
+
pluginName: plugin.name,
|
|
1753
|
+
skill
|
|
1754
|
+
})));
|
|
1755
|
+
if (skillEntries.length > 0) {
|
|
1756
|
+
await materializeSkills(projectRoot, skillEntries);
|
|
1757
|
+
}
|
|
1758
|
+
} catch (err) {
|
|
1759
|
+
console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
|
|
1902
1760
|
}
|
|
1903
1761
|
return {
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
|
|
1912
|
-
...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
|
|
1913
|
-
...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
|
|
1914
|
-
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
1915
|
-
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
1762
|
+
config,
|
|
1763
|
+
pluginHost,
|
|
1764
|
+
validatorRegistry,
|
|
1765
|
+
taskSourceRegistry,
|
|
1766
|
+
repoRegistry,
|
|
1767
|
+
agentRoleRegistry,
|
|
1768
|
+
taskFieldRegistry
|
|
1916
1769
|
};
|
|
1917
1770
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1771
|
+
|
|
1772
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
1773
|
+
import { spawnSync } from "child_process";
|
|
1774
|
+
import { existsSync as existsSync13, readFileSync as readFileSync7, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1775
|
+
import { basename as basename4, join as join2, resolve as resolve13 } from "path";
|
|
1776
|
+
|
|
1777
|
+
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
1778
|
+
import { existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
|
|
1779
|
+
import { resolve as resolve12 } from "path";
|
|
1780
|
+
|
|
1781
|
+
// packages/runtime/src/control-plane/tasks/task-record-reader.ts
|
|
1782
|
+
async function findTaskById(reader, id) {
|
|
1783
|
+
const tasks = await reader.listTasks();
|
|
1784
|
+
return tasks.find((task) => task.id === id) ?? null;
|
|
1929
1785
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1786
|
+
|
|
1787
|
+
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
1788
|
+
class LegacyTaskConfigReadError extends Error {
|
|
1789
|
+
code = "LEGACY_TASK_CONFIG_READ_FAILED";
|
|
1790
|
+
projectRoot;
|
|
1791
|
+
configPath;
|
|
1792
|
+
cause;
|
|
1793
|
+
constructor(input) {
|
|
1794
|
+
super(input.message, { cause: input.cause });
|
|
1795
|
+
this.name = "LegacyTaskConfigReadError";
|
|
1796
|
+
this.projectRoot = input.projectRoot;
|
|
1797
|
+
this.configPath = input.configPath;
|
|
1798
|
+
this.cause = input.cause;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
|
|
1802
|
+
const configPath = options.configPath ?? resolve12(projectRoot, ".rig", "task-config.json");
|
|
1803
|
+
const reader = {
|
|
1804
|
+
async listTasks() {
|
|
1805
|
+
return readLegacyTaskRecords(projectRoot, configPath);
|
|
1806
|
+
},
|
|
1807
|
+
async getTask(id) {
|
|
1808
|
+
return findTaskById(reader, id);
|
|
1809
|
+
}
|
|
1946
1810
|
};
|
|
1811
|
+
return reader;
|
|
1947
1812
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
import { tmpdir as tmpdir3 } from "os";
|
|
1955
|
-
import { dirname as dirname6, isAbsolute, resolve as resolve13 } from "path";
|
|
1956
|
-
import { createHash } from "crypto";
|
|
1957
|
-
var sharedGitNativeOutputDir = resolve13(tmpdir3(), "rig-native");
|
|
1958
|
-
var sharedGitNativeOutputPath = resolve13(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
1959
|
-
var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
1960
|
-
function temporaryGitBinaryOutputPath(outputPath) {
|
|
1961
|
-
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
1962
|
-
return resolve13(dirname6(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
|
|
1813
|
+
function readLegacyTaskRecords(projectRoot, configPath = resolve12(projectRoot, ".rig", "task-config.json")) {
|
|
1814
|
+
if (!existsSync12(configPath)) {
|
|
1815
|
+
return [];
|
|
1816
|
+
}
|
|
1817
|
+
const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
|
|
1818
|
+
return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
|
|
1963
1819
|
}
|
|
1964
|
-
function
|
|
1820
|
+
function readLegacyTaskConfigJson(projectRoot, configPath) {
|
|
1965
1821
|
try {
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
rmSync5(outputPath, { force: true });
|
|
1970
|
-
renameSync(tempOutputPath, outputPath);
|
|
1971
|
-
return;
|
|
1822
|
+
const parsed = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
1823
|
+
if (isPlainRecord(parsed)) {
|
|
1824
|
+
return parsed;
|
|
1972
1825
|
}
|
|
1973
|
-
throw
|
|
1826
|
+
throw new Error("task config root must be a JSON object");
|
|
1827
|
+
} catch (cause) {
|
|
1828
|
+
throw new LegacyTaskConfigReadError({
|
|
1829
|
+
projectRoot,
|
|
1830
|
+
configPath,
|
|
1831
|
+
message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1832
|
+
cause
|
|
1833
|
+
});
|
|
1974
1834
|
}
|
|
1975
1835
|
}
|
|
1976
|
-
function
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
function rigGitSourceCandidates() {
|
|
1980
|
-
const execDir = process.execPath?.trim() ? dirname6(process.execPath.trim()) : "";
|
|
1981
|
-
const cwd = process.cwd()?.trim() || "";
|
|
1982
|
-
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
1983
|
-
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
1984
|
-
const moduleRelativeSource = resolve13(import.meta.dir, "../../../native/rig-git.zig");
|
|
1985
|
-
return [...new Set([
|
|
1986
|
-
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
1987
|
-
moduleRelativeSource,
|
|
1988
|
-
projectRoot ? resolve13(projectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
1989
|
-
hostProjectRoot ? resolve13(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
1990
|
-
cwd ? resolve13(cwd, "packages/runtime/native/rig-git.zig") : "",
|
|
1991
|
-
execDir ? resolve13(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
|
|
1992
|
-
execDir ? resolve13(execDir, "..", "native", "rig-git.zig") : ""
|
|
1993
|
-
].filter(Boolean))];
|
|
1836
|
+
function stripLegacyTaskConfigMetadata(raw) {
|
|
1837
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1838
|
+
return tasks;
|
|
1994
1839
|
}
|
|
1995
|
-
function
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
for (let index = 0;index < 8; index += 1) {
|
|
1999
|
-
candidates.push(resolve13(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve13(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve13(cursor, "native", fileName), resolve13(cursor, "native", "bin", fileName));
|
|
2000
|
-
const parent = dirname6(cursor);
|
|
2001
|
-
if (parent === cursor)
|
|
2002
|
-
break;
|
|
2003
|
-
cursor = parent;
|
|
1840
|
+
function legacyTaskConfigEntryToRecord(id, entry) {
|
|
1841
|
+
if (!isPlainRecord(entry)) {
|
|
1842
|
+
return null;
|
|
2004
1843
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
const
|
|
2009
|
-
const
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
1844
|
+
const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
|
|
1845
|
+
const validation = readStringList(entry.validation);
|
|
1846
|
+
const validators = readStringList(entry.validators);
|
|
1847
|
+
const scope = readStringList(entry.scope);
|
|
1848
|
+
const status = typeof entry.status === "string" ? entry.status : "open";
|
|
1849
|
+
const title = typeof entry.title === "string" ? entry.title : undefined;
|
|
1850
|
+
const description = typeof entry.description === "string" ? entry.description : undefined;
|
|
1851
|
+
const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
|
|
1852
|
+
return {
|
|
1853
|
+
id,
|
|
1854
|
+
deps,
|
|
1855
|
+
status,
|
|
1856
|
+
source: "legacy-task-config",
|
|
1857
|
+
...title ? { title } : {},
|
|
1858
|
+
...description ? { description } : {},
|
|
1859
|
+
...acceptanceCriteria ? { acceptanceCriteria } : {},
|
|
1860
|
+
...scope.length > 0 ? { scope } : {},
|
|
1861
|
+
...validation.length > 0 ? { validation } : {},
|
|
1862
|
+
...validators.length > 0 ? { validators } : {},
|
|
1863
|
+
...preservedLegacyFields(entry)
|
|
1864
|
+
};
|
|
2019
1865
|
}
|
|
2020
|
-
function
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
1866
|
+
function preservedLegacyFields(entry) {
|
|
1867
|
+
const preserved = {};
|
|
1868
|
+
for (const key of [
|
|
1869
|
+
"role",
|
|
1870
|
+
"browser",
|
|
1871
|
+
"repo_pins",
|
|
1872
|
+
"criticality",
|
|
1873
|
+
"queue_weight",
|
|
1874
|
+
"creates_repo",
|
|
1875
|
+
"auto_synced"
|
|
1876
|
+
]) {
|
|
1877
|
+
if (entry[key] !== undefined) {
|
|
1878
|
+
preserved[key] = entry[key];
|
|
2024
1879
|
}
|
|
2025
1880
|
}
|
|
2026
|
-
return
|
|
1881
|
+
return preserved;
|
|
2027
1882
|
}
|
|
2028
|
-
function
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
if (candidate && existsSync13(candidate)) {
|
|
2034
|
-
return candidate;
|
|
1883
|
+
function firstStringList(...candidates) {
|
|
1884
|
+
for (const candidate of candidates) {
|
|
1885
|
+
const list = readStringList(candidate);
|
|
1886
|
+
if (list.length > 0) {
|
|
1887
|
+
return list;
|
|
2035
1888
|
}
|
|
2036
1889
|
}
|
|
2037
|
-
return
|
|
2038
|
-
}
|
|
2039
|
-
function preferredGitBinaryOutputPath() {
|
|
2040
|
-
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
2041
|
-
return explicit || sharedGitNativeOutputPath;
|
|
1890
|
+
return [];
|
|
2042
1891
|
}
|
|
2043
|
-
function
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
stdout: "pipe",
|
|
2047
|
-
stderr: "pipe"
|
|
2048
|
-
});
|
|
2049
|
-
const stdout = probe.stdout.toString().trim();
|
|
2050
|
-
const stderr = probe.stderr.toString().trim();
|
|
2051
|
-
if (stdout.includes('"error":"unknown command"')) {
|
|
2052
|
-
return false;
|
|
2053
|
-
}
|
|
2054
|
-
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
2055
|
-
} catch {
|
|
2056
|
-
return false;
|
|
1892
|
+
function readStringList(candidate) {
|
|
1893
|
+
if (!Array.isArray(candidate)) {
|
|
1894
|
+
return [];
|
|
2057
1895
|
}
|
|
1896
|
+
return candidate.filter((value) => typeof value === "string");
|
|
2058
1897
|
}
|
|
2059
|
-
function
|
|
2060
|
-
return
|
|
1898
|
+
function isPlainRecord(candidate) {
|
|
1899
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
2061
1900
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
1901
|
+
|
|
1902
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
1903
|
+
var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
|
|
1904
|
+
var FILE_TASK_PATTERN = /\.(task\.)?json$/;
|
|
1905
|
+
function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
|
|
1906
|
+
const configPath = options.configPath ?? resolve13(projectRoot, ".rig", "task-config.json");
|
|
1907
|
+
const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
|
|
1908
|
+
const spawnFn = options.spawn ?? spawnSync;
|
|
1909
|
+
const ghBinary = options.ghBinary ?? "gh";
|
|
1910
|
+
const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
|
|
1911
|
+
return {
|
|
1912
|
+
async listTasks() {
|
|
1913
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
1914
|
+
if (!rawConfig) {
|
|
1915
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
1916
|
+
return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
|
|
1917
|
+
}
|
|
1918
|
+
const tasks = [];
|
|
1919
|
+
const legacyTasks = await legacy.listTasks();
|
|
1920
|
+
const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
|
|
1921
|
+
for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
|
|
1922
|
+
if (!isPlainRecord2(rawEntry)) {
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1926
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
1927
|
+
tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1931
|
+
const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1932
|
+
if (fileTask)
|
|
1933
|
+
tasks.push(fileTask);
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (!allowLocalFallback) {
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
const legacyTask = legacyById.get(id);
|
|
1940
|
+
if (legacyTask) {
|
|
1941
|
+
tasks.push(legacyTask);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
return tasks;
|
|
1945
|
+
},
|
|
1946
|
+
async getTask(id) {
|
|
1947
|
+
const rawEntry = readRawTaskEntry(configPath, id);
|
|
1948
|
+
if (!rawEntry) {
|
|
1949
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
1950
|
+
return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
|
|
1951
|
+
}
|
|
1952
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1953
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
1954
|
+
return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
|
|
1955
|
+
}
|
|
1956
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1957
|
+
return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1958
|
+
}
|
|
1959
|
+
return allowLocalFallback ? legacy.getTask(id) : null;
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
function readMaterializedTaskMetadata(entry) {
|
|
1964
|
+
const rawRig = entry._rig;
|
|
1965
|
+
if (!isPlainRecord2(rawRig)) {
|
|
1966
|
+
return {};
|
|
2065
1967
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
1968
|
+
const rawSource = rawRig.taskSource;
|
|
1969
|
+
const metadata = {};
|
|
1970
|
+
if (isPlainRecord2(rawSource)) {
|
|
1971
|
+
const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
|
|
1972
|
+
if (kind.length > 0) {
|
|
1973
|
+
metadata.taskSource = {
|
|
1974
|
+
kind,
|
|
1975
|
+
...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
|
|
1976
|
+
...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
|
|
1977
|
+
...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
|
|
1978
|
+
...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
|
|
1979
|
+
...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
if (typeof rawRig.sourceIssueId === "string") {
|
|
1984
|
+
metadata.sourceIssueId = rawRig.sourceIssueId;
|
|
1985
|
+
}
|
|
1986
|
+
return metadata;
|
|
1987
|
+
}
|
|
1988
|
+
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
1989
|
+
const jsonPath = resolve13(projectRoot, "rig.config.json");
|
|
1990
|
+
if (existsSync13(jsonPath)) {
|
|
1991
|
+
try {
|
|
1992
|
+
const parsed = JSON.parse(readFileSync7(jsonPath, "utf8"));
|
|
1993
|
+
if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
|
|
1994
|
+
const source = parsed.taskSource;
|
|
1995
|
+
return source.kind === "files" && typeof source.path === "string" ? source.path : null;
|
|
1996
|
+
}
|
|
1997
|
+
} catch {
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2071
2000
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
return false;
|
|
2001
|
+
const tsPath = resolve13(projectRoot, "rig.config.ts");
|
|
2002
|
+
if (!existsSync13(tsPath)) {
|
|
2003
|
+
return null;
|
|
2076
2004
|
}
|
|
2077
2005
|
try {
|
|
2078
|
-
const
|
|
2079
|
-
|
|
2006
|
+
const source = readFileSync7(tsPath, "utf8");
|
|
2007
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
2008
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
2009
|
+
if (kind !== "files") {
|
|
2010
|
+
return null;
|
|
2011
|
+
}
|
|
2012
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
2080
2013
|
} catch {
|
|
2081
|
-
return
|
|
2014
|
+
return null;
|
|
2082
2015
|
}
|
|
2083
2016
|
}
|
|
2084
|
-
|
|
2085
|
-
const
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
}
|
|
2089
|
-
function sha256FileSync(path) {
|
|
2090
|
-
return createHash("sha256").update(readFileSync7(path)).digest("hex");
|
|
2091
|
-
}
|
|
2092
|
-
async function ensureRigGitBinaryPath(outputPath = sharedGitNativeOutputPath) {
|
|
2093
|
-
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
2094
|
-
throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
|
|
2095
|
-
}
|
|
2096
|
-
const sourcePath = resolveGitSourcePath();
|
|
2097
|
-
if (!sourcePath) {
|
|
2098
|
-
const binaryPath = resolveGitBinaryPath();
|
|
2099
|
-
if (binaryPath) {
|
|
2100
|
-
return binaryPath;
|
|
2101
|
-
}
|
|
2102
|
-
throw new Error("rig-git.zig source file not found.");
|
|
2017
|
+
function readRawTaskEntry(configPath, taskId) {
|
|
2018
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
2019
|
+
if (!rawConfig) {
|
|
2020
|
+
return null;
|
|
2103
2021
|
}
|
|
2104
|
-
const
|
|
2105
|
-
|
|
2106
|
-
|
|
2022
|
+
const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
|
|
2023
|
+
return isPlainRecord2(entry) ? entry : null;
|
|
2024
|
+
}
|
|
2025
|
+
function readRawTaskConfig(configPath) {
|
|
2026
|
+
if (!existsSync13(configPath)) {
|
|
2027
|
+
return null;
|
|
2107
2028
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
const needsBuild = !existsSync13(outputPath) || !await hasMatchingNativeBuildManifest3(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
2120
|
-
if (!needsBuild) {
|
|
2121
|
-
chmodSync3(outputPath, 493);
|
|
2122
|
-
return outputPath;
|
|
2029
|
+
const parsed = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
2030
|
+
return isPlainRecord2(parsed) ? parsed : null;
|
|
2031
|
+
}
|
|
2032
|
+
function stripLegacyTaskConfigMetadata2(raw) {
|
|
2033
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
2034
|
+
return tasks;
|
|
2035
|
+
}
|
|
2036
|
+
function listFileBackedTasks(projectRoot, sourcePath) {
|
|
2037
|
+
const directory = resolve13(projectRoot, sourcePath);
|
|
2038
|
+
if (!existsSync13(directory)) {
|
|
2039
|
+
return [];
|
|
2123
2040
|
}
|
|
2124
|
-
const
|
|
2125
|
-
const
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
], {
|
|
2133
|
-
cwd: dirname6(sourcePath),
|
|
2134
|
-
stdout: "pipe",
|
|
2135
|
-
stderr: "pipe"
|
|
2136
|
-
});
|
|
2137
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
2138
|
-
build.exited,
|
|
2139
|
-
new Response(build.stdout).text(),
|
|
2140
|
-
new Response(build.stderr).text()
|
|
2141
|
-
]);
|
|
2142
|
-
if (exitCode !== 0 || !existsSync13(tempOutputPath)) {
|
|
2143
|
-
const details = [stderr.trim(), stdout.trim()].filter(Boolean).join(`
|
|
2144
|
-
`);
|
|
2145
|
-
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${exitCode}`}`);
|
|
2041
|
+
const tasks = [];
|
|
2042
|
+
for (const name of readdirSync2(directory)) {
|
|
2043
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
2044
|
+
continue;
|
|
2045
|
+
const inferredId = basename4(name).replace(FILE_TASK_PATTERN, "");
|
|
2046
|
+
const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
|
|
2047
|
+
if (task)
|
|
2048
|
+
tasks.push(task);
|
|
2146
2049
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2050
|
+
return tasks;
|
|
2051
|
+
}
|
|
2052
|
+
function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
|
|
2053
|
+
const file = findFileBackedTaskFile(resolve13(projectRoot, sourcePath), taskId);
|
|
2054
|
+
if (!file) {
|
|
2055
|
+
return null;
|
|
2152
2056
|
}
|
|
2153
|
-
|
|
2154
|
-
if (!
|
|
2155
|
-
|
|
2156
|
-
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
2057
|
+
const raw = JSON.parse(readFileSync7(file, "utf8"));
|
|
2058
|
+
if (!isPlainRecord2(raw)) {
|
|
2059
|
+
return null;
|
|
2157
2060
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2061
|
+
return {
|
|
2062
|
+
id: typeof raw.id === "string" ? raw.id : taskId,
|
|
2063
|
+
deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
|
|
2064
|
+
status: typeof raw.status === "string" ? raw.status : "ready",
|
|
2065
|
+
title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
|
|
2066
|
+
...raw
|
|
2067
|
+
};
|
|
2161
2068
|
}
|
|
2162
|
-
function
|
|
2163
|
-
if (
|
|
2164
|
-
|
|
2069
|
+
function findFileBackedTaskFile(directory, taskId) {
|
|
2070
|
+
if (!existsSync13(directory)) {
|
|
2071
|
+
return null;
|
|
2165
2072
|
}
|
|
2166
|
-
const
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2073
|
+
for (const name of readdirSync2(directory)) {
|
|
2074
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
2075
|
+
continue;
|
|
2076
|
+
const file = join2(directory, name);
|
|
2077
|
+
try {
|
|
2078
|
+
if (!statSync2(file).isFile())
|
|
2079
|
+
continue;
|
|
2080
|
+
const raw = JSON.parse(readFileSync7(file, "utf8"));
|
|
2081
|
+
const inferredId = basename4(file).replace(FILE_TASK_PATTERN, "");
|
|
2082
|
+
const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
|
|
2083
|
+
if (id === taskId) {
|
|
2084
|
+
return file;
|
|
2085
|
+
}
|
|
2086
|
+
} catch {}
|
|
2173
2087
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
|
|
2091
|
+
const source = requireGithubIssueSource(metadata, id);
|
|
2092
|
+
const issue = runGh(bin, [
|
|
2093
|
+
"issue",
|
|
2094
|
+
"view",
|
|
2095
|
+
String(id),
|
|
2096
|
+
"--repo",
|
|
2097
|
+
`${source.owner}/${source.repo}`,
|
|
2098
|
+
"--json",
|
|
2099
|
+
"number,title,body,labels,state,url,assignees"
|
|
2100
|
+
], spawnFn);
|
|
2101
|
+
return githubIssueToTask(issue, source, rawEntry);
|
|
2102
|
+
}
|
|
2103
|
+
function requireGithubIssueSource(metadata, id) {
|
|
2104
|
+
const source = metadata.taskSource;
|
|
2105
|
+
if (source?.kind === "github-issues" && source.owner && source.repo) {
|
|
2106
|
+
return { owner: source.owner, repo: source.repo };
|
|
2177
2107
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
version: 1,
|
|
2182
|
-
zigBinary,
|
|
2183
|
-
platform: process.platform,
|
|
2184
|
-
arch: process.arch,
|
|
2185
|
-
sourcePath,
|
|
2186
|
-
sourceDigest
|
|
2187
|
-
});
|
|
2188
|
-
const manifestPath = nativeBuildManifestPath3(outputPath);
|
|
2189
|
-
const needsBuild = !existsSync13(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
2190
|
-
if (!needsBuild) {
|
|
2191
|
-
chmodSync3(outputPath, 493);
|
|
2192
|
-
return outputPath;
|
|
2108
|
+
const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
2109
|
+
if (parsed && parsed[3] === id) {
|
|
2110
|
+
return { owner: parsed[1], repo: parsed[2] };
|
|
2193
2111
|
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2112
|
+
throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
|
|
2113
|
+
}
|
|
2114
|
+
function githubIssueToTask(issue, source, rawEntry) {
|
|
2115
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
2116
|
+
const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
|
|
2117
|
+
const roleLabel = labelNames.find((label) => label.startsWith("role:"));
|
|
2118
|
+
const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
|
|
2119
|
+
const body = issue.body ?? "";
|
|
2120
|
+
const repo = `${source.owner}/${source.repo}`;
|
|
2121
|
+
return {
|
|
2122
|
+
id: String(issue.number),
|
|
2123
|
+
deps: parseDeps(body),
|
|
2124
|
+
status: githubStatusFor(issue),
|
|
2125
|
+
title: issue.title,
|
|
2126
|
+
body,
|
|
2127
|
+
...scope.length > 0 ? { scope } : {},
|
|
2128
|
+
...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
|
|
2129
|
+
...validators.length > 0 ? { validators } : {},
|
|
2130
|
+
...issue.url ? { url: issue.url } : {},
|
|
2131
|
+
issueType: issueTypeFor(labelNames),
|
|
2132
|
+
sourceIssueId: `${repo}#${issue.number}`,
|
|
2133
|
+
parentChildDeps: parseParents(body),
|
|
2134
|
+
labels: labelNames,
|
|
2135
|
+
raw: issue,
|
|
2136
|
+
source: "github-issues",
|
|
2137
|
+
_rig: {
|
|
2138
|
+
taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
|
|
2139
|
+
sourceIssueId: `${repo}#${issue.number}`
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
function githubStatusFor(issue) {
|
|
2144
|
+
const state = (issue.state ?? "").toUpperCase();
|
|
2145
|
+
if (state === "CLOSED")
|
|
2146
|
+
return "closed";
|
|
2147
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
2148
|
+
if (labelNames.includes("in-progress"))
|
|
2149
|
+
return "in_progress";
|
|
2150
|
+
if (labelNames.includes("blocked"))
|
|
2151
|
+
return "blocked";
|
|
2152
|
+
if (labelNames.includes("ready"))
|
|
2153
|
+
return "ready";
|
|
2154
|
+
if (labelNames.includes("under-review"))
|
|
2155
|
+
return "under_review";
|
|
2156
|
+
if (labelNames.includes("failed"))
|
|
2157
|
+
return "failed";
|
|
2158
|
+
if (labelNames.includes("cancelled"))
|
|
2159
|
+
return "cancelled";
|
|
2160
|
+
return "open";
|
|
2161
|
+
}
|
|
2162
|
+
function selectedGitHubEnv() {
|
|
2163
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
2164
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
2165
|
+
}
|
|
2166
|
+
function ghSpawnOptions() {
|
|
2167
|
+
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
2168
|
+
}
|
|
2169
|
+
function runGh(bin, args, spawnFn) {
|
|
2170
|
+
const res = spawnFn(bin, [...args], ghSpawnOptions());
|
|
2171
|
+
assertGhSuccess(args, res);
|
|
2172
|
+
if (!res.stdout || res.stdout.trim() === "") {
|
|
2173
|
+
throw new Error(`gh ${args.join(" ")} returned empty stdout`);
|
|
2213
2174
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2175
|
+
return JSON.parse(res.stdout);
|
|
2176
|
+
}
|
|
2177
|
+
function assertGhSuccess(args, res) {
|
|
2178
|
+
if (res.error) {
|
|
2179
|
+
const msg = res.error.message ?? String(res.error);
|
|
2180
|
+
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
2219
2181
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
rmSync5(outputPath, { force: true });
|
|
2223
|
-
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
2182
|
+
if (res.status !== 0) {
|
|
2183
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}`);
|
|
2224
2184
|
}
|
|
2225
|
-
writeFileSync5(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
2226
|
-
`, "utf8");
|
|
2227
|
-
return outputPath;
|
|
2228
2185
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2186
|
+
function parseDeps(body) {
|
|
2187
|
+
return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
|
|
2188
|
+
}
|
|
2189
|
+
function parseParents(body) {
|
|
2190
|
+
return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
|
|
2191
|
+
}
|
|
2192
|
+
function parseIssueRefs(body, pattern) {
|
|
2193
|
+
const match = body.match(pattern);
|
|
2194
|
+
if (!match)
|
|
2195
|
+
return [];
|
|
2196
|
+
return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
|
|
2197
|
+
}
|
|
2198
|
+
function issueTypeFor(labels) {
|
|
2199
|
+
const typed = labels.find((label) => label.startsWith("type:"));
|
|
2200
|
+
if (typed)
|
|
2201
|
+
return typed.slice("type:".length);
|
|
2202
|
+
if (labels.includes("epic"))
|
|
2203
|
+
return "epic";
|
|
2204
|
+
return "task";
|
|
2205
|
+
}
|
|
2206
|
+
function isPlainRecord2(candidate) {
|
|
2207
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// packages/runtime/src/control-plane/tasks/source-lifecycle.ts
|
|
2211
|
+
function hasRunnableTaskSource(source) {
|
|
2212
|
+
return Boolean(source && typeof source === "object" && !Array.isArray(source));
|
|
2213
|
+
}
|
|
2214
|
+
async function getPluginTask(projectRoot, taskId) {
|
|
2215
|
+
const ctx = await buildPluginHostContext(projectRoot);
|
|
2216
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
2217
|
+
if (!hasRunnableTaskSource(source)) {
|
|
2218
|
+
return ctx ? { configured: false, sourceKind: null, task: null } : null;
|
|
2245
2219
|
}
|
|
2246
|
-
|
|
2220
|
+
const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
|
|
2221
|
+
return {
|
|
2222
|
+
configured: true,
|
|
2223
|
+
sourceKind: source.kind,
|
|
2224
|
+
task
|
|
2225
|
+
};
|
|
2247
2226
|
}
|
|
2248
|
-
function
|
|
2249
|
-
|
|
2250
|
-
|
|
2227
|
+
async function readConfiguredTaskSourceTask(projectRoot, taskId) {
|
|
2228
|
+
const pluginResult = await getPluginTask(projectRoot, taskId);
|
|
2229
|
+
if (pluginResult)
|
|
2230
|
+
return pluginResult;
|
|
2231
|
+
const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
|
|
2232
|
+
return {
|
|
2233
|
+
configured: false,
|
|
2234
|
+
sourceKind: null,
|
|
2235
|
+
task
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// packages/runtime/src/control-plane/native/task-state.ts
|
|
2240
|
+
import { existsSync as existsSync19, readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
2241
|
+
import { basename as basename6, resolve as resolve19 } from "path";
|
|
2242
|
+
|
|
2243
|
+
// packages/runtime/src/control-plane/state-sync/types.ts
|
|
2244
|
+
var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
|
|
2245
|
+
var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
|
|
2246
|
+
"draft",
|
|
2247
|
+
"open",
|
|
2248
|
+
"ready",
|
|
2249
|
+
"queued",
|
|
2250
|
+
"in_progress",
|
|
2251
|
+
"under_review",
|
|
2252
|
+
"blocked",
|
|
2253
|
+
"completed",
|
|
2254
|
+
"cancelled"
|
|
2255
|
+
]);
|
|
2256
|
+
function normalizeTaskLifecycleStatus(status) {
|
|
2257
|
+
switch (status) {
|
|
2258
|
+
case "draft":
|
|
2259
|
+
case "open":
|
|
2260
|
+
case "ready":
|
|
2261
|
+
case "queued":
|
|
2262
|
+
case "in_progress":
|
|
2263
|
+
case "under_review":
|
|
2264
|
+
case "blocked":
|
|
2265
|
+
case "completed":
|
|
2266
|
+
case "cancelled":
|
|
2267
|
+
return status;
|
|
2268
|
+
case "closed":
|
|
2269
|
+
return "completed";
|
|
2270
|
+
case "running":
|
|
2271
|
+
return "in_progress";
|
|
2272
|
+
case "failed":
|
|
2273
|
+
return "ready";
|
|
2274
|
+
default:
|
|
2275
|
+
return null;
|
|
2251
2276
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
if (
|
|
2255
|
-
|
|
2256
|
-
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
2257
|
-
} catch (error) {
|
|
2258
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2259
|
-
if (message.includes("rig-git.zig source file not found")) {
|
|
2260
|
-
return { ok: false, error: "rig-git binary not found" };
|
|
2261
|
-
}
|
|
2262
|
-
return { ok: false, error: message };
|
|
2263
|
-
}
|
|
2264
|
-
} else {
|
|
2265
|
-
const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
2266
|
-
binaryPath = explicitBinaryPath && existsSync13(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
|
|
2267
|
-
if (!binaryPath) {
|
|
2268
|
-
try {
|
|
2269
|
-
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
2270
|
-
} catch (error) {
|
|
2271
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2272
|
-
if (message.includes("rig-git.zig source file not found")) {
|
|
2273
|
-
return { ok: false, error: "rig-git binary not found" };
|
|
2274
|
-
}
|
|
2275
|
-
return { ok: false, error: message };
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2277
|
+
}
|
|
2278
|
+
function normalizeTaskStateMetadataStatus(status) {
|
|
2279
|
+
if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
|
|
2280
|
+
return status;
|
|
2278
2281
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
try {
|
|
2289
|
-
const parsed = JSON.parse(stdoutText);
|
|
2290
|
-
if (!parsed.ok) {
|
|
2291
|
-
return parsed;
|
|
2292
|
-
}
|
|
2293
|
-
} catch {}
|
|
2294
|
-
}
|
|
2295
|
-
const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
2296
|
-
return { ok: false, error: errText };
|
|
2297
|
-
}
|
|
2298
|
-
const output = proc.stdout.toString().trim();
|
|
2299
|
-
return JSON.parse(output);
|
|
2300
|
-
} catch (err) {
|
|
2301
|
-
return { ok: false, error: String(err) };
|
|
2282
|
+
switch (status) {
|
|
2283
|
+
case "closed":
|
|
2284
|
+
return "completed";
|
|
2285
|
+
case "running":
|
|
2286
|
+
return "in_progress";
|
|
2287
|
+
case "failed":
|
|
2288
|
+
return "ready";
|
|
2289
|
+
default:
|
|
2290
|
+
return;
|
|
2302
2291
|
}
|
|
2303
2292
|
}
|
|
2304
|
-
function
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
2293
|
+
function canonicalizeTaskStateMetadata(raw) {
|
|
2294
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2295
|
+
return null;
|
|
2308
2296
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
return result.value;
|
|
2297
|
+
const metadata = raw;
|
|
2298
|
+
const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
|
|
2299
|
+
const status = normalizeTaskStateMetadataStatus(metadata.status);
|
|
2300
|
+
if (!status) {
|
|
2301
|
+
return null;
|
|
2315
2302
|
}
|
|
2316
|
-
|
|
2303
|
+
return {
|
|
2304
|
+
...claimId ? { claimId } : {},
|
|
2305
|
+
status,
|
|
2306
|
+
...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
|
|
2307
|
+
...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
|
|
2308
|
+
...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
|
|
2309
|
+
...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
|
|
2310
|
+
...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
|
|
2311
|
+
...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
|
|
2312
|
+
...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
|
|
2313
|
+
...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
|
|
2314
|
+
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
2315
|
+
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
2316
|
+
};
|
|
2317
2317
|
}
|
|
2318
|
-
function
|
|
2319
|
-
|
|
2320
|
-
|
|
2318
|
+
function discardMismatchedTaskStateMetadata(input) {
|
|
2319
|
+
input.taskId;
|
|
2320
|
+
const canonicalMetadata = canonicalizeTaskStateMetadata(input.metadata);
|
|
2321
|
+
if (!canonicalMetadata || !input.lifecycleStatus) {
|
|
2322
|
+
return null;
|
|
2323
|
+
}
|
|
2324
|
+
const metadataStatus = canonicalMetadata.status ?? null;
|
|
2325
|
+
if (metadataStatus && metadataStatus !== input.lifecycleStatus) {
|
|
2321
2326
|
return null;
|
|
2322
|
-
if ("value" in result && typeof result.value === "string")
|
|
2323
|
-
return result.value;
|
|
2324
|
-
return null;
|
|
2325
|
-
}
|
|
2326
|
-
function nativeFetchRef(repoPath, remote, branch) {
|
|
2327
|
-
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
2328
|
-
}
|
|
2329
|
-
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
2330
|
-
const requestDir = resolve13(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
2331
|
-
mkdirSync6(requestDir, { recursive: true });
|
|
2332
|
-
const outputPath = resolve13(requestDir, "blob.txt");
|
|
2333
|
-
try {
|
|
2334
|
-
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
2335
|
-
return readFileSync7(outputPath, "utf8");
|
|
2336
|
-
} finally {
|
|
2337
|
-
rmSync5(requestDir, { recursive: true, force: true });
|
|
2338
2327
|
}
|
|
2328
|
+
return canonicalMetadata;
|
|
2339
2329
|
}
|
|
2340
|
-
function
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
const outputPath = resolve13(requestDir, "blob.bin");
|
|
2344
|
-
try {
|
|
2345
|
-
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
2346
|
-
return readFileSync7(outputPath);
|
|
2347
|
-
} finally {
|
|
2348
|
-
rmSync5(requestDir, { recursive: true, force: true });
|
|
2330
|
+
function readTaskStateMetadataEnvelope(raw) {
|
|
2331
|
+
if (!raw || typeof raw !== "object") {
|
|
2332
|
+
return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
|
|
2349
2333
|
}
|
|
2334
|
+
const envelope = raw;
|
|
2335
|
+
const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
|
|
2336
|
+
if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
|
|
2337
|
+
return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
|
|
2338
|
+
}
|
|
2339
|
+
const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
|
|
2340
|
+
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
2341
|
+
return {
|
|
2342
|
+
schemaVersion,
|
|
2343
|
+
supported: true,
|
|
2344
|
+
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
2345
|
+
tasks
|
|
2346
|
+
};
|
|
2350
2347
|
}
|
|
2348
|
+
// packages/runtime/src/control-plane/state-sync/read.ts
|
|
2349
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
|
|
2350
|
+
import { resolve as resolve18 } from "path";
|
|
2351
2351
|
|
|
2352
2352
|
// packages/runtime/src/control-plane/native/utils.ts
|
|
2353
2353
|
init_layout();
|