@kolisachint/hoocode-agent 0.5.32 → 0.5.34
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/CHANGELOG.md +65 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +16 -3
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +18 -10
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-chain-summary.d.ts +9 -2
- package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-chain-summary.js +132 -29
- package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -1
- package/dist/modes/interactive/components/tool-chain.d.ts +12 -1
- package/dist/modes/interactive/components/tool-chain.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-chain.js +15 -2
- package/dist/modes/interactive/components/tool-chain.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +19 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +45 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/keybindings.md +5 -0
- package/docs/terminal-setup.md +51 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -133,6 +133,17 @@ export function chainStats(entries, state) {
|
|
|
133
133
|
}
|
|
134
134
|
return parts.join(" · ");
|
|
135
135
|
}
|
|
136
|
+
/** Leading `cd <dir> &&` runs, however many are chained. */
|
|
137
|
+
const CD_PREFIX_RE = /^(?:cd\s+(?:"[^"]*"|'[^']*'|[^\s;&|]+)\s*&&\s*)+/;
|
|
138
|
+
/**
|
|
139
|
+
* A command's act, with the navigation stripped off the front.
|
|
140
|
+
*
|
|
141
|
+
* `cd packages/tui && bun run check` is one act performed somewhere, not two,
|
|
142
|
+
* and the somewhere is not what the chain did.
|
|
143
|
+
*/
|
|
144
|
+
function commandAct(subject) {
|
|
145
|
+
return subject.replace(CD_PREFIX_RE, "").trim() || subject;
|
|
146
|
+
}
|
|
136
147
|
/**
|
|
137
148
|
* Tool families, in the order a settled chain reports them.
|
|
138
149
|
*
|
|
@@ -140,17 +151,54 @@ export function chainStats(entries, state) {
|
|
|
140
151
|
* one is remembered as the edit. Mutation outranks execution outranks reading
|
|
141
152
|
* outranks searching, so the phrase names the most consequential thing the
|
|
142
153
|
* chain did rather than the most frequent.
|
|
154
|
+
*
|
|
155
|
+
* Priority alone is only sound while the chain is small enough for every call
|
|
156
|
+
* to plausibly serve the headline act. See `INCIDENTAL_SHARE`.
|
|
143
157
|
*/
|
|
144
158
|
const FAMILIES = [
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
{
|
|
160
|
+
verb: "Edited",
|
|
161
|
+
noun: "file",
|
|
162
|
+
tail: "edit",
|
|
163
|
+
tools: ["edit", "write", "MultiEdit", "NotebookEdit"],
|
|
164
|
+
subjectIsPath: true,
|
|
165
|
+
},
|
|
166
|
+
{ verb: "Ran", noun: "command", tail: "command", tools: ["bash"], subjectIsPath: false, act: commandAct },
|
|
167
|
+
{ verb: "Delegated", noun: "task", tail: "task", tools: ["Task", "TaskOutput"], subjectIsPath: false },
|
|
168
|
+
{ verb: "Fetched", noun: "page", tail: "fetch", tools: ["webfetch", "websearch"], subjectIsPath: false },
|
|
169
|
+
{ verb: "Read", noun: "file", tail: "read", tools: ["read"], subjectIsPath: true },
|
|
170
|
+
{ verb: "Searched", noun: "search", tail: "search", tools: ["grep", "find", "search", "ls"], subjectIsPath: true },
|
|
151
171
|
];
|
|
152
|
-
/**
|
|
153
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Calls from here up, a chain is long enough that priority order alone starts
|
|
174
|
+
* lying, and the three rules below switch on.
|
|
175
|
+
*
|
|
176
|
+
* Below it the existing behaviour is left exactly as it was. Measured over this
|
|
177
|
+
* repo's own sessions, 87% of chains are three calls or fewer and 75% touch a
|
|
178
|
+
* single family; the failure these rules address does not exist down there, so
|
|
179
|
+
* neither should the rules.
|
|
180
|
+
*/
|
|
181
|
+
const LONG_CHAIN_CALLS = 10;
|
|
182
|
+
/**
|
|
183
|
+
* A family this far below the chain's own size did not characterise it.
|
|
184
|
+
*
|
|
185
|
+
* Priority order says a chain that read six files and changed one is the edit,
|
|
186
|
+
* and at seven calls that is true. At seventy-three it is not: 28 greps, 37
|
|
187
|
+
* reads and one edit is an investigation that ended in a small change, and
|
|
188
|
+
* calling it "Edited subagent.ts" describes 1 call out of 73.
|
|
189
|
+
*/
|
|
190
|
+
const INCIDENTAL_SHARE = 0.1;
|
|
191
|
+
/** Below this, a secondary family is a footnote and not worth the width. */
|
|
192
|
+
const MIN_SECONDARY_CALLS = 3;
|
|
193
|
+
/**
|
|
194
|
+
* Longest common directory prefix of the given paths, or "" when they share none.
|
|
195
|
+
*
|
|
196
|
+
* `long` chains additionally reject a single-segment prefix: across forty files
|
|
197
|
+
* the shared root collapses to something like `packages`, which names a location
|
|
198
|
+
* so broad it is worse than the count it displaced. Short chains keep theirs —
|
|
199
|
+
* `Edited docs` is a genuine location, and three files rarely share a vague one.
|
|
200
|
+
*/
|
|
201
|
+
function commonPathPrefix(paths, long) {
|
|
154
202
|
if (paths.length === 0)
|
|
155
203
|
return "";
|
|
156
204
|
const split = paths.map((p) => p.split("/").filter(Boolean));
|
|
@@ -162,7 +210,13 @@ function commonPathPrefix(paths) {
|
|
|
162
210
|
else
|
|
163
211
|
break;
|
|
164
212
|
}
|
|
165
|
-
|
|
213
|
+
if (prefix.length === 0)
|
|
214
|
+
return "";
|
|
215
|
+
if (long && prefix.length < 2)
|
|
216
|
+
return "";
|
|
217
|
+
const joined = prefix.join("/");
|
|
218
|
+
// `filter(Boolean)` drops the empty leading segment of an absolute path.
|
|
219
|
+
return paths[0].startsWith("/") ? `/${joined}` : joined;
|
|
166
220
|
}
|
|
167
221
|
/**
|
|
168
222
|
* Whether a subject can stand in for a location.
|
|
@@ -175,13 +229,51 @@ function usableAsTarget(subject) {
|
|
|
175
229
|
return false;
|
|
176
230
|
return subject.includes("/") || /\.[a-zA-Z0-9]+$/.test(subject);
|
|
177
231
|
}
|
|
232
|
+
/** The headline clause for one family's calls. */
|
|
233
|
+
function familyPhrase(family, matched, long) {
|
|
234
|
+
const subjects = matched.map((e) => (family.act ? family.act(e.subject) : e.subject)).filter(Boolean);
|
|
235
|
+
// A lone call is fully described by its own subject, whatever shape it is:
|
|
236
|
+
// a command, a pattern, a URL, a path. So is a run that did the same thing
|
|
237
|
+
// every time — `bun run check` twice is that command, not "2 commands".
|
|
238
|
+
if (subjects.length === matched.length && new Set(subjects).size === 1) {
|
|
239
|
+
return `${family.verb} ${subjects[0]}`;
|
|
240
|
+
}
|
|
241
|
+
// Several calls: name the place they share, or fall back to a count.
|
|
242
|
+
const target = family.subjectIsPath ? commonPathPrefix(subjects.filter(usableAsTarget), long) : "";
|
|
243
|
+
if (target)
|
|
244
|
+
return `${family.verb} ${target}`;
|
|
245
|
+
// Searching has no natural plural noun ("3 searches" says nothing the
|
|
246
|
+
// stats do not), so the bare verb carries it.
|
|
247
|
+
if (family.noun === "search")
|
|
248
|
+
return "Explored";
|
|
249
|
+
return `${family.verb} ${plural(matched.length, family.noun)}`;
|
|
250
|
+
}
|
|
251
|
+
/** The family with the most calls other than `chosen`, if any reaches the floor. */
|
|
252
|
+
function largestOtherFamily(entries, chosen) {
|
|
253
|
+
let best;
|
|
254
|
+
for (const family of FAMILIES) {
|
|
255
|
+
if (family === chosen)
|
|
256
|
+
continue;
|
|
257
|
+
const count = entries.filter((e) => family.tools.includes(e.tool)).length;
|
|
258
|
+
if (count > (best?.count ?? 0))
|
|
259
|
+
best = { family, count };
|
|
260
|
+
}
|
|
261
|
+
return best && best.count >= MIN_SECONDARY_CALLS ? best : undefined;
|
|
262
|
+
}
|
|
178
263
|
/**
|
|
179
264
|
* The settled chain's phrase: what this run amounted to.
|
|
180
265
|
*
|
|
181
|
-
* Deliberately one clause, not an inventory. "Edited packages/tui, read
|
|
182
|
-
* files, searched packages" is a worse line than "Edited packages/tui" — the
|
|
266
|
+
* Deliberately close to one clause, not an inventory. "Edited packages/tui, read
|
|
267
|
+
* 6 files, searched packages" is a worse line than "Edited packages/tui" — the
|
|
183
268
|
* count is already in the stats, and the per-call detail is one `alt+u` away.
|
|
184
269
|
*
|
|
270
|
+
* A long chain earns at most one more clause, and only when its headline family
|
|
271
|
+
* is a minority of the calls. That is the case where a single clause stops being
|
|
272
|
+
* terse and starts being false: 31 edits among 182 calls is worth naming, but so
|
|
273
|
+
* is the fact that 76 of the rest were commands. Listing every family it touched
|
|
274
|
+
* is not the alternative — a chain that reports "(ran, delegated, read,
|
|
275
|
+
* searched)" has said only that it was busy.
|
|
276
|
+
*
|
|
185
277
|
* A location is only named when the calls actually share one. Naming a single
|
|
186
278
|
* arbitrary file out of five would read as a claim the chain never made, so
|
|
187
279
|
* several unrelated targets collapse to a count instead.
|
|
@@ -189,28 +281,39 @@ function usableAsTarget(subject) {
|
|
|
189
281
|
export function chainPhrase(entries) {
|
|
190
282
|
if (entries.length === 0)
|
|
191
283
|
return "No calls";
|
|
284
|
+
const long = entries.length >= LONG_CHAIN_CALLS;
|
|
285
|
+
let chosen;
|
|
286
|
+
let matched = [];
|
|
192
287
|
for (const family of FAMILIES) {
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
288
|
+
const m = entries.filter((e) => family.tools.includes(e.tool));
|
|
289
|
+
if (m.length === 0)
|
|
195
290
|
continue;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
291
|
+
if (long && m.length / entries.length < INCIDENTAL_SHARE)
|
|
292
|
+
continue;
|
|
293
|
+
chosen = family;
|
|
294
|
+
matched = m;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
// Every family was incidental — a long chain spread thin across many tools.
|
|
298
|
+
// The largest still describes it better than the priority order's first hit.
|
|
299
|
+
if (!chosen) {
|
|
300
|
+
for (const family of FAMILIES) {
|
|
301
|
+
const m = entries.filter((e) => family.tools.includes(e.tool));
|
|
302
|
+
if (m.length > matched.length) {
|
|
303
|
+
chosen = family;
|
|
304
|
+
matched = m;
|
|
305
|
+
}
|
|
201
306
|
}
|
|
202
|
-
// Several calls: name the place they share, or fall back to a count.
|
|
203
|
-
const target = commonPathPrefix(subjects.filter(usableAsTarget));
|
|
204
|
-
if (target)
|
|
205
|
-
return `${family.verb} ${target}`;
|
|
206
|
-
// Searching has no natural plural noun ("3 searches" says nothing the
|
|
207
|
-
// stats do not), so the bare verb carries it.
|
|
208
|
-
if (family.noun === "search")
|
|
209
|
-
return "Explored";
|
|
210
|
-
return `${family.verb} ${plural(matched.length, family.noun)}`;
|
|
211
307
|
}
|
|
212
308
|
// An unrecognised tool set still gets a line rather than a blank.
|
|
213
|
-
|
|
214
|
-
|
|
309
|
+
if (!chosen) {
|
|
310
|
+
const names = [...new Set(entries.map((e) => e.tool))];
|
|
311
|
+
return names.length === 1 ? `Called ${names[0]}` : `Called ${plural(names.length, "tool")}`;
|
|
312
|
+
}
|
|
313
|
+
const head = familyPhrase(chosen, matched, long);
|
|
314
|
+
if (!long || matched.length / entries.length >= 0.5)
|
|
315
|
+
return head;
|
|
316
|
+
const secondary = largestOtherFamily(entries, chosen);
|
|
317
|
+
return secondary ? `${head} · ${plural(secondary.count, secondary.family.tail)}` : head;
|
|
215
318
|
}
|
|
216
319
|
//# sourceMappingURL=tool-chain-summary.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-chain-summary.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA6BH,8EAA8E;AAC9E,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,qCAAqC;AACrC,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,qCAAqC;AACrC,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,SAAS,IAAI,CAAC,KAAiB,EAAe;IAC7C,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC;IAClC,IAAI,KAAK,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACtC,OAAO,IAAI,CAAC;AAAA,CACZ;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,OAAqB,EAAkB;IAC/D,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,OACC,KAAK,GAAG,GAAG,GAAG,OAAO,CAAC,MAAM;YAC5B,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;YACxC,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,KAAK,SAAS;YACxC,SAAS,KAAK,IAAI,EACjB,CAAC;YACF,GAAG,EAAE,CAAC;QACP,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,KAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,MAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACxG,KAAK,IAAI,GAAG,CAAC;IACd,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,QAAwB,EAAkB;IAC9D,IAAI,QAAQ,CAAC,MAAM,IAAI,YAAY;QAAE,OAAO,QAAQ,CAAC;IAErD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpD,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,aAAa,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpF,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAAA,CAC1C,CAAC,CAAC;IAEH,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC;QACtB,IAAI,GAAG,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAK,GAAG,WAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,GAAG,GAAG,CAAC,CAAC;IAAA,CACR,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjB,QAAQ,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACP,GAAG,EAAE,CAAC;QACP,CAAC;IAAA,CACD,CAAC,CAAC;IACH,QAAQ,EAAE,CAAC;IACX,OAAO,GAAG,CAAC;AAAA,CACX;AAED,yEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,OAAqB,EAAkB;IACpE,OAAO,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CAC7C;AAED,SAAS,MAAM,CAAC,CAAS,EAAE,GAAW,EAAE,IAAI,GAAG,GAAG,GAAG,GAAG,EAAU;IACjE,OAAO,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAAA,CACtC;AAED,kDAAkD;AAClD,MAAM,UAAU,UAAU,CAAC,OAAqB,EAAE,KAAiB,EAAU;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACvD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACP,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC;IAE/C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC;SAAM,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3B,CAAC;SAAM,CAAC;QACP,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,KAAK,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAK,CAAC,CAAC;AAAA,CACzB;AAED;;;;;;;GAOG;AACH,MAAM,QAAQ,GAA2D;IACxE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE;IACvF,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IACjD,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE;IAClE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE;IACnE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IAC/C,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE;CAC7E,CAAC;AAEF,sFAAsF;AACtF,SAAS,gBAAgB,CAAC,KAAe,EAAU;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;;YACnE,MAAM;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAAA,CACxB;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,OAAe,EAAW;IACjD,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAAA,CAChE;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,OAAqB,EAAU;IAC1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC;IAE5C,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEnC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAE/D,2EAA2E;QAC3E,uCAAuC;QACvC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,CAAC;QAED,qEAAqE;QACrE,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;QACjE,IAAI,MAAM;YAAE,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;QAC9C,sEAAsE;QACtE,8CAA8C;QAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,UAAU,CAAC;QAChD,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IAChE,CAAC;IAED,kEAAkE;IAClE,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAAA,CAC5F","sourcesContent":["/**\n * The radar view's chain line: one line for a run of consecutive tool calls.\n *\n * Two renderings of the same chain, because a run that is still going and a run\n * that is over answer different questions.\n *\n * While it runs you want the shape — what it did, in order, and where it broke:\n *\n * ◐ grep › read › bash✗ › edit › bash… 4 done · 1 failed · running\n *\n * Once it is over the order has stopped mattering and what is left is what it\n * amounted to:\n *\n * ● Edited packages/tui/src/keys.ts 5 calls · 1 failed\n *\n * A chain that did *not* finish keeps the running form plus a marker. The\n * settled form is a claim about what was accomplished, and a chain cut off\n * partway through has no such claim to make — the same reason\n * `settleDanglingMainTasks` refuses to mark an aborted turn's plan items done.\n *\n * Everything here is pure and deterministic. A model-written phase label would\n * read better than the inferred one, but it would cost tokens on every turn and\n * this agent's whole premise is that nothing happens you did not pay for on\n * purpose.\n */\n\n/** One tool call's contribution to its chain. */\nexport interface ChainEntry {\n\ttool: string;\n\t/** The call's subject — a path, a command, a pattern. */\n\tsubject: string;\n\tisError: boolean;\n\t/** Still executing. */\n\tisPartial: boolean;\n\t/** Lines of text output the call returned. */\n\toutputLines: number;\n}\n\nexport type ChainState =\n\t/** Calls are still arriving or executing. */\n\t| \"running\"\n\t/** Every call finished and the turn moved on cleanly. */\n\t| \"done\"\n\t/** The turn was aborted, errored, or hit a length cap partway through. */\n\t| \"interrupted\";\n\nexport type SegmentTone = \"ok\" | \"error\" | \"running\";\n\nexport interface ChainSegment {\n\tlabel: string;\n\ttone: SegmentTone;\n}\n\n/** Segments beyond this many are elided in the middle rather than wrapped. */\nconst MAX_SEGMENTS = 8;\n/** Kept at the head when eliding. */\nconst HEAD_SEGMENTS = 3;\n/** Kept at the tail when eliding. */\nconst TAIL_SEGMENTS = 2;\n\nfunction tone(entry: ChainEntry): SegmentTone {\n\tif (entry.isError) return \"error\";\n\tif (entry.isPartial) return \"running\";\n\treturn \"ok\";\n}\n\n/**\n * Collapse consecutive calls to the same tool into one segment.\n *\n * A run of five reads is one act, not five, and spelling it out crowds out the\n * calls either side of it. A failure never merges: `read ×4` hiding one broken\n * read would defeat the point of the line.\n */\nfunction collapseRepeats(entries: ChainEntry[]): ChainSegment[] {\n\tconst segments: ChainSegment[] = [];\n\tlet index = 0;\n\twhile (index < entries.length) {\n\t\tconst entry = entries[index];\n\t\tconst entryTone = tone(entry);\n\t\tlet run = 1;\n\t\twhile (\n\t\t\tindex + run < entries.length &&\n\t\t\tentries[index + run].tool === entry.tool &&\n\t\t\ttone(entries[index + run]) === entryTone &&\n\t\t\tentryTone === \"ok\"\n\t\t) {\n\t\t\trun++;\n\t\t}\n\t\tconst marker = entryTone === \"error\" ? \"✗\" : \"\";\n\t\tsegments.push({ label: run > 1 ? `${entry.tool} ×${run}` : `${entry.tool}${marker}`, tone: entryTone });\n\t\tindex += run;\n\t}\n\treturn segments;\n}\n\n/**\n * Elide the middle of a long chain, keeping both ends legible.\n *\n * Failures are never elided. Where a run broke is the one thing the line exists\n * to show, and a long chain is exactly when you most need it — hiding the ✗\n * inside \"… 22 more …\" would leave the stats claiming a failure the line cannot\n * point at.\n */\nfunction capSegments(segments: ChainSegment[]): ChainSegment[] {\n\tif (segments.length <= MAX_SEGMENTS) return segments;\n\n\tconst keep = new Set<number>();\n\tfor (let i = 0; i < HEAD_SEGMENTS; i++) keep.add(i);\n\tfor (let i = segments.length - TAIL_SEGMENTS; i < segments.length; i++) keep.add(i);\n\tsegments.forEach((segment, i) => {\n\t\tif (segment.tone === \"error\") keep.add(i);\n\t});\n\n\tconst out: ChainSegment[] = [];\n\tlet gap = 0;\n\tconst flushGap = () => {\n\t\tif (gap > 0) out.push({ label: `… ${gap} more …`, tone: \"ok\" });\n\t\tgap = 0;\n\t};\n\tsegments.forEach((segment, i) => {\n\t\tif (keep.has(i)) {\n\t\t\tflushGap();\n\t\t\tout.push(segment);\n\t\t} else {\n\t\t\tgap++;\n\t\t}\n\t});\n\tflushGap();\n\treturn out;\n}\n\n/** The `grep › read × 3 › bash✗` part of a running chain line. */\nexport function chainSegments(entries: ChainEntry[]): ChainSegment[] {\n\treturn capSegments(collapseRepeats(entries));\n}\n\nfunction plural(n: number, one: string, many = `${one}s`): string {\n\treturn `${n} ${n === 1 ? one : many}`;\n}\n\n/** The flush-right stats for either rendering. */\nexport function chainStats(entries: ChainEntry[], state: ChainState): string {\n\tconst failed = entries.filter((e) => e.isError).length;\n\tconst parts: string[] = [];\n\n\tif (state === \"running\") {\n\t\tparts.push(`${entries.filter((e) => !e.isPartial).length} done`);\n\t} else {\n\t\tparts.push(plural(entries.length, \"call\"));\n\t}\n\tif (failed > 0) parts.push(`${failed} failed`);\n\n\tif (state === \"running\") {\n\t\tparts.push(\"running\");\n\t} else if (state === \"interrupted\") {\n\t\tparts.push(\"interrupted\");\n\t} else {\n\t\tconst lines = entries.reduce((sum, e) => sum + e.outputLines, 0);\n\t\tif (lines > 0) parts.push(plural(lines, \"line\"));\n\t}\n\treturn parts.join(\" · \");\n}\n\n/**\n * Tool families, in the order a settled chain reports them.\n *\n * The ordering is the whole trick: a chain that read six files and then changed\n * one is remembered as the edit. Mutation outranks execution outranks reading\n * outranks searching, so the phrase names the most consequential thing the\n * chain did rather than the most frequent.\n */\nconst FAMILIES: Array<{ verb: string; noun: string; tools: string[] }> = [\n\t{ verb: \"Edited\", noun: \"file\", tools: [\"edit\", \"write\", \"MultiEdit\", \"NotebookEdit\"] },\n\t{ verb: \"Ran\", noun: \"command\", tools: [\"bash\"] },\n\t{ verb: \"Delegated\", noun: \"task\", tools: [\"Task\", \"TaskOutput\"] },\n\t{ verb: \"Fetched\", noun: \"page\", tools: [\"webfetch\", \"websearch\"] },\n\t{ verb: \"Read\", noun: \"file\", tools: [\"read\"] },\n\t{ verb: \"Searched\", noun: \"search\", tools: [\"grep\", \"find\", \"search\", \"ls\"] },\n];\n\n/** Longest common directory prefix of the given paths, or \"\" when they share none. */\nfunction commonPathPrefix(paths: string[]): string {\n\tif (paths.length === 0) return \"\";\n\tconst split = paths.map((p) => p.split(\"/\").filter(Boolean));\n\tconst [first, ...rest] = split;\n\tconst prefix: string[] = [];\n\tfor (let i = 0; i < first.length; i++) {\n\t\tif (rest.every((parts) => parts[i] === first[i])) prefix.push(first[i]);\n\t\telse break;\n\t}\n\treturn prefix.join(\"/\");\n}\n\n/**\n * Whether a subject can stand in for a location.\n *\n * Globs are excluded even though they look pathlike: \"Searched *.test.ts\" reads\n * as a place the chain worked in, which is exactly what it is not.\n */\nfunction usableAsTarget(subject: string): boolean {\n\tif (/[*?]/.test(subject)) return false;\n\treturn subject.includes(\"/\") || /\\.[a-zA-Z0-9]+$/.test(subject);\n}\n\n/**\n * The settled chain's phrase: what this run amounted to.\n *\n * Deliberately one clause, not an inventory. \"Edited packages/tui, read 6\n * files, searched packages\" is a worse line than \"Edited packages/tui\" — the\n * count is already in the stats, and the per-call detail is one `alt+u` away.\n *\n * A location is only named when the calls actually share one. Naming a single\n * arbitrary file out of five would read as a claim the chain never made, so\n * several unrelated targets collapse to a count instead.\n */\nexport function chainPhrase(entries: ChainEntry[]): string {\n\tif (entries.length === 0) return \"No calls\";\n\n\tfor (const family of FAMILIES) {\n\t\tconst matched = entries.filter((e) => family.tools.includes(e.tool));\n\t\tif (matched.length === 0) continue;\n\n\t\tconst subjects = matched.map((e) => e.subject).filter(Boolean);\n\n\t\t// A lone call is fully described by its own subject, whatever shape it is:\n\t\t// a command, a pattern, a URL, a path.\n\t\tif (matched.length === 1 && subjects.length === 1) {\n\t\t\treturn `${family.verb} ${subjects[0]}`;\n\t\t}\n\n\t\t// Several calls: name the place they share, or fall back to a count.\n\t\tconst target = commonPathPrefix(subjects.filter(usableAsTarget));\n\t\tif (target) return `${family.verb} ${target}`;\n\t\t// Searching has no natural plural noun (\"3 searches\" says nothing the\n\t\t// stats do not), so the bare verb carries it.\n\t\tif (family.noun === \"search\") return \"Explored\";\n\t\treturn `${family.verb} ${plural(matched.length, family.noun)}`;\n\t}\n\n\t// An unrecognised tool set still gets a line rather than a blank.\n\tconst names = [...new Set(entries.map((e) => e.tool))];\n\treturn names.length === 1 ? `Called ${names[0]}` : `Called ${plural(names.length, \"tool\")}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-chain-summary.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain-summary.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The radar view's chain line: one line for a run of consecutive tool calls.\n *\n * Two renderings of the same chain, because a run that is still going and a run\n * that is over answer different questions.\n *\n * While it runs you want the shape — what it did, in order, and where it broke:\n *\n * ◐ grep › read › bash✗ › edit › bash… 4 done · 1 failed · running\n *\n * Once it is over the order has stopped mattering and what is left is what it\n * amounted to:\n *\n * ● Edited packages/tui/src/keys.ts 5 calls · 1 failed\n *\n * A chain that did *not* finish keeps the running form plus a marker. The\n * settled form is a claim about what was accomplished, and a chain cut off\n * partway through has no such claim to make — the same reason\n * `settleDanglingMainTasks` refuses to mark an aborted turn's plan items done.\n *\n * Everything here is pure and deterministic. A model-written phase label would\n * read better than the inferred one, but it would cost tokens on every turn and\n * this agent's whole premise is that nothing happens you did not pay for on\n * purpose.\n */\n\n/** One tool call's contribution to its chain. */\nexport interface ChainEntry {\n\ttool: string;\n\t/** The call's subject — a path, a command, a pattern. */\n\tsubject: string;\n\tisError: boolean;\n\t/** Still executing. */\n\tisPartial: boolean;\n\t/** Lines of text output the call returned. */\n\toutputLines: number;\n}\n\nexport type ChainState =\n\t/** Calls are still arriving or executing. */\n\t| \"running\"\n\t/** Every call finished and the turn moved on cleanly. */\n\t| \"done\"\n\t/** The turn was aborted, errored, or hit a length cap partway through. */\n\t| \"interrupted\";\n\nexport type SegmentTone = \"ok\" | \"error\" | \"running\";\n\nexport interface ChainSegment {\n\tlabel: string;\n\ttone: SegmentTone;\n}\n\n/** Segments beyond this many are elided in the middle rather than wrapped. */\nconst MAX_SEGMENTS = 8;\n/** Kept at the head when eliding. */\nconst HEAD_SEGMENTS = 3;\n/** Kept at the tail when eliding. */\nconst TAIL_SEGMENTS = 2;\n\nfunction tone(entry: ChainEntry): SegmentTone {\n\tif (entry.isError) return \"error\";\n\tif (entry.isPartial) return \"running\";\n\treturn \"ok\";\n}\n\n/**\n * Collapse consecutive calls to the same tool into one segment.\n *\n * A run of five reads is one act, not five, and spelling it out crowds out the\n * calls either side of it. A failure never merges: `read ×4` hiding one broken\n * read would defeat the point of the line.\n */\nfunction collapseRepeats(entries: ChainEntry[]): ChainSegment[] {\n\tconst segments: ChainSegment[] = [];\n\tlet index = 0;\n\twhile (index < entries.length) {\n\t\tconst entry = entries[index];\n\t\tconst entryTone = tone(entry);\n\t\tlet run = 1;\n\t\twhile (\n\t\t\tindex + run < entries.length &&\n\t\t\tentries[index + run].tool === entry.tool &&\n\t\t\ttone(entries[index + run]) === entryTone &&\n\t\t\tentryTone === \"ok\"\n\t\t) {\n\t\t\trun++;\n\t\t}\n\t\tconst marker = entryTone === \"error\" ? \"✗\" : \"\";\n\t\tsegments.push({ label: run > 1 ? `${entry.tool} ×${run}` : `${entry.tool}${marker}`, tone: entryTone });\n\t\tindex += run;\n\t}\n\treturn segments;\n}\n\n/**\n * Elide the middle of a long chain, keeping both ends legible.\n *\n * Failures are never elided. Where a run broke is the one thing the line exists\n * to show, and a long chain is exactly when you most need it — hiding the ✗\n * inside \"… 22 more …\" would leave the stats claiming a failure the line cannot\n * point at.\n */\nfunction capSegments(segments: ChainSegment[]): ChainSegment[] {\n\tif (segments.length <= MAX_SEGMENTS) return segments;\n\n\tconst keep = new Set<number>();\n\tfor (let i = 0; i < HEAD_SEGMENTS; i++) keep.add(i);\n\tfor (let i = segments.length - TAIL_SEGMENTS; i < segments.length; i++) keep.add(i);\n\tsegments.forEach((segment, i) => {\n\t\tif (segment.tone === \"error\") keep.add(i);\n\t});\n\n\tconst out: ChainSegment[] = [];\n\tlet gap = 0;\n\tconst flushGap = () => {\n\t\tif (gap > 0) out.push({ label: `… ${gap} more …`, tone: \"ok\" });\n\t\tgap = 0;\n\t};\n\tsegments.forEach((segment, i) => {\n\t\tif (keep.has(i)) {\n\t\t\tflushGap();\n\t\t\tout.push(segment);\n\t\t} else {\n\t\t\tgap++;\n\t\t}\n\t});\n\tflushGap();\n\treturn out;\n}\n\n/** The `grep › read × 3 › bash✗` part of a running chain line. */\nexport function chainSegments(entries: ChainEntry[]): ChainSegment[] {\n\treturn capSegments(collapseRepeats(entries));\n}\n\nfunction plural(n: number, one: string, many = `${one}s`): string {\n\treturn `${n} ${n === 1 ? one : many}`;\n}\n\n/** The flush-right stats for either rendering. */\nexport function chainStats(entries: ChainEntry[], state: ChainState): string {\n\tconst failed = entries.filter((e) => e.isError).length;\n\tconst parts: string[] = [];\n\n\tif (state === \"running\") {\n\t\tparts.push(`${entries.filter((e) => !e.isPartial).length} done`);\n\t} else {\n\t\tparts.push(plural(entries.length, \"call\"));\n\t}\n\tif (failed > 0) parts.push(`${failed} failed`);\n\n\tif (state === \"running\") {\n\t\tparts.push(\"running\");\n\t} else if (state === \"interrupted\") {\n\t\tparts.push(\"interrupted\");\n\t} else {\n\t\tconst lines = entries.reduce((sum, e) => sum + e.outputLines, 0);\n\t\tif (lines > 0) parts.push(plural(lines, \"line\"));\n\t}\n\treturn parts.join(\" · \");\n}\n\ninterface ToolFamily {\n\tverb: string;\n\t/** Noun for the headline's count form: \"Edited 3 files\". */\n\tnoun: string;\n\t/** Noun for the secondary clause, which has no verb to lean on: \"· 38 reads\". */\n\ttail: string;\n\ttools: string[];\n\t/**\n\t * Whether this family's subjects name a place on disk.\n\t *\n\t * Only these may be reduced to a shared location. A command is not a path\n\t * however much it looks like one: three calls that all begin\n\t * `cd /Users/me/repo && …` share a long slash-separated prefix, and treating\n\t * it as a location produced `Ran cd /Users/me/repo` — a headline naming the\n\t * one part of the command that did nothing.\n\t */\n\tsubjectIsPath: boolean;\n\t/** Reduce a subject to the act it performed, when the raw form carries noise. */\n\tact?: (subject: string) => string;\n}\n\n/** Leading `cd <dir> &&` runs, however many are chained. */\nconst CD_PREFIX_RE = /^(?:cd\\s+(?:\"[^\"]*\"|'[^']*'|[^\\s;&|]+)\\s*&&\\s*)+/;\n\n/**\n * A command's act, with the navigation stripped off the front.\n *\n * `cd packages/tui && bun run check` is one act performed somewhere, not two,\n * and the somewhere is not what the chain did.\n */\nfunction commandAct(subject: string): string {\n\treturn subject.replace(CD_PREFIX_RE, \"\").trim() || subject;\n}\n\n/**\n * Tool families, in the order a settled chain reports them.\n *\n * The ordering is the whole trick: a chain that read six files and then changed\n * one is remembered as the edit. Mutation outranks execution outranks reading\n * outranks searching, so the phrase names the most consequential thing the\n * chain did rather than the most frequent.\n *\n * Priority alone is only sound while the chain is small enough for every call\n * to plausibly serve the headline act. See `INCIDENTAL_SHARE`.\n */\nconst FAMILIES: ToolFamily[] = [\n\t{\n\t\tverb: \"Edited\",\n\t\tnoun: \"file\",\n\t\ttail: \"edit\",\n\t\ttools: [\"edit\", \"write\", \"MultiEdit\", \"NotebookEdit\"],\n\t\tsubjectIsPath: true,\n\t},\n\t{ verb: \"Ran\", noun: \"command\", tail: \"command\", tools: [\"bash\"], subjectIsPath: false, act: commandAct },\n\t{ verb: \"Delegated\", noun: \"task\", tail: \"task\", tools: [\"Task\", \"TaskOutput\"], subjectIsPath: false },\n\t{ verb: \"Fetched\", noun: \"page\", tail: \"fetch\", tools: [\"webfetch\", \"websearch\"], subjectIsPath: false },\n\t{ verb: \"Read\", noun: \"file\", tail: \"read\", tools: [\"read\"], subjectIsPath: true },\n\t{ verb: \"Searched\", noun: \"search\", tail: \"search\", tools: [\"grep\", \"find\", \"search\", \"ls\"], subjectIsPath: true },\n];\n\n/**\n * Calls from here up, a chain is long enough that priority order alone starts\n * lying, and the three rules below switch on.\n *\n * Below it the existing behaviour is left exactly as it was. Measured over this\n * repo's own sessions, 87% of chains are three calls or fewer and 75% touch a\n * single family; the failure these rules address does not exist down there, so\n * neither should the rules.\n */\nconst LONG_CHAIN_CALLS = 10;\n\n/**\n * A family this far below the chain's own size did not characterise it.\n *\n * Priority order says a chain that read six files and changed one is the edit,\n * and at seven calls that is true. At seventy-three it is not: 28 greps, 37\n * reads and one edit is an investigation that ended in a small change, and\n * calling it \"Edited subagent.ts\" describes 1 call out of 73.\n */\nconst INCIDENTAL_SHARE = 0.1;\n\n/** Below this, a secondary family is a footnote and not worth the width. */\nconst MIN_SECONDARY_CALLS = 3;\n\n/**\n * Longest common directory prefix of the given paths, or \"\" when they share none.\n *\n * `long` chains additionally reject a single-segment prefix: across forty files\n * the shared root collapses to something like `packages`, which names a location\n * so broad it is worse than the count it displaced. Short chains keep theirs —\n * `Edited docs` is a genuine location, and three files rarely share a vague one.\n */\nfunction commonPathPrefix(paths: string[], long: boolean): string {\n\tif (paths.length === 0) return \"\";\n\tconst split = paths.map((p) => p.split(\"/\").filter(Boolean));\n\tconst [first, ...rest] = split;\n\tconst prefix: string[] = [];\n\tfor (let i = 0; i < first.length; i++) {\n\t\tif (rest.every((parts) => parts[i] === first[i])) prefix.push(first[i]);\n\t\telse break;\n\t}\n\tif (prefix.length === 0) return \"\";\n\tif (long && prefix.length < 2) return \"\";\n\tconst joined = prefix.join(\"/\");\n\t// `filter(Boolean)` drops the empty leading segment of an absolute path.\n\treturn paths[0].startsWith(\"/\") ? `/${joined}` : joined;\n}\n\n/**\n * Whether a subject can stand in for a location.\n *\n * Globs are excluded even though they look pathlike: \"Searched *.test.ts\" reads\n * as a place the chain worked in, which is exactly what it is not.\n */\nfunction usableAsTarget(subject: string): boolean {\n\tif (/[*?]/.test(subject)) return false;\n\treturn subject.includes(\"/\") || /\\.[a-zA-Z0-9]+$/.test(subject);\n}\n\n/** The headline clause for one family's calls. */\nfunction familyPhrase(family: ToolFamily, matched: ChainEntry[], long: boolean): string {\n\tconst subjects = matched.map((e) => (family.act ? family.act(e.subject) : e.subject)).filter(Boolean);\n\n\t// A lone call is fully described by its own subject, whatever shape it is:\n\t// a command, a pattern, a URL, a path. So is a run that did the same thing\n\t// every time — `bun run check` twice is that command, not \"2 commands\".\n\tif (subjects.length === matched.length && new Set(subjects).size === 1) {\n\t\treturn `${family.verb} ${subjects[0]}`;\n\t}\n\n\t// Several calls: name the place they share, or fall back to a count.\n\tconst target = family.subjectIsPath ? commonPathPrefix(subjects.filter(usableAsTarget), long) : \"\";\n\tif (target) return `${family.verb} ${target}`;\n\t// Searching has no natural plural noun (\"3 searches\" says nothing the\n\t// stats do not), so the bare verb carries it.\n\tif (family.noun === \"search\") return \"Explored\";\n\treturn `${family.verb} ${plural(matched.length, family.noun)}`;\n}\n\n/** The family with the most calls other than `chosen`, if any reaches the floor. */\nfunction largestOtherFamily(\n\tentries: ChainEntry[],\n\tchosen: ToolFamily,\n): { family: ToolFamily; count: number } | undefined {\n\tlet best: { family: ToolFamily; count: number } | undefined;\n\tfor (const family of FAMILIES) {\n\t\tif (family === chosen) continue;\n\t\tconst count = entries.filter((e) => family.tools.includes(e.tool)).length;\n\t\tif (count > (best?.count ?? 0)) best = { family, count };\n\t}\n\treturn best && best.count >= MIN_SECONDARY_CALLS ? best : undefined;\n}\n\n/**\n * The settled chain's phrase: what this run amounted to.\n *\n * Deliberately close to one clause, not an inventory. \"Edited packages/tui, read\n * 6 files, searched packages\" is a worse line than \"Edited packages/tui\" — the\n * count is already in the stats, and the per-call detail is one `alt+u` away.\n *\n * A long chain earns at most one more clause, and only when its headline family\n * is a minority of the calls. That is the case where a single clause stops being\n * terse and starts being false: 31 edits among 182 calls is worth naming, but so\n * is the fact that 76 of the rest were commands. Listing every family it touched\n * is not the alternative — a chain that reports \"(ran, delegated, read,\n * searched)\" has said only that it was busy.\n *\n * A location is only named when the calls actually share one. Naming a single\n * arbitrary file out of five would read as a claim the chain never made, so\n * several unrelated targets collapse to a count instead.\n */\nexport function chainPhrase(entries: ChainEntry[]): string {\n\tif (entries.length === 0) return \"No calls\";\n\tconst long = entries.length >= LONG_CHAIN_CALLS;\n\n\tlet chosen: ToolFamily | undefined;\n\tlet matched: ChainEntry[] = [];\n\tfor (const family of FAMILIES) {\n\t\tconst m = entries.filter((e) => family.tools.includes(e.tool));\n\t\tif (m.length === 0) continue;\n\t\tif (long && m.length / entries.length < INCIDENTAL_SHARE) continue;\n\t\tchosen = family;\n\t\tmatched = m;\n\t\tbreak;\n\t}\n\n\t// Every family was incidental — a long chain spread thin across many tools.\n\t// The largest still describes it better than the priority order's first hit.\n\tif (!chosen) {\n\t\tfor (const family of FAMILIES) {\n\t\t\tconst m = entries.filter((e) => family.tools.includes(e.tool));\n\t\t\tif (m.length > matched.length) {\n\t\t\t\tchosen = family;\n\t\t\t\tmatched = m;\n\t\t\t}\n\t\t}\n\t}\n\n\t// An unrecognised tool set still gets a line rather than a blank.\n\tif (!chosen) {\n\t\tconst names = [...new Set(entries.map((e) => e.tool))];\n\t\treturn names.length === 1 ? `Called ${names[0]}` : `Called ${plural(names.length, \"tool\")}`;\n\t}\n\n\tconst head = familyPhrase(chosen, matched, long);\n\tif (!long || matched.length / entries.length >= 0.5) return head;\n\tconst secondary = largestOtherFamily(entries, chosen);\n\treturn secondary ? `${head} · ${plural(secondary.count, secondary.family.tail)}` : head;\n}\n"]}
|
|
@@ -27,6 +27,8 @@ export declare class ToolChainComponent extends Container {
|
|
|
27
27
|
/** True until the agent speaks or the turn settles. */
|
|
28
28
|
get isOpen(): boolean;
|
|
29
29
|
get isOpened(): boolean;
|
|
30
|
+
/** Whether a summary line is standing in for this chain's calls right now. */
|
|
31
|
+
get isSummarised(): boolean;
|
|
30
32
|
get isEmpty(): boolean;
|
|
31
33
|
get toolBlocks(): readonly ToolExecutionComponent[];
|
|
32
34
|
/**
|
|
@@ -44,7 +46,16 @@ export declare class ToolChainComponent extends Container {
|
|
|
44
46
|
/** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */
|
|
45
47
|
setOpened(opened: boolean): void;
|
|
46
48
|
invalidate(): void;
|
|
47
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Whether this chain is currently drawn as a single summary line.
|
|
51
|
+
*
|
|
52
|
+
* A chain of one is not summarised. Its phrase would be `Ran npm run check`,
|
|
53
|
+
* which is strictly less than the radar row it replaced: the row names the
|
|
54
|
+
* tool and how much came back, and the phrase drops both to say the same
|
|
55
|
+
* thing in prose. Summarising is only worth a lossy rewrite when there is
|
|
56
|
+
* more than one call to fold — and by measurement most chains have exactly
|
|
57
|
+
* one, so this is the common case, not an edge.
|
|
58
|
+
*/
|
|
48
59
|
private isCollapsed;
|
|
49
60
|
render(width: number): string[];
|
|
50
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-chain.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACpG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAGxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;IAChD,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,KAAK,CAAyB;IACtC,8DAA8D;IAC9D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAC,CAAmC;IAEhD,YAAY,IAAI,EAAE,cAAc,EAG/B;IAED,GAAG,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAIvC;IAED,uDAAuD;IACvD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,SAAS,sBAAsB,EAAE,CAElD;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAG3C;IAED,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAIlC;IAED,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAInC;IAED,iFAAiF;IACjF,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAG/B;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED
|
|
1
|
+
{"version":3,"file":"tool-chain.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACpG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAGxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;IAChD,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,KAAK,CAAyB;IACtC,8DAA8D;IAC9D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAC,CAAmC;IAEhD,YAAY,IAAI,EAAE,cAAc,EAG/B;IAED,GAAG,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAIvC;IAED,uDAAuD;IACvD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,8EAA8E;IAC9E,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,SAAS,sBAAsB,EAAE,CAElD;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAG3C;IAED,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAIlC;IAED,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAInC;IAED,iFAAiF;IACjF,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAG/B;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,WAAW;IAIV,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA4CvC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;CAcpB;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,IAAI,kBAAkB,CAEzE","sourcesContent":["import { type Component, Container, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { type ChainState, chainPhrase, chainSegments, chainStats } from \"./tool-chain-summary.js\";\nimport type { ToolExecutionComponent } from \"./tool-execution.js\";\n\n/**\n * A run of consecutive tool calls, rendered as one line in the radar view.\n *\n * The chain owns its blocks rather than sitting beside them, so there is one\n * place that decides whether you are looking at a summary or at the calls. In\n * every view but a collapsed radar it is a plain pass-through container and the\n * blocks render exactly as they always did.\n *\n * A chain closes when the agent next speaks, or when the turn settles. Closing\n * early matters more than it looks: rewriting a line that has scrolled above\n * the viewport forces the TUI into a full redraw, which clears the terminal's\n * scrollback. Chains that close while they are still the bottom of the screen\n * flip for the price of one line.\n */\nexport class ToolChainComponent extends Container {\n\tprivate blocks: ToolExecutionComponent[] = [];\n\tprivate view: ToolOutputView;\n\tprivate state: ChainState = \"running\";\n\t/** Opened into its per-call rows by `app.tools.unfoldOne`. */\n\tprivate opened = false;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(view: ToolOutputView) {\n\t\tsuper();\n\t\tthis.view = view;\n\t}\n\n\tadd(block: ToolExecutionComponent): void {\n\t\tthis.blocks.push(block);\n\t\tthis.addChild(block);\n\t\tthis.memo = undefined;\n\t}\n\n\t/** True until the agent speaks or the turn settles. */\n\tget isOpen(): boolean {\n\t\treturn this.state === \"running\";\n\t}\n\n\tget isOpened(): boolean {\n\t\treturn this.opened;\n\t}\n\n\t/** Whether a summary line is standing in for this chain's calls right now. */\n\tget isSummarised(): boolean {\n\t\treturn this.isCollapsed();\n\t}\n\n\tget isEmpty(): boolean {\n\t\treturn this.blocks.length === 0;\n\t}\n\n\tget toolBlocks(): readonly ToolExecutionComponent[] {\n\t\treturn this.blocks;\n\t}\n\n\t/**\n\t * Settle the chain.\n\t *\n\t * `interrupted` keeps the running rendering, because the settled phrase is a\n\t * claim about what the run amounted to and a run cut off partway through has\n\t * no such claim to make — the same reason an aborted turn's plan items settle\n\t * to cancelled rather than done.\n\t */\n\tclose(outcome: \"done\" | \"interrupted\"): void {\n\t\tthis.state = outcome;\n\t\tthis.memo = undefined;\n\t}\n\n\tsetView(view: ToolOutputView): void {\n\t\tthis.view = view;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setView(view);\n\t}\n\n\t/** The global expand key opens every block, which also opens the chain. */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.opened = expanded;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setExpanded(expanded);\n\t}\n\n\t/** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */\n\tsetOpened(opened: boolean): void {\n\t\tthis.opened = opened;\n\t\tthis.memo = undefined;\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.memo = undefined;\n\t}\n\n\t/**\n\t * Whether this chain is currently drawn as a single summary line.\n\t *\n\t * A chain of one is not summarised. Its phrase would be `Ran npm run check`,\n\t * which is strictly less than the radar row it replaced: the row names the\n\t * tool and how much came back, and the phrase drops both to say the same\n\t * thing in prose. Summarising is only worth a lossy rewrite when there is\n\t * more than one call to fold — and by measurement most chains have exactly\n\t * one, so this is the common case, not an edge.\n\t */\n\tprivate isCollapsed(): boolean {\n\t\treturn this.view === \"radar\" && !this.opened && this.blocks.length > 1;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (!this.isCollapsed()) return super.render(width);\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\n\t\tconst entries = this.blocks.map((block) => block.chainEntry());\n\t\tconst running = this.state === \"running\";\n\t\tconst glyph = running ? \"◐\" : \"●\";\n\t\tconst glyphTone = entries.some((e) => e.isError) ? \"error\" : running ? \"warning\" : \"success\";\n\n\t\t// While it runs the chain shows its shape in order; once it is over, what\n\t\t// it amounted to. See tool-chain-summary.ts for why they differ.\n\t\tlet leftPlain: string;\n\t\tlet leftStyled: string;\n\t\tif (this.state === \"done\") {\n\t\t\tconst phrase = chainPhrase(entries);\n\t\t\tleftPlain = phrase;\n\t\t\tleftStyled = theme.fg(\"toolTitle\", phrase);\n\t\t} else {\n\t\t\tconst segments = chainSegments(entries);\n\t\t\tconst sep = \" › \";\n\t\t\tleftPlain = segments.map((s) => s.label).join(sep) + (running ? \"…\" : \"\");\n\t\t\tleftStyled =\n\t\t\t\tsegments\n\t\t\t\t\t.map((s) =>\n\t\t\t\t\t\ttheme.fg(s.tone === \"error\" ? \"error\" : s.tone === \"running\" ? \"warning\" : \"toolTitle\", s.label),\n\t\t\t\t\t)\n\t\t\t\t\t.join(theme.fg(\"dim\", sep)) + (running ? theme.fg(\"dim\", \"…\") : \"\");\n\t\t}\n\n\t\tconst stats = chainStats(entries, this.state);\n\t\t// \" ● \" + left, then stats flush right when there is room for both.\n\t\tconst prefix = `${glyph} `;\n\t\tconst budget = Math.max(0, width - 1 - visibleWidth(prefix));\n\t\tlet line: string;\n\t\tif (visibleWidth(leftPlain) + 2 + visibleWidth(stats) <= budget) {\n\t\t\tconst pad = \" \".repeat(budget - visibleWidth(leftPlain) - visibleWidth(stats));\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${leftStyled}${pad}${theme.fg(\"muted\", stats)}`;\n\t\t} else {\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${truncateToWidth(leftStyled, budget)}`;\n\t\t}\n\n\t\tconst out = [line, ...this.failureLines(width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n\n\t/**\n\t * Each failed call's reason, indented under the chain line.\n\t *\n\t * A collapsed chain hides its blocks, so without this a failure would be a\n\t * count in the stats and nothing else. Whatever else these views fold away,\n\t * they never fold away why something broke.\n\t */\n\tprivate failureLines(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const block of this.blocks) {\n\t\t\tconst entry = block.chainEntry();\n\t\t\tif (!entry.isError) continue;\n\t\t\tconst header = ` ${theme.fg(\"error\", \"✗\")} ${theme.fg(\"toolTitle\", entry.tool)} ${theme.fg(\"toolOutput\", entry.subject)}`;\n\t\t\tlines.push(truncateToWidth(header, width));\n\t\t\tfor (const raw of block.errorText().split(\"\\n\")) {\n\t\t\t\tif (!raw.trim()) continue;\n\t\t\t\tlines.push(truncateToWidth(` ${theme.fg(\"toolOutput\", raw)}`, width));\n\t\t\t}\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/** Narrow a transcript child to a chain. */\nexport function isToolChain(child: Component): child is ToolChainComponent {\n\treturn child instanceof ToolChainComponent;\n}\n"]}
|
|
@@ -38,6 +38,10 @@ export class ToolChainComponent extends Container {
|
|
|
38
38
|
get isOpened() {
|
|
39
39
|
return this.opened;
|
|
40
40
|
}
|
|
41
|
+
/** Whether a summary line is standing in for this chain's calls right now. */
|
|
42
|
+
get isSummarised() {
|
|
43
|
+
return this.isCollapsed();
|
|
44
|
+
}
|
|
41
45
|
get isEmpty() {
|
|
42
46
|
return this.blocks.length === 0;
|
|
43
47
|
}
|
|
@@ -78,9 +82,18 @@ export class ToolChainComponent extends Container {
|
|
|
78
82
|
super.invalidate();
|
|
79
83
|
this.memo = undefined;
|
|
80
84
|
}
|
|
81
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Whether this chain is currently drawn as a single summary line.
|
|
87
|
+
*
|
|
88
|
+
* A chain of one is not summarised. Its phrase would be `Ran npm run check`,
|
|
89
|
+
* which is strictly less than the radar row it replaced: the row names the
|
|
90
|
+
* tool and how much came back, and the phrase drops both to say the same
|
|
91
|
+
* thing in prose. Summarising is only worth a lossy rewrite when there is
|
|
92
|
+
* more than one call to fold — and by measurement most chains have exactly
|
|
93
|
+
* one, so this is the common case, not an edge.
|
|
94
|
+
*/
|
|
82
95
|
isCollapsed() {
|
|
83
|
-
return this.view === "radar" && !this.opened && this.blocks.length >
|
|
96
|
+
return this.view === "radar" && !this.opened && this.blocks.length > 1;
|
|
84
97
|
}
|
|
85
98
|
render(width) {
|
|
86
99
|
if (!this.isCollapsed())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-chain.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, Container, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { type ChainState, chainPhrase, chainSegments, chainStats } from \"./tool-chain-summary.js\";\nimport type { ToolExecutionComponent } from \"./tool-execution.js\";\n\n/**\n * A run of consecutive tool calls, rendered as one line in the radar view.\n *\n * The chain owns its blocks rather than sitting beside them, so there is one\n * place that decides whether you are looking at a summary or at the calls. In\n * every view but a collapsed radar it is a plain pass-through container and the\n * blocks render exactly as they always did.\n *\n * A chain closes when the agent next speaks, or when the turn settles. Closing\n * early matters more than it looks: rewriting a line that has scrolled above\n * the viewport forces the TUI into a full redraw, which clears the terminal's\n * scrollback. Chains that close while they are still the bottom of the screen\n * flip for the price of one line.\n */\nexport class ToolChainComponent extends Container {\n\tprivate blocks: ToolExecutionComponent[] = [];\n\tprivate view: ToolOutputView;\n\tprivate state: ChainState = \"running\";\n\t/** Opened into its per-call rows by `app.tools.unfoldOne`. */\n\tprivate opened = false;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(view: ToolOutputView) {\n\t\tsuper();\n\t\tthis.view = view;\n\t}\n\n\tadd(block: ToolExecutionComponent): void {\n\t\tthis.blocks.push(block);\n\t\tthis.addChild(block);\n\t\tthis.memo = undefined;\n\t}\n\n\t/** True until the agent speaks or the turn settles. */\n\tget isOpen(): boolean {\n\t\treturn this.state === \"running\";\n\t}\n\n\tget isOpened(): boolean {\n\t\treturn this.opened;\n\t}\n\n\tget isEmpty(): boolean {\n\t\treturn this.blocks.length === 0;\n\t}\n\n\tget toolBlocks(): readonly ToolExecutionComponent[] {\n\t\treturn this.blocks;\n\t}\n\n\t/**\n\t * Settle the chain.\n\t *\n\t * `interrupted` keeps the running rendering, because the settled phrase is a\n\t * claim about what the run amounted to and a run cut off partway through has\n\t * no such claim to make — the same reason an aborted turn's plan items settle\n\t * to cancelled rather than done.\n\t */\n\tclose(outcome: \"done\" | \"interrupted\"): void {\n\t\tthis.state = outcome;\n\t\tthis.memo = undefined;\n\t}\n\n\tsetView(view: ToolOutputView): void {\n\t\tthis.view = view;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setView(view);\n\t}\n\n\t/** The global expand key opens every block, which also opens the chain. */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.opened = expanded;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setExpanded(expanded);\n\t}\n\n\t/** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */\n\tsetOpened(opened: boolean): void {\n\t\tthis.opened = opened;\n\t\tthis.memo = undefined;\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.memo = undefined;\n\t}\n\n\t/** Whether this chain is currently drawn as a single summary line. */\n\tprivate isCollapsed(): boolean {\n\t\treturn this.view === \"radar\" && !this.opened && this.blocks.length > 0;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (!this.isCollapsed()) return super.render(width);\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\n\t\tconst entries = this.blocks.map((block) => block.chainEntry());\n\t\tconst running = this.state === \"running\";\n\t\tconst glyph = running ? \"◐\" : \"●\";\n\t\tconst glyphTone = entries.some((e) => e.isError) ? \"error\" : running ? \"warning\" : \"success\";\n\n\t\t// While it runs the chain shows its shape in order; once it is over, what\n\t\t// it amounted to. See tool-chain-summary.ts for why they differ.\n\t\tlet leftPlain: string;\n\t\tlet leftStyled: string;\n\t\tif (this.state === \"done\") {\n\t\t\tconst phrase = chainPhrase(entries);\n\t\t\tleftPlain = phrase;\n\t\t\tleftStyled = theme.fg(\"toolTitle\", phrase);\n\t\t} else {\n\t\t\tconst segments = chainSegments(entries);\n\t\t\tconst sep = \" › \";\n\t\t\tleftPlain = segments.map((s) => s.label).join(sep) + (running ? \"…\" : \"\");\n\t\t\tleftStyled =\n\t\t\t\tsegments\n\t\t\t\t\t.map((s) =>\n\t\t\t\t\t\ttheme.fg(s.tone === \"error\" ? \"error\" : s.tone === \"running\" ? \"warning\" : \"toolTitle\", s.label),\n\t\t\t\t\t)\n\t\t\t\t\t.join(theme.fg(\"dim\", sep)) + (running ? theme.fg(\"dim\", \"…\") : \"\");\n\t\t}\n\n\t\tconst stats = chainStats(entries, this.state);\n\t\t// \" ● \" + left, then stats flush right when there is room for both.\n\t\tconst prefix = `${glyph} `;\n\t\tconst budget = Math.max(0, width - 1 - visibleWidth(prefix));\n\t\tlet line: string;\n\t\tif (visibleWidth(leftPlain) + 2 + visibleWidth(stats) <= budget) {\n\t\t\tconst pad = \" \".repeat(budget - visibleWidth(leftPlain) - visibleWidth(stats));\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${leftStyled}${pad}${theme.fg(\"muted\", stats)}`;\n\t\t} else {\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${truncateToWidth(leftStyled, budget)}`;\n\t\t}\n\n\t\tconst out = [line, ...this.failureLines(width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n\n\t/**\n\t * Each failed call's reason, indented under the chain line.\n\t *\n\t * A collapsed chain hides its blocks, so without this a failure would be a\n\t * count in the stats and nothing else. Whatever else these views fold away,\n\t * they never fold away why something broke.\n\t */\n\tprivate failureLines(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const block of this.blocks) {\n\t\t\tconst entry = block.chainEntry();\n\t\t\tif (!entry.isError) continue;\n\t\t\tconst header = ` ${theme.fg(\"error\", \"✗\")} ${theme.fg(\"toolTitle\", entry.tool)} ${theme.fg(\"toolOutput\", entry.subject)}`;\n\t\t\tlines.push(truncateToWidth(header, width));\n\t\t\tfor (const raw of block.errorText().split(\"\\n\")) {\n\t\t\t\tif (!raw.trim()) continue;\n\t\t\t\tlines.push(truncateToWidth(` ${theme.fg(\"toolOutput\", raw)}`, width));\n\t\t\t}\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/** Narrow a transcript child to a chain. */\nexport function isToolChain(child: Component): child is ToolChainComponent {\n\treturn child instanceof ToolChainComponent;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-chain.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-chain.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, Container, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { type ChainState, chainPhrase, chainSegments, chainStats } from \"./tool-chain-summary.js\";\nimport type { ToolExecutionComponent } from \"./tool-execution.js\";\n\n/**\n * A run of consecutive tool calls, rendered as one line in the radar view.\n *\n * The chain owns its blocks rather than sitting beside them, so there is one\n * place that decides whether you are looking at a summary or at the calls. In\n * every view but a collapsed radar it is a plain pass-through container and the\n * blocks render exactly as they always did.\n *\n * A chain closes when the agent next speaks, or when the turn settles. Closing\n * early matters more than it looks: rewriting a line that has scrolled above\n * the viewport forces the TUI into a full redraw, which clears the terminal's\n * scrollback. Chains that close while they are still the bottom of the screen\n * flip for the price of one line.\n */\nexport class ToolChainComponent extends Container {\n\tprivate blocks: ToolExecutionComponent[] = [];\n\tprivate view: ToolOutputView;\n\tprivate state: ChainState = \"running\";\n\t/** Opened into its per-call rows by `app.tools.unfoldOne`. */\n\tprivate opened = false;\n\tprivate memo?: { width: number; out: string[] };\n\n\tconstructor(view: ToolOutputView) {\n\t\tsuper();\n\t\tthis.view = view;\n\t}\n\n\tadd(block: ToolExecutionComponent): void {\n\t\tthis.blocks.push(block);\n\t\tthis.addChild(block);\n\t\tthis.memo = undefined;\n\t}\n\n\t/** True until the agent speaks or the turn settles. */\n\tget isOpen(): boolean {\n\t\treturn this.state === \"running\";\n\t}\n\n\tget isOpened(): boolean {\n\t\treturn this.opened;\n\t}\n\n\t/** Whether a summary line is standing in for this chain's calls right now. */\n\tget isSummarised(): boolean {\n\t\treturn this.isCollapsed();\n\t}\n\n\tget isEmpty(): boolean {\n\t\treturn this.blocks.length === 0;\n\t}\n\n\tget toolBlocks(): readonly ToolExecutionComponent[] {\n\t\treturn this.blocks;\n\t}\n\n\t/**\n\t * Settle the chain.\n\t *\n\t * `interrupted` keeps the running rendering, because the settled phrase is a\n\t * claim about what the run amounted to and a run cut off partway through has\n\t * no such claim to make — the same reason an aborted turn's plan items settle\n\t * to cancelled rather than done.\n\t */\n\tclose(outcome: \"done\" | \"interrupted\"): void {\n\t\tthis.state = outcome;\n\t\tthis.memo = undefined;\n\t}\n\n\tsetView(view: ToolOutputView): void {\n\t\tthis.view = view;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setView(view);\n\t}\n\n\t/** The global expand key opens every block, which also opens the chain. */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.opened = expanded;\n\t\tthis.memo = undefined;\n\t\tfor (const block of this.blocks) block.setExpanded(expanded);\n\t}\n\n\t/** `alt+u` in radar: open this chain into its per-call rows, or fold it back. */\n\tsetOpened(opened: boolean): void {\n\t\tthis.opened = opened;\n\t\tthis.memo = undefined;\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.memo = undefined;\n\t}\n\n\t/**\n\t * Whether this chain is currently drawn as a single summary line.\n\t *\n\t * A chain of one is not summarised. Its phrase would be `Ran npm run check`,\n\t * which is strictly less than the radar row it replaced: the row names the\n\t * tool and how much came back, and the phrase drops both to say the same\n\t * thing in prose. Summarising is only worth a lossy rewrite when there is\n\t * more than one call to fold — and by measurement most chains have exactly\n\t * one, so this is the common case, not an edge.\n\t */\n\tprivate isCollapsed(): boolean {\n\t\treturn this.view === \"radar\" && !this.opened && this.blocks.length > 1;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (!this.isCollapsed()) return super.render(width);\n\t\tif (this.memo && this.memo.width === width) return this.memo.out;\n\n\t\tconst entries = this.blocks.map((block) => block.chainEntry());\n\t\tconst running = this.state === \"running\";\n\t\tconst glyph = running ? \"◐\" : \"●\";\n\t\tconst glyphTone = entries.some((e) => e.isError) ? \"error\" : running ? \"warning\" : \"success\";\n\n\t\t// While it runs the chain shows its shape in order; once it is over, what\n\t\t// it amounted to. See tool-chain-summary.ts for why they differ.\n\t\tlet leftPlain: string;\n\t\tlet leftStyled: string;\n\t\tif (this.state === \"done\") {\n\t\t\tconst phrase = chainPhrase(entries);\n\t\t\tleftPlain = phrase;\n\t\t\tleftStyled = theme.fg(\"toolTitle\", phrase);\n\t\t} else {\n\t\t\tconst segments = chainSegments(entries);\n\t\t\tconst sep = \" › \";\n\t\t\tleftPlain = segments.map((s) => s.label).join(sep) + (running ? \"…\" : \"\");\n\t\t\tleftStyled =\n\t\t\t\tsegments\n\t\t\t\t\t.map((s) =>\n\t\t\t\t\t\ttheme.fg(s.tone === \"error\" ? \"error\" : s.tone === \"running\" ? \"warning\" : \"toolTitle\", s.label),\n\t\t\t\t\t)\n\t\t\t\t\t.join(theme.fg(\"dim\", sep)) + (running ? theme.fg(\"dim\", \"…\") : \"\");\n\t\t}\n\n\t\tconst stats = chainStats(entries, this.state);\n\t\t// \" ● \" + left, then stats flush right when there is room for both.\n\t\tconst prefix = `${glyph} `;\n\t\tconst budget = Math.max(0, width - 1 - visibleWidth(prefix));\n\t\tlet line: string;\n\t\tif (visibleWidth(leftPlain) + 2 + visibleWidth(stats) <= budget) {\n\t\t\tconst pad = \" \".repeat(budget - visibleWidth(leftPlain) - visibleWidth(stats));\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${leftStyled}${pad}${theme.fg(\"muted\", stats)}`;\n\t\t} else {\n\t\t\tline = ` ${theme.fg(glyphTone, prefix)}${truncateToWidth(leftStyled, budget)}`;\n\t\t}\n\n\t\tconst out = [line, ...this.failureLines(width)];\n\t\tthis.memo = { width, out };\n\t\treturn out;\n\t}\n\n\t/**\n\t * Each failed call's reason, indented under the chain line.\n\t *\n\t * A collapsed chain hides its blocks, so without this a failure would be a\n\t * count in the stats and nothing else. Whatever else these views fold away,\n\t * they never fold away why something broke.\n\t */\n\tprivate failureLines(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const block of this.blocks) {\n\t\t\tconst entry = block.chainEntry();\n\t\t\tif (!entry.isError) continue;\n\t\t\tconst header = ` ${theme.fg(\"error\", \"✗\")} ${theme.fg(\"toolTitle\", entry.tool)} ${theme.fg(\"toolOutput\", entry.subject)}`;\n\t\t\tlines.push(truncateToWidth(header, width));\n\t\t\tfor (const raw of block.errorText().split(\"\\n\")) {\n\t\t\t\tif (!raw.trim()) continue;\n\t\t\t\tlines.push(truncateToWidth(` ${theme.fg(\"toolOutput\", raw)}`, width));\n\t\t\t}\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/** Narrow a transcript child to a chain. */\nexport function isToolChain(child: Component): child is ToolChainComponent {\n\treturn child instanceof ToolChainComponent;\n}\n"]}
|
|
@@ -344,6 +344,25 @@ export declare class InteractiveMode {
|
|
|
344
344
|
* what is in front of me"), which is why it does not.
|
|
345
345
|
*/
|
|
346
346
|
private cycleToolOutputView;
|
|
347
|
+
/**
|
|
348
|
+
* How much of a thinking trace this view shows.
|
|
349
|
+
*
|
|
350
|
+
* Radar drops them outright, regardless of the setting. The dial is one
|
|
351
|
+
* question asked once — "how much do I ever want to see" — and a view whose
|
|
352
|
+
* whole job is to fold a run of tool calls down to a row cannot then spend
|
|
353
|
+
* forty lines on the reasoning that led to it. Folding to the label is not
|
|
354
|
+
* enough either: a message that only thought and called tools has nothing
|
|
355
|
+
* else on screen once its calls join the chain, so its label would stand
|
|
356
|
+
* alone under the summary and one row per chain would become one row plus a
|
|
357
|
+
* label per call.
|
|
358
|
+
*
|
|
359
|
+
* It is also what keeps a running chain on screen. A chain stays open across
|
|
360
|
+
* a thinking block (thinking is not text, so it is not a chain boundary), and
|
|
361
|
+
* a trace rendering below pushes the chain's own summary line off the top —
|
|
362
|
+
* where every subsequent call rewrites a line above the viewport and forces
|
|
363
|
+
* the full redraw that clears terminal scrollback.
|
|
364
|
+
*/
|
|
365
|
+
private thinkingDisplayForView;
|
|
347
366
|
private applyToolOutputView;
|
|
348
367
|
private setToolsExpanded;
|
|
349
368
|
private cycleAgentMode;
|