diffsplain 0.10.0 → 0.11.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/README.md +1 -0
- package/dist/assets/{angular-html-2u-u5H-N.js → angular-html-CjYwnGyV.js} +1 -1
- package/dist/assets/{angular-ts-4_HVGlKp.js → angular-ts-DSPf2G4O.js} +1 -1
- package/dist/assets/{apl-icaNWZX3.js → apl-B9ytghJx.js} +1 -1
- package/dist/assets/{astro-HvIOE4vz.js → astro-BsGV0yBF.js} +1 -1
- package/dist/assets/{blade-BpKJV8ev.js → blade-Cc2eCYlt.js} +1 -1
- package/dist/assets/{c-BNvtxyRQ.js → c-CWKyCnUx.js} +1 -1
- package/dist/assets/{cobol-8js4hfxa.js → cobol-DkamOI1E.js} +1 -1
- package/dist/assets/{coffee-DXg_piQ9.js → coffee-DyaEXePe.js} +1 -1
- package/dist/assets/{cpp-BZDFAjyh.js → cpp-16-d2Eyo.js} +1 -1
- package/dist/assets/{crystal-aCF3ira4.js → crystal-CavPPRhQ.js} +1 -1
- package/dist/assets/{css-DfRtUkhE.js → css-BME_wHJq.js} +1 -1
- package/dist/assets/{edge-vS6_AmQv.js → edge-DeVi2hvK.js} +1 -1
- package/dist/assets/{elixir-Ce0nUHSq.js → elixir-CCWgH_vc.js} +1 -1
- package/dist/assets/{elm-DmaeyizZ.js → elm-CZ570J7x.js} +1 -1
- package/dist/assets/{erb-DD6OUyvt.js → erb-CDPT5bea.js} +1 -1
- package/dist/assets/{git-rebase-HbJMn7iS.js → git-rebase-Dq5Z37DJ.js} +1 -1
- package/dist/assets/{glimmer-js-BRuqbNaV.js → glimmer-js-BoPVZK8p.js} +1 -1
- package/dist/assets/{glimmer-ts-ZBpkdEEA.js → glimmer-ts-DazEWfg1.js} +1 -1
- package/dist/assets/{glsl-CrQ4GScf.js → glsl-DCUjYCZY.js} +1 -1
- package/dist/assets/{graphql-Tn6PBdQY.js → graphql-DkCeSxy7.js} +1 -1
- package/dist/assets/{hack-CoApvrbo.js → hack-tiY3l-W-.js} +1 -1
- package/dist/assets/{haml-COKq8Xjp.js → haml-DyQZrJg_.js} +1 -1
- package/dist/assets/{handlebars-CAna2DTA.js → handlebars-Bvu5Rr0t.js} +1 -1
- package/dist/assets/{html-Ch4xJ7YF.js → html-C60VFgKW.js} +1 -1
- package/dist/assets/{html-derivative-0ITK-MAI.js → html-derivative-DB69gpnL.js} +1 -1
- package/dist/assets/{http-DiMWQ8Qr.js → http-DmS8hddd.js} +1 -1
- package/dist/assets/{hurl-OcXymC0J.js → hurl-Cdp6lmUx.js} +1 -1
- package/dist/assets/{index-BmcCug2M.js → index-CUiLG0r9.js} +4 -4
- package/dist/assets/{index-CADUNiTy.css → index-YnLIxcTm.css} +1 -1
- package/dist/assets/{java-CFb3qN4r.js → java-CkOnwoVQ.js} +1 -1
- package/dist/assets/{javascript-CRmk2JrF.js → javascript-D2tF_Ylj.js} +1 -1
- package/dist/assets/{jinja-Da6lDTEy.js → jinja-CdWpLcff.js} +1 -1
- package/dist/assets/{jison-Bnivzmtu.js → jison-BII58uuR.js} +1 -1
- package/dist/assets/{json-m8vJP_KK.js → json-BKlPRLrU.js} +1 -1
- package/dist/assets/{jsx-C3HX_Sbu.js → jsx-Ba5UouxN.js} +1 -1
- package/dist/assets/{julia-Ckg-wSHA.js → julia-CwdQF8Ff.js} +1 -1
- package/dist/assets/{just-DCjIAyvO.js → just-Be3sr1-h.js} +1 -1
- package/dist/assets/{latex-CrYSm4pl.js → latex-CDF6t93N.js} +1 -1
- package/dist/assets/{liquid-MQUFSPYU.js → liquid-BE3LVHkw.js} +1 -1
- package/dist/assets/{lua-DcuzJJ77.js → lua-D_W8Ux9S.js} +1 -1
- package/dist/assets/{marko-4JNmQaB4.js → marko-BeUfMD-U.js} +1 -1
- package/dist/assets/{mdc-C_j8HvRL.js → mdc-C8tRJlVU.js} +1 -1
- package/dist/assets/{nginx-ta3Gn1HE.js → nginx-CLPimMM6.js} +1 -1
- package/dist/assets/{nim-BwWLst9M.js → nim-ikQhXKQf.js} +1 -1
- package/dist/assets/{perl-DfW7Uq5O.js → perl-C27cToFa.js} +1 -1
- package/dist/assets/{php-DGf5G3Cf.js → php-Btdg-RP3.js} +1 -1
- package/dist/assets/{pug-CWI-NaYz.js → pug-Bk73ERXL.js} +1 -1
- package/dist/assets/{qml-Csblgh7U.js → qml-DhY7FDIl.js} +1 -1
- package/dist/assets/{r-QGooaGDI.js → r-CEwua7nj.js} +1 -1
- package/dist/assets/{razor-Dp9ihLYf.js → razor-sPO7Sh9d.js} +1 -1
- package/dist/assets/{regexp-DwUnLcVr.js → regexp-D9DLFGXE.js} +1 -1
- package/dist/assets/{rst-BY8GIwDJ.js → rst-YhauCnId.js} +1 -1
- package/dist/assets/{ruby-VgzI9htT.js → ruby-DpMkRaoz.js} +1 -1
- package/dist/assets/{sas-DYLsm-0j.js → sas-0KdmKGRO.js} +1 -1
- package/dist/assets/{scss-tIFkeUQX.js → scss-CohvHWvq.js} +1 -1
- package/dist/assets/{shellscript-CHjuQ4Pl.js → shellscript-C93Q8fLt.js} +1 -1
- package/dist/assets/{shellsession-Bv0S0hj9.js → shellsession-YCh2AQXu.js} +1 -1
- package/dist/assets/{soy-D6U1tZod.js → soy-CWVYdXUH.js} +1 -1
- package/dist/assets/{sql-CGz8kjKv.js → sql-CJNtax5I.js} +1 -1
- package/dist/assets/{stata-C9ytERUq.js → stata-C7v41XAM.js} +1 -1
- package/dist/assets/{surrealql-DRfRnaYu.js → surrealql-CRUSOxDf.js} +1 -1
- package/dist/assets/{svelte-wYnk_1sI.js → svelte-Z0harRr1.js} +1 -1
- package/dist/assets/{templ-DWrGVE0u.js → templ-BplDouJX.js} +1 -1
- package/dist/assets/{tex-CYTzoUJh.js → tex-MsB6StN5.js} +1 -1
- package/dist/assets/{ts-tags-1TIXEGea.js → ts-tags-BWx8aq5t.js} +1 -1
- package/dist/assets/{tsx-a3BoAoOh.js → tsx-DBT1gmNl.js} +1 -1
- package/dist/assets/{twig-e3dvNv6o.js → twig-jz67rR3C.js} +1 -1
- package/dist/assets/{typescript-BBhnpnTf.js → typescript-CzwGN36B.js} +1 -1
- package/dist/assets/{vue-html-quBNv38K.js → vue-html-Dhrmu7sO.js} +1 -1
- package/dist/assets/{vue-vine-CDuR69FE.js → vue-vine-BfQGXZs6.js} +1 -1
- package/dist/assets/{vue-DzzVbu9q.js → vue-xsrSiVMC.js} +1 -1
- package/dist/assets/{xml-DMifOLSe.js → xml-W7cP4qeN.js} +1 -1
- package/dist/assets/{xsl-D8Ef-5HK.js → xsl-BlXf8qm_.js} +1 -1
- package/dist/assets/{yaml-5c38gpmA.js → yaml-BtNwxUT9.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +3 -2
- package/scripts/agent-usage.mjs +141 -0
- package/scripts/build-diff-data.mjs +32 -12
- package/scripts/cli-args.mjs +7 -0
- package/scripts/coding-agents.mjs +208 -7
- package/scripts/generate-summaries.mjs +61 -9
- package/scripts/present.mjs +22 -7
- package/scripts/review-chat-context.mjs +6 -4
- package/scripts/review-chat-controller.mjs +5 -3
- package/scripts/review-chat-provider.mjs +26 -4
- package/scripts/serve-built.mjs +100 -4
|
@@ -11,11 +11,26 @@ import {
|
|
|
11
11
|
import { readConfiguredAgent } from './agent-config.mjs';
|
|
12
12
|
|
|
13
13
|
export const codingAgentCapabilities = {
|
|
14
|
-
codex: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
codex: {
|
|
15
|
+
binary: 'codex',
|
|
16
|
+
model: true,
|
|
17
|
+
reasoning: true,
|
|
18
|
+
fast: { label: 'Codex CLI', minimumVersion: '0.108.0' },
|
|
19
|
+
},
|
|
20
|
+
claude: {
|
|
21
|
+
binary: 'claude',
|
|
22
|
+
model: true,
|
|
23
|
+
reasoning: false,
|
|
24
|
+
fast: { label: 'Claude Code', minimumVersion: '2.1.36' },
|
|
25
|
+
},
|
|
26
|
+
copilot: { binary: 'copilot', model: true, reasoning: false, fast: false },
|
|
27
|
+
cursor: {
|
|
28
|
+
binary: 'cursor-agent',
|
|
29
|
+
model: true,
|
|
30
|
+
reasoning: false,
|
|
31
|
+
fast: false,
|
|
32
|
+
},
|
|
33
|
+
opencode: { binary: 'opencode', model: true, reasoning: true, fast: false },
|
|
19
34
|
};
|
|
20
35
|
|
|
21
36
|
export const codingAgents = Object.keys(codingAgentCapabilities);
|
|
@@ -24,6 +39,10 @@ export function agentSupportsReasoning(agent) {
|
|
|
24
39
|
return codingAgentCapabilities[agent]?.reasoning === true;
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
export function agentSupportsFast(agent) {
|
|
43
|
+
return Boolean(codingAgentCapabilities[agent]?.fast);
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
export function assertReasoningSupported(agent, reasoning) {
|
|
28
47
|
if (reasoning && !agentSupportsReasoning(agent)) {
|
|
29
48
|
throw new Error(
|
|
@@ -89,6 +108,67 @@ function versionAtLeast(current, minimum) {
|
|
|
89
108
|
return true;
|
|
90
109
|
}
|
|
91
110
|
|
|
111
|
+
function semanticVersion(value) {
|
|
112
|
+
const match = value?.match(/(?:^|\D)(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?/);
|
|
113
|
+
if (!match) return undefined;
|
|
114
|
+
return {
|
|
115
|
+
parts: match.slice(1, 4).map(Number),
|
|
116
|
+
prerelease: Boolean(match[4]),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function inspectFastCompatibility(
|
|
121
|
+
agent,
|
|
122
|
+
command,
|
|
123
|
+
{
|
|
124
|
+
env = process.env,
|
|
125
|
+
timeout = 5_000,
|
|
126
|
+
run = spawnSync,
|
|
127
|
+
} = {},
|
|
128
|
+
) {
|
|
129
|
+
const capability = codingAgentCapabilities[agent]?.fast;
|
|
130
|
+
if (!capability) {
|
|
131
|
+
return {
|
|
132
|
+
compatible: false,
|
|
133
|
+
reason: `--fast is supported only by codex and claude; ${agent} does not support it.`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const result = run(command, ['--version'], {
|
|
137
|
+
encoding: 'utf8',
|
|
138
|
+
env,
|
|
139
|
+
timeout,
|
|
140
|
+
windowsHide: true,
|
|
141
|
+
});
|
|
142
|
+
const version = firstLine(`${result.stdout || ''}\n${result.stderr || ''}`);
|
|
143
|
+
const current = semanticVersion(version);
|
|
144
|
+
const minimum = semanticVersion(capability.minimumVersion);
|
|
145
|
+
const compatible = current && minimum &&
|
|
146
|
+
(versionAtLeast(current.parts, minimum.parts) &&
|
|
147
|
+
!(current.prerelease &&
|
|
148
|
+
current.parts.every((part, index) => part === minimum.parts[index])));
|
|
149
|
+
if (result.error || result.status !== 0 || !version || !current) {
|
|
150
|
+
return {
|
|
151
|
+
compatible: false,
|
|
152
|
+
version,
|
|
153
|
+
reason: `${capability.label} ${capability.minimumVersion} or newer is required for --fast, but its version could not be checked. Upgrade ${capability.label}.`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (!compatible) {
|
|
157
|
+
return {
|
|
158
|
+
compatible: false,
|
|
159
|
+
version,
|
|
160
|
+
reason: `${capability.label} ${capability.minimumVersion} or newer is required for --fast; found ${version}. Upgrade ${capability.label}.`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return { compatible: true, version };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function assertFastCompatible(agent, command, fast, options) {
|
|
167
|
+
if (!fast) return;
|
|
168
|
+
const inspection = inspectFastCompatibility(agent, command, options);
|
|
169
|
+
if (!inspection.compatible) throw new Error(inspection.reason);
|
|
170
|
+
}
|
|
171
|
+
|
|
92
172
|
function cursorCompatibilityError(detail) {
|
|
93
173
|
return `Cursor Agent is incompatible: ${detail} ${cursorRequirementSummary} Upgrade Cursor Agent.`;
|
|
94
174
|
}
|
|
@@ -463,6 +543,27 @@ function parseClaudeResponse(stdout) {
|
|
|
463
543
|
return envelope;
|
|
464
544
|
}
|
|
465
545
|
|
|
546
|
+
function jsonLines(stdout) {
|
|
547
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
548
|
+
if (!lines.length) return undefined;
|
|
549
|
+
const events = lines.map(parseEvent);
|
|
550
|
+
return events.every(Boolean) ? events : undefined;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function parseCodexResponse(stdout) {
|
|
554
|
+
const events = jsonLines(stdout);
|
|
555
|
+
const message = events && [...events]
|
|
556
|
+
.reverse()
|
|
557
|
+
.find((event) =>
|
|
558
|
+
event.type === 'item.completed' &&
|
|
559
|
+
event.item?.type === 'agent_message' &&
|
|
560
|
+
typeof event.item.text === 'string',
|
|
561
|
+
);
|
|
562
|
+
return message
|
|
563
|
+
? parseJsonText(message.item.text, 'Codex')
|
|
564
|
+
: parseJsonText(stdout, 'Codex');
|
|
565
|
+
}
|
|
566
|
+
|
|
466
567
|
function parseEvent(line) {
|
|
467
568
|
try {
|
|
468
569
|
return JSON.parse(line);
|
|
@@ -521,8 +622,94 @@ export function parseAgentResponse(agent, stdout) {
|
|
|
521
622
|
if (agent === 'claude') return parseClaudeResponse(stdout);
|
|
522
623
|
if (agent === 'opencode') return parseOpenCodeResponse(stdout);
|
|
523
624
|
if (agent === 'cursor') return parseCursorResponse(stdout);
|
|
524
|
-
|
|
525
|
-
return parseJsonText(stdout,
|
|
625
|
+
if (agent === 'codex') return parseCodexResponse(stdout);
|
|
626
|
+
return parseJsonText(stdout, 'Copilot');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function addUsageField(totals, [field, count]) {
|
|
630
|
+
if (!Number.isSafeInteger(count) || count < 0) return;
|
|
631
|
+
totals[field] = (totals[field] || 0) + count;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function addProviderUsage(totals, usage) {
|
|
635
|
+
Object.entries(usage).forEach((entry) => addUsageField(totals, entry));
|
|
636
|
+
return totals;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function sumProviderUsage(usages) {
|
|
640
|
+
const totals = usages.reduce(addProviderUsage, {});
|
|
641
|
+
return Object.keys(totals).length ? totals : undefined;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function providerUsage(inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens) {
|
|
645
|
+
return { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function codexEventUsage(event) {
|
|
649
|
+
if (event.type !== 'turn.completed' || !event.usage) return undefined;
|
|
650
|
+
const usage = event.usage;
|
|
651
|
+
return providerUsage(
|
|
652
|
+
usage.input_tokens,
|
|
653
|
+
usage.output_tokens,
|
|
654
|
+
usage.cached_input_tokens,
|
|
655
|
+
usage.cache_write_input_tokens,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function eventPartTokens(event, type) {
|
|
660
|
+
if (event.type !== type) return undefined;
|
|
661
|
+
return event.part?.tokens;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function openCodeEventUsage(event) {
|
|
665
|
+
const tokens = eventPartTokens(event, 'step_finish');
|
|
666
|
+
if (!tokens) return undefined;
|
|
667
|
+
const cache = tokens.cache || {};
|
|
668
|
+
return providerUsage(
|
|
669
|
+
tokens.input,
|
|
670
|
+
tokens.output,
|
|
671
|
+
cache.read,
|
|
672
|
+
cache.write,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function eventUsage(stdout, extractUsage) {
|
|
677
|
+
const events = jsonLines(stdout) || [];
|
|
678
|
+
return sumProviderUsage(events.map(extractUsage).filter(Boolean));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function codexUsage(stdout) {
|
|
682
|
+
return eventUsage(stdout, codexEventUsage);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function safeProviderEnvelope(stdout, label) {
|
|
686
|
+
try {
|
|
687
|
+
return parseJsonText(stdout, label);
|
|
688
|
+
} catch {
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function claudeUsage(stdout) {
|
|
694
|
+
const usage = safeProviderEnvelope(stdout, 'Claude')?.usage;
|
|
695
|
+
if (!usage || typeof usage !== 'object') return undefined;
|
|
696
|
+
return sumProviderUsage([providerUsage(
|
|
697
|
+
usage.input_tokens,
|
|
698
|
+
usage.output_tokens,
|
|
699
|
+
usage.cache_read_input_tokens,
|
|
700
|
+
usage.cache_creation_input_tokens,
|
|
701
|
+
)]);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function openCodeUsage(stdout) {
|
|
705
|
+
return eventUsage(stdout, openCodeEventUsage);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export function parseAgentUsage(agent, stdout) {
|
|
709
|
+
if (agent === 'codex') return codexUsage(stdout);
|
|
710
|
+
if (agent === 'claude') return claudeUsage(stdout);
|
|
711
|
+
if (agent === 'opencode') return openCodeUsage(stdout);
|
|
712
|
+
return undefined;
|
|
526
713
|
}
|
|
527
714
|
|
|
528
715
|
function codexIsolationArgs(snapshotOnly) {
|
|
@@ -554,6 +741,7 @@ function codexRepoArgs(snapshotOnly) {
|
|
|
554
741
|
|
|
555
742
|
function codexCommand({
|
|
556
743
|
binary,
|
|
744
|
+
fast,
|
|
557
745
|
model,
|
|
558
746
|
reasoning,
|
|
559
747
|
prompt,
|
|
@@ -565,6 +753,7 @@ function codexCommand({
|
|
|
565
753
|
const args = [
|
|
566
754
|
'exec',
|
|
567
755
|
'--ephemeral',
|
|
756
|
+
'--json',
|
|
568
757
|
'--sandbox',
|
|
569
758
|
'read-only',
|
|
570
759
|
...codexIsolationArgs(snapshotOnly),
|
|
@@ -577,6 +766,14 @@ function codexCommand({
|
|
|
577
766
|
schemaPath,
|
|
578
767
|
...codexConfigArgs(snapshotOnly),
|
|
579
768
|
];
|
|
769
|
+
if (fast) {
|
|
770
|
+
args.push(
|
|
771
|
+
'--config',
|
|
772
|
+
'service_tier="fast"',
|
|
773
|
+
'--config',
|
|
774
|
+
'features.fast_mode=true',
|
|
775
|
+
);
|
|
776
|
+
}
|
|
580
777
|
if (model) args.push('--model', model);
|
|
581
778
|
if (reasoning) {
|
|
582
779
|
args.push(
|
|
@@ -596,6 +793,7 @@ function codexCommand({
|
|
|
596
793
|
|
|
597
794
|
function claudeCommand({
|
|
598
795
|
binary,
|
|
796
|
+
fast,
|
|
599
797
|
model,
|
|
600
798
|
prompt,
|
|
601
799
|
schema,
|
|
@@ -612,6 +810,7 @@ function claudeCommand({
|
|
|
612
810
|
...(snapshotOnly ? ['--tools', ''] : ['--permission-mode', 'plan']),
|
|
613
811
|
'--no-session-persistence',
|
|
614
812
|
];
|
|
813
|
+
if (fast) args.push('--settings', JSON.stringify({ fastMode: true }));
|
|
615
814
|
if (model) args.push('--model', model);
|
|
616
815
|
args.push(prompt);
|
|
617
816
|
return {
|
|
@@ -764,6 +963,7 @@ export function agentCommand({
|
|
|
764
963
|
binary = agent,
|
|
765
964
|
model,
|
|
766
965
|
reasoning,
|
|
966
|
+
fast = false,
|
|
767
967
|
prompt,
|
|
768
968
|
schema,
|
|
769
969
|
schemaPath,
|
|
@@ -775,6 +975,7 @@ export function agentCommand({
|
|
|
775
975
|
const checkoutAccess = accessMode?.mode === 'checkout-read-only';
|
|
776
976
|
const options = {
|
|
777
977
|
binary,
|
|
978
|
+
fast,
|
|
778
979
|
inputPath,
|
|
779
980
|
model,
|
|
780
981
|
prompt,
|
|
@@ -15,13 +15,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
import {
|
|
16
16
|
agentCommand,
|
|
17
17
|
agentReadOnlyWarning,
|
|
18
|
+
assertFastCompatible,
|
|
18
19
|
assertReasoningSupported,
|
|
19
20
|
codingAgentFromSelectionError,
|
|
20
21
|
codingAgentAvailability,
|
|
21
22
|
codingAgentBinary,
|
|
22
23
|
parseAgentResponse,
|
|
24
|
+
parseAgentUsage,
|
|
23
25
|
selectCodingAgent,
|
|
24
26
|
} from './coding-agents.mjs';
|
|
27
|
+
import {
|
|
28
|
+
emptyUsageAccumulator,
|
|
29
|
+
formatReviewUsage,
|
|
30
|
+
recordUsage,
|
|
31
|
+
reviewUsage,
|
|
32
|
+
usageSummary,
|
|
33
|
+
} from './agent-usage.mjs';
|
|
25
34
|
import {
|
|
26
35
|
isBaseWorktreeTarget,
|
|
27
36
|
reviewAccessMode,
|
|
@@ -76,6 +85,7 @@ const valueFlags = new Set([
|
|
|
76
85
|
const booleanFlags = new Set([
|
|
77
86
|
'--checkout',
|
|
78
87
|
'--force',
|
|
88
|
+
'--fast',
|
|
79
89
|
'--support-record',
|
|
80
90
|
'--worktree',
|
|
81
91
|
'--no-checkout-access',
|
|
@@ -140,6 +150,7 @@ Options:
|
|
|
140
150
|
--codex-bin FILE Codex CLI path (default: codex)
|
|
141
151
|
--model NAME Model passed to the coding agent
|
|
142
152
|
--reasoning LEVEL Agent reasoning effort when supported
|
|
153
|
+
--fast Enable provider Fast mode for every agent call
|
|
143
154
|
--batch-size COUNT Maximum files per agent pass (default: 12)
|
|
144
155
|
--jobs COUNT Agent passes to run at once (default: 3)
|
|
145
156
|
--support-record Print a safe record if this run fails
|
|
@@ -178,6 +189,7 @@ let selectedAgent;
|
|
|
178
189
|
let agentBinary;
|
|
179
190
|
const model = option('--model');
|
|
180
191
|
const reasoning = option('--reasoning');
|
|
192
|
+
const fast = rawArgs.includes('--fast');
|
|
181
193
|
const batchSizeValue = option('--batch-size') || '12';
|
|
182
194
|
const reasoningLevels = new Set([
|
|
183
195
|
'minimal',
|
|
@@ -377,6 +389,18 @@ const summariesPath = summaryPath({
|
|
|
377
389
|
const ownershipPath = `${summariesPath}.lock`;
|
|
378
390
|
let ownership;
|
|
379
391
|
let ownershipHeartbeat;
|
|
392
|
+
let agentUsage = emptyUsageAccumulator();
|
|
393
|
+
|
|
394
|
+
function currentAgentUsage() {
|
|
395
|
+
return usageSummary(agentUsage);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function recordAgentUsage(usage) {
|
|
399
|
+
agentUsage = recordUsage(agentUsage, usage);
|
|
400
|
+
const emptyChatUsage = usageSummary(emptyUsageAccumulator());
|
|
401
|
+
console.log(formatReviewUsage(reviewUsage(currentAgentUsage(), emptyChatUsage)));
|
|
402
|
+
}
|
|
403
|
+
|
|
380
404
|
function acquireOwnership() {
|
|
381
405
|
ownership = acquireLease(ownershipPath);
|
|
382
406
|
ownershipHeartbeat = setInterval(() => {
|
|
@@ -1039,8 +1063,13 @@ function publishSnapshot(
|
|
|
1039
1063
|
...(state.errors.length ? { errors: state.errors } : {}),
|
|
1040
1064
|
...(model ? { model } : {}),
|
|
1041
1065
|
...(reasoning ? { reasoning } : {}),
|
|
1066
|
+
fast,
|
|
1042
1067
|
accessMode: accessMode.mode,
|
|
1043
1068
|
},
|
|
1069
|
+
usage: reviewUsage(
|
|
1070
|
+
currentAgentUsage(),
|
|
1071
|
+
usageSummary(emptyUsageAccumulator()),
|
|
1072
|
+
),
|
|
1044
1073
|
};
|
|
1045
1074
|
delete content.version;
|
|
1046
1075
|
delete content.generatedAt;
|
|
@@ -1065,6 +1094,7 @@ function summariesForSnapshot(summaries, snapshot) {
|
|
|
1065
1094
|
meta: {
|
|
1066
1095
|
...summaries.meta,
|
|
1067
1096
|
reviewFingerprint: snapshot.notes.reviewFingerprint,
|
|
1097
|
+
usage: currentAgentUsage(),
|
|
1068
1098
|
},
|
|
1069
1099
|
};
|
|
1070
1100
|
}
|
|
@@ -1208,8 +1238,9 @@ async function selectAgentForNotes() {
|
|
|
1208
1238
|
signal: selectionAbortController.signal,
|
|
1209
1239
|
},
|
|
1210
1240
|
);
|
|
1211
|
-
assertReasoningSupported(selectedAgent, reasoning);
|
|
1212
1241
|
agentBinary = codingAgentBinary(selectedAgent, { codexBin });
|
|
1242
|
+
assertReasoningSupported(selectedAgent, reasoning);
|
|
1243
|
+
assertFastCompatible(selectedAgent, agentBinary, fast);
|
|
1213
1244
|
supportRecorder?.setProvider(
|
|
1214
1245
|
selectedAgent,
|
|
1215
1246
|
safeCommandVersion(selectedAgent, agentBinary),
|
|
@@ -1265,9 +1296,12 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1265
1296
|
const timeout = timeoutMs
|
|
1266
1297
|
? setTimeout(() => {
|
|
1267
1298
|
child.kill('SIGTERM');
|
|
1268
|
-
|
|
1269
|
-
|
|
1299
|
+
const error = new Error(`${selectedAgent} timed out`);
|
|
1300
|
+
error.providerUsage = parseAgentUsage(
|
|
1301
|
+
selectedAgent,
|
|
1302
|
+
Buffer.concat(stdout).toString('utf8'),
|
|
1270
1303
|
);
|
|
1304
|
+
rejectPromise(error);
|
|
1271
1305
|
}, timeoutMs)
|
|
1272
1306
|
: undefined;
|
|
1273
1307
|
timeout?.unref();
|
|
@@ -1316,14 +1350,18 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1316
1350
|
.filter((line) => line.trim() && line.length < 600)
|
|
1317
1351
|
.slice(-8)
|
|
1318
1352
|
.join('\n');
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
`${selectedAgent} exited with status ${status ?? signal}${detail ? `\n${detail}` : ''}`,
|
|
1322
|
-
),
|
|
1353
|
+
const error = new Error(
|
|
1354
|
+
`${selectedAgent} exited with status ${status ?? signal}${detail ? `\n${detail}` : ''}`,
|
|
1323
1355
|
);
|
|
1356
|
+
error.providerUsage = parseAgentUsage(selectedAgent, stdoutText);
|
|
1357
|
+
rejectPromise(error);
|
|
1324
1358
|
return;
|
|
1325
1359
|
}
|
|
1326
|
-
resolvePromise({
|
|
1360
|
+
resolvePromise({
|
|
1361
|
+
stdout: stdoutText,
|
|
1362
|
+
stderr: stderrText,
|
|
1363
|
+
providerUsage: parseAgentUsage(selectedAgent, stdoutText),
|
|
1364
|
+
});
|
|
1327
1365
|
});
|
|
1328
1366
|
if (invocation.input === 'stdin') child.stdin.end(input);
|
|
1329
1367
|
else child.stdin.end();
|
|
@@ -1333,8 +1371,16 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1333
1371
|
async function requestAgent(invocation, input, normalize, snapshot) {
|
|
1334
1372
|
supportRecorder?.addBytes('agentInput', Buffer.byteLength(input));
|
|
1335
1373
|
return recordAsyncStage('agent', async () => {
|
|
1336
|
-
|
|
1374
|
+
let result;
|
|
1375
|
+
try {
|
|
1376
|
+
result = await runAgent(invocation, input);
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
assertAgentReviewFresh(snapshot);
|
|
1379
|
+
recordAgentUsage(error?.providerUsage);
|
|
1380
|
+
throw error;
|
|
1381
|
+
}
|
|
1337
1382
|
assertAgentReviewFresh(snapshot);
|
|
1383
|
+
recordAgentUsage(result.providerUsage);
|
|
1338
1384
|
if (result.stderr.trim()) {
|
|
1339
1385
|
console.error(
|
|
1340
1386
|
`${selectedAgent} wrote diagnostic output:\n${result.stderr.trim()}`,
|
|
@@ -1567,6 +1613,7 @@ try {
|
|
|
1567
1613
|
generatedAt: new Date().toISOString(),
|
|
1568
1614
|
...(model ? { model } : {}),
|
|
1569
1615
|
...(reasoning ? { reasoning } : {}),
|
|
1616
|
+
fast,
|
|
1570
1617
|
accessMode: accessMode.mode,
|
|
1571
1618
|
},
|
|
1572
1619
|
};
|
|
@@ -1613,6 +1660,7 @@ try {
|
|
|
1613
1660
|
generatedAt: new Date().toISOString(),
|
|
1614
1661
|
...(model ? { model } : {}),
|
|
1615
1662
|
...(reasoning ? { reasoning } : {}),
|
|
1663
|
+
fast,
|
|
1616
1664
|
accessMode: accessMode.mode,
|
|
1617
1665
|
},
|
|
1618
1666
|
};
|
|
@@ -1694,6 +1742,7 @@ try {
|
|
|
1694
1742
|
: 'complete',
|
|
1695
1743
|
...(model ? { model } : {}),
|
|
1696
1744
|
...(reasoning ? { reasoning } : {}),
|
|
1745
|
+
fast,
|
|
1697
1746
|
accessMode: accessMode.mode,
|
|
1698
1747
|
},
|
|
1699
1748
|
},
|
|
@@ -1749,6 +1798,7 @@ try {
|
|
|
1749
1798
|
binary: agentBinary,
|
|
1750
1799
|
model,
|
|
1751
1800
|
reasoning,
|
|
1801
|
+
fast,
|
|
1752
1802
|
prompt: promptFor(batchPaths, { accessMode, includeChange: false }),
|
|
1753
1803
|
schema: outputSchema(batchPaths, { includeChange: false }),
|
|
1754
1804
|
schemaPath,
|
|
@@ -1875,6 +1925,7 @@ try {
|
|
|
1875
1925
|
binary: agentBinary,
|
|
1876
1926
|
model,
|
|
1877
1927
|
reasoning,
|
|
1928
|
+
fast,
|
|
1878
1929
|
prompt: promptFor([], { accessMode }),
|
|
1879
1930
|
schema,
|
|
1880
1931
|
schemaPath,
|
|
@@ -1945,6 +1996,7 @@ try {
|
|
|
1945
1996
|
} catch (error) {
|
|
1946
1997
|
if (!(error instanceof ReviewChangedError)) throw error;
|
|
1947
1998
|
reviewChanged = true;
|
|
1999
|
+
agentUsage = emptyUsageAccumulator();
|
|
1948
2000
|
console.log('The review changed while notes were generated. Rebuilding the snapshot.');
|
|
1949
2001
|
}
|
|
1950
2002
|
} while (reviewChanged && !interrupted);
|
package/scripts/present.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { helpText, parseCliArgs } from './cli-args.mjs';
|
|
|
20
20
|
import { applyAgentConfigOperation } from './agent-config.mjs';
|
|
21
21
|
import {
|
|
22
22
|
agentReadOnlyWarning,
|
|
23
|
+
assertFastCompatible,
|
|
23
24
|
assertReasoningSupported,
|
|
24
25
|
codingAgentFromSelectionError,
|
|
25
26
|
codingAgentAvailability,
|
|
@@ -33,6 +34,11 @@ import {
|
|
|
33
34
|
} from './local-target.mjs';
|
|
34
35
|
import { doctorReport } from './doctor.mjs';
|
|
35
36
|
import { accessTokenDirectory } from './access-token.mjs';
|
|
37
|
+
import {
|
|
38
|
+
emptyUsageAccumulator,
|
|
39
|
+
reviewUsage,
|
|
40
|
+
usageSummary,
|
|
41
|
+
} from './agent-usage.mjs';
|
|
36
42
|
import { cacheStatus, clearCache, formatCacheStatus, pruneCache } from './cache.mjs';
|
|
37
43
|
import {
|
|
38
44
|
agentFallbackRecordNeeded,
|
|
@@ -256,10 +262,11 @@ if (agentEnabled) {
|
|
|
256
262
|
}),
|
|
257
263
|
},
|
|
258
264
|
);
|
|
259
|
-
assertReasoningSupported(selectedAgent, cli.reasoning);
|
|
260
265
|
selectedAgentBinary = codingAgentBinary(selectedAgent, {
|
|
261
266
|
codexBin: cli.codexBin,
|
|
262
267
|
});
|
|
268
|
+
assertReasoningSupported(selectedAgent, cli.reasoning);
|
|
269
|
+
assertFastCompatible(selectedAgent, selectedAgentBinary, cli.fast);
|
|
263
270
|
recordSelectedProvider(selectedAgent, selectedAgentBinary);
|
|
264
271
|
supportRecorder?.addStage(
|
|
265
272
|
'agent',
|
|
@@ -454,6 +461,7 @@ function chatAgentArguments() {
|
|
|
454
461
|
...chatAccessArguments(),
|
|
455
462
|
...optionalSiteArgument('--chat-model', cli.model),
|
|
456
463
|
...optionalSiteArgument('--chat-reasoning', cli.reasoning),
|
|
464
|
+
...(cli.fast ? ['--chat-fast'] : []),
|
|
457
465
|
];
|
|
458
466
|
}
|
|
459
467
|
|
|
@@ -778,15 +786,22 @@ function markSnapshotReady() {
|
|
|
778
786
|
}
|
|
779
787
|
|
|
780
788
|
function snapshotForPresentation(snapshot) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
789
|
+
const zeroUsage = usageSummary(emptyUsageAccumulator());
|
|
790
|
+
const hasCurrentNotes = snapshotStateFromSnapshot(snapshot)
|
|
791
|
+
.hasCurrentAgentNotes;
|
|
792
|
+
const current = {
|
|
785
793
|
...snapshot,
|
|
794
|
+
usage: reviewUsage(zeroUsage, zeroUsage),
|
|
795
|
+
};
|
|
796
|
+
if (hasCurrentNotes && snapshot.notes?.fast === cli.fast) return current;
|
|
797
|
+
const content = {
|
|
798
|
+
...current,
|
|
786
799
|
notes: {
|
|
787
800
|
...snapshot.notes,
|
|
788
|
-
|
|
789
|
-
|
|
801
|
+
fast: cli.fast,
|
|
802
|
+
...(hasCurrentNotes
|
|
803
|
+
? {}
|
|
804
|
+
: { complete: false, status: 'generating' }),
|
|
790
805
|
},
|
|
791
806
|
};
|
|
792
807
|
delete content.version;
|
|
@@ -307,8 +307,10 @@ export function makeInput({ kind, context, messages, question, accessMode }) {
|
|
|
307
307
|
};
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
function providerResult(provider, input) {
|
|
311
|
-
return typeof provider === 'function'
|
|
310
|
+
function providerResult(provider, input, context) {
|
|
311
|
+
return typeof provider === 'function'
|
|
312
|
+
? provider(input, context)
|
|
313
|
+
: provider.run(input, context);
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
function isExecution(value) {
|
|
@@ -324,8 +326,8 @@ function executionPromise(value) {
|
|
|
324
326
|
return isExecution(value) ? value.promise : value;
|
|
325
327
|
}
|
|
326
328
|
|
|
327
|
-
export function providerExecution(provider, input) {
|
|
328
|
-
const result = providerResult(provider, input);
|
|
329
|
+
export function providerExecution(provider, input, context) {
|
|
330
|
+
const result = providerResult(provider, input, context);
|
|
329
331
|
return {
|
|
330
332
|
promise: Promise.resolve(executionPromise(result)),
|
|
331
333
|
cancel: cancellationFor(result),
|
|
@@ -130,9 +130,9 @@ function validatedAnswer(value, paths) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
function executionResult(provider, input) {
|
|
133
|
+
function executionResult(provider, input, context) {
|
|
134
134
|
try {
|
|
135
|
-
return { execution: providerExecution(provider, input) };
|
|
135
|
+
return { execution: providerExecution(provider, input, context) };
|
|
136
136
|
} catch (error) {
|
|
137
137
|
return { error };
|
|
138
138
|
}
|
|
@@ -424,7 +424,9 @@ export class ReviewChatController {
|
|
|
424
424
|
thread.error = undefined;
|
|
425
425
|
this.notify();
|
|
426
426
|
this.supervise(thread, run);
|
|
427
|
-
const result = executionResult(this.provider, input
|
|
427
|
+
const result = executionResult(this.provider, input, {
|
|
428
|
+
reviewFingerprint: run.fingerprint,
|
|
429
|
+
});
|
|
428
430
|
if (result.error) {
|
|
429
431
|
this.finishFailure(thread, run, result.error);
|
|
430
432
|
return;
|