@h-rig/runtime 0.0.6-alpha.32 → 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.
@@ -457,121 +457,47 @@ function readBuildConfig() {
457
457
  }
458
458
  }
459
459
 
460
- // packages/runtime/src/control-plane/runtime/tooling/gateway.ts
461
- import { existsSync as existsSync7, rmSync as rmSync3, symlinkSync as symlinkSync3 } from "fs";
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 { basename as basename2, dirname as dirname3, resolve as resolve4 } from "path";
468
- var sharedNativeShellOutputDir = resolve4(tmpdir(), "rig-native");
469
- var sharedNativeShellOutputPath = resolve4(sharedNativeShellOutputDir, `rig-shell-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
470
- function runtimeRigShellFileName() {
471
- return `rig-shell${process.platform === "win32" ? ".exe" : ""}`;
472
- }
473
- async function ensureRigShellBinaryPath(outputPath = sharedNativeShellOutputPath) {
474
- const sourcePath = resolveRigShellSourcePath();
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 resolveRigShellSourcePath() {
547
- for (const candidate of rigShellSourceCandidates()) {
548
- if (candidate && existsSync4(candidate)) {
549
- return candidate;
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 resolveBundledRigShellBinaryPath() {
555
- for (const candidate of rigShellBinaryCandidates()) {
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 rigShellSourceCandidates() {
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.RIG_NATIVE_SHELL_SOURCE?.trim() || "",
569
- cwd ? resolve4(cwd, "packages/runtime/native/rig-shell.zig") : "",
570
- projectRoot ? resolve4(projectRoot, "packages/runtime/native/rig-shell.zig") : "",
571
- hostProjectRoot ? resolve4(hostProjectRoot, "packages/runtime/native/rig-shell.zig") : "",
572
- execDir ? resolve4(execDir, "..", "..", "packages/runtime/native/rig-shell.zig") : "",
573
- execDir ? resolve4(execDir, "..", "native", "rig-shell.zig") : "",
574
- resolve4(import.meta.dir, "../../../../native/rig-shell.zig")
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 rigShellBinaryCandidates() {
515
+ function rigGitBinaryCandidates() {
590
516
  const execDir = process.execPath?.trim() ? dirname3(process.execPath.trim()) : "";
591
- const fileName = runtimeRigShellFileName();
517
+ const fileName = runtimeRigGitFileName();
518
+ const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
592
519
  return [...new Set([
593
- process.env.RIG_NATIVE_SHELL_BIN?.trim() || "",
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 runtimeToolGatewayNames() {
601
- return [
602
- "bash",
603
- "sh",
604
- "zsh",
605
- "git",
606
- "bun",
607
- "node",
608
- "python3",
609
- "rg",
610
- "grep",
611
- "sed",
612
- "cat",
613
- "ls",
614
- "find",
615
- "tsc",
616
- "gh",
617
- "mkdir",
618
- "rm",
619
- "mv",
620
- "cp",
621
- "touch",
622
- "pwd",
623
- "head",
624
- "tail",
625
- "wc",
626
- "sort",
627
- "uniq",
628
- "awk",
629
- "xargs",
630
- "dirname",
631
- "basename",
632
- "realpath",
633
- "env",
634
- "jq",
635
- "tee",
636
- "which"
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
- // packages/runtime/src/control-plane/runtime/tooling/browser-tools.ts
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 materializeRuntimeBrowserTools(targetDir) {
675
- const binaryPath = resolve5(targetDir, runtimeBrowserToolBinaryName());
676
- for (const tool of runtimeBrowserToolNames()) {
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
- return binaryPath;
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 bundledBinary = resolveBundledRigToolsBinaryPath();
707
- if (bundledBinary) {
708
- return bundledBinary;
606
+ const binaryPath = resolveGitBinaryPath();
607
+ if (binaryPath) {
608
+ return binaryPath;
709
609
  }
710
- throw new Error("rig-tools.zig source file not found.");
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 file tools.");
614
+ throw new Error("zig is required to build native Rig git tools.");
715
615
  }
716
- mkdirSync3(dirname4(outputPath), { recursive: true });
717
- const sourceDigest = await sha256File2(sourcePath);
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 = nativeBuildManifestPath2(outputPath);
727
- const needsBuild = !existsSync6(outputPath) || !await hasMatchingNativeBuildManifest2(manifestPath, buildKey);
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=${outputPath}`
639
+ `-femit-bin=${tempOutputPath}`
738
640
  ], {
739
- cwd: dirname4(sourcePath),
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 || !existsSync6(outputPath)) {
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 file tools: ${details || `zig exited with code ${exitCode}`}`);
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
- async function materializeRuntimeFileTools(targetDir) {
758
- const sourcePath = await ensureRigToolsBinaryPath();
759
- const targetPath = resolve6(targetDir, runtimeRigToolsFileName());
760
- mkdirSync3(targetDir, { recursive: true });
761
- const sourceDigest = await sha256File2(sourcePath);
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 needsCopy = !existsSync6(targetPath) || !await hasMatchingNativeBuildManifest2(nativeBuildManifestPath2(targetPath), buildKey);
768
- if (needsCopy) {
769
- copyFileSync2(sourcePath, targetPath);
770
- chmodSync2(targetPath, 493);
771
- await Bun.write(nativeBuildManifestPath2(targetPath), `${JSON.stringify({ version: 1, buildKey }, null, 2)}
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
- for (const tool of runtimeFileToolNames()) {
775
- const toolPath = resolve6(targetDir, tool);
776
- if (existsSync6(toolPath)) {
777
- rmSync2(toolPath, { force: true, recursive: true });
778
- }
779
- symlinkSync2(targetPath, toolPath);
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 resolveRigToolsSourcePath() {
784
- for (const candidate of rigToolsSourceCandidates()) {
785
- if (candidate && existsSync6(candidate)) {
786
- return candidate;
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
- return null;
790
- }
791
- function resolveBundledRigToolsBinaryPath() {
792
- for (const candidate of rigToolsBinaryCandidates()) {
793
- if (candidate && existsSync6(candidate)) {
794
- return candidate;
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 rigToolsSourceCandidates() {
800
- const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
801
- const cwd = process.cwd()?.trim() || "";
802
- const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
803
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
804
- return [...new Set([
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 nativePackageBinaryCandidates2(fromDir, fileName) {
815
- const candidates = [];
816
- let cursor = resolve6(fromDir);
817
- for (let index = 0;index < 8; index += 1) {
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
- return candidates;
824
+ throw new Error(`rig-git ${command} returned an unexpected result payload`);
825
825
  }
826
- function rigToolsBinaryCandidates() {
827
- const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
828
- const fileName = runtimeRigToolsFileName();
829
- return [...new Set([
830
- process.env.RIG_NATIVE_TOOLS_BIN?.trim() || "",
831
- ...nativePackageBinaryCandidates2(import.meta.dir, fileName),
832
- execDir ? resolve6(execDir, fileName) : "",
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 nativeBuildManifestPath2(outputPath) {
838
- return `${outputPath}.build-manifest.json`;
834
+ function nativeFetchRef(repoPath, remote, branch) {
835
+ return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
839
836
  }
840
- async function hasMatchingNativeBuildManifest2(manifestPath, buildKey) {
841
- if (!existsSync6(manifestPath)) {
842
- return false;
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
- const manifest = await Bun.file(manifestPath).json();
846
- return manifest.version === 1 && manifest.buildKey === buildKey;
847
- } catch {
848
- return false;
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
- async function sha256File2(path) {
852
- const hasher = new Bun.CryptoHasher("sha256");
853
- hasher.update(await Bun.file(path).arrayBuffer());
854
- return hasher.digest("hex");
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
- function runtimeGatewayToolNames() {
859
- return runtimeToolGatewayNames();
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 materializeRuntimeToolGateway(binDir) {
862
- const shellPath = await materializeRigShellBinary(binDir);
863
- for (const tool of runtimeGatewayToolNames()) {
864
- const toolPath = resolve7(binDir, tool);
865
- if (existsSync7(toolPath)) {
866
- rmSync3(toolPath, { force: true, recursive: true });
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
- symlinkSync3(shellPath, toolPath);
880
+ throw new Error("rig-shell.zig source file not found.");
869
881
  }
870
- await materializeRuntimeBrowserTools(binDir);
871
- return resolve7(binDir, runtimeRigShellFileName());
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
- if (browser.defaultProfile !== browser.effectiveProfile) {
961
- lines.push(`Base profile: ${browser.defaultProfile}. Runtime launches derive an isolated effective profile.`);
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
- return lines;
964
- }
965
-
966
- // packages/runtime/src/control-plane/plugin-host-context.ts
967
- import { createPluginHost } from "@rig/core";
968
- import { loadConfig } from "@rig/core/load-config";
969
-
970
- // packages/runtime/src/control-plane/task-source.ts
971
- function createTaskSourceRegistry() {
972
- const byId = new Map;
973
- const order = [];
974
- return {
975
- register(s) {
976
- if (byId.has(s.id))
977
- throw new Error(`task source already registered: ${s.id}`);
978
- byId.set(s.id, s);
979
- order.push(s);
980
- },
981
- resolveById(id) {
982
- const s = byId.get(id);
983
- if (!s)
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
- registry.register(factory.factory(taskSourceConfig));
1010
- return registry;
923
+ await Bun.write(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
924
+ `);
925
+ return outputPath;
1011
926
  }
1012
-
1013
- // packages/runtime/src/control-plane/repos/registry.ts
1014
- function createRepoRegistry(entries) {
1015
- const map = new Map;
1016
- for (const e of entries) {
1017
- if (map.has(e.id))
1018
- throw new Error(`repo already registered: ${e.id}`);
1019
- map.set(e.id, { ...e });
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
- const ordered = Array.from(map.values());
1022
- return {
1023
- getById: (id) => map.get(id),
1024
- list: () => ordered
1025
- };
944
+ return targetPath;
1026
945
  }
1027
- var MANAGED_REPOS = new Map;
1028
- function setManagedRepos(entries) {
1029
- const next = new Map;
1030
- for (const e of entries) {
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
- MANAGED_REPOS = next;
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 resolveManagedRepoIdByAlias(alias) {
1049
- for (const entry of MANAGED_REPOS.values()) {
1050
- if (entry.alias === alias) {
1051
- return entry.id;
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 repoRegistrationToManagedEntry(reg) {
1057
- if (!reg.defaultBranch) {
1058
- return null;
1059
- }
1060
- return {
1061
- id: reg.id,
1062
- alias: reg.defaultPath ?? reg.id,
1063
- defaultBranch: reg.defaultBranch,
1064
- defaultRemoteUrl: reg.url,
1065
- remoteEnvVar: reg.remoteEnvVar,
1066
- checkoutEnvVar: reg.checkoutEnvVar
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
- // packages/runtime/src/control-plane/agent-roles.ts
1071
- function createAgentRoleRegistry(pluginRoles, configOverrides) {
1072
- const map = new Map;
1073
- for (const r of pluginRoles) {
1074
- if (map.has(r.id))
1075
- throw new Error(`agent role already registered: ${r.id}`);
1076
- const override = configOverrides?.[r.id];
1077
- const model = override?.model ?? r.defaultModel;
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
- // packages/runtime/src/control-plane/task-fields.ts
1095
- function createTaskFieldRegistry(extensions) {
1096
- const byId = new Map;
1097
- for (const e of extensions) {
1098
- if (byId.has(e.id))
1099
- throw new Error(`task field extension already registered: ${e.id}`);
1100
- byId.set(e.id, e);
1101
- }
1102
- return {
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
- // packages/runtime/src/control-plane/validators/runtime-registration.ts
1130
- import { existsSync as existsSync8 } from "fs";
1131
- import { join } from "path";
1132
- function createValidatorRegistry() {
1133
- const map = new Map;
1134
- const order = [];
1135
- const registry = {
1136
- register(v) {
1137
- if (map.has(v.id))
1138
- throw new Error(`validator already registered: ${v.id}`);
1139
- map.set(v.id, v);
1140
- order.push(v);
1141
- },
1142
- resolve(id) {
1143
- const v = map.get(id);
1144
- if (!v)
1145
- throw new Error(`validator not registered: ${id}`);
1146
- return v;
1147
- },
1148
- list: () => order
1149
- };
1150
- registerBuiltInValidators(registry);
1151
- return registry;
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 registerBuiltInValidators(registry) {
1154
- registry.register({
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 runStdTypecheck(ctx) {
1162
- const packageJsonPath = join(ctx.workspaceRoot, "package.json");
1163
- if (!existsSync8(packageJsonPath)) {
1164
- return {
1165
- id: "std:typecheck",
1166
- passed: false,
1167
- summary: `package.json not found at ${packageJsonPath}`
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 getScopeRules() {
1196
- return activeRules;
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/hook-materializer.ts
1200
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1201
- import { dirname as dirname5, resolve as resolve9 } from "path";
1202
- var MARKER_PLUGIN = "_rigPlugin";
1203
- var MARKER_HOOK_ID = "_rigHookId";
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 isPluginOwned(cmd) {
1212
- return typeof cmd[MARKER_PLUGIN] === "string";
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 materializeHooks(projectRoot, entries) {
1215
- const settingsPath = resolve9(projectRoot, ".claude", "settings.json");
1216
- const existing = existsSync9(settingsPath) ? safeReadJson(settingsPath) : {};
1217
- const hooks = existing.hooks ?? {};
1218
- for (const event of Object.keys(hooks)) {
1219
- const groups = hooks[event] ?? [];
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
- for (const { pluginName, hook } of entries) {
1234
- if (!hook.command) {
1235
- continue;
1236
- }
1237
- const event = hook.event;
1238
- const matcherString = matcherToString(hook.matcher);
1239
- const groups = hooks[event] ??= [];
1240
- let group = groups.find((g) => g.matcher === matcherString);
1241
- if (!group) {
1242
- group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
1243
- groups.push(group);
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
- group.hooks.push({
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 next = { ...existing };
1253
- if (Object.keys(hooks).length > 0) {
1254
- next.hooks = hooks;
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(settingsPath), { recursive: true });
1259
- writeFileSync2(settingsPath, `${JSON.stringify(next, null, 2)}
1260
- `, "utf-8");
1261
- return settingsPath;
1262
- }
1263
- function safeReadJson(path) {
1264
- try {
1265
- return JSON.parse(readFileSync3(path, "utf-8"));
1266
- } catch {
1267
- return {};
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
- // packages/runtime/src/control-plane/skill-materializer.ts
1272
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readdirSync, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
1273
- import { resolve as resolve10 } from "path";
1274
- import { loadSkill } from "@rig/skill-loader";
1275
- var MARKER_FILENAME = ".rig-plugin";
1276
- function skillDirName(id) {
1277
- return id.replace(/[^a-zA-Z0-9._-]+/g, "-");
1278
- }
1279
- async function materializeSkills(projectRoot, entries) {
1280
- const skillsRoot = resolve10(projectRoot, ".pi", "skills");
1281
- if (existsSync10(skillsRoot)) {
1282
- for (const name of readdirSync(skillsRoot)) {
1283
- const dir = resolve10(skillsRoot, name);
1284
- if (existsSync10(resolve10(dir, MARKER_FILENAME))) {
1285
- rmSync4(dir, { recursive: true, force: true });
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 written = [];
1290
- for (const { pluginName, skill } of entries) {
1291
- const sourcePath = resolve10(projectRoot, skill.path);
1292
- if (!existsSync10(sourcePath)) {
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
- let body;
1297
- try {
1298
- await loadSkill(sourcePath);
1299
- body = readFileSync4(sourcePath, "utf-8");
1300
- } catch (err) {
1301
- console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${err instanceof Error ? err.message : err}`);
1302
- continue;
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 written;
1189
+ return null;
1312
1190
  }
1313
-
1314
- // packages/runtime/src/control-plane/plugin-host-context.ts
1315
- async function buildPluginHostContext(projectRoot) {
1316
- let config;
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
- const pluginHost = createPluginHost(config.plugins);
1327
- setScopeRules(config.workspace.scopeNormalization);
1328
- const validatorRegistry = createValidatorRegistry();
1329
- for (const impl of pluginHost.listExecutableValidators()) {
1330
- validatorRegistry.register(impl);
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
- const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost);
1333
- const repoRegistry = createRepoRegistry(pluginHost.listRepoSources());
1334
- const managedEntries = pluginHost.listRepoSources().map(repoRegistrationToManagedEntry).filter((e) => e !== null);
1335
- setManagedRepos(managedEntries);
1336
- const configRoleOverrides = config.runtime?.agentRoles;
1337
- const agentRoleRegistry = createAgentRoleRegistry(pluginHost.listAgentRoles(), configRoleOverrides);
1338
- const taskFieldRegistry = createTaskFieldRegistry(pluginHost.listTaskFieldExtensions());
1339
- try {
1340
- const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
1341
- pluginName: plugin.name,
1342
- hook
1343
- })));
1344
- if (hookEntries.length > 0) {
1345
- materializeHooks(projectRoot, hookEntries);
1346
- }
1347
- } catch (err) {
1348
- console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
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 skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
1352
- pluginName: plugin.name,
1353
- skill
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
- // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
1373
- import { spawnSync } from "child_process";
1374
- import { existsSync as existsSync12, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
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/tasks/legacy-task-config-source.ts
1388
- class LegacyTaskConfigReadError extends Error {
1389
- code = "LEGACY_TASK_CONFIG_READ_FAILED";
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 createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
1402
- const configPath = options.configPath ?? resolve11(projectRoot, ".rig", "task-config.json");
1403
- const reader = {
1404
- async listTasks() {
1405
- return readLegacyTaskRecords(projectRoot, configPath);
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
- return reader;
1268
+ symlinkSync3(shellPath, toolPath);
1269
+ }
1270
+ await materializeRuntimeBrowserTools(binDir);
1271
+ return resolve8(binDir, runtimeRigShellFileName());
1412
1272
  }
1413
- function readLegacyTaskRecords(projectRoot, configPath = resolve11(projectRoot, ".rig", "task-config.json")) {
1414
- if (!existsSync11(configPath)) {
1415
- return [];
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
- const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
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 readLegacyTaskConfigJson(projectRoot, configPath) {
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
- const parsed = JSON.parse(readFileSync5(configPath, "utf8"));
1423
- if (isPlainRecord(parsed)) {
1424
- return parsed;
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 stripLegacyTaskConfigMetadata(raw) {
1437
- const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
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 legacyTaskConfigEntryToRecord(id, entry) {
1441
- if (!isPlainRecord(entry)) {
1442
- return null;
1306
+ function resolveBrowserStateDir(projectRoot, configuredStateDir) {
1307
+ const trimmed = configuredStateDir?.trim() || ".tmp/rig-browser";
1308
+ if (trimmed.startsWith("/")) {
1309
+ return resolve9(trimmed);
1443
1310
  }
1444
- const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
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 preservedLegacyFields(entry) {
1467
- const preserved = {};
1468
- for (const key of [
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
- return preserved;
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 firstStringList(...candidates) {
1484
- for (const candidate of candidates) {
1485
- const list = readStringList(candidate);
1486
- if (list.length > 0) {
1487
- return list;
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
- return [];
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 candidate.filter((value) => typeof value === "string");
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/tasks/source-aware-task-config-source.ts
1503
- var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
1504
- var FILE_TASK_PATTERN = /\.(task\.)?json$/;
1505
- function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
1506
- const configPath = options.configPath ?? resolve12(projectRoot, ".rig", "task-config.json");
1507
- const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
1508
- const spawnFn = options.spawn ?? spawnSync;
1509
- const ghBinary = options.ghBinary ?? "gh";
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
- async listTasks() {
1513
- const rawConfig = readRawTaskConfig(configPath);
1514
- if (!rawConfig) {
1515
- const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
1516
- return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
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
- async getTask(id) {
1547
- const rawEntry = readRawTaskEntry(configPath, id);
1548
- if (!rawEntry) {
1549
- const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
1550
- return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
1551
- }
1552
- const metadata = readMaterializedTaskMetadata(rawEntry);
1553
- if (metadata.taskSource?.kind === "github-issues") {
1554
- return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
1555
- }
1556
- if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
1557
- return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
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
- function readMaterializedTaskMetadata(entry) {
1564
- const rawRig = entry._rig;
1565
- if (!isPlainRecord2(rawRig)) {
1566
- return {};
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 readConfiguredFilesTaskSourcePath(projectRoot) {
1589
- const jsonPath = resolve12(projectRoot, "rig.config.json");
1590
- if (existsSync12(jsonPath)) {
1591
- try {
1592
- const parsed = JSON.parse(readFileSync6(jsonPath, "utf8"));
1593
- if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
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
- try {
1606
- const source = readFileSync6(tsPath, "utf8");
1607
- const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
1608
- const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
1609
- if (kind !== "files") {
1610
- return null;
1611
- }
1612
- return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
1613
- } catch {
1614
- return null;
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
- function readRawTaskEntry(configPath, taskId) {
1618
- const rawConfig = readRawTaskConfig(configPath);
1619
- if (!rawConfig) {
1620
- return null;
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
- const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
1623
- return isPlainRecord2(entry) ? entry : null;
1436
+ MANAGED_REPOS = next;
1624
1437
  }
1625
- function readRawTaskConfig(configPath) {
1626
- if (!existsSync12(configPath)) {
1627
- return null;
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
- const parsed = JSON.parse(readFileSync6(configPath, "utf8"));
1630
- return isPlainRecord2(parsed) ? parsed : null;
1443
+ return entry;
1631
1444
  }
1632
- function stripLegacyTaskConfigMetadata2(raw) {
1633
- const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
1634
- return tasks;
1445
+ function listManagedRepoEntries() {
1446
+ return Array.from(MANAGED_REPOS.values());
1635
1447
  }
1636
- function listFileBackedTasks(projectRoot, sourcePath) {
1637
- const directory = resolve12(projectRoot, sourcePath);
1638
- if (!existsSync12(directory)) {
1639
- return [];
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 tasks;
1454
+ return null;
1651
1455
  }
1652
- function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
1653
- const file = findFileBackedTaskFile(resolve12(projectRoot, sourcePath), taskId);
1654
- if (!file) {
1456
+ function repoRegistrationToManagedEntry(reg) {
1457
+ if (!reg.defaultBranch) {
1655
1458
  return null;
1656
1459
  }
1657
- const raw = JSON.parse(readFileSync6(file, "utf8"));
1658
- if (!isPlainRecord2(raw)) {
1659
- return null;
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: typeof raw.id === "string" ? raw.id : taskId,
1663
- deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
1664
- status: typeof raw.status === "string" ? raw.status : "ready",
1665
- title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
1666
- ...raw
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
- function findFileBackedTaskFile(directory, taskId) {
1670
- if (!existsSync12(directory)) {
1671
- return null;
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
- for (const name of readdirSync2(directory)) {
1674
- if (!FILE_TASK_PATTERN.test(name))
1675
- continue;
1676
- const file = join2(directory, name);
1677
- try {
1678
- if (!statSync2(file).isFile())
1679
- continue;
1680
- const raw = JSON.parse(readFileSync6(file, "utf8"));
1681
- const inferredId = basename4(file).replace(FILE_TASK_PATTERN, "");
1682
- const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
1683
- if (id === taskId) {
1684
- return file;
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
- } catch {}
1687
- }
1688
- return null;
1524
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
1525
+ }
1526
+ };
1689
1527
  }
1690
- function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
1691
- const source = requireGithubIssueSource(metadata, id);
1692
- const issue = runGh(bin, [
1693
- "issue",
1694
- "view",
1695
- String(id),
1696
- "--repo",
1697
- `${source.owner}/${source.repo}`,
1698
- "--json",
1699
- "number,title,body,labels,state,url,assignees"
1700
- ], spawnFn);
1701
- return githubIssueToTask(issue, source, rawEntry);
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 requireGithubIssueSource(metadata, id) {
1704
- const source = metadata.taskSource;
1705
- if (source?.kind === "github-issues" && source.owner && source.repo) {
1706
- return { owner: source.owner, repo: source.repo };
1707
- }
1708
- const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
1709
- if (parsed && parsed[3] === id) {
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 githubIssueToTask(issue, source, rawEntry) {
1715
- const labelNames = (issue.labels ?? []).map((label) => label.name);
1716
- const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
1717
- const roleLabel = labelNames.find((label) => label.startsWith("role:"));
1718
- const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
1719
- const body = issue.body ?? "";
1720
- const repo = `${source.owner}/${source.repo}`;
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: String(issue.number),
1723
- deps: parseDeps(body),
1724
- status: githubStatusFor(issue),
1725
- title: issue.title,
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
- function githubStatusFor(issue) {
1744
- const state = (issue.state ?? "").toUpperCase();
1745
- if (state === "CLOSED")
1746
- return "closed";
1747
- const labelNames = (issue.labels ?? []).map((label) => label.name);
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 selectedGitHubEnv() {
1763
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1764
- return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1595
+ function getScopeRules() {
1596
+ return activeRules;
1765
1597
  }
1766
- function ghSpawnOptions() {
1767
- return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
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 runGh(bin, args, spawnFn) {
1770
- const res = spawnFn(bin, [...args], ghSpawnOptions());
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 assertGhSuccess(args, res) {
1778
- if (res.error) {
1779
- const msg = res.error.message ?? String(res.error);
1780
- throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
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
- if (res.status !== 0) {
1783
- throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}`);
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 parseDeps(body) {
1787
- return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
1788
- }
1789
- function parseParents(body) {
1790
- return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
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/tasks/source-lifecycle.ts
1811
- function hasRunnableTaskSource(source) {
1812
- return Boolean(source && typeof source === "object" && !Array.isArray(source));
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 getPluginTask(projectRoot, taskId) {
1815
- const ctx = await buildPluginHostContext(projectRoot);
1816
- const [source] = ctx?.taskSourceRegistry.list() ?? [];
1817
- if (!hasRunnableTaskSource(source)) {
1818
- return ctx ? { configured: false, sourceKind: null, task: null } : null;
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 task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
1821
- return {
1822
- configured: true,
1823
- sourceKind: source.kind,
1824
- task
1825
- };
1826
- }
1827
- async function readConfiguredTaskSourceTask(projectRoot, taskId) {
1828
- const pluginResult = await getPluginTask(projectRoot, taskId);
1829
- if (pluginResult)
1830
- return pluginResult;
1831
- const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
1832
- return {
1833
- configured: false,
1834
- sourceKind: null,
1835
- task
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/native/task-state.ts
1840
- import { existsSync as existsSync19, readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
1841
- import { basename as basename6, resolve as resolve19 } from "path";
1842
-
1843
- // packages/runtime/src/control-plane/state-sync/types.ts
1844
- var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
1845
- var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
1846
- "draft",
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
- function normalizeTaskStateMetadataStatus(status) {
1879
- if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
1880
- return status;
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
- function canonicalizeTaskStateMetadata(raw) {
1894
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1895
- return null;
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
- const metadata = raw;
1898
- const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
1899
- const status = normalizeTaskStateMetadataStatus(metadata.status);
1900
- if (!status) {
1901
- return null;
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
- ...claimId ? { claimId } : {},
1905
- status,
1906
- ...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
1907
- ...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
1908
- ...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
1909
- ...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
1910
- ...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
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
- function discardMismatchedTaskStateMetadata(input) {
1919
- input.taskId;
1920
- const canonicalMetadata = canonicalizeTaskStateMetadata(input.metadata);
1921
- if (!canonicalMetadata || !input.lifecycleStatus) {
1922
- return null;
1923
- }
1924
- const metadataStatus = canonicalMetadata.status ?? null;
1925
- if (metadataStatus && metadataStatus !== input.lifecycleStatus) {
1926
- return null;
1927
- }
1928
- return canonicalMetadata;
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
- function readTaskStateMetadataEnvelope(raw) {
1931
- if (!raw || typeof raw !== "object") {
1932
- return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
1933
- }
1934
- const envelope = raw;
1935
- const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
1936
- if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
1937
- return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
1938
- }
1939
- const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
1940
- const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
1941
- return {
1942
- schemaVersion,
1943
- supported: true,
1944
- baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
1945
- tasks
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
- // packages/runtime/src/control-plane/state-sync/read.ts
1949
- import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
1950
- import { resolve as resolve18 } from "path";
1951
-
1952
- // packages/runtime/src/control-plane/native/git-native.ts
1953
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync7, renameSync, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
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 publishGitBinary(tempOutputPath, outputPath) {
1820
+ function readLegacyTaskConfigJson(projectRoot, configPath) {
1965
1821
  try {
1966
- renameSync(tempOutputPath, outputPath);
1967
- } catch (error) {
1968
- if (process.platform === "win32" && existsSync13(outputPath)) {
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 error;
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 runtimeRigGitFileName() {
1977
- return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
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 nativePackageBinaryCandidates3(fromDir, fileName) {
1996
- const candidates = [];
1997
- let cursor = resolve13(fromDir);
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
- return candidates;
2006
- }
2007
- function rigGitBinaryCandidates() {
2008
- const execDir = process.execPath?.trim() ? dirname6(process.execPath.trim()) : "";
2009
- const fileName = runtimeRigGitFileName();
2010
- const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
2011
- return [...new Set([
2012
- explicit,
2013
- ...nativePackageBinaryCandidates3(import.meta.dir, fileName),
2014
- execDir ? resolve13(execDir, fileName) : "",
2015
- execDir ? resolve13(execDir, "..", fileName) : "",
2016
- execDir ? resolve13(execDir, "..", "bin", fileName) : "",
2017
- sharedGitNativeOutputPath
2018
- ].filter(Boolean))];
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 resolveGitSourcePath() {
2021
- for (const candidate of rigGitSourceCandidates()) {
2022
- if (candidate && existsSync13(candidate)) {
2023
- return candidate;
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 null;
1881
+ return preserved;
2027
1882
  }
2028
- function resolveGitBinaryPath() {
2029
- if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
2030
- return null;
2031
- }
2032
- for (const candidate of rigGitBinaryCandidates()) {
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 null;
2038
- }
2039
- function preferredGitBinaryOutputPath() {
2040
- const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
2041
- return explicit || sharedGitNativeOutputPath;
1890
+ return [];
2042
1891
  }
2043
- function binarySupportsTrackerCommandsSync(binaryPath) {
2044
- try {
2045
- const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
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 nativeBuildManifestPath3(outputPath) {
2060
- return `${outputPath}.build-manifest.json`;
1898
+ function isPlainRecord(candidate) {
1899
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
2061
1900
  }
2062
- async function hasMatchingNativeBuildManifest3(manifestPath, buildKey) {
2063
- if (!existsSync13(manifestPath)) {
2064
- return false;
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
- try {
2067
- const manifest = await Bun.file(manifestPath).json();
2068
- return manifest.version === 1 && manifest.buildKey === buildKey;
2069
- } catch {
2070
- return false;
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
- function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
2074
- if (!existsSync13(manifestPath)) {
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 manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
2079
- return manifest.version === 1 && manifest.buildKey === buildKey;
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 false;
2014
+ return null;
2082
2015
  }
2083
2016
  }
2084
- async function sha256File3(path) {
2085
- const hasher = new Bun.CryptoHasher("sha256");
2086
- hasher.update(await Bun.file(path).arrayBuffer());
2087
- return hasher.digest("hex");
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 zigBinary = Bun.which("zig");
2105
- if (!zigBinary) {
2106
- throw new Error("zig is required to build native Rig git tools.");
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
- mkdirSync6(dirname6(outputPath), { recursive: true });
2109
- const sourceDigest = await sha256File3(sourcePath);
2110
- const buildKey = JSON.stringify({
2111
- version: 1,
2112
- zigBinary,
2113
- platform: process.platform,
2114
- arch: process.arch,
2115
- sourcePath,
2116
- sourceDigest
2117
- });
2118
- const manifestPath = nativeBuildManifestPath3(outputPath);
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 tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
2125
- const build = Bun.spawn([
2126
- zigBinary,
2127
- "build-exe",
2128
- sourcePath,
2129
- "-O",
2130
- "ReleaseFast",
2131
- `-femit-bin=${tempOutputPath}`
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
- chmodSync3(tempOutputPath, 493);
2148
- if (existsSync13(outputPath) && await hasMatchingNativeBuildManifest3(manifestPath, buildKey)) {
2149
- rmSync5(tempOutputPath, { force: true });
2150
- chmodSync3(outputPath, 493);
2151
- return outputPath;
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
- publishGitBinary(tempOutputPath, outputPath);
2154
- if (!binarySupportsTrackerCommandsSync(outputPath)) {
2155
- rmSync5(outputPath, { force: true });
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
- await Bun.write(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
2159
- `);
2160
- return outputPath;
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 ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
2163
- if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
2164
- throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
2069
+ function findFileBackedTaskFile(directory, taskId) {
2070
+ if (!existsSync13(directory)) {
2071
+ return null;
2165
2072
  }
2166
- const sourcePath = resolveGitSourcePath();
2167
- if (!sourcePath) {
2168
- const binaryPath = resolveGitBinaryPath();
2169
- if (binaryPath) {
2170
- return binaryPath;
2171
- }
2172
- throw new Error("rig-git.zig source file not found.");
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
- const zigBinary = Bun.which("zig");
2175
- if (!zigBinary) {
2176
- throw new Error("zig is required to build native Rig git tools.");
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
- mkdirSync6(dirname6(outputPath), { recursive: true });
2179
- const sourceDigest = sha256FileSync(sourcePath);
2180
- const buildKey = JSON.stringify({
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
- const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
2195
- const build = Bun.spawnSync([
2196
- zigBinary,
2197
- "build-exe",
2198
- sourcePath,
2199
- "-O",
2200
- "ReleaseFast",
2201
- `-femit-bin=${tempOutputPath}`
2202
- ], {
2203
- cwd: dirname6(sourcePath),
2204
- stdout: "pipe",
2205
- stderr: "pipe"
2206
- });
2207
- if (build.exitCode !== 0 || !existsSync13(tempOutputPath)) {
2208
- const stderr = build.stderr.toString().trim();
2209
- const stdout = build.stdout.toString().trim();
2210
- const details = [stderr, stdout].filter(Boolean).join(`
2211
- `);
2212
- throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
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
- chmodSync3(tempOutputPath, 493);
2215
- if (existsSync13(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
2216
- rmSync5(tempOutputPath, { force: true });
2217
- chmodSync3(outputPath, 493);
2218
- return outputPath;
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
- publishGitBinary(tempOutputPath, outputPath);
2221
- if (!binarySupportsTrackerCommandsSync(outputPath)) {
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
- async function materializeRigGitBinary(targetDir) {
2230
- const sourcePath = await ensureRigGitBinaryPath();
2231
- const targetPath = resolve13(targetDir, runtimeRigGitFileName());
2232
- mkdirSync6(targetDir, { recursive: true });
2233
- const sourceDigest = await sha256File3(sourcePath);
2234
- const buildKey = JSON.stringify({
2235
- version: 1,
2236
- sourcePath,
2237
- sourceDigest
2238
- });
2239
- const needsCopy = !existsSync13(targetPath) || !await hasMatchingNativeBuildManifest3(nativeBuildManifestPath3(targetPath), buildKey);
2240
- if (needsCopy) {
2241
- copyFileSync3(sourcePath, targetPath);
2242
- chmodSync3(targetPath, 493);
2243
- await Bun.write(nativeBuildManifestPath3(targetPath), `${JSON.stringify({ version: 1, buildKey }, null, 2)}
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
- return targetPath;
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 runGitNative(command, args) {
2249
- if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
2250
- return { ok: false, error: "rig-git native disabled" };
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
- const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
2253
- let binaryPath = null;
2254
- if (trackerCommand) {
2255
- try {
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
- try {
2280
- const proc = Bun.spawnSync([binaryPath, command, ...args], {
2281
- stdout: "pipe",
2282
- stderr: "pipe",
2283
- env: process.env
2284
- });
2285
- if (proc.exitCode !== 0) {
2286
- const stdoutText = proc.stdout.toString().trim();
2287
- if (stdoutText) {
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 requireGitNative(command, args) {
2305
- const result = runGitNative(command, args);
2306
- if (!result.ok) {
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
- return result;
2310
- }
2311
- function requireGitNativeString(command, args) {
2312
- const result = requireGitNative(command, args);
2313
- if ("value" in result && typeof result.value === "string") {
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
- throw new Error(`rig-git ${command} returned an unexpected result payload`);
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 nativeBranchName(repoPath) {
2319
- const result = runGitNative("branch-name", [repoPath]);
2320
- if (!result.ok)
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 nativeReadBlobBytesAtRef(repoPath, ref, path) {
2341
- const requestDir = resolve13(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
2342
- mkdirSync6(requestDir, { recursive: true });
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();