xab 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +132 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -433,11 +433,65 @@ function parseJson(raw, fallback) {
|
|
|
433
433
|
}
|
|
434
434
|
return fallback;
|
|
435
435
|
}
|
|
436
|
+
async function runStreamedWithProgress(thread, prompt, onProgress, turnOpts) {
|
|
437
|
+
if (!onProgress) {
|
|
438
|
+
const turn = await thread.run(prompt, turnOpts);
|
|
439
|
+
return turn.finalResponse;
|
|
440
|
+
}
|
|
441
|
+
const { events } = await thread.runStreamed(prompt, turnOpts);
|
|
442
|
+
let finalResponse = "";
|
|
443
|
+
for await (const event of events) {
|
|
444
|
+
if (event.type === "item.started" || event.type === "item.updated" || event.type === "item.completed") {
|
|
445
|
+
const item = event.item;
|
|
446
|
+
switch (item.type) {
|
|
447
|
+
case "command_execution": {
|
|
448
|
+
const cmd = item.command ?? "";
|
|
449
|
+
const status = item.status;
|
|
450
|
+
if (status === "in_progress") {
|
|
451
|
+
onProgress("exec", `$ ${cmd}`);
|
|
452
|
+
} else if (status === "completed") {
|
|
453
|
+
const output = item.aggregated_output ?? "";
|
|
454
|
+
if (output) {
|
|
455
|
+
const lines = output.split(`
|
|
456
|
+
`).filter(Boolean);
|
|
457
|
+
for (const line of lines.slice(-3)) {
|
|
458
|
+
onProgress("exec", ` ${line.slice(0, 120)}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case "file_change": {
|
|
465
|
+
const changes = item.changes ?? [];
|
|
466
|
+
for (const c of changes) {
|
|
467
|
+
onProgress("file", `${c.kind} ${c.path}`);
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "reasoning": {
|
|
472
|
+
const text = item.text ?? "";
|
|
473
|
+
if (text && event.type === "item.completed") {
|
|
474
|
+
onProgress("think", text.split(`
|
|
475
|
+
`)[0].slice(0, 120));
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "agent_message": {
|
|
480
|
+
if (event.type === "item.completed") {
|
|
481
|
+
finalResponse = item.text ?? "";
|
|
482
|
+
}
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return finalResponse;
|
|
489
|
+
}
|
|
436
490
|
async function analyzeCommit(opts) {
|
|
437
491
|
const codex = new Codex;
|
|
438
492
|
const thread = codex.startThread({
|
|
439
493
|
workingDirectory: opts.worktreePath,
|
|
440
|
-
sandboxMode: "
|
|
494
|
+
sandboxMode: "danger-full-access",
|
|
441
495
|
model: "gpt-5.4",
|
|
442
496
|
modelReasoningEffort: "high"
|
|
443
497
|
});
|
|
@@ -481,7 +535,7 @@ You are looking at a worktree based on the TARGET branch "${opts.targetBranch}".
|
|
|
481
535
|
- Config files that need manual updates on servers? \u2192 note which
|
|
482
536
|
- Dependencies on external services being added or removed? \u2192 note what
|
|
483
537
|
- If the commit is just normal code changes that only need a deploy+restart, leave opsNotes as []`;
|
|
484
|
-
let
|
|
538
|
+
let response;
|
|
485
539
|
if (diffChunks.length > 1) {
|
|
486
540
|
await thread.run(firstPrompt);
|
|
487
541
|
for (let i = 1;i < diffChunks.length - 1; i++) {
|
|
@@ -493,17 +547,17 @@ ${diffChunks[i]}
|
|
|
493
547
|
Continue reading. More parts coming.`);
|
|
494
548
|
}
|
|
495
549
|
const lastIdx = diffChunks.length - 1;
|
|
496
|
-
|
|
550
|
+
response = await runStreamedWithProgress(thread, `### Diff (part ${lastIdx + 1}/${diffChunks.length} \u2014 final):
|
|
497
551
|
\`\`\`diff
|
|
498
552
|
${diffChunks[lastIdx]}
|
|
499
553
|
\`\`\`
|
|
500
554
|
|
|
501
|
-
You now have the complete diff. Analyze and produce your structured response.`, { outputSchema: analysisSchema });
|
|
555
|
+
You now have the complete diff. Analyze and produce your structured response.`, opts.onProgress, { outputSchema: analysisSchema });
|
|
502
556
|
} else {
|
|
503
|
-
|
|
557
|
+
response = await runStreamedWithProgress(thread, firstPrompt, opts.onProgress, { outputSchema: analysisSchema });
|
|
504
558
|
}
|
|
505
|
-
return parseJson(
|
|
506
|
-
summary:
|
|
559
|
+
return parseJson(response, {
|
|
560
|
+
summary: response.slice(0, 500),
|
|
507
561
|
alreadyInTarget: "no",
|
|
508
562
|
reasoning: "Could not parse structured output",
|
|
509
563
|
applicationStrategy: "Manual review recommended",
|
|
@@ -515,7 +569,7 @@ async function applyCommit(opts) {
|
|
|
515
569
|
const codex = new Codex;
|
|
516
570
|
const thread = codex.startThread({
|
|
517
571
|
workingDirectory: opts.worktreePath,
|
|
518
|
-
sandboxMode: "
|
|
572
|
+
sandboxMode: "danger-full-access",
|
|
519
573
|
model: "gpt-5.4",
|
|
520
574
|
modelReasoningEffort: "high"
|
|
521
575
|
});
|
|
@@ -560,7 +614,7 @@ ${diffChunks[0]}
|
|
|
560
614
|
\`\`\`
|
|
561
615
|
|
|
562
616
|
${diffChunks.length > 1 ? "I will send the remaining diff parts next. Read them all before applying." : instructions}`;
|
|
563
|
-
let
|
|
617
|
+
let response;
|
|
564
618
|
if (diffChunks.length > 1) {
|
|
565
619
|
await thread.run(firstPrompt);
|
|
566
620
|
for (let i = 1;i < diffChunks.length - 1; i++) {
|
|
@@ -572,22 +626,22 @@ ${diffChunks[i]}
|
|
|
572
626
|
Continue reading. More parts coming.`);
|
|
573
627
|
}
|
|
574
628
|
const lastIdx = diffChunks.length - 1;
|
|
575
|
-
|
|
629
|
+
response = await runStreamedWithProgress(thread, `### Diff (part ${lastIdx + 1}/${diffChunks.length} \u2014 final):
|
|
576
630
|
\`\`\`diff
|
|
577
631
|
${diffChunks[lastIdx]}
|
|
578
632
|
\`\`\`
|
|
579
633
|
|
|
580
634
|
You now have the complete diff.
|
|
581
635
|
|
|
582
|
-
${instructions}`, { outputSchema: applyResultSchema });
|
|
636
|
+
${instructions}`, opts.onProgress, { outputSchema: applyResultSchema });
|
|
583
637
|
} else {
|
|
584
|
-
|
|
638
|
+
response = await runStreamedWithProgress(thread, firstPrompt, opts.onProgress, { outputSchema: applyResultSchema });
|
|
585
639
|
}
|
|
586
|
-
return parseJson(
|
|
640
|
+
return parseJson(response, {
|
|
587
641
|
applied: false,
|
|
588
642
|
filesChanged: [],
|
|
589
643
|
commitMessage: commitMsg,
|
|
590
|
-
notes:
|
|
644
|
+
notes: response.slice(0, 1000),
|
|
591
645
|
adaptations: ""
|
|
592
646
|
});
|
|
593
647
|
}
|
|
@@ -595,7 +649,7 @@ async function fixFromReview(opts) {
|
|
|
595
649
|
const codex = new Codex;
|
|
596
650
|
const thread = codex.startThread({
|
|
597
651
|
workingDirectory: opts.worktreePath,
|
|
598
|
-
sandboxMode: "
|
|
652
|
+
sandboxMode: "danger-full-access",
|
|
599
653
|
model: "gpt-5.4",
|
|
600
654
|
modelReasoningEffort: "high"
|
|
601
655
|
});
|
|
@@ -714,7 +768,7 @@ function writeReviewPacket(audit, packet, attempt) {
|
|
|
714
768
|
audit.writeAppliedPatch(commitHash, attempt, packet.appliedDiff);
|
|
715
769
|
}
|
|
716
770
|
}
|
|
717
|
-
async function reviewAppliedDiff(worktreePath, packet) {
|
|
771
|
+
async function reviewAppliedDiff(worktreePath, packet, onProgress) {
|
|
718
772
|
const strictnessInstructions = {
|
|
719
773
|
strict: "Be very strict. Any questionable change should be rejected. Err on the side of caution.",
|
|
720
774
|
normal: "Be thorough but reasonable. Reject clear issues, accept minor style differences.",
|
|
@@ -800,14 +854,20 @@ You can:
|
|
|
800
854
|
- Run tests, linters, type-checkers, and build commands via Bash to verify correctness
|
|
801
855
|
- Run any read-only shell command (cat, ls, git diff, git log, etc.)
|
|
802
856
|
|
|
803
|
-
You MUST NOT modify the worktree
|
|
857
|
+
You MUST NOT modify the worktree. Specifically:
|
|
858
|
+
- NO file writes, edits, or creates
|
|
859
|
+
- NO git commit, git add, git reset, or any git mutation
|
|
860
|
+
- NO npm install, bun install, yarn install, pnpm install, or any package manager install
|
|
861
|
+
- NO rm, mv, cp, or any file mutation commands
|
|
862
|
+
- NO pip install, cargo build, go get, or anything that writes to disk
|
|
804
863
|
|
|
805
864
|
Testing guidelines:
|
|
865
|
+
- Only run tests that work without installing dependencies (assume deps are already installed if node_modules exists)
|
|
806
866
|
- Only run tests that work without API keys, secrets, or external service connections
|
|
807
867
|
- Before running a test, check if it needs env vars by reading the test file or relevant .env.example
|
|
808
|
-
- If a test needs keys, only run it if you can see a .env file with those vars already populated
|
|
809
868
|
- Prefer: type-checks (tsc --noEmit), linters (eslint), unit tests, build checks (forge build, go build)
|
|
810
869
|
- Avoid: integration tests hitting external APIs, tests requiring running databases/services
|
|
870
|
+
- Do NOT run bun install, npm install, or equivalent \u2014 deps are already there if they exist
|
|
811
871
|
- If you can't determine whether a test needs keys, skip it \u2014 don't run and fail
|
|
812
872
|
|
|
813
873
|
Your objections will be sent back to the apply agent for fixing, so be specific and actionable.`
|
|
@@ -815,6 +875,34 @@ Your objections will be sent back to the apply agent for fixing, so be specific
|
|
|
815
875
|
});
|
|
816
876
|
let resultText = "";
|
|
817
877
|
for await (const message of q) {
|
|
878
|
+
if (onProgress && message.type === "assistant") {
|
|
879
|
+
const betaMsg = message.message;
|
|
880
|
+
const content = betaMsg?.content;
|
|
881
|
+
if (content) {
|
|
882
|
+
for (const block of content) {
|
|
883
|
+
if (block.type === "tool_use") {
|
|
884
|
+
const name = block.name;
|
|
885
|
+
const input = block.input;
|
|
886
|
+
if (name === "Bash") {
|
|
887
|
+
onProgress("review", `$ ${(input.command ?? "").slice(0, 120)}`);
|
|
888
|
+
} else if (name === "Read") {
|
|
889
|
+
onProgress("review", `read ${(input.file_path ?? "").replace(worktreePath + "/", "")}`);
|
|
890
|
+
} else if (name === "Grep") {
|
|
891
|
+
onProgress("review", `grep "${(input.pattern ?? "").slice(0, 60)}"`);
|
|
892
|
+
} else if (name === "Glob") {
|
|
893
|
+
onProgress("review", `glob ${(input.pattern ?? "").slice(0, 60)}`);
|
|
894
|
+
} else {
|
|
895
|
+
onProgress("review", `${name}`);
|
|
896
|
+
}
|
|
897
|
+
} else if (block.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
|
898
|
+
const firstLine = block.text.split(`
|
|
899
|
+
`)[0].slice(0, 120);
|
|
900
|
+
if (firstLine)
|
|
901
|
+
onProgress("review", firstLine);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
818
906
|
if (message.type === "result") {
|
|
819
907
|
if ("result" in message) {
|
|
820
908
|
resultText = message.result;
|
|
@@ -848,17 +936,22 @@ async function verifyReviewIntegrity(wtGit, expectedHead) {
|
|
|
848
936
|
return `Review mutated HEAD: expected ${expectedHead.slice(0, 8)}, got ${currentHead.slice(0, 8)}`;
|
|
849
937
|
}
|
|
850
938
|
const status = await wtGit.status();
|
|
851
|
-
const
|
|
939
|
+
const isInfra = (f) => f.startsWith(".backmerge/") || f.startsWith(".git-local/") || f.startsWith("node_modules/") || f.startsWith(".cache/") || f.startsWith("dist/") || f.startsWith("build/") || f.startsWith("target/");
|
|
940
|
+
const modified = status.modified.filter((f) => !isInfra(f));
|
|
941
|
+
const created = status.created.filter((f) => !isInfra(f));
|
|
942
|
+
const deleted = status.deleted.filter((f) => !isInfra(f));
|
|
943
|
+
const notAdded = status.not_added.filter((f) => !isInfra(f));
|
|
944
|
+
const dirty = modified.length + created.length + deleted.length + notAdded.length + status.conflicted.length;
|
|
852
945
|
if (dirty > 0) {
|
|
853
946
|
const parts = [];
|
|
854
|
-
if (
|
|
855
|
-
parts.push(`${
|
|
856
|
-
if (
|
|
857
|
-
parts.push(`${
|
|
858
|
-
if (
|
|
859
|
-
parts.push(`${
|
|
860
|
-
if (status.
|
|
861
|
-
parts.push(`${status.
|
|
947
|
+
if (modified.length)
|
|
948
|
+
parts.push(`${modified.length} modified`);
|
|
949
|
+
if (notAdded.length)
|
|
950
|
+
parts.push(`${notAdded.length} untracked`);
|
|
951
|
+
if (deleted.length)
|
|
952
|
+
parts.push(`${deleted.length} deleted`);
|
|
953
|
+
if (status.conflicted.length)
|
|
954
|
+
parts.push(`${status.conflicted.length} conflicted`);
|
|
862
955
|
return `Review left dirty worktree: ${parts.join(", ")}`;
|
|
863
956
|
}
|
|
864
957
|
return null;
|
|
@@ -1501,7 +1594,8 @@ async function processOneCommit(o) {
|
|
|
1501
1594
|
sourceBranch: o.sourceRef,
|
|
1502
1595
|
targetBranch: o.targetRef,
|
|
1503
1596
|
sourceLatestDiff: o.sourceLatestDiff,
|
|
1504
|
-
repoContext: commitCtx.promptBlock
|
|
1597
|
+
repoContext: commitCtx.promptBlock,
|
|
1598
|
+
onProgress: o.cb.onProgress ? (phase, msg) => o.cb.onProgress("analyze", `[${phase}] ${msg}`) : undefined
|
|
1505
1599
|
});
|
|
1506
1600
|
audit.writeAnalysis(commit.hash, 1, analysis);
|
|
1507
1601
|
cb.onAnalysis(commit, analysis);
|
|
@@ -1564,7 +1658,8 @@ async function processOneCommit(o) {
|
|
|
1564
1658
|
sourceBranch: o.sourceRef,
|
|
1565
1659
|
targetBranch: o.targetRef,
|
|
1566
1660
|
repoContext: commitCtx.promptBlock,
|
|
1567
|
-
commitPrefix: o.commitPrefix
|
|
1661
|
+
commitPrefix: o.commitPrefix,
|
|
1662
|
+
onProgress: o.cb.onProgress ? (phase, msg) => o.cb.onProgress("apply", `[${phase}] ${msg}`) : undefined
|
|
1568
1663
|
});
|
|
1569
1664
|
audit.writeAnalysis(commit.hash, attempt, { ...analysis, applyResult });
|
|
1570
1665
|
} catch (e) {
|
|
@@ -1630,7 +1725,7 @@ async function processOneCommit(o) {
|
|
|
1630
1725
|
};
|
|
1631
1726
|
writeReviewPacket(audit, packet, attempt);
|
|
1632
1727
|
const headBeforeReview = await getHead(o.wtGit);
|
|
1633
|
-
reviewResult = await reviewAppliedDiff(o.wtPath, packet);
|
|
1728
|
+
reviewResult = await reviewAppliedDiff(o.wtPath, packet, o.cb.onProgress);
|
|
1634
1729
|
audit.writeReviewResult(commit.hash, attempt, reviewResult);
|
|
1635
1730
|
const mutation = await verifyReviewIntegrity(o.wtGit, headBeforeReview);
|
|
1636
1731
|
if (mutation) {
|
|
@@ -1682,7 +1777,7 @@ async function processOneCommit(o) {
|
|
|
1682
1777
|
appliedDiffStat: fixedStat
|
|
1683
1778
|
};
|
|
1684
1779
|
const headBeforeReReview = await getHead(o.wtGit);
|
|
1685
|
-
reviewResult = await reviewAppliedDiff(o.wtPath, fixPacket);
|
|
1780
|
+
reviewResult = await reviewAppliedDiff(o.wtPath, fixPacket, o.cb.onProgress);
|
|
1686
1781
|
audit.writeReviewResult(commit.hash, attempt, {
|
|
1687
1782
|
...reviewResult,
|
|
1688
1783
|
fixRound
|
|
@@ -1877,6 +1972,12 @@ async function runBatch(opts) {
|
|
|
1877
1972
|
log(` ${divider()}`);
|
|
1878
1973
|
log("");
|
|
1879
1974
|
const cb = {
|
|
1975
|
+
onProgress(phase, msg) {
|
|
1976
|
+
if (jsonl)
|
|
1977
|
+
emitJsonl({ event: "progress", phase, msg });
|
|
1978
|
+
const icon = phase === "apply" ? chalk.green("\u25B8") : chalk.blue("\u25B8");
|
|
1979
|
+
log(` ${ts()} ${icon} ${chalk.dim(msg)}`);
|
|
1980
|
+
},
|
|
1880
1981
|
onLog(msg, color) {
|
|
1881
1982
|
if (jsonl)
|
|
1882
1983
|
emitJsonl({ event: "log", msg, color });
|