@rallycry/conveyor-agent 10.13.69 → 10.13.71
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.
|
@@ -490,7 +490,108 @@ import {
|
|
|
490
490
|
symlinkSync,
|
|
491
491
|
writeFileSync as writeFileSync2
|
|
492
492
|
} from "fs";
|
|
493
|
-
import { dirname, join as
|
|
493
|
+
import { dirname, join as join3 } from "path";
|
|
494
|
+
|
|
495
|
+
// src/boot/memory-integrity.ts
|
|
496
|
+
import { readFile, readdir, stat, writeFile } from "fs/promises";
|
|
497
|
+
import { join as join2 } from "path";
|
|
498
|
+
var FRONTMATTER_OPEN = "---\n";
|
|
499
|
+
var FRONTMATTER_CLOSE = "\n---\n";
|
|
500
|
+
var MAX_MEMORY_FILE_BYTES = 512 * 1024;
|
|
501
|
+
var READ_CONCURRENCY = 16;
|
|
502
|
+
function readFrontmatterBlock(content) {
|
|
503
|
+
if (!content.startsWith(FRONTMATTER_OPEN)) return null;
|
|
504
|
+
const close = content.indexOf(FRONTMATTER_CLOSE, FRONTMATTER_OPEN.length - 1);
|
|
505
|
+
if (close === -1) return null;
|
|
506
|
+
return content.slice(0, close + FRONTMATTER_CLOSE.length);
|
|
507
|
+
}
|
|
508
|
+
function inspectMemoryFile(content) {
|
|
509
|
+
const header = readFrontmatterBlock(content);
|
|
510
|
+
if (!header) return { status: "clean" };
|
|
511
|
+
const repeat = content.indexOf(header, header.length);
|
|
512
|
+
if (repeat === -1) return { status: "clean" };
|
|
513
|
+
const half = content.length / 2;
|
|
514
|
+
if (repeat === half && content.slice(0, half) === content.slice(half)) {
|
|
515
|
+
return { status: "self-append", repaired: content.slice(0, half) };
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
status: "suspicious",
|
|
519
|
+
detail: `frontmatter block repeats at offset ${repeat} of ${content.length} without identical halves`
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async function candidateSize(path) {
|
|
523
|
+
try {
|
|
524
|
+
const info = await stat(path);
|
|
525
|
+
if (!info.isFile() || info.size === 0) return null;
|
|
526
|
+
if (info.size > MAX_MEMORY_FILE_BYTES || info.size % 2 !== 0) return null;
|
|
527
|
+
return info.size;
|
|
528
|
+
} catch {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function repairOneFile(dir, name, log, out) {
|
|
533
|
+
const path = join2(dir, name);
|
|
534
|
+
const size = await candidateSize(path);
|
|
535
|
+
if (size === null) {
|
|
536
|
+
out.skipped += 1;
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
out.scanned += 1;
|
|
540
|
+
const verdict = inspectMemoryFile(await readFile(path, "utf8"));
|
|
541
|
+
if (verdict.status === "suspicious") {
|
|
542
|
+
out.suspicious.push(name);
|
|
543
|
+
log.warn(
|
|
544
|
+
`[boot] WARN shared memory ${name} looks doubled but was left alone: ${verdict.detail}`
|
|
545
|
+
);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (verdict.status !== "self-append" || verdict.repaired === void 0) return;
|
|
549
|
+
if (await candidateSize(path) !== size) {
|
|
550
|
+
log.warn(`[boot] WARN shared memory ${name} changed mid-check; left alone`);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
await writeFile(path, verdict.repaired);
|
|
554
|
+
out.repaired.push(name);
|
|
555
|
+
}
|
|
556
|
+
async function forEachLimited(items, limit, fn) {
|
|
557
|
+
let cursor = 0;
|
|
558
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
559
|
+
for (let i = cursor++; i < items.length; i = cursor++) {
|
|
560
|
+
const item = items[i];
|
|
561
|
+
if (item !== void 0) await fn(item);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
await Promise.all(workers);
|
|
565
|
+
}
|
|
566
|
+
async function repairSharedMemory(dir, log) {
|
|
567
|
+
const out = { scanned: 0, repaired: [], suspicious: [], skipped: 0 };
|
|
568
|
+
let entries;
|
|
569
|
+
try {
|
|
570
|
+
entries = await readdir(dir);
|
|
571
|
+
} catch {
|
|
572
|
+
return out;
|
|
573
|
+
}
|
|
574
|
+
const names = entries.filter((name) => name.endsWith(".md"));
|
|
575
|
+
await forEachLimited(names, READ_CONCURRENCY, async (name) => {
|
|
576
|
+
try {
|
|
577
|
+
await repairOneFile(dir, name, log, out);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
log.warn(
|
|
580
|
+
`[boot] WARN shared memory integrity check failed for ${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
out.repaired.sort();
|
|
585
|
+
out.suspicious.sort();
|
|
586
|
+
if (out.repaired.length > 0) {
|
|
587
|
+
log.info(
|
|
588
|
+
`[boot] shared memory: repaired ${out.repaired.length} self-appended file(s): ${out.repaired.join(", ")}`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return out;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/boot/user-home.ts
|
|
494
595
|
var SEED_GATES = {
|
|
495
596
|
hasCompletedOnboarding: true,
|
|
496
597
|
bypassPermissionsModeAccepted: true,
|
|
@@ -566,8 +667,8 @@ function relink(target, link) {
|
|
|
566
667
|
}
|
|
567
668
|
function wireCredentials(opts) {
|
|
568
669
|
const { home, root, sharedDir, log } = opts;
|
|
569
|
-
const shared =
|
|
570
|
-
const podLocal = sharedDir ?
|
|
670
|
+
const shared = join3(root, ".claude", ".credentials.json");
|
|
671
|
+
const podLocal = sharedDir ? join3(sharedDir, "claude-credentials.pod.json") : join3(home, ".claude-credentials.pod.json");
|
|
571
672
|
safely(log, "carry legacy shared credentials", () => {
|
|
572
673
|
if (isRegularFile(shared)) copyFileSync(shared, podLocal);
|
|
573
674
|
});
|
|
@@ -583,66 +684,78 @@ function migrateMemories(from, to, log) {
|
|
|
583
684
|
return;
|
|
584
685
|
}
|
|
585
686
|
for (const entry of entries) {
|
|
586
|
-
const target =
|
|
687
|
+
const target = join3(to, entry);
|
|
587
688
|
if (existsSync(target)) continue;
|
|
588
|
-
safely(log, `migrate memory ${entry}`, () => copyFileSync(
|
|
689
|
+
safely(log, `migrate memory ${entry}`, () => copyFileSync(join3(from, entry), target));
|
|
589
690
|
}
|
|
590
691
|
}
|
|
692
|
+
function startSharedMemoryRepair(shared, log) {
|
|
693
|
+
return repairSharedMemory(shared, log).then(
|
|
694
|
+
() => void 0,
|
|
695
|
+
(err) => {
|
|
696
|
+
log.warn(
|
|
697
|
+
`[boot] WARN repair self-appended shared memories failed: ${err instanceof Error ? err.message : String(err)}`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
}
|
|
591
702
|
function wireSharedMemory(root, userHomeMount, projectId, log) {
|
|
592
|
-
const shared =
|
|
593
|
-
const link =
|
|
703
|
+
const shared = join3(userHomeMount, "users", "_shared", "memory", projectId);
|
|
704
|
+
const link = join3(root, ".claude", "projects", AGENT_MEMORY_PROJECT_DIR, "memory");
|
|
594
705
|
safely(log, "mkdir shared memory dir", () => mkdirSync(shared, { recursive: true }));
|
|
595
706
|
safely(log, "migrate per-user memories", () => {
|
|
596
707
|
if (existsSync(link) && !lstatSync(link).isSymbolicLink()) migrateMemories(link, shared, log);
|
|
597
708
|
});
|
|
598
709
|
safely(log, "wire shared memory symlink", () => relink(shared, link));
|
|
710
|
+
return startSharedMemoryRepair(shared, log);
|
|
599
711
|
}
|
|
600
712
|
function wireMountedUserHome(opts) {
|
|
601
713
|
const { home, userHomeMount, userId, projectId, sharedDir, log } = opts;
|
|
602
|
-
const root =
|
|
714
|
+
const root = join3(userHomeMount, "users", userId, projectId);
|
|
603
715
|
safely(log, "mkdir user-home root", () => {
|
|
604
|
-
mkdirSync(
|
|
605
|
-
mkdirSync(
|
|
716
|
+
mkdirSync(join3(root, ".claude"), { recursive: true });
|
|
717
|
+
mkdirSync(join3(root, ".config", "claude"), { recursive: true });
|
|
606
718
|
});
|
|
607
|
-
safely(log, "wire ~/.claude symlink", () => relink(
|
|
719
|
+
safely(log, "wire ~/.claude symlink", () => relink(join3(root, ".claude"), join3(home, ".claude")));
|
|
608
720
|
safely(
|
|
609
721
|
log,
|
|
610
722
|
"wire ~/.config/claude symlink",
|
|
611
|
-
() => relink(
|
|
723
|
+
() => relink(join3(root, ".config", "claude"), join3(home, ".config", "claude"))
|
|
612
724
|
);
|
|
613
725
|
safely(log, "seed pod-local ~/.claude.json", () => {
|
|
614
|
-
rmSync(
|
|
615
|
-
seedClaudeJson(
|
|
726
|
+
rmSync(join3(home, ".claude.json"), { recursive: true, force: true });
|
|
727
|
+
seedClaudeJson(join3(home, ".claude.json"));
|
|
616
728
|
});
|
|
617
|
-
wireSharedMemory(root, userHomeMount, projectId, log);
|
|
729
|
+
const pendingRepair = wireSharedMemory(root, userHomeMount, projectId, log);
|
|
618
730
|
wireCredentials({ home, root, sharedDir, log });
|
|
619
731
|
safely(log, "mkdir opencode root", () => {
|
|
620
|
-
mkdirSync(
|
|
621
|
-
mkdirSync(
|
|
732
|
+
mkdirSync(join3(root, ".local", "share", "opencode"), { recursive: true });
|
|
733
|
+
mkdirSync(join3(root, ".config", "opencode"), { recursive: true });
|
|
622
734
|
});
|
|
623
735
|
safely(
|
|
624
736
|
log,
|
|
625
737
|
"wire opencode data symlink",
|
|
626
|
-
() => relink(
|
|
738
|
+
() => relink(join3(root, ".local", "share", "opencode"), join3(home, ".local", "share", "opencode"))
|
|
627
739
|
);
|
|
628
740
|
safely(
|
|
629
741
|
log,
|
|
630
742
|
"wire opencode config symlink",
|
|
631
|
-
() => relink(
|
|
743
|
+
() => relink(join3(root, ".config", "opencode"), join3(home, ".config", "opencode"))
|
|
632
744
|
);
|
|
745
|
+
return pendingRepair;
|
|
633
746
|
}
|
|
634
747
|
function wireNoMountUserHome(opts) {
|
|
635
748
|
const { home, sharedDir, log } = opts;
|
|
636
749
|
safely(
|
|
637
750
|
log,
|
|
638
751
|
"seed pod-local ~/.claude.json (no mount)",
|
|
639
|
-
() => seedClaudeJson(
|
|
752
|
+
() => seedClaudeJson(join3(home, ".claude.json"))
|
|
640
753
|
);
|
|
641
754
|
if (sharedDir) {
|
|
642
755
|
safely(log, "wire ~/.claude to shared emptyDir", () => {
|
|
643
|
-
const target =
|
|
756
|
+
const target = join3(sharedDir, "claude-home", ".claude");
|
|
644
757
|
mkdirSync(target, { recursive: true });
|
|
645
|
-
relink(target,
|
|
758
|
+
relink(target, join3(home, ".claude"));
|
|
646
759
|
});
|
|
647
760
|
}
|
|
648
761
|
}
|
|
@@ -654,14 +767,14 @@ function wireUserHome(opts) {
|
|
|
654
767
|
() => mkdirSync(opts.scratchDir ?? "/tmp/claude-cmd", { recursive: true })
|
|
655
768
|
);
|
|
656
769
|
if (userId && projectId && isDir(userHomeMount)) {
|
|
657
|
-
wireMountedUserHome({ home, userHomeMount, userId, projectId, sharedDir, log });
|
|
658
|
-
} else {
|
|
659
|
-
wireNoMountUserHome({ home, sharedDir, log });
|
|
770
|
+
return wireMountedUserHome({ home, userHomeMount, userId, projectId, sharedDir, log });
|
|
660
771
|
}
|
|
772
|
+
wireNoMountUserHome({ home, sharedDir, log });
|
|
773
|
+
return Promise.resolve();
|
|
661
774
|
}
|
|
662
775
|
var RECLAIM_FIND_TIMEOUT_MS = 1e4;
|
|
663
776
|
async function reclaimHomeOwnership(home, exec, log) {
|
|
664
|
-
for (const dir of [
|
|
777
|
+
for (const dir of [join3(home, ".config"), join3(home, ".local")]) {
|
|
665
778
|
if (!isDir(dir)) continue;
|
|
666
779
|
try {
|
|
667
780
|
await exec(
|
|
@@ -691,9 +804,9 @@ async function reclaimHomeOwnership(home, exec, log) {
|
|
|
691
804
|
|
|
692
805
|
// src/boot/sshd.ts
|
|
693
806
|
import { chmodSync as chmodSync2, closeSync, copyFileSync as copyFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
694
|
-
import { join as
|
|
807
|
+
import { join as join4 } from "path";
|
|
695
808
|
function persistedAuthorizedKeys() {
|
|
696
|
-
return
|
|
809
|
+
return join4(
|
|
697
810
|
process.env.CONVEYOR_SHARED_DIR || "/var/run/conveyor-workbench",
|
|
698
811
|
"ssh",
|
|
699
812
|
"authorized_keys"
|
|
@@ -701,10 +814,10 @@ function persistedAuthorizedKeys() {
|
|
|
701
814
|
}
|
|
702
815
|
async function startWorkspaceSshd(exec, log, port = process.env.CONVEYOR_WORKSPACE_SSH_PORT || "2222", home = "/home/conveyor") {
|
|
703
816
|
try {
|
|
704
|
-
const sshDir =
|
|
817
|
+
const sshDir = join4(home, ".ssh");
|
|
705
818
|
mkdirSync2(sshDir, { recursive: true });
|
|
706
819
|
chmodSync2(sshDir, 448);
|
|
707
|
-
const authorizedKeys =
|
|
820
|
+
const authorizedKeys = join4(sshDir, "authorized_keys");
|
|
708
821
|
const persisted = persistedAuthorizedKeys();
|
|
709
822
|
if (existsSync2(persisted)) {
|
|
710
823
|
copyFileSync2(persisted, authorizedKeys);
|
|
@@ -746,7 +859,7 @@ import {
|
|
|
746
859
|
statSync as statSync3,
|
|
747
860
|
symlinkSync as symlinkSync2
|
|
748
861
|
} from "fs";
|
|
749
|
-
import { join as
|
|
862
|
+
import { join as join5 } from "path";
|
|
750
863
|
function errText(err) {
|
|
751
864
|
return redactToken(err instanceof Error ? err.message : String(err));
|
|
752
865
|
}
|
|
@@ -782,7 +895,7 @@ function bindGraphifyBundle(workspace, graphifyEnv, log) {
|
|
|
782
895
|
log.info(`[boot] Graphify bundle not found for '${slug}' at ${sourceDir}`);
|
|
783
896
|
return;
|
|
784
897
|
}
|
|
785
|
-
const targetDir =
|
|
898
|
+
const targetDir = join5(workspace, "graphify-out");
|
|
786
899
|
try {
|
|
787
900
|
mkdirSync3(targetDir, { recursive: true });
|
|
788
901
|
} catch {
|
|
@@ -790,8 +903,8 @@ function bindGraphifyBundle(workspace, graphifyEnv, log) {
|
|
|
790
903
|
return;
|
|
791
904
|
}
|
|
792
905
|
for (const rel of GRAPHIFY_BUNDLE_FILES) {
|
|
793
|
-
const source =
|
|
794
|
-
const target =
|
|
906
|
+
const source = join5(sourceDir, rel);
|
|
907
|
+
const target = join5(targetDir, rel);
|
|
795
908
|
if (!existsSync3(source)) continue;
|
|
796
909
|
if (existsSync3(target) || isSymlink(target)) continue;
|
|
797
910
|
try {
|
|
@@ -803,7 +916,7 @@ function bindGraphifyBundle(workspace, graphifyEnv, log) {
|
|
|
803
916
|
}
|
|
804
917
|
async function ensureGrimoireSubmodule(workspace, githubToken, git, log) {
|
|
805
918
|
try {
|
|
806
|
-
if (!existsSync3(
|
|
919
|
+
if (!existsSync3(join5(workspace, ".gitmodules"))) return;
|
|
807
920
|
let paths = "";
|
|
808
921
|
try {
|
|
809
922
|
({ stdout: paths } = await git(
|
|
@@ -814,7 +927,7 @@ async function ensureGrimoireSubmodule(workspace, githubToken, git, log) {
|
|
|
814
927
|
return;
|
|
815
928
|
}
|
|
816
929
|
if (!paths.split("\n").some((line) => line.trim().endsWith(".claude/grimoire"))) return;
|
|
817
|
-
if (isDir2(
|
|
930
|
+
if (isDir2(join5(workspace, ".claude", "grimoire", "skills"))) return;
|
|
818
931
|
if (!githubToken) {
|
|
819
932
|
log.warn("[boot] WARN: grimoire submodule absent and no token to fetch it");
|
|
820
933
|
return;
|
|
@@ -843,11 +956,11 @@ async function ensureGrimoireSubmodule(workspace, githubToken, git, log) {
|
|
|
843
956
|
}
|
|
844
957
|
function excludeLinkedSkills(workspace, names, log) {
|
|
845
958
|
if (names.length === 0) return;
|
|
846
|
-
const infoDir =
|
|
847
|
-
if (!isDir2(
|
|
959
|
+
const infoDir = join5(workspace, ".git", "info");
|
|
960
|
+
if (!isDir2(join5(workspace, ".git"))) return;
|
|
848
961
|
try {
|
|
849
962
|
mkdirSync3(infoDir, { recursive: true });
|
|
850
|
-
const excludePath =
|
|
963
|
+
const excludePath = join5(infoDir, "exclude");
|
|
851
964
|
const existing = existsSync3(excludePath) ? readFileSync2(excludePath, "utf8") : "";
|
|
852
965
|
const missing = names.map((name) => `.claude/skills/${name}`).filter((line) => !existing.split("\n").includes(line));
|
|
853
966
|
if (missing.length === 0) return;
|
|
@@ -860,14 +973,14 @@ function excludeLinkedSkills(workspace, names, log) {
|
|
|
860
973
|
}
|
|
861
974
|
function symlinkTargetsGrimoire(path) {
|
|
862
975
|
try {
|
|
863
|
-
return readlinkSync(path).includes(
|
|
976
|
+
return readlinkSync(path).includes(join5("grimoire", "skills"));
|
|
864
977
|
} catch {
|
|
865
978
|
return false;
|
|
866
979
|
}
|
|
867
980
|
}
|
|
868
981
|
function linkGrimoireSkills(workspace, log) {
|
|
869
|
-
const sourceDir =
|
|
870
|
-
const targetDir =
|
|
982
|
+
const sourceDir = join5(workspace, ".claude", "grimoire", "skills");
|
|
983
|
+
const targetDir = join5(workspace, ".claude", "skills");
|
|
871
984
|
if (!isDir2(sourceDir)) return;
|
|
872
985
|
try {
|
|
873
986
|
mkdirSync3(targetDir, { recursive: true });
|
|
@@ -883,8 +996,8 @@ function linkGrimoireSkills(workspace, log) {
|
|
|
883
996
|
return;
|
|
884
997
|
}
|
|
885
998
|
for (const name of names) {
|
|
886
|
-
if (!isDir2(
|
|
887
|
-
const target =
|
|
999
|
+
if (!isDir2(join5(sourceDir, name))) continue;
|
|
1000
|
+
const target = join5(targetDir, name);
|
|
888
1001
|
if (existsSync3(target) && !isSymlink(target)) {
|
|
889
1002
|
log.warn(`[boot] WARN: ${target} exists and is not a symlink; skipping`);
|
|
890
1003
|
continue;
|
|
@@ -892,7 +1005,7 @@ function linkGrimoireSkills(workspace, log) {
|
|
|
892
1005
|
if (isSymlink(target) && !symlinkTargetsGrimoire(target)) continue;
|
|
893
1006
|
try {
|
|
894
1007
|
rmSync2(target, { force: true });
|
|
895
|
-
symlinkSync2(
|
|
1008
|
+
symlinkSync2(join5("..", "grimoire", "skills", name), target);
|
|
896
1009
|
linkedNames.push(name);
|
|
897
1010
|
} catch {
|
|
898
1011
|
}
|
|
@@ -901,7 +1014,7 @@ function linkGrimoireSkills(workspace, log) {
|
|
|
901
1014
|
log.info(`[boot] Linked ${linkedNames.length} grimoire skills into ${targetDir}`);
|
|
902
1015
|
}
|
|
903
1016
|
async function neutralizeGrimoirePointer(workspace, git, log) {
|
|
904
|
-
if (!existsSync3(
|
|
1017
|
+
if (!existsSync3(join5(workspace, ".claude", "grimoire"))) return;
|
|
905
1018
|
try {
|
|
906
1019
|
await git(["update-index", "--assume-unchanged", ".claude/grimoire"], {
|
|
907
1020
|
cwd: workspace,
|
|
@@ -953,8 +1066,8 @@ async function cloneReferenceRepos(refsJson, git, log, referencesDir = "/workspa
|
|
|
953
1066
|
return;
|
|
954
1067
|
}
|
|
955
1068
|
for (const ref of refs) {
|
|
956
|
-
const dest =
|
|
957
|
-
if (existsSync3(
|
|
1069
|
+
const dest = join5(referencesDir, ref.slug);
|
|
1070
|
+
if (existsSync3(join5(dest, ".git"))) continue;
|
|
958
1071
|
try {
|
|
959
1072
|
await git(
|
|
960
1073
|
[
|
|
@@ -1024,7 +1137,7 @@ function defaultWorkbenchBootDeps() {
|
|
|
1024
1137
|
ctx.log.info(`[boot] ensured workspace dir ${WORKSPACE_DIR}`);
|
|
1025
1138
|
},
|
|
1026
1139
|
wireHome: (ctx) => {
|
|
1027
|
-
wireUserHome({
|
|
1140
|
+
void wireUserHome({
|
|
1028
1141
|
home: HOME_DIR,
|
|
1029
1142
|
userHomeMount: USER_HOME_MOUNT2,
|
|
1030
1143
|
userId: ctx.childEnv?.CONVEYOR_USER_ID,
|
|
@@ -1183,7 +1296,7 @@ function agentBootSteps(deps = defaultBootDeps(), signal = new AbortController()
|
|
|
1183
1296
|
critical: true,
|
|
1184
1297
|
run: (ctx) => {
|
|
1185
1298
|
if (ctx.bundle) {
|
|
1186
|
-
wireUserHome({
|
|
1299
|
+
void wireUserHome({
|
|
1187
1300
|
home: HOME_DIR2,
|
|
1188
1301
|
userHomeMount: USER_HOME_MOUNT3,
|
|
1189
1302
|
userId: ctx.childEnv?.CONVEYOR_USER_ID,
|
|
@@ -1613,6 +1613,18 @@ async function hasUnpushedCommits(cwd) {
|
|
|
1613
1613
|
return false;
|
|
1614
1614
|
}
|
|
1615
1615
|
}
|
|
1616
|
+
async function remoteMatchesLocalHead(cwd, branch) {
|
|
1617
|
+
try {
|
|
1618
|
+
const [remote, local] = await Promise.all([
|
|
1619
|
+
git(cwd, ["ls-remote", "origin", `refs/heads/${branch}`], GIT_SLOW_TIMEOUT_MS),
|
|
1620
|
+
git(cwd, ["rev-parse", "HEAD"])
|
|
1621
|
+
]);
|
|
1622
|
+
const remoteSha = remote.split(/\s+/)[0] ?? "";
|
|
1623
|
+
return /^[0-9a-f]{40}$/i.test(remoteSha) && remoteSha === local.trim();
|
|
1624
|
+
} catch {
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1616
1628
|
async function stageAndCommit(cwd, message) {
|
|
1617
1629
|
try {
|
|
1618
1630
|
await git(cwd, ["add", "-A"], GIT_SLOW_TIMEOUT_MS);
|
|
@@ -2029,7 +2041,8 @@ var projectCheckpointSettingsSchema = z.object({
|
|
|
2029
2041
|
finalizeCommand: z.string().trim().min(1),
|
|
2030
2042
|
credentialEpoch: z.string().trim().min(1),
|
|
2031
2043
|
requiredSecretNames: uniqueSortedArray(secretNameSchema).optional(),
|
|
2032
|
-
optionalSecretNames: uniqueSortedArray(secretNameSchema).optional()
|
|
2044
|
+
optionalSecretNames: uniqueSortedArray(secretNameSchema).optional(),
|
|
2045
|
+
bakeWebAppBuild: z.boolean().optional()
|
|
2033
2046
|
}).superRefine((checkpoint, ctx) => {
|
|
2034
2047
|
const required = new Set(checkpoint.requiredSecretNames ?? []);
|
|
2035
2048
|
for (const name of checkpoint.optionalSecretNames ?? []) {
|
|
@@ -2279,7 +2292,15 @@ var PostToChatInputSchema = z4.object({
|
|
|
2279
2292
|
});
|
|
2280
2293
|
var GetTaskContextRequestSchema = z4.object({
|
|
2281
2294
|
sessionId: z4.string(),
|
|
2282
|
-
includeHistory: z4.boolean().optional().default(false)
|
|
2295
|
+
includeHistory: z4.boolean().optional().default(false),
|
|
2296
|
+
/**
|
|
2297
|
+
* Read the plan-revised marker WITHOUT consuming it. Bookkeeping fetches
|
|
2298
|
+
* (the session-identity check, the branch refresh) pass true so they cannot
|
|
2299
|
+
* race the boot fetch and swallow the notice before it reaches the prompt.
|
|
2300
|
+
* Defaults to false — consuming — so a pod running an older agent build still
|
|
2301
|
+
* clears the marker instead of showing the notice on every boot forever.
|
|
2302
|
+
*/
|
|
2303
|
+
peekPlanRevision: z4.boolean().optional().default(false)
|
|
2283
2304
|
});
|
|
2284
2305
|
var GetChatMessagesRequestSchema = z4.object({
|
|
2285
2306
|
sessionId: z4.string(),
|
|
@@ -3844,6 +3865,17 @@ var PRE_BUILD_TASK_STATUSES = /* @__PURE__ */ new Set(["Planning", "Open"]);
|
|
|
3844
3865
|
function hasTaskPlan(plan) {
|
|
3845
3866
|
return !!plan?.trim();
|
|
3846
3867
|
}
|
|
3868
|
+
var CARD_TYPE_SURFACE = {
|
|
3869
|
+
task: "board",
|
|
3870
|
+
chat: "board",
|
|
3871
|
+
incident: "report",
|
|
3872
|
+
suggestion: "report"
|
|
3873
|
+
};
|
|
3874
|
+
var surfaceTypes = (surface) => Object.keys(CARD_TYPE_SURFACE).filter(
|
|
3875
|
+
(type) => CARD_TYPE_SURFACE[type] === surface
|
|
3876
|
+
);
|
|
3877
|
+
var BOARD_CARD_TYPES = surfaceTypes("board");
|
|
3878
|
+
var REPORT_CARD_TYPES = surfaceTypes("report");
|
|
3847
3879
|
|
|
3848
3880
|
// src/runner/mode-controller.ts
|
|
3849
3881
|
var ModeController = class {
|
|
@@ -8343,7 +8375,7 @@ function wrapBridgeWithDirectStream(inner, reporter, options = {}) {
|
|
|
8343
8375
|
|
|
8344
8376
|
// src/execution/query-executor.ts
|
|
8345
8377
|
import { createHash as createHash2 } from "crypto";
|
|
8346
|
-
import { existsSync as existsSync2, readFileSync as
|
|
8378
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, truncateSync } from "fs";
|
|
8347
8379
|
|
|
8348
8380
|
// src/execution/chat-instructions.ts
|
|
8349
8381
|
function buildChatInstructions(context, scenario, newMessages) {
|
|
@@ -8603,6 +8635,36 @@ After addressing the feedback, resume your autonomous loop: call list_subtasks a
|
|
|
8603
8635
|
return parts;
|
|
8604
8636
|
}
|
|
8605
8637
|
|
|
8638
|
+
// src/execution/plan-revision-notice.ts
|
|
8639
|
+
var MINUTE_MS = 60 * 1e3;
|
|
8640
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
8641
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
8642
|
+
function describeAge(revisedAt, now) {
|
|
8643
|
+
const at = Date.parse(revisedAt);
|
|
8644
|
+
if (Number.isNaN(at)) return "";
|
|
8645
|
+
const ago = now - at;
|
|
8646
|
+
if (ago < MINUTE_MS) return "just now";
|
|
8647
|
+
if (ago < HOUR_MS) return `${Math.round(ago / MINUTE_MS)} minutes ago`;
|
|
8648
|
+
if (ago < DAY_MS) return `${Math.round(ago / HOUR_MS)} hours ago`;
|
|
8649
|
+
return `${Math.round(ago / DAY_MS)} days ago`;
|
|
8650
|
+
}
|
|
8651
|
+
function buildPlanRevisionNotice(context, now = Date.now()) {
|
|
8652
|
+
const revisedAt = context.planRevisedAt;
|
|
8653
|
+
if (!revisedAt) return [];
|
|
8654
|
+
const age = describeAge(revisedAt, now);
|
|
8655
|
+
const when = age ? `${age} (${revisedAt})` : revisedAt;
|
|
8656
|
+
return [
|
|
8657
|
+
`
|
|
8658
|
+
## \u26A0\uFE0F The plan changed since this build was dispatched`,
|
|
8659
|
+
`Someone revised this card's plan ${when}. It was NOT you \u2014 an agent is never told about its own plan edit.`,
|
|
8660
|
+
`Before your next Write or Edit:`,
|
|
8661
|
+
`1. Call \`get_current_plan\` and read the current plan in full.`,
|
|
8662
|
+
`2. Compare it against the plan you were launched with. Treat the current plan as the truth.`,
|
|
8663
|
+
`3. Drop or redo any work the revision supersedes. Do NOT keep building the old plan.`,
|
|
8664
|
+
`If the revision invalidates work you already committed, say so with post_to_chat before you continue.`
|
|
8665
|
+
];
|
|
8666
|
+
}
|
|
8667
|
+
|
|
8606
8668
|
// src/execution/prompt-formatters.ts
|
|
8607
8669
|
function baseDiffCommand(baseBranch, flags) {
|
|
8608
8670
|
const base = baseBranch ?? "dev";
|
|
@@ -8612,7 +8674,7 @@ function baseDiffCommand(baseBranch, flags) {
|
|
|
8612
8674
|
function gateFailureModes() {
|
|
8613
8675
|
return [
|
|
8614
8676
|
`Reading a gate result correctly is what keeps this to ONE pass:`,
|
|
8615
|
-
`- Capture the exit code
|
|
8677
|
+
`- Capture AND propagate the exit code (\`<gate> > <log> 2>&1; ec=$?; echo "EXIT:$ec" >> <log>; (exit $ec)\`) \u2014 piping a gate through \`tail\` masks it, and a bare trailing \`echo "EXIT:$?"\` makes the wrapper exit 0 over a failed gate. That \`EXIT:\` line, plus turbo's final \`Tasks:\` count, is the authority on pass/fail.`,
|
|
8616
8678
|
`- A green gate that printed no per-suite summary (no \`Test Files\`/\`Tests\` line) is still green: \`turbo.json\` sets \`outputLogs: "errors-only"\`, so a PASSING run prints nothing per suite. Do NOT re-run a clean-exit gate to "see the counts" \u2014 if you genuinely need them, run one package's script directly (\`bun run --cwd <pkg> test\`).`,
|
|
8617
8679
|
`- Exit 143, or \`singleton: stopping running '<label>'\` on stderr, means a second gate you started evicted this one (on a pod the heavy gates share one lock). 143 is never a pass \u2014 but an evicted run never finished, so re-running it alone is still your one pass, not a repeat. Run one gate at a time; \`bun run check\` is not lock-wrapped, so it is safe alongside a test run.`,
|
|
8618
8680
|
`- Confirm every package your diff touches actually appears in the run. \`--affected\` can select nothing for a package you changed; when that happens run just that package's suite directly, e.g. \`bun run --cwd apps/api test:unit <changed test files>\` \u2014 not the whole gate again.`,
|
|
@@ -8625,7 +8687,7 @@ function gateWaitProtocol() {
|
|
|
8625
8687
|
`- Launch it ONCE with \`run_in_background: true\`, then END YOUR TURN. The completion notification re-invokes you. Do not read the output file, \`tail\`/\`wc\`/\`pgrep\` it, or start a second watcher on a log another run already owns \u2014 a quiet gate is still running.`,
|
|
8626
8688
|
`- Never wrap a gate in a foreground \`timeout\`: killing your own run at 590s produces exit 124/143 and ZERO information, and you then have to run it again unbounded. \`sleep N; <cmd>\` is hard-blocked for the same reason.`,
|
|
8627
8689
|
`- Never relaunch a command you just killed without changing something. State cwd explicitly in the command (\`cd /workspaces/repo && \u2026\`, \`--root\`, \`git -C\`) rather than trusting the shell's working directory.`,
|
|
8628
|
-
`- The completion notification reports the WRAPPER's exit code,
|
|
8690
|
+
`- The completion notification reports the WRAPPER's exit code. With the propagating idiom above, \`(exit $ec)\` makes that the gate's own code; a wrapper that ends on a bare \`echo\` reports 0 no matter what failed. Whenever the notification and the log disagree, the \`EXIT:\` line in the log is the authority.`,
|
|
8629
8691
|
`- Scope heavy gates to what you changed (\`--filter=<pkg>\`). An unscoped \`check:affected\` can pull a large web typecheck into scope for a diff touching no web files and get OOM-killed (exit 137) after minutes, where the scoped run takes seconds.`,
|
|
8630
8692
|
`- Merge the base BEFORE the gate pass, never after (see the Pre-PR Protocol). If that merge touched \`package.json\`/\`bun.lock\`, run \`bun install\` before starting the gate \u2014 a changed lockfile makes gates fail for reasons unrelated to your diff.`
|
|
8631
8693
|
];
|
|
@@ -9162,6 +9224,10 @@ function buildPmRelaunchParts(context, lastAgentIdx, isAuto, agentMode) {
|
|
|
9162
9224
|
return parts;
|
|
9163
9225
|
}
|
|
9164
9226
|
|
|
9227
|
+
// src/execution/system-prompt.ts
|
|
9228
|
+
import { readFileSync } from "fs";
|
|
9229
|
+
import { join as join11 } from "path";
|
|
9230
|
+
|
|
9165
9231
|
// src/execution/mode-prompt.ts
|
|
9166
9232
|
var SP_DESC_MAX_CHARS = 80;
|
|
9167
9233
|
function truncateDescription(desc, maxChars) {
|
|
@@ -9249,6 +9315,17 @@ function buildPlanDocumentationSection(context) {
|
|
|
9249
9315
|
`- Identification auto-fills title, story points, and icon with quick AI guesses. After exploring, refine the title, story points, and risk with update_task_properties whenever they no longer match what the work actually is \u2014 adjust in either direction. Icons are automatic \u2014 never set them.`
|
|
9250
9316
|
];
|
|
9251
9317
|
}
|
|
9318
|
+
function buildPlanRevisionSection() {
|
|
9319
|
+
return [
|
|
9320
|
+
``,
|
|
9321
|
+
`### The plan can change while you sleep`,
|
|
9322
|
+
`If a turn opens with "The plan changed since this build was dispatched", honor it BEFORE your next Write or Edit:`,
|
|
9323
|
+
`1. Call get_current_plan and read the current plan in full.`,
|
|
9324
|
+
`2. Compare it against the plan you were launched with \u2014 the current plan wins.`,
|
|
9325
|
+
`3. Drop or redo whatever the revision supersedes, and say so with post_to_chat if it invalidates work you already committed.`,
|
|
9326
|
+
`Building on a superseded plan is the most expensive mistake a resumed card can make.`
|
|
9327
|
+
];
|
|
9328
|
+
}
|
|
9252
9329
|
function buildNoPrWhenNoCodeSection(baseBranch) {
|
|
9253
9330
|
const diffCommand = baseDiffCommand(baseBranch);
|
|
9254
9331
|
return [
|
|
@@ -9448,6 +9525,7 @@ function buildAutoPrompt(context, runnerMode) {
|
|
|
9448
9525
|
...buildPrGuideSection(),
|
|
9449
9526
|
...buildNoPrWhenNoCodeSection(context?.baseBranch)
|
|
9450
9527
|
],
|
|
9528
|
+
...buildPlanRevisionSection(),
|
|
9451
9529
|
``,
|
|
9452
9530
|
`### Autonomous Guidelines:`,
|
|
9453
9531
|
`- Make decisions independently \u2014 do not ask the team for approval at each step`,
|
|
@@ -9477,7 +9555,8 @@ function buildBuildingPrompt(context) {
|
|
|
9477
9555
|
...buildPrGuideSection(),
|
|
9478
9556
|
...buildNoPrWhenNoCodeSection(context?.baseBranch),
|
|
9479
9557
|
...context?.isAuto || !context?.plan?.trim() ? buildPlanDocumentationSection(context) : []
|
|
9480
|
-
]
|
|
9558
|
+
],
|
|
9559
|
+
...buildPlanRevisionSection()
|
|
9481
9560
|
];
|
|
9482
9561
|
if (context) parts.push(...buildPropertyInstructions(context));
|
|
9483
9562
|
return parts.join("\n");
|
|
@@ -9633,6 +9712,14 @@ function buildReviewPrompt(context) {
|
|
|
9633
9712
|
}
|
|
9634
9713
|
|
|
9635
9714
|
// src/execution/system-prompt.ts
|
|
9715
|
+
function repoHasScript(workspaceDir, script) {
|
|
9716
|
+
try {
|
|
9717
|
+
const pkg = JSON.parse(readFileSync(join11(workspaceDir, "package.json"), "utf8"));
|
|
9718
|
+
return typeof pkg.scripts?.[script] === "string";
|
|
9719
|
+
} catch {
|
|
9720
|
+
return false;
|
|
9721
|
+
}
|
|
9722
|
+
}
|
|
9636
9723
|
function formatProjectAgentLine(agent) {
|
|
9637
9724
|
const role = agent.role ? `role: ${agent.role}` : "role: unassigned";
|
|
9638
9725
|
const sp = agent.storyPoints === null || agent.storyPoints === void 0 ? "" : `, story points: ${agent.storyPoints}`;
|
|
@@ -9700,27 +9787,34 @@ Workflow:`,
|
|
|
9700
9787
|
`- If you toggled into active mode temporarily, mention when you're done so the team can switch you back to planning mode.`
|
|
9701
9788
|
].filter(Boolean);
|
|
9702
9789
|
}
|
|
9703
|
-
function buildTaskAgentPreamble(context) {
|
|
9790
|
+
function buildTaskAgentPreamble(context, workspaceDir) {
|
|
9791
|
+
const managedStack = repoHasScript(workspaceDir, "web:rebuild");
|
|
9792
|
+
const stackLines = managedStack ? [
|
|
9793
|
+
`- The web app is served on port 3050, the API on port 7090.`,
|
|
9794
|
+
`- Web is served from a production build, not \`next dev\` \u2014 your edits do NOT hot-reload. Run \`bun run web:rebuild\` (rebuild + restart lands in ~2s) before re-testing a UI change. The API hot-reloads on its own.`
|
|
9795
|
+
] : [
|
|
9796
|
+
`- Ports, running services, and rebuild commands differ per repo, so this brief asserts none of them. Whether an app is already running depends on the Start command this project configured. Read this repo's CLAUDE.md and its rules files for those facts, check what is actually listening before you use a port, and start or rebuild the app with the repo's own scripts.`
|
|
9797
|
+
];
|
|
9704
9798
|
return [
|
|
9705
9799
|
`You are an AI agent working on a task for the "${context.title}" project.`,
|
|
9706
9800
|
`You are running inside a Claudespace pod (a Kubernetes container, NOT a GitHub Codespace) with full access to the repository.`,
|
|
9707
9801
|
`
|
|
9708
|
-
Environment \u2014 already built and running. These are the facts you would otherwise spend calls discovering:`,
|
|
9709
|
-
`- The repo is cloned at your working directory with \`${context.githubBranch}\` checked out, dependencies installed, database migrated, git configured, and the dev stack up. Commit and push directly to this branch.`,
|
|
9710
|
-
|
|
9711
|
-
`- Web is served from a production build, not \`next dev\` \u2014 your edits do NOT hot-reload. Run \`bun run web:rebuild\` (rebuild + restart lands in ~2s) before re-testing a UI change. The API hot-reloads on its own.`,
|
|
9802
|
+
Environment \u2014 already built${managedStack ? " and running" : ""}. These are the facts you would otherwise spend calls discovering:`,
|
|
9803
|
+
`- The repo is cloned at your working directory with \`${context.githubBranch}\` checked out, dependencies installed, database migrated, git configured${managedStack ? ", and the dev stack up" : ""}. Commit and push directly to this branch.`,
|
|
9804
|
+
...stackLines,
|
|
9712
9805
|
`- Browser automation is the Playwright CLI (\`playwright\`, pinned 1.62.1), NOT an MCP server \u2014 there are no \`mcp__playwright__*\` tools here. Only the headless shell is baked, so a launch must name it AND disable the sandbox: \`chromium.launch({ channel: "chromium-headless-shell", args: ["--no-sandbox"] })\`. A bare \`chromium.launch()\` FAILS: since playwright 1.49 that resolves to the full browser, which is deliberately not installed (pods have no display and cannot run Chromium's sandbox). Screenshots land wherever you write them \u2014 move or delete them before committing.`,
|
|
9713
9806
|
`- The clone is \`--single-branch\`, so a bare \`git fetch origin <branch>\` does NOT create \`origin/<branch>\`. To reference any other branch, use the explicit refspec: \`git fetch origin <branch>:refs/remotes/origin/<branch>\`.`,
|
|
9714
9807
|
`- The shell cwd resets between Bash calls, and so does every shell variable. A var you export in one call is EMPTY in the next, which silently redirects output to \`/\` and loses it. Write literal absolute paths (\`git -C\`, \`bun run --cwd\`), and \`mkdir -p\` a directory in the SAME call as the redirect that writes to it.`,
|
|
9715
9808
|
`- The \`gh\` CLI is available for READ-ONLY PR and CI state (\`gh pr view\`, \`gh pr checks\`, \`gh pr diff\`). Use the mcp__conveyor__* tools for anything that mutates a PR or card.`,
|
|
9716
9809
|
`- The core mcp__conveyor__* tools are preloaded \u2014 call them directly; do NOT spend a ToolSearch call on them. For any tool that IS still deferred (schema not loaded), load ALL the schemas you expect to need in ONE ToolSearch call using fully-qualified names (query "select:Monitor,mcp__conveyor__<name>" \u2014 bare MCP tool names without the mcp__<server>__ prefix do not match); never guess a deferred tool's parameters.`,
|
|
9717
|
-
`Because the environment is already up, do not run installs, builds, database setup, dev-server starts, or exploratory \`pwd\`/\`ls\` probes to confirm any of the above. Run them only when a specific error demands it.`,
|
|
9810
|
+
managedStack ? `Because the environment is already up, do not run installs, builds, database setup, dev-server starts, or exploratory \`pwd\`/\`ls\` probes to confirm any of the above. Run them only when a specific error demands it.` : `Do not run installs, database setup, or exploratory \`pwd\`/\`ls\` probes to confirm any of the above \u2014 they are done. If the task needs a running app, check whether one is already up before you start it, and use the repo's own scripts.`,
|
|
9718
9811
|
`
|
|
9719
9812
|
Working rules:`,
|
|
9720
9813
|
`- Read a file before your first Write/Edit to it, and batch multiple changes to the same file into a single call instead of many sequential edits.`,
|
|
9721
9814
|
`- To learn what calls a symbol or where it lives, query the prebuilt code graph before grepping: \`graphify query "<SymbolName>"\` from the repo root. Query a SYMBOL, never a sentence \u2014 \`graphify query "resolveTaskBaseBranch"\` returns the definition plus every call site, while "how does a task get its base branch" seeds unrelated start nodes and returns test files and loggers. Don't know the symbol yet? Grep for the name first, then query it: grep finds names, the graph finds relationships. \`No matching nodes found\` means "not in this graph" (it is prebuilt, so very recent code is absent), NOT "not in the codebase" \u2014 fall back to \`git grep\`. Skip all of this if \`graphify-out/graph.json\` is not present.`,
|
|
9722
9815
|
`- When a build/lint/test run fails, capture its output to a file once and grep the file \u2014 never re-run the suite just to re-filter the same output.`,
|
|
9723
9816
|
`- Waiting on long-running commands: if a gate finishes in under ~2 minutes, run it in the foreground with a timeout. For a longer one, launch it with run_in_background and STOP; a completion notification arrives when it finishes, and the workspace stays awake for as long as background work is outstanding, so a backgrounded gate will not be killed by an idle sleep. For the final pre-PR gate a bounded foreground run (\`timeout 590 <gate>\` with Bash \`timeout: 600000\`) is still preferred as defense in depth \u2014 it survives a pod resume, which a background job does not. Never busy-wait with sleep/pgrep/tail loops, and never re-run the suite to escape a wait that looks stalled.`,
|
|
9817
|
+
`- Ending your turn with NO tool call is the correct way to wait, and it is safe: the pod stays alive and the next notification re-invokes you. Never emit filler commands (\`echo waiting\`, \`true\`, \`sleep N; echo done\`) to "stay alive" \u2014 they are detected and blocked. The proven long-wait shape: start the job with run_in_background, then end the turn. Arm a ScheduleWakeup (delaySeconds 900-1500, prompt restating your next steps) only when nothing will notify you \u2014 an external CI run, a deploy, a remote queue \u2014 never as insurance against a background job's own notification, which does fire.`,
|
|
9724
9818
|
`
|
|
9725
9819
|
Git:`,
|
|
9726
9820
|
`- Stay on \`${context.githubBranch}\` for the whole task: do not check out another branch and do not create one. It was cut from \`${context.baseBranch}\`, and PRs target that automatically.`,
|
|
@@ -9734,7 +9828,7 @@ function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
|
9734
9828
|
if (isPackRunner) {
|
|
9735
9829
|
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
9736
9830
|
}
|
|
9737
|
-
const parts = isPmActive ? buildActivePreamble(context, config.workspaceDir) : isPm ? buildPmPreamble(context) : buildTaskAgentPreamble(context);
|
|
9831
|
+
const parts = isPmActive ? buildActivePreamble(context, config.workspaceDir) : isPm ? buildPmPreamble(context) : buildTaskAgentPreamble(context, config.workspaceDir);
|
|
9738
9832
|
if (setupLog.length > 0) {
|
|
9739
9833
|
parts.push(
|
|
9740
9834
|
`
|
|
@@ -9908,6 +10002,7 @@ ${context.description}`);
|
|
|
9908
10002
|
## Plan
|
|
9909
10003
|
${truncatePlanForPrompt(context.plan)}`);
|
|
9910
10004
|
}
|
|
10005
|
+
parts.push(...buildPlanRevisionNotice(context));
|
|
9911
10006
|
if (context.files && context.files.length > 0) {
|
|
9912
10007
|
parts.push(`
|
|
9913
10008
|
## Attached Files`);
|
|
@@ -10178,7 +10273,9 @@ async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
|
10178
10273
|
const isPackRunner = mode === "pack" || mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
10179
10274
|
if (!isPackRunner) {
|
|
10180
10275
|
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode, isAuto);
|
|
10181
|
-
if (sessionRelaunch)
|
|
10276
|
+
if (sessionRelaunch) {
|
|
10277
|
+
return [...buildPlanRevisionNotice(context), sessionRelaunch].join("\n");
|
|
10278
|
+
}
|
|
10182
10279
|
}
|
|
10183
10280
|
const isPm = mode === "pm";
|
|
10184
10281
|
let scenario = detectRelaunchScenario(context, isPm);
|
|
@@ -10926,7 +11023,7 @@ var getAttachmentContract = defineToolContract({
|
|
|
10926
11023
|
});
|
|
10927
11024
|
var attachmentTags = f.optional(
|
|
10928
11025
|
f.array(f.string(), {
|
|
10929
|
-
desc: `Glossary tag names this file is a relevant example of, e.g.
|
|
11026
|
+
desc: `Glossary tag names this file is a relevant example of. MUST be a JSON array of quoted strings, e.g. ["ops-hub", "platform-support"] \u2014 bare unquoted words are invalid JSON and fail the whole call before it is parsed (21 fleet calls in one week died this way). Use it when the file shows a tagged entity in a particular state: the tag's page lists its recent tagged attachments, so a reader can see what the entity looks like across the app and spot visual changes over time. Names are matched case-insensitively within the project; a name that matches no tag is reported back and never fails the upload. Max 5.`
|
|
10930
11027
|
})
|
|
10931
11028
|
);
|
|
10932
11029
|
var uploadAttachmentContract = defineToolContract({
|
|
@@ -11129,7 +11226,12 @@ function buildGetCurrentPlanTool(connection) {
|
|
|
11129
11226
|
const ctx = await connection.call("getTaskContext", {
|
|
11130
11227
|
sessionId: connection.sessionId
|
|
11131
11228
|
});
|
|
11132
|
-
|
|
11229
|
+
const plan = ctx.plan ?? "No plan available.";
|
|
11230
|
+
return textResult(
|
|
11231
|
+
ctx.planRevisedAt ? `\u26A0\uFE0F This plan was revised ${ctx.planRevisedAt}, after your build was dispatched. Treat it as the truth and drop any work it supersedes.
|
|
11232
|
+
|
|
11233
|
+
${plan}` : plan
|
|
11234
|
+
);
|
|
11133
11235
|
} catch {
|
|
11134
11236
|
return textResult("Could not fetch updated plan.");
|
|
11135
11237
|
}
|
|
@@ -11478,6 +11580,35 @@ function buildForceUpdateTaskStatusTool(connection) {
|
|
|
11478
11580
|
}
|
|
11479
11581
|
);
|
|
11480
11582
|
}
|
|
11583
|
+
async function ensureHeadPushed(connection, cwd, headBranch, skipVerify) {
|
|
11584
|
+
if (!await hasUnpushedCommits(cwd)) return null;
|
|
11585
|
+
const pushSuccess = await pushToOrigin(
|
|
11586
|
+
cwd,
|
|
11587
|
+
async () => {
|
|
11588
|
+
try {
|
|
11589
|
+
const result = await connection.call("refreshGithubToken", {
|
|
11590
|
+
sessionId: connection.sessionId
|
|
11591
|
+
});
|
|
11592
|
+
return result.token;
|
|
11593
|
+
} catch {
|
|
11594
|
+
return void 0;
|
|
11595
|
+
}
|
|
11596
|
+
},
|
|
11597
|
+
skipVerify
|
|
11598
|
+
);
|
|
11599
|
+
if (pushSuccess) {
|
|
11600
|
+
connection.sendEvent({ type: "message", content: "Auto-pushed committed changes to origin" });
|
|
11601
|
+
return null;
|
|
11602
|
+
}
|
|
11603
|
+
if (await remoteMatchesLocalHead(cwd, headBranch)) {
|
|
11604
|
+
connection.sendEvent({
|
|
11605
|
+
type: "message",
|
|
11606
|
+
content: "Push reported an error but origin already has this HEAD \u2014 continuing"
|
|
11607
|
+
});
|
|
11608
|
+
return null;
|
|
11609
|
+
}
|
|
11610
|
+
return `Failed to push changes to origin, and origin does not have this HEAD. Verify before treating this as fatal: \`git ls-remote origin ${headBranch}\` \u2014 if the tip equals your HEAD the push actually landed and you can retry this tool. Otherwise refresh the credential (refresh_github_token) or push manually, then retry.`;
|
|
11611
|
+
}
|
|
11481
11612
|
function buildCreatePullRequestTool(connection, config) {
|
|
11482
11613
|
return defineContractTool(
|
|
11483
11614
|
createPullRequestContract,
|
|
@@ -11506,32 +11637,8 @@ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
|
|
|
11506
11637
|
);
|
|
11507
11638
|
}
|
|
11508
11639
|
}
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
cwd,
|
|
11512
|
-
async () => {
|
|
11513
|
-
try {
|
|
11514
|
-
const result2 = await connection.call("refreshGithubToken", {
|
|
11515
|
-
sessionId: connection.sessionId
|
|
11516
|
-
});
|
|
11517
|
-
return result2.token;
|
|
11518
|
-
} catch {
|
|
11519
|
-
return void 0;
|
|
11520
|
-
}
|
|
11521
|
-
},
|
|
11522
|
-
skipVerify ?? true
|
|
11523
|
-
);
|
|
11524
|
-
if (pushSuccess) {
|
|
11525
|
-
connection.sendEvent({
|
|
11526
|
-
type: "message",
|
|
11527
|
-
content: "Auto-pushed committed changes to origin"
|
|
11528
|
-
});
|
|
11529
|
-
} else {
|
|
11530
|
-
return textResult(
|
|
11531
|
-
"Failed to push changes to origin. Please check git status and push manually before creating PR."
|
|
11532
|
-
);
|
|
11533
|
-
}
|
|
11534
|
-
}
|
|
11640
|
+
const pushError = await ensureHeadPushed(connection, cwd, headBranch, skipVerify ?? true);
|
|
11641
|
+
if (pushError) return textResult(pushError);
|
|
11535
11642
|
const result = await connection.call("createPullRequest", {
|
|
11536
11643
|
sessionId: connection.sessionId,
|
|
11537
11644
|
title,
|
|
@@ -11556,10 +11663,10 @@ ${result.glossaryNote}` : "";
|
|
|
11556
11663
|
`Failed to create pull request: ${msg}
|
|
11557
11664
|
|
|
11558
11665
|
Troubleshooting:
|
|
11666
|
+
- FIRST verify the failure is real: \`gh pr list --head <branch>\` \u2014 if a PR exists, it opened and this error is stale; do not re-create, bundle, or post a blocked escalation
|
|
11559
11667
|
- Ensure all changes are committed and pushed to the remote branch
|
|
11560
11668
|
- Check that the branch exists on the remote (run: git push -u origin HEAD)
|
|
11561
|
-
-
|
|
11562
|
-
- If git auth fails, the token may have expired \u2014 retry the operation`
|
|
11669
|
+
- If git auth fails, the token may have expired \u2014 refresh_github_token once, then retry`
|
|
11563
11670
|
);
|
|
11564
11671
|
}
|
|
11565
11672
|
}
|
|
@@ -11785,7 +11892,7 @@ function buildMutationTools(connection, config) {
|
|
|
11785
11892
|
}
|
|
11786
11893
|
|
|
11787
11894
|
// src/tools/attachment-tools.ts
|
|
11788
|
-
import { basename, extname, isAbsolute, join as
|
|
11895
|
+
import { basename, extname, isAbsolute, join as join12 } from "path";
|
|
11789
11896
|
var MIME_BY_EXT = {
|
|
11790
11897
|
".png": "image/png",
|
|
11791
11898
|
".jpg": "image/jpeg",
|
|
@@ -11838,7 +11945,7 @@ ${snippet}`;
|
|
|
11838
11945
|
function buildUploadAttachmentTool(connection, config) {
|
|
11839
11946
|
return defineContractTool(uploadAttachmentContract, async ({ path: path2, title, tags }) => {
|
|
11840
11947
|
try {
|
|
11841
|
-
const filePath = isAbsolute(path2) ? path2 :
|
|
11948
|
+
const filePath = isAbsolute(path2) ? path2 : join12(config.workspaceDir, path2);
|
|
11842
11949
|
const mimeType = inferMimeType(filePath);
|
|
11843
11950
|
const info = await statWorkspacePath(filePath);
|
|
11844
11951
|
if (!info.isFile) {
|
|
@@ -12378,7 +12485,7 @@ import { z as z16 } from "zod";
|
|
|
12378
12485
|
|
|
12379
12486
|
// src/execution/context-path-verifier.ts
|
|
12380
12487
|
import { readFile as readFile2 } from "fs/promises";
|
|
12381
|
-
import { isAbsolute as isAbsolute2, join as
|
|
12488
|
+
import { isAbsolute as isAbsolute2, join as join13, normalize } from "path";
|
|
12382
12489
|
var PROBLEM_TEXT = {
|
|
12383
12490
|
not_found: "does not exist in the repo",
|
|
12384
12491
|
expected_folder: "is a file, not a folder \u2014 use type 'file', 'rule', or 'doc'",
|
|
@@ -12423,7 +12530,7 @@ async function verifyContextPaths(links, workspaceDir) {
|
|
|
12423
12530
|
problems.push({ type: link.type, path: link.path, reason: shape });
|
|
12424
12531
|
continue;
|
|
12425
12532
|
}
|
|
12426
|
-
const absolutePath =
|
|
12533
|
+
const absolutePath = join13(workspaceDir, toRelativePath(link.path));
|
|
12427
12534
|
const stat = await statWorkspacePath(absolutePath);
|
|
12428
12535
|
const wantsDirectory = expectsDirectory(link.type);
|
|
12429
12536
|
if (!stat.exists) {
|
|
@@ -12983,17 +13090,26 @@ var ReviewGuideToolSchema = z18.strictObject({
|
|
|
12983
13090
|
"REQUIRED top-level array (never text appended to overview) of ordered conceptual sections."
|
|
12984
13091
|
)
|
|
12985
13092
|
});
|
|
12986
|
-
var FLATTENED_SECTIONS_PATTERN = /<\/overview>\s
|
|
13093
|
+
var FLATTENED_SECTIONS_PATTERN = /<\/overview>\s*(?:<parameter name="sections">|<sections>)/;
|
|
13094
|
+
var TRAILING_TAGS_PATTERN = /(?:\s*<\/(?:parameter|sections|invoke)>)+\s*$/;
|
|
13095
|
+
function sliceOutermostArray(tail) {
|
|
13096
|
+
const start = tail.indexOf("[");
|
|
13097
|
+
const end = tail.lastIndexOf("]");
|
|
13098
|
+
return start >= 0 && end > start ? tail.slice(start, end + 1) : null;
|
|
13099
|
+
}
|
|
12987
13100
|
function recoverFlattenedGuide(overview) {
|
|
12988
13101
|
const match = FLATTENED_SECTIONS_PATTERN.exec(overview);
|
|
12989
13102
|
if (!match) return null;
|
|
12990
|
-
const head = overview.slice(0, match.index);
|
|
12991
|
-
const tail = overview.slice(match.index + match[0].length).replace(
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
13103
|
+
const head = overview.slice(0, match.index).trim();
|
|
13104
|
+
const tail = overview.slice(match.index + match[0].length).replace(TRAILING_TAGS_PATTERN, "").trim();
|
|
13105
|
+
for (const candidate of [tail, sliceOutermostArray(tail)]) {
|
|
13106
|
+
if (!candidate) continue;
|
|
13107
|
+
try {
|
|
13108
|
+
return { overview: head, sections: JSON.parse(candidate) };
|
|
13109
|
+
} catch {
|
|
13110
|
+
}
|
|
12996
13111
|
}
|
|
13112
|
+
return null;
|
|
12997
13113
|
}
|
|
12998
13114
|
async function resolveGitHeadSha(cwd) {
|
|
12999
13115
|
try {
|
|
@@ -13020,7 +13136,7 @@ function buildPublishReviewGuideTool(connection, options = {}) {
|
|
|
13020
13136
|
const recovered = recoverFlattenedGuide(overview);
|
|
13021
13137
|
if (!recovered) {
|
|
13022
13138
|
throw new Error(
|
|
13023
|
-
"publish_review_guide requires a top-level `sections` array \u2014 an ordered list of conceptual sections, each with title, explanation, and files[]. Do not append the sections to `overview` as text. Retry with sections as a real array argument."
|
|
13139
|
+
"publish_review_guide requires a top-level `sections` array \u2014 an ordered list of conceptual sections, each with title, explanation, and files[]. Your call arrived with only `reviewedSha` and `overview`; the `sections` parameter never reached the server, which usually means the call encoding flattened it into `overview`. Do not append the sections to `overview` as text. Retry with sections as a real array argument, and shorten `overview` if the failure repeats."
|
|
13024
13140
|
);
|
|
13025
13141
|
}
|
|
13026
13142
|
resolvedOverview = recovered.overview;
|
|
@@ -13746,7 +13862,7 @@ function collectMissingProps(taskProps) {
|
|
|
13746
13862
|
}
|
|
13747
13863
|
|
|
13748
13864
|
// src/runner/heavy-gate.ts
|
|
13749
|
-
import { readFileSync } from "fs";
|
|
13865
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
13750
13866
|
import path from "path";
|
|
13751
13867
|
var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
|
|
13752
13868
|
function runDir() {
|
|
@@ -13763,7 +13879,7 @@ function pidAlive(pid) {
|
|
|
13763
13879
|
function isHeavyGateActive() {
|
|
13764
13880
|
for (const key of GATE_KEYS) {
|
|
13765
13881
|
try {
|
|
13766
|
-
const raw =
|
|
13882
|
+
const raw = readFileSync2(path.join(runDir(), `${key}.pid`), "utf8").trim();
|
|
13767
13883
|
const pid = Number.parseInt(raw, 10);
|
|
13768
13884
|
if (Number.isInteger(pid) && pid > 0 && pidAlive(pid)) return true;
|
|
13769
13885
|
} catch {
|
|
@@ -13776,6 +13892,18 @@ function isHeavyGateActive() {
|
|
|
13776
13892
|
var REPEAT_INTERRUPT_THRESHOLD = 4;
|
|
13777
13893
|
var REPEAT_INTERRUPT_INTERVAL = 4;
|
|
13778
13894
|
var REPEAT_FORCE_STOP_THRESHOLD = 12;
|
|
13895
|
+
var NOOP_ALLOWANCE = 2;
|
|
13896
|
+
var NOOP_FORCE_STOP_THRESHOLD = 30;
|
|
13897
|
+
var NOOP_FINGERPRINT = "Bash:NOOP_KEEPALIVE";
|
|
13898
|
+
function isNoOpKeepAlive(command) {
|
|
13899
|
+
const cmd = command.trim().replace(/\s+/g, " ");
|
|
13900
|
+
if (/^(true|:)$/.test(cmd)) return true;
|
|
13901
|
+
const constantEcho = String.raw`echo( -[neE]+)?( ["']?[A-Za-z0-9 ._,:-]{0,60}["']?)?`;
|
|
13902
|
+
if (new RegExp(`^${constantEcho}$`).test(cmd)) return true;
|
|
13903
|
+
if (new RegExp(`^sleep \\d+ ?(?:(?:;|&&) ?${constantEcho})?$`).test(cmd)) return true;
|
|
13904
|
+
if (/^date( \+\S+)?$/.test(cmd)) return true;
|
|
13905
|
+
return false;
|
|
13906
|
+
}
|
|
13779
13907
|
var UNCOUNTED_TOOLS = /* @__PURE__ */ new Set(["ExitPlanMode", "AskUserQuestion"]);
|
|
13780
13908
|
function stableStringify(value) {
|
|
13781
13909
|
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
@@ -13787,7 +13915,9 @@ function fingerprintToolCall(toolName, input) {
|
|
|
13787
13915
|
if (!toolName || UNCOUNTED_TOOLS.has(toolName)) return null;
|
|
13788
13916
|
if (toolName === "Bash") {
|
|
13789
13917
|
const command = String(input.command ?? "").trim().replace(/\s+/g, " ");
|
|
13790
|
-
|
|
13918
|
+
if (!command) return null;
|
|
13919
|
+
if (isNoOpKeepAlive(command)) return NOOP_FINGERPRINT;
|
|
13920
|
+
return `Bash:${command}`;
|
|
13791
13921
|
}
|
|
13792
13922
|
return `${toolName}:${stableStringify(input)}`;
|
|
13793
13923
|
}
|
|
@@ -13796,10 +13926,16 @@ var ToolLoopTracker = class {
|
|
|
13796
13926
|
streak = 0;
|
|
13797
13927
|
/** True once this streak has been reported to chat — one message per streak. */
|
|
13798
13928
|
reported = false;
|
|
13929
|
+
/** Session-cumulative no-op keep-alive count (never reset by other calls). */
|
|
13930
|
+
noOps = 0;
|
|
13799
13931
|
/** Repeats of the current fingerprint, including the call being recorded. */
|
|
13800
13932
|
get repeatCount() {
|
|
13801
13933
|
return this.streak;
|
|
13802
13934
|
}
|
|
13935
|
+
/** Total no-op keep-alive calls recorded this session. */
|
|
13936
|
+
get noOpCount() {
|
|
13937
|
+
return this.noOps;
|
|
13938
|
+
}
|
|
13803
13939
|
/** True when the current streak has already been posted to chat. */
|
|
13804
13940
|
get alreadyReported() {
|
|
13805
13941
|
return this.reported;
|
|
@@ -13810,6 +13946,14 @@ var ToolLoopTracker = class {
|
|
|
13810
13946
|
}
|
|
13811
13947
|
/** Record one call and decide what to do about it. */
|
|
13812
13948
|
record(fingerprint) {
|
|
13949
|
+
if (fingerprint === NOOP_FINGERPRINT) {
|
|
13950
|
+
this.noOps++;
|
|
13951
|
+
this.fingerprint = fingerprint;
|
|
13952
|
+
this.streak = 1;
|
|
13953
|
+
if (this.noOps >= NOOP_FORCE_STOP_THRESHOLD) return "force_stop";
|
|
13954
|
+
if (this.noOps > NOOP_ALLOWANCE) return "interrupt";
|
|
13955
|
+
return "ok";
|
|
13956
|
+
}
|
|
13813
13957
|
if (fingerprint !== this.fingerprint) {
|
|
13814
13958
|
this.fingerprint = fingerprint;
|
|
13815
13959
|
this.streak = 1;
|
|
@@ -13829,6 +13973,9 @@ function buildRepeatLoopMessage(repeatCount, heavyGateActive) {
|
|
|
13829
13973
|
const advice = heavyGateActive ? `A build gate (test/typecheck/build) is running on this pod right now. Do NOT poll its log. End your turn \u2014 the completion notification re-invokes you when the gate finishes.` : `If you are waiting on a background job, end your turn instead of polling; the completion notification re-invokes you. If you are stuck, change your approach: read a different file, run a different command, or post to chat and ask the team.`;
|
|
13830
13974
|
return `${head} ${advice} Call a different tool now.`;
|
|
13831
13975
|
}
|
|
13976
|
+
function buildNoOpKeepAliveMessage(noOpCount) {
|
|
13977
|
+
return `Conveyor blocked this call: it is a no-op keep-alive (${noOpCount} this session). You do not need to emit tool calls to stay alive. Ending your turn with NO tool call is safe and expected while waiting: the pod stays up and the completion notification re-invokes you. If nothing is running, do real work or end the turn. For a long wait with no notification source, use Monitor or ScheduleWakeup instead of filler commands.`;
|
|
13978
|
+
}
|
|
13832
13979
|
function buildRepeatLoopChatMessage(repeatCount, forceStopped) {
|
|
13833
13980
|
if (forceStopped) {
|
|
13834
13981
|
return `Agent force-stopped after repeating the same tool call ${repeatCount} times in a row. It appears stuck \u2014 send a message to resume.`;
|
|
@@ -14078,6 +14225,20 @@ function checkToolLoop(host, toolName, input) {
|
|
|
14078
14225
|
const tracker = host.toolLoop ??= new ToolLoopTracker();
|
|
14079
14226
|
const verdict = tracker.record(fingerprint);
|
|
14080
14227
|
if (verdict === "ok") return null;
|
|
14228
|
+
if (fingerprint === NOOP_FINGERPRINT) {
|
|
14229
|
+
const noOps = tracker.noOpCount;
|
|
14230
|
+
if (verdict === "force_stop") {
|
|
14231
|
+
host.connection.postChatMessage(
|
|
14232
|
+
`Agent force-stopped after ${noOps} no-op keep-alive commands. It appears to be spinning instead of waiting \u2014 send a message to resume.`
|
|
14233
|
+
);
|
|
14234
|
+
host.requestStop();
|
|
14235
|
+
return {
|
|
14236
|
+
behavior: "deny",
|
|
14237
|
+
message: `Stopped after ${noOps} no-op keep-alive calls this session.`
|
|
14238
|
+
};
|
|
14239
|
+
}
|
|
14240
|
+
return { behavior: "deny", message: buildNoOpKeepAliveMessage(noOps) };
|
|
14241
|
+
}
|
|
14081
14242
|
const repeats = tracker.repeatCount;
|
|
14082
14243
|
if (verdict === "force_stop") {
|
|
14083
14244
|
host.connection.postChatMessage(buildRepeatLoopChatMessage(repeats, true));
|
|
@@ -14220,7 +14381,7 @@ function resolveSessionStart(lineageKey, cwd) {
|
|
|
14220
14381
|
function repairTornSessionFile(path2) {
|
|
14221
14382
|
try {
|
|
14222
14383
|
if (!existsSync2(path2)) return false;
|
|
14223
|
-
const content =
|
|
14384
|
+
const content = readFileSync3(path2, "utf8");
|
|
14224
14385
|
if (content.length === 0) return false;
|
|
14225
14386
|
let keepEnd = content.length;
|
|
14226
14387
|
if (!content.endsWith("\n")) {
|
|
@@ -16803,6 +16964,9 @@ var SessionRunner = class _SessionRunner {
|
|
|
16803
16964
|
title: ctx.title,
|
|
16804
16965
|
description: ctx.description,
|
|
16805
16966
|
plan: ctx.plan,
|
|
16967
|
+
// The server hands this to exactly one context fetch per revision, so it
|
|
16968
|
+
// must survive the mapping or the notice is lost.
|
|
16969
|
+
planRevisedAt: ctx.planRevisedAt ?? null,
|
|
16806
16970
|
status: ctx.status,
|
|
16807
16971
|
chatHistory,
|
|
16808
16972
|
agentId: ctx.agentId ?? null,
|
|
@@ -16975,7 +17139,10 @@ ${outcome.failures.join("\n")}
|
|
|
16975
17139
|
try {
|
|
16976
17140
|
const ctx = await this.connection.call("getTaskContext", {
|
|
16977
17141
|
sessionId: this.sessionId,
|
|
16978
|
-
includeHistory: false
|
|
17142
|
+
includeHistory: false,
|
|
17143
|
+
// Bookkeeping fetch — it must not swallow a plan-revised notice the
|
|
17144
|
+
// next prompt is about to carry.
|
|
17145
|
+
peekPlanRevision: true
|
|
16979
17146
|
});
|
|
16980
17147
|
if (ctx?.githubBranch && this.fullContext) {
|
|
16981
17148
|
this.fullContext.githubBranch = ctx.githubBranch;
|
|
@@ -17084,12 +17251,12 @@ ${outcome.failures.join("\n")}
|
|
|
17084
17251
|
};
|
|
17085
17252
|
|
|
17086
17253
|
// src/setup/config.ts
|
|
17087
|
-
import { join as
|
|
17254
|
+
import { join as join14 } from "path";
|
|
17088
17255
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
17089
17256
|
var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
|
|
17090
17257
|
async function loadForwardPorts(workspaceDir) {
|
|
17091
17258
|
try {
|
|
17092
|
-
const raw = await readWorkspaceFile(
|
|
17259
|
+
const raw = await readWorkspaceFile(join14(workspaceDir, DEVCONTAINER_PATH));
|
|
17093
17260
|
const parsed = JSON.parse(raw);
|
|
17094
17261
|
const ports = (parsed.forwardPorts ?? []).filter(
|
|
17095
17262
|
(p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
|
package/dist/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
sampleKeyUsage,
|
|
36
36
|
statWorkspacePath,
|
|
37
37
|
workspacePathExists
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-7MMECTTJ.js";
|
|
39
39
|
import {
|
|
40
40
|
reportBootMilestone
|
|
41
41
|
} from "./chunk-QU53HND5.js";
|
|
@@ -1715,7 +1715,7 @@ function hostsSpawnedChildren(mode) {
|
|
|
1715
1715
|
|
|
1716
1716
|
// src/cli.ts
|
|
1717
1717
|
if (process.argv[2] === "boot") {
|
|
1718
|
-
const { runBoot } = await import("./boot-
|
|
1718
|
+
const { runBoot } = await import("./boot-PKHZUDAC.js");
|
|
1719
1719
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1720
1720
|
}
|
|
1721
1721
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -2100,7 +2100,10 @@ void checkSessionTaskIdentity({
|
|
|
2100
2100
|
sessionId: process.env.CONVEYOR_SESSION_ID,
|
|
2101
2101
|
taskId: CONVEYOR_TASK_ID,
|
|
2102
2102
|
fetchSessionTaskId: async (sessionId) => {
|
|
2103
|
-
const ctx = await runner.connection.call("getTaskContext", {
|
|
2103
|
+
const ctx = await runner.connection.call("getTaskContext", {
|
|
2104
|
+
sessionId,
|
|
2105
|
+
peekPlanRevision: true
|
|
2106
|
+
});
|
|
2104
2107
|
return ctx.id;
|
|
2105
2108
|
},
|
|
2106
2109
|
logger: logger5
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "10.13.
|
|
3
|
+
"version": "10.13.71",
|
|
4
4
|
"description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|