@iowarp/clio-coder 0.3.2 → 0.3.3
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 +229 -457
- package/README.md +2 -2
- package/dist/{acp-BIYHVZIM.js → acp-P2AQILE2.js} +2 -2
- package/dist/{agents-YT6SSRIT.js → agents-72W3BI7I.js} +8 -8
- package/dist/assets/codewiki.json +1 -1
- package/dist/{chunk-WMSVI4G2.js → chunk-2DJ2KNFG.js} +3 -3
- package/dist/{chunk-2EHAIA3X.js → chunk-2TLUCQVG.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-4XUGQOHA.js} +2 -2
- package/dist/{chunk-IGLFWIYI.js → chunk-5UFT4SUX.js} +2 -2
- package/dist/{chunk-AO4RKG4M.js → chunk-6SGHMWE3.js} +3 -3
- package/dist/{chunk-X75S7HFS.js → chunk-COU2UHX6.js} +45 -19
- package/dist/{chunk-KJ5LWLOE.js → chunk-DSELYM6W.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-DUYJ5IO6.js} +2 -2
- package/dist/{chunk-EPVUXGXG.js → chunk-FNTMWMX5.js} +9 -9
- package/dist/{chunk-MNA4JGU4.js → chunk-J7CWMCQD.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-KZWTDYJF.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-LM5TQCJZ.js} +3 -3
- package/dist/{chunk-OHHN2SO4.js → chunk-LW6DSM3M.js} +7 -7
- package/dist/{chunk-MQSRRFWA.js → chunk-LWLEKMDQ.js} +56 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-OC7FIQPC.js} +2 -2
- package/dist/{chunk-3ZXDFGR5.js → chunk-PAJK6MAQ.js} +2 -2
- package/dist/{chunk-7EYHLWU7.js → chunk-PIWWS5BL.js} +3 -3
- package/dist/{chunk-6EJV5X2W.js → chunk-SRDMMSEP.js} +3 -3
- package/dist/{chunk-ARBGF5F7.js → chunk-TZK7PACC.js} +2 -2
- package/dist/{chunk-QTYWRVRA.js → chunk-UFIIWP2H.js} +2 -2
- package/dist/{chunk-J5HN4RYU.js → chunk-V6RTAOC2.js} +2 -2
- package/dist/{chunk-SRF2PJNW.js → chunk-VPAYEGVX.js} +2 -2
- package/dist/{chunk-77VKQEHF.js → chunk-X6IAEBZR.js} +2 -2
- package/dist/{chunk-4KLWL3UC.js → chunk-XBXAASKX.js} +2 -2
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/cli/index.js +17 -17
- package/dist/{clio-4LY5K2AC.js → clio-JOU4FXVA.js} +2 -2
- package/dist/{config-GTLUW2PR.js → config-XCDVKR23.js} +7 -7
- package/dist/{configure-R6A64DHX.js → configure-4GAP54ZW.js} +5 -5
- package/dist/{context-RW5HC47S.js → context-4UOGGLQ5.js} +6 -6
- package/dist/{context-JFZEJ7W5.js → context-77FM5DV5.js} +9 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-XXJRLCJJ.js} +6 -6
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-QPRDDBDX.js} +7 -7
- package/dist/{doctor-KI767GSN.js → doctor-HR46URBJ.js} +5 -5
- package/dist/{evidence-UA6AWDQQ.js → evidence-6HG2PY2B.js} +4 -4
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7YU3NCY.js} +4 -4
- package/dist/{fleet-Q7UOMUSG.js → fleet-VY3HHKN6.js} +15 -15
- package/dist/{init-WBB65ZHQ.js → init-JYGXI3FK.js} +12 -12
- package/dist/{memory-MD3O64RI.js → memory-WFZMGYHX.js} +5 -5
- package/dist/{models-BZU34YWD.js → models-I5QWSEOM.js} +7 -7
- package/dist/{monitor-MEQA5C3I.js → monitor-GE4ID3IA.js} +5 -5
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-EM5MC3HM.js} +1482 -1055
- package/dist/{run-IV4Q6RLN.js → run-ZU3QMZPZ.js} +18 -18
- package/dist/{skills-LQEKRDTN.js → skills-X5VXCRNQ.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WKIHWTHR.js} +5 -5
- package/dist/{targets-C4SSGQOB.js → targets-SNCPI2NR.js} +8 -8
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-BNAHVHBS.js} +2 -2
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-JQHHPQ4K.js} +7 -7
- package/dist/{usage-GV4PKT3M.js → usage-OR4O5SMZ.js} +5 -5
- package/dist/{verify-G6V4D2G7.js → verify-375KUB3Y.js} +4 -4
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-UEXP2ARI.js} +11 -11
- package/dist/worker/entry.js +8 -8
- package/docs/README.md +3 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +1 -1
- package/docs/configuration-and-targets.md +1 -1
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +2 -2
- package/docs/documentation-guide.md +1 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +2 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +1 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +3 -3
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +1 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +30 -30
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +2 -2
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +2 -2
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +2 -2
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +1 -1
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/domains/config/keybindings.ts +3 -3
- package/src/interactive/chat-panel.ts +555 -244
- package/src/interactive/chat-renderer.ts +50 -19
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +12 -7
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +151 -56
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/tools/builtin-tool-catalog.ts +7 -1
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +6 -0
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
} from "../../engine/tui.js";
|
|
14
14
|
import type { AskUserAnswer, AskUserQuestion, AskUserResult } from "../../tools/ask-user.js";
|
|
15
15
|
import { ASK_USER_OTHER_LABEL, cancelledAskUserResult } from "../../tools/ask-user.js";
|
|
16
|
-
import { buildHint, DEFAULT_SELECT_THEME, showClioOverlayFrame } from "../overlay-frame.js";
|
|
16
|
+
import { buildHint, DEFAULT_SELECT_THEME, type HintEntry, showClioOverlayFrame } from "../overlay-frame.js";
|
|
17
17
|
import { type ClioToken, clioTheme, dotSep, GLYPH, screenTitle } from "../theme/index.js";
|
|
18
|
+
import { nextContentScrollOffset, type ViewScrollAction } from "../view/view-overlay.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* The border and title token while a decision is pending.
|
|
@@ -54,7 +55,7 @@ export const ASK_USER_DEFAULT_SURFACE: AskUserSurface = "panel";
|
|
|
54
55
|
interface AskUserSurfaceGeometry {
|
|
55
56
|
/** Box width in columns; zero fills the row. The frame clamps to the terminal. */
|
|
56
57
|
width: number;
|
|
57
|
-
/** Rows the body may use, before the borders. */
|
|
58
|
+
/** Rows the body may use, before the borders. Zero means every row the frame has. */
|
|
58
59
|
maxInnerRows: number;
|
|
59
60
|
anchor: NonNullable<OverlayOptions["anchor"]>;
|
|
60
61
|
margin: NonNullable<OverlayOptions["margin"]>;
|
|
@@ -74,12 +75,18 @@ interface AskUserSurfaceGeometry {
|
|
|
74
75
|
export const ASK_USER_SURFACE_GEOMETRY: Record<AskUserSurface, AskUserSurfaceGeometry> = {
|
|
75
76
|
compact: { width: 68, maxInnerRows: 10, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
|
|
76
77
|
panel: { width: 88, maxInnerRows: 18, anchor: "center", margin: 1 },
|
|
77
|
-
interview
|
|
78
|
+
// The interview is a workspace, not a modal. A cap of 42 turned a 66-row
|
|
79
|
+
// terminal into a centered box with twenty blank rows around it, so the
|
|
80
|
+
// interview takes every row the frame has and lays its own chrome out inside
|
|
81
|
+
// them. The one-row margin keeps the border off the top row and the footer.
|
|
82
|
+
interview: { width: 0, maxInnerRows: 0, anchor: "center", margin: 1 },
|
|
78
83
|
};
|
|
79
84
|
|
|
80
85
|
const ASK_USER_FRAME_ROWS = 2;
|
|
81
86
|
const ASK_USER_FRAME_AND_MARGIN_ROWS = 4;
|
|
82
87
|
const MIN_VISIBLE_OPTIONS = 3;
|
|
88
|
+
/** Interview rows spent on chrome before an option row: strip, header, blanks, status, one question row. */
|
|
89
|
+
const INTERVIEW_CHROME_ROWS = 7;
|
|
83
90
|
const MAX_VISIBLE_OPTIONS = 12;
|
|
84
91
|
const ELLIPSIS = "…";
|
|
85
92
|
|
|
@@ -227,6 +234,14 @@ class AskUserOverlayView implements Component {
|
|
|
227
234
|
private list: SelectList | null = null;
|
|
228
235
|
private input: Input | null = null;
|
|
229
236
|
private resolveCurrent: ((result: AskUserResult) => void) | null = null;
|
|
237
|
+
/** Rows of the interview's scrollable middle already scrolled past. */
|
|
238
|
+
private contentScrollOffset = 0;
|
|
239
|
+
/** Whether the last interview render had more content than the region held. */
|
|
240
|
+
private contentOverflows = false;
|
|
241
|
+
/** Rows the scroll region drew on the last render, for a page-sized jump. */
|
|
242
|
+
private contentRegionHeight = 1;
|
|
243
|
+
/** Rows the scroll region wanted on the last render. */
|
|
244
|
+
private contentTotalRows = 0;
|
|
230
245
|
|
|
231
246
|
constructor(private readonly deps: AskUserOverlayViewDeps) {}
|
|
232
247
|
|
|
@@ -240,6 +255,7 @@ class AskUserOverlayView implements Component {
|
|
|
240
255
|
this.states = this.questions.map((question) => createQuestionState(question));
|
|
241
256
|
this.list = null;
|
|
242
257
|
this.input = null;
|
|
258
|
+
this.resetContentScroll();
|
|
243
259
|
// Before the controls, because how many option rows fit is a property of
|
|
244
260
|
// the box this round is about to be drawn in.
|
|
245
261
|
this.setSurface(askUserSurface(this.questions, this.history.length));
|
|
@@ -283,6 +299,10 @@ class AskUserOverlayView implements Component {
|
|
|
283
299
|
const state = this.currentState();
|
|
284
300
|
if (!question || !state) return;
|
|
285
301
|
|
|
302
|
+
// Scrolling the transcript must not cost the operator their place in the
|
|
303
|
+
// options, so the region has keys of its own that neither control claims.
|
|
304
|
+
if (this.handleScrollInput(data, state.mode)) return;
|
|
305
|
+
|
|
286
306
|
if (state.mode === "text") {
|
|
287
307
|
if (this.isTextModePreviousKey(data)) {
|
|
288
308
|
this.goToRelativeQuestion(-1);
|
|
@@ -331,21 +351,106 @@ class AskUserOverlayView implements Component {
|
|
|
331
351
|
const question = this.currentQuestion();
|
|
332
352
|
if (!question) return [clioTheme().fg("muted", "No questions.")];
|
|
333
353
|
|
|
334
|
-
|
|
354
|
+
if (this.surface === "interview") return this.renderInterview(question, safeWidth, maxRows);
|
|
355
|
+
|
|
356
|
+
this.contentOverflows = false;
|
|
335
357
|
const controlLines = this.renderControlLines(safeWidth);
|
|
336
|
-
const ledgerLines = interview ? this.renderAnswerLedger(safeWidth) : [];
|
|
337
358
|
const statusLines = this.status.length > 0 ? ["", fitLine(clioTheme().fg("dim", this.status), safeWidth)] : [];
|
|
338
|
-
const stripLines = interview ? [this.renderQuestionStrip(safeWidth), ""] : [];
|
|
339
359
|
const headerLine = this.renderQuestionHeader(question, safeWidth);
|
|
340
360
|
const headerLines = headerLine.length > 0 ? [headerLine] : [];
|
|
341
|
-
const spent =
|
|
342
|
-
stripLines.length + headerLines.length + statusLines.length + 1 + controlLines.length + ledgerLines.length;
|
|
361
|
+
const spent = headerLines.length + statusLines.length + 1 + controlLines.length;
|
|
343
362
|
const questionLines = wrapTextWithAnsi(question.question, safeWidth).slice(0, Math.max(1, maxRows - spent));
|
|
344
363
|
|
|
345
|
-
return [...
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
return [...headerLines, ...questionLines, ...statusLines, "", ...controlLines].slice(0, maxRows);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The interview, as a workspace with fixed chrome and a scrolling middle.
|
|
369
|
+
*
|
|
370
|
+
* The round strip and the question header hold the top; the status line and
|
|
371
|
+
* the active control hold the bottom, at their full row cost, so the thing the
|
|
372
|
+
* operator has to act on is never the thing that gets clipped. The question
|
|
373
|
+
* text and the answer ledger share whatever is left and scroll through it.
|
|
374
|
+
* Everything is re-derived per render, so a resize is just the next frame.
|
|
375
|
+
*/
|
|
376
|
+
private renderInterview(question: AskUserQuestion, width: number, maxRows: number): string[] {
|
|
377
|
+
const theme = clioTheme();
|
|
378
|
+
const controlLines = this.renderControlLines(width);
|
|
379
|
+
const statusLines = this.status.length > 0 ? ["", fitLine(theme.fg("dim", this.status), width)] : [];
|
|
380
|
+
const headerLine = this.renderQuestionHeader(question, width);
|
|
381
|
+
const headerLines = headerLine.length > 0 ? [headerLine] : [];
|
|
382
|
+
const stripLines = [this.renderQuestionStrip(width), ""];
|
|
383
|
+
const content = [...wrapTextWithAnsi(question.question, width), ...this.renderAnswerLedger(width)];
|
|
384
|
+
|
|
385
|
+
// Chrome sheds from the outside in when the terminal is too short to carry
|
|
386
|
+
// all of it: the round strip first, then the header, then the status, and
|
|
387
|
+
// the control only if a terminal is short enough that nothing else is left.
|
|
388
|
+
let top = [...stripLines, ...headerLines];
|
|
389
|
+
let bottom = [...statusLines, "", ...controlLines];
|
|
390
|
+
const region = (): number => maxRows - top.length - bottom.length;
|
|
391
|
+
if (region() < 1) top = [...headerLines];
|
|
392
|
+
if (region() < 1) top = [];
|
|
393
|
+
if (region() < 1) bottom = ["", ...controlLines];
|
|
394
|
+
if (region() < 1) bottom = [...controlLines];
|
|
395
|
+
const regionRows = Math.max(1, region());
|
|
396
|
+
|
|
397
|
+
const overflows = content.length > regionRows;
|
|
398
|
+
this.contentOverflows = overflows;
|
|
399
|
+
this.contentTotalRows = content.length;
|
|
400
|
+
// One row of the region pays for the indicator when there is more than the
|
|
401
|
+
// region holds, so the marker never covers a row it is reporting on.
|
|
402
|
+
const viewRows = overflows ? Math.max(1, regionRows - 1) : regionRows;
|
|
403
|
+
this.contentRegionHeight = viewRows;
|
|
404
|
+
this.contentScrollOffset = Math.max(0, Math.min(this.contentScrollOffset, Math.max(0, content.length - viewRows)));
|
|
405
|
+
|
|
406
|
+
const visible = content.slice(this.contentScrollOffset, this.contentScrollOffset + viewRows);
|
|
407
|
+
const middle = [...visible];
|
|
408
|
+
if (overflows) middle.push(fitLine(theme.fg("dim", this.scrollIndicator(content.length, viewRows)), width));
|
|
409
|
+
while (middle.length < regionRows) middle.push("");
|
|
410
|
+
|
|
411
|
+
return [...top, ...middle, ...bottom].slice(0, maxRows);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private scrollIndicator(total: number, viewRows: number): string {
|
|
415
|
+
const above = this.contentScrollOffset;
|
|
416
|
+
const below = Math.max(0, total - viewRows - above);
|
|
417
|
+
const parts: string[] = [];
|
|
418
|
+
if (above > 0) parts.push(`↑ ${above} more`);
|
|
419
|
+
if (below > 0) parts.push(`↓ ${below} more`);
|
|
420
|
+
parts.push(`(${Math.min(total, above + viewRows)}/${total})`);
|
|
421
|
+
return parts.join(" ");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** True when the key belonged to the scroll region rather than to a control. */
|
|
425
|
+
private handleScrollInput(data: string, mode: Mode): boolean {
|
|
426
|
+
if (this.surface !== "interview") return false;
|
|
427
|
+
const action = this.scrollAction(data, mode);
|
|
428
|
+
if (!action) return false;
|
|
429
|
+
const next = nextContentScrollOffset(
|
|
430
|
+
this.contentScrollOffset,
|
|
431
|
+
this.contentTotalRows,
|
|
432
|
+
this.contentRegionHeight,
|
|
433
|
+
action,
|
|
348
434
|
);
|
|
435
|
+
if (next !== this.contentScrollOffset) {
|
|
436
|
+
this.contentScrollOffset = next;
|
|
437
|
+
this.deps.requestRender();
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private scrollAction(data: string, mode: Mode): ViewScrollAction | null {
|
|
443
|
+
if (matchesKey(data, "pageUp")) return "page-up";
|
|
444
|
+
if (matchesKey(data, "pageDown")) return "page-down";
|
|
445
|
+
// Ctrl+U and Ctrl+D are line-kill and half-page in a text field, so they
|
|
446
|
+
// stay with the input while one is focused.
|
|
447
|
+
if (mode === "select" && matchesKey(data, "ctrl+u")) return "half-up";
|
|
448
|
+
if (mode === "select" && matchesKey(data, "ctrl+d")) return "half-down";
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private resetContentScroll(): void {
|
|
453
|
+
this.contentScrollOffset = 0;
|
|
349
454
|
}
|
|
350
455
|
|
|
351
456
|
footerHint(): string {
|
|
@@ -357,11 +462,11 @@ class AskUserOverlayView implements Component {
|
|
|
357
462
|
if (!question || !state) return buildHint([]);
|
|
358
463
|
if (state.mode === "text") {
|
|
359
464
|
return this.questions.length > 1
|
|
360
|
-
?
|
|
465
|
+
? this.withScrollHint([
|
|
361
466
|
{ key: "Enter", verb: "submit" },
|
|
362
467
|
{ key: "Alt+Left/Right", verb: "question" },
|
|
363
468
|
])
|
|
364
|
-
:
|
|
469
|
+
: this.withScrollHint([{ key: "Enter", verb: "submit" }]);
|
|
365
470
|
}
|
|
366
471
|
// `accept` rather than `select` or `commit`: the footer of a decision prompt
|
|
367
472
|
// has one job, which is to name the key that answers it. Esc keeps the
|
|
@@ -369,22 +474,32 @@ class AskUserOverlayView implements Component {
|
|
|
369
474
|
// interview together.
|
|
370
475
|
if (question.multi_select === true) {
|
|
371
476
|
return this.questions.length > 1
|
|
372
|
-
?
|
|
477
|
+
? this.withScrollHint([
|
|
373
478
|
{ key: "Left/Right", verb: "question" },
|
|
374
479
|
{ key: "Space", verb: "toggle" },
|
|
375
480
|
{ key: "Enter", verb: "accept" },
|
|
376
481
|
])
|
|
377
|
-
:
|
|
482
|
+
: this.withScrollHint([
|
|
378
483
|
{ key: "Space", verb: "toggle" },
|
|
379
484
|
{ key: "Enter", verb: "accept" },
|
|
380
485
|
]);
|
|
381
486
|
}
|
|
382
487
|
return this.questions.length > 1
|
|
383
|
-
?
|
|
488
|
+
? this.withScrollHint([
|
|
384
489
|
{ key: "Left/Right", verb: "question" },
|
|
385
490
|
{ key: "Enter", verb: "accept" },
|
|
386
491
|
])
|
|
387
|
-
:
|
|
492
|
+
: this.withScrollHint([{ key: "Enter", verb: "accept" }]);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* The keys for this question, plus the scroll keys when there is something to
|
|
497
|
+
* scroll. A footer that always advertised PgUp would be advertising a key that
|
|
498
|
+
* does nothing on most rounds.
|
|
499
|
+
*/
|
|
500
|
+
private withScrollHint(entries: ReadonlyArray<HintEntry>): string {
|
|
501
|
+
if (this.surface !== "interview" || !this.contentOverflows) return buildHint(entries);
|
|
502
|
+
return buildHint([...entries, { key: "PgUp/PgDn", verb: "scroll" }]);
|
|
388
503
|
}
|
|
389
504
|
|
|
390
505
|
private setSurface(next: AskUserSurface): void {
|
|
@@ -443,17 +558,20 @@ class AskUserOverlayView implements Component {
|
|
|
443
558
|
private maxInnerRows(): number {
|
|
444
559
|
const cap = ASK_USER_SURFACE_GEOMETRY[this.surface].maxInnerRows;
|
|
445
560
|
const rows = this.deps.getTerminalRows();
|
|
446
|
-
|
|
447
|
-
|
|
561
|
+
// A zero cap is the interview's "every row the frame has", which is the
|
|
562
|
+
// same number the frame budgets itself when the mount passes no maxHeight.
|
|
563
|
+
if (!Number.isFinite(rows) || rows <= 0) return cap > 0 ? cap : 1;
|
|
564
|
+
const available = Math.max(1, Math.floor(rows) - ASK_USER_FRAME_AND_MARGIN_ROWS);
|
|
565
|
+
return cap > 0 ? Math.min(cap, available) : available;
|
|
448
566
|
}
|
|
449
567
|
|
|
450
568
|
private maxVisibleOptions(): number {
|
|
451
569
|
const inner = this.maxInnerRows();
|
|
452
|
-
// The interview
|
|
453
|
-
//
|
|
454
|
-
//
|
|
570
|
+
// The interview's option list is budgeted off the live row count rather
|
|
571
|
+
// than a fixed heuristic: the strip, header, blank, status, blank and one
|
|
572
|
+
// row of question are what the control shares the workspace with.
|
|
455
573
|
if (this.surface === "interview") {
|
|
456
|
-
return Math.max(MIN_VISIBLE_OPTIONS, Math.min(MAX_VISIBLE_OPTIONS, inner -
|
|
574
|
+
return Math.max(MIN_VISIBLE_OPTIONS, Math.min(MAX_VISIBLE_OPTIONS, inner - INTERVIEW_CHROME_ROWS));
|
|
457
575
|
}
|
|
458
576
|
return Math.max(1, Math.min(MAX_VISIBLE_OPTIONS, inner - 4));
|
|
459
577
|
}
|
|
@@ -809,6 +927,7 @@ class AskUserOverlayView implements Component {
|
|
|
809
927
|
const next = this.nextUnansweredIndex();
|
|
810
928
|
if (next !== null) {
|
|
811
929
|
this.index = next;
|
|
930
|
+
this.resetContentScroll();
|
|
812
931
|
const nextState = this.currentState();
|
|
813
932
|
const nextQuestion = this.currentQuestion();
|
|
814
933
|
if (nextState && nextQuestion && !nextState.answer.trim()) nextState.mode = initialMode(nextQuestion);
|
|
@@ -844,6 +963,7 @@ class AskUserOverlayView implements Component {
|
|
|
844
963
|
this.syncActiveControl();
|
|
845
964
|
this.index = (this.index + delta + this.questions.length) % this.questions.length;
|
|
846
965
|
this.status = "";
|
|
966
|
+
this.resetContentScroll();
|
|
847
967
|
this.rebuildControl();
|
|
848
968
|
this.deps.requestRender();
|
|
849
969
|
}
|
|
@@ -888,7 +1008,9 @@ export function openAskUserOverlay(tui: TUI, deps: OpenAskUserOverlayDeps): AskU
|
|
|
888
1008
|
handle = showClioOverlayFrame(tui, view, {
|
|
889
1009
|
anchor: geometry.anchor,
|
|
890
1010
|
width: geometry.width,
|
|
891
|
-
|
|
1011
|
+
// A zero cap means the frame keeps its own budget, which is every row
|
|
1012
|
+
// the terminal has left after the margins.
|
|
1013
|
+
...(geometry.maxInnerRows > 0 ? { maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS } : {}),
|
|
892
1014
|
margin: geometry.margin,
|
|
893
1015
|
title: () => (view.isDecisionPending() ? ASK_USER_DECISION_TITLE : ASK_USER_WAITING_TITLE),
|
|
894
1016
|
tone: () => (view.isDecisionPending() ? ASK_USER_DECISION_TONE : undefined),
|
|
@@ -15,11 +15,14 @@
|
|
|
15
15
|
import { sanitizeCallTargetText } from "../../domains/safety/call-target.js";
|
|
16
16
|
import { formatSize } from "../../engine/truncate.js";
|
|
17
17
|
import { visibleWidth, wrapTextWithAnsi } from "../../engine/tui.js";
|
|
18
|
+
import { classifyResourceRead, toolPresentationPolicy } from "../../tools/presentation.js";
|
|
18
19
|
import type { ApprovalRequestView } from "../permission-overlay.js";
|
|
19
20
|
import { clioTheme, formatCompactMs, GLYPH } from "../theme/index.js";
|
|
20
21
|
import { renderDiffLines } from "./diff.js";
|
|
21
22
|
import { tryRenderJson, tryRenderXml } from "./structured.js";
|
|
22
23
|
|
|
24
|
+
export { classifyResourceRead };
|
|
25
|
+
|
|
23
26
|
const theme = clioTheme();
|
|
24
27
|
const dim = (text: string): string => theme.fg("dim", text);
|
|
25
28
|
const red = (text: string): string => theme.fg("error", text);
|
|
@@ -40,6 +43,8 @@ const FULL_RESULT_PREVIEW_LIMIT = 60_000;
|
|
|
40
43
|
const FULL_RESULT_ROW_LIMIT = 120;
|
|
41
44
|
const STREAMING_RESULT_ROW_LIMIT = 20;
|
|
42
45
|
const ARGS_BODY_LINE_LIMIT = 24;
|
|
46
|
+
/** Rows of mutation diff a folded edit/write row keeps visible before it defers to the body. */
|
|
47
|
+
const FOLDED_DIFF_ROW_LIMIT = 24;
|
|
43
48
|
const STATUS_OK_GLYPH = GLYPH.ok;
|
|
44
49
|
const STATUS_ERROR_GLYPH = GLYPH.error;
|
|
45
50
|
|
|
@@ -286,24 +291,6 @@ function summarizeArgs(toolName: string, args: unknown): string {
|
|
|
286
291
|
return truncate(jsonStringifySafe(safeArgs), ARG_PREVIEW_LIMIT);
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
/**
|
|
290
|
-
* Compact resource-read classification. Reads of skill/handbook/agent
|
|
291
|
-
* instruction files and docs pages collapse to one labeled line and never
|
|
292
|
-
* auto-expand; their bodies are reference material, not task output.
|
|
293
|
-
*/
|
|
294
|
-
export function classifyResourceRead(toolName: string, args: unknown): string | null {
|
|
295
|
-
if (toolName !== "read") return null;
|
|
296
|
-
const path = readStringField(args, "path");
|
|
297
|
-
if (path === null) return null;
|
|
298
|
-
const normalized = path.replace(/\\/g, "/");
|
|
299
|
-
const base = normalized.split("/").pop() ?? "";
|
|
300
|
-
if (base === "SKILL.md") return "skill";
|
|
301
|
-
if (base === "CLIO-CODER.md") return "handbook";
|
|
302
|
-
if (base === "AGENTS.md") return "agents";
|
|
303
|
-
if (/(^|\/)docs\//.test(normalized)) return "docs";
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
294
|
function detailsOf(result: unknown): Record<string, unknown> | null {
|
|
308
295
|
if (!isPlainObject(result)) return null;
|
|
309
296
|
return isPlainObject(result.details) ? result.details : null;
|
|
@@ -448,15 +435,58 @@ function ledgerTail(finished: ToolExecutionFinished): { facts: string; offload:
|
|
|
448
435
|
const parts: string[] = [];
|
|
449
436
|
const outcome = outcomeSummary(finished);
|
|
450
437
|
if (outcome !== null) parts.push(outcome);
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
438
|
+
const executed = !isNonExecutedOutcome(finished.outcome);
|
|
439
|
+
// A folded bash row is the only view of the call the operator gets by
|
|
440
|
+
// default, so its settlement facts have to be here rather than only in the
|
|
441
|
+
// expanded body. A failed row already carries `(exit N)` on the status glyph.
|
|
442
|
+
if (executed && finished.toolName === "bash" && !finished.isError) {
|
|
443
|
+
parts.push(`exit ${structuredExitCode(finished) ?? "0"}`);
|
|
444
|
+
}
|
|
445
|
+
const bytes = executed ? shownBytesOf(finished) : null;
|
|
446
|
+
if (bytes !== null && bytes > 0) {
|
|
447
|
+
const total = totalBytesOf(finished);
|
|
448
|
+
parts.push(
|
|
449
|
+
total !== null && total > bytes ? `${formatSize(bytes)} shown / ${formatSize(total)} total` : formatSize(bytes),
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (executed) {
|
|
453
|
+
if (isTruncatedResult(finished)) parts.push("truncated");
|
|
454
|
+
const details = detailsOf(finished.result);
|
|
455
|
+
if (details?.timedOut === true) parts.push("timed out");
|
|
456
|
+
if (details?.outputCapped === true) parts.push("output capped");
|
|
457
|
+
}
|
|
458
|
+
if (finished.excludeFromContext === true) parts.push("excluded from context");
|
|
459
|
+
const offloadPath = executed ? offloadPathOf(finished) : null;
|
|
454
460
|
return {
|
|
455
461
|
facts: parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "",
|
|
456
462
|
offload: offloadPath !== null ? dim(` · full: ${offloadPath}`) : "",
|
|
457
463
|
};
|
|
458
464
|
}
|
|
459
465
|
|
|
466
|
+
/** Longest diagnostic excerpt a folded failure row may carry. */
|
|
467
|
+
const FAILURE_EXCERPT_LIMIT = 80;
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Last non-empty output line of a failed call, bounded so the folded row stays
|
|
471
|
+
* diagnostically useful without opening the body. Sanitized to one line: the
|
|
472
|
+
* source is raw tool output. The tool's presentation policy decides whether
|
|
473
|
+
* its failures carry one; the renderer never names a tool here.
|
|
474
|
+
*/
|
|
475
|
+
function failureExcerpt(finished: ToolExecutionFinished, width: number): string {
|
|
476
|
+
if (!finished.isError || !toolPresentationPolicy(finished.toolName, finished.args).failureExcerpt) return "";
|
|
477
|
+
if (isNonExecutedOutcome(finished.outcome)) return "";
|
|
478
|
+
const text = unwrapResultEnvelope(finished.result);
|
|
479
|
+
if (typeof text !== "string") return "";
|
|
480
|
+
let excerpt: string | undefined;
|
|
481
|
+
for (const raw of text.split("\n")) {
|
|
482
|
+
const line = sanitizeCallTargetText(raw).trim();
|
|
483
|
+
if (line.length > 0) excerpt = line;
|
|
484
|
+
}
|
|
485
|
+
if (excerpt === undefined) return "";
|
|
486
|
+
const limit = Math.min(FAILURE_EXCERPT_LIMIT, Math.max(20, width - 20));
|
|
487
|
+
return dim(` · ${truncate(excerpt, limit)}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
460
490
|
/**
|
|
461
491
|
* Marker for a call parked at admission awaiting operator approval. Rendered
|
|
462
492
|
* in place of the elapsed counter so a parked call never reads as executing
|
|
@@ -531,8 +561,8 @@ function statusGlyph(status: HeaderStatus, meta: StatusMeta = {}): string {
|
|
|
531
561
|
}
|
|
532
562
|
|
|
533
563
|
function headerLine(toolName: string, args: unknown, status: HeaderStatus, meta: StatusMeta = {}): string {
|
|
534
|
-
const
|
|
535
|
-
const head = `${dim(HEADER_PREFIX_PLAIN)}${
|
|
564
|
+
const body = styleSublineBody(buildSublineBody(toolName, args, status, undefined, meta.outcome));
|
|
565
|
+
const head = `${dim(HEADER_PREFIX_PLAIN)}${body}`;
|
|
536
566
|
return `${head}${statusGlyph(status, meta)}`;
|
|
537
567
|
}
|
|
538
568
|
|
|
@@ -546,8 +576,9 @@ function styleSublineBody(body: string): string {
|
|
|
546
576
|
return sublineLead(match.groups.lead, match.groups.rest);
|
|
547
577
|
}
|
|
548
578
|
|
|
549
|
-
function
|
|
550
|
-
|
|
579
|
+
function buildGenericToolBody(toolName: string, args: unknown): string {
|
|
580
|
+
const summary = summarizeArgs(toolName, args);
|
|
581
|
+
return summary.length > 0 ? `tool action ${summary}` : "tool action";
|
|
551
582
|
}
|
|
552
583
|
|
|
553
584
|
function buildFieldSublineBody(
|
|
@@ -604,6 +635,7 @@ const SUBLINE_BODY_BUILDERS: Readonly<Record<string, (args: unknown) => string |
|
|
|
604
635
|
bash: (args) => buildFieldSublineBody(args, "command", "running ", { wrapInBackticks: true }),
|
|
605
636
|
grep: (args) => buildFieldSublineBody(args, "pattern", "searching for ", { wrapInBackticks: true }),
|
|
606
637
|
find: (args) => buildFieldSublineBody(args, "pattern", "finding ", { wrapInBackticks: true }),
|
|
638
|
+
web_fetch: (args) => buildFieldSublineBody(args, "url", "fetching "),
|
|
607
639
|
git: (args) => buildFieldSublineBody(args, "op", "git "),
|
|
608
640
|
verify: (args) => buildFieldSublineBody(args, "check", "verifying "),
|
|
609
641
|
code_nav: (args) => {
|
|
@@ -641,7 +673,7 @@ const SUBLINE_BODY_BUILDERS: Readonly<Record<string, (args: unknown) => string |
|
|
|
641
673
|
/**
|
|
642
674
|
* Per-tool subline templates. Maps a tool name to a function that builds the
|
|
643
675
|
* verb-led subline body without the leading glyph and without the trailing
|
|
644
|
-
* status glyph. Unknown tools fall back to
|
|
676
|
+
* status glyph. Unknown tools fall back to a tool-neutral action summary.
|
|
645
677
|
*/
|
|
646
678
|
function webFetchMeta(result: unknown): string | null {
|
|
647
679
|
if (!isPlainObject(result) || !isPlainObject(result.details)) return null;
|
|
@@ -658,11 +690,10 @@ function webFetchMeta(result: unknown): string | null {
|
|
|
658
690
|
|
|
659
691
|
/**
|
|
660
692
|
* A call the permission gate blocked never executed, so the collapsed row must
|
|
661
|
-
* not claim it did.
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
* output.
|
|
693
|
+
* not claim it did. Blocked calls keep the ordinary operator-facing action
|
|
694
|
+
* description but use a neutral noun for commands; the status tail supplies the
|
|
695
|
+
* blocked outcome. The ledger byte count is suppressed elsewhere because those
|
|
696
|
+
* bytes are the denial text, not output.
|
|
666
697
|
*/
|
|
667
698
|
function isNonExecutedOutcome(outcome: ToolExecutionFinished["outcome"]): boolean {
|
|
668
699
|
return outcome === "blocked";
|
|
@@ -675,21 +706,29 @@ function buildSublineBody(
|
|
|
675
706
|
result?: unknown,
|
|
676
707
|
outcome?: ToolExecutionFinished["outcome"],
|
|
677
708
|
): string {
|
|
678
|
-
if (isNonExecutedOutcome(outcome))
|
|
679
|
-
|
|
709
|
+
if (isNonExecutedOutcome(outcome)) {
|
|
710
|
+
if (toolName === "bash") {
|
|
711
|
+
return (
|
|
712
|
+
buildFieldSublineBody(args, "command", "command ", { wrapInBackticks: true }) ??
|
|
713
|
+
buildGenericToolBody(toolName, args)
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
return SUBLINE_BODY_BUILDERS[toolName]?.(args) ?? buildGenericToolBody(toolName, args);
|
|
717
|
+
}
|
|
680
718
|
if (toolName === "web_fetch") {
|
|
681
719
|
const meta = status === undefined ? null : webFetchMeta(result);
|
|
682
|
-
|
|
720
|
+
const body = SUBLINE_BODY_BUILDERS.web_fetch?.(args) ?? buildGenericToolBody(toolName, args);
|
|
721
|
+
return `${body}${meta ? dim(` · ${meta}`) : ""}`;
|
|
683
722
|
}
|
|
684
723
|
if (toolName === "bash") {
|
|
685
|
-
const lead = status === "
|
|
724
|
+
const lead = status === "ok" || status === "error" ? "ran " : "running ";
|
|
686
725
|
return (
|
|
687
|
-
buildFieldSublineBody(args, "command", lead, { wrapInBackticks: true }) ??
|
|
726
|
+
buildFieldSublineBody(args, "command", lead, { wrapInBackticks: true }) ?? buildGenericToolBody(toolName, args)
|
|
688
727
|
);
|
|
689
728
|
}
|
|
690
729
|
const body = SUBLINE_BODY_BUILDERS[toolName]?.(args);
|
|
691
730
|
if (body !== null && body !== undefined) return body;
|
|
692
|
-
return
|
|
731
|
+
return buildGenericToolBody(toolName, args);
|
|
693
732
|
}
|
|
694
733
|
|
|
695
734
|
interface SublineParts {
|
|
@@ -1116,10 +1155,40 @@ function sublineStatus(call: ToolExecutionStart | ToolExecutionFinished): Header
|
|
|
1116
1155
|
* imports the keybindings manager directly; the caller resolves the key string
|
|
1117
1156
|
* and passes it in to keep this module pure.
|
|
1118
1157
|
*/
|
|
1158
|
+
export interface ToolSublineRenderOptions {
|
|
1159
|
+
/** Live rows color the folded mutation diff; replay and export request plain rows. */
|
|
1160
|
+
diffStyle?: "color" | "plain";
|
|
1161
|
+
/**
|
|
1162
|
+
* Whether the folded row carries the extras its tool's presentation asks
|
|
1163
|
+
* for (today the bounded mutation diff). The bare subline level drops them;
|
|
1164
|
+
* the failure excerpt is not one of them and always rides the row.
|
|
1165
|
+
*/
|
|
1166
|
+
foldedExtras?: "per-tool" | "none";
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* The bounded diff a folded mutation row keeps under itself, when the tool's
|
|
1171
|
+
* presentation asks for one and the result carries one. A folded `edit` that
|
|
1172
|
+
* hid its change told the operator nothing they could act on.
|
|
1173
|
+
*/
|
|
1174
|
+
function foldedDiffRows(finished: ToolExecutionFinished, width: number, opts: ToolSublineRenderOptions): string[] {
|
|
1175
|
+
if (opts.foldedExtras === "none") return [];
|
|
1176
|
+
if (finished.isError || isNonExecutedOutcome(finished.outcome)) return [];
|
|
1177
|
+
if (!toolPresentationPolicy(finished.toolName, finished.args).showDiffWhenFolded) return [];
|
|
1178
|
+
const diff = resultDiff(finished.result);
|
|
1179
|
+
if (diff === null) return [];
|
|
1180
|
+
return truncateRowsMiddle(
|
|
1181
|
+
renderMutationDiffBlock(diff, width, opts.diffStyle !== "plain"),
|
|
1182
|
+
FOLDED_DIFF_ROW_LIMIT,
|
|
1183
|
+
false,
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1119
1187
|
export function renderToolSubline(
|
|
1120
1188
|
call: ToolExecutionStart | ToolExecutionFinished,
|
|
1121
1189
|
width: number,
|
|
1122
1190
|
expandKey?: string,
|
|
1191
|
+
opts: ToolSublineRenderOptions = {},
|
|
1123
1192
|
): string[] {
|
|
1124
1193
|
const status = sublineStatus(call);
|
|
1125
1194
|
const meta: StatusMeta =
|
|
@@ -1132,9 +1201,12 @@ export function renderToolSubline(
|
|
|
1132
1201
|
}
|
|
1133
1202
|
: { elapsedMs: call.elapsedMs };
|
|
1134
1203
|
const parts = sublineParts(call, status, meta);
|
|
1204
|
+
const excerpt = "result" in call ? failureExcerpt(call, width) : "";
|
|
1135
1205
|
const showHint = "result" in call && expandKey !== undefined && expandKey.length > 0;
|
|
1136
1206
|
const tail = showHint ? `${parts.tail}${dim(` (${expandKey})`)}` : parts.tail;
|
|
1137
|
-
|
|
1207
|
+
const lines = wrapSublineWithTail(`${parts.lead}${excerpt}`, tail, width);
|
|
1208
|
+
if ("result" in call) lines.push(...foldedDiffRows(call, width, opts));
|
|
1209
|
+
return lines;
|
|
1138
1210
|
}
|
|
1139
1211
|
|
|
1140
1212
|
/**
|
|
@@ -1294,6 +1366,13 @@ export interface BashTranscriptExecution {
|
|
|
1294
1366
|
fullOutputPath?: string | undefined;
|
|
1295
1367
|
excludeFromContext?: boolean | undefined;
|
|
1296
1368
|
error?: string | undefined;
|
|
1369
|
+
/**
|
|
1370
|
+
* Whether the block draws its one-line row instead of the full body. The
|
|
1371
|
+
* caller resolves this from the transcript detail policy and the operator's
|
|
1372
|
+
* override, the same way the panel resolves a model bash call. Omitted means
|
|
1373
|
+
* folded.
|
|
1374
|
+
*/
|
|
1375
|
+
folded?: boolean | undefined;
|
|
1297
1376
|
}
|
|
1298
1377
|
|
|
1299
1378
|
/**
|
|
@@ -1301,7 +1380,12 @@ export interface BashTranscriptExecution {
|
|
|
1301
1380
|
* as model-initiated bash. This is a view projection only; callers keep Clio's
|
|
1302
1381
|
* existing immutable `bashExecution` ledger entry.
|
|
1303
1382
|
*/
|
|
1304
|
-
export function renderBashTranscriptExecution(
|
|
1383
|
+
export function renderBashTranscriptExecution(
|
|
1384
|
+
execution: BashTranscriptExecution,
|
|
1385
|
+
width: number,
|
|
1386
|
+
expandKey?: string,
|
|
1387
|
+
bodyOptions: ToolBodyRenderOptions = {},
|
|
1388
|
+
): string[] {
|
|
1305
1389
|
const shownBytes = Buffer.byteLength(execution.output, "utf8");
|
|
1306
1390
|
const totalBytes = execution.totalBytes ?? shownBytes;
|
|
1307
1391
|
const args: Record<string, unknown> = { command: execution.command };
|
|
@@ -1319,7 +1403,20 @@ export function renderBashTranscriptExecution(execution: BashTranscriptExecution
|
|
|
1319
1403
|
content: [{ type: "text", text: execution.output }],
|
|
1320
1404
|
details,
|
|
1321
1405
|
};
|
|
1406
|
+
const folded = execution.folded !== false;
|
|
1322
1407
|
if (execution.running) {
|
|
1408
|
+
if (folded) {
|
|
1409
|
+
return renderToolSubline(
|
|
1410
|
+
{
|
|
1411
|
+
toolCallId: "local-bash",
|
|
1412
|
+
toolName: "bash",
|
|
1413
|
+
args,
|
|
1414
|
+
elapsedMs: execution.elapsedMs,
|
|
1415
|
+
phase: "running",
|
|
1416
|
+
},
|
|
1417
|
+
width,
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1323
1420
|
return renderToolStreamingExecution(
|
|
1324
1421
|
{
|
|
1325
1422
|
toolCallId: "local-bash",
|
|
@@ -1336,22 +1433,20 @@ export function renderBashTranscriptExecution(execution: BashTranscriptExecution
|
|
|
1336
1433
|
const output = message
|
|
1337
1434
|
? `${execution.output}${execution.output.length > 0 ? "\n\n" : ""}${message}`
|
|
1338
1435
|
: execution.output;
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
...(execution.fullOutputPath !== undefined ? { offloadPath: execution.fullOutputPath } : {}),
|
|
1353
|
-
},
|
|
1436
|
+
const finished: ToolExecutionFinished = {
|
|
1437
|
+
toolCallId: "local-bash",
|
|
1438
|
+
toolName: "bash",
|
|
1439
|
+
args,
|
|
1440
|
+
result: { ...result, content: [{ type: "text", text: output }] },
|
|
1441
|
+
isError: execution.cancelled === true || execution.error !== undefined || (execution.exitCode ?? 0) !== 0,
|
|
1442
|
+
exitCode: execution.exitCode,
|
|
1443
|
+
...(execution.cancelled === true ? { outcome: "aborted" as const } : {}),
|
|
1444
|
+
excludeFromContext: execution.excludeFromContext,
|
|
1445
|
+
resultSummary: {
|
|
1446
|
+
bytes: shownBytes,
|
|
1447
|
+
truncated: execution.truncated === true,
|
|
1448
|
+
...(execution.fullOutputPath !== undefined ? { offloadPath: execution.fullOutputPath } : {}),
|
|
1354
1449
|
},
|
|
1355
|
-
|
|
1356
|
-
);
|
|
1450
|
+
};
|
|
1451
|
+
return folded ? renderToolSubline(finished, width, expandKey) : renderToolExecution(finished, width, bodyOptions);
|
|
1357
1452
|
}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
export type { StatusController, StatusControllerDeps } from "./controller.js";
|
|
2
2
|
export { createStatusController } from "./controller.js";
|
|
3
|
+
export {
|
|
4
|
+
compactReasoningTokens,
|
|
5
|
+
formatReasoningChip,
|
|
6
|
+
formatReasoningLabel,
|
|
7
|
+
type ReasoningProvenance,
|
|
8
|
+
type ReasoningUsageView,
|
|
9
|
+
reasoningFromSummary,
|
|
10
|
+
reasoningFromTally,
|
|
11
|
+
UNMEASURED_REASONING,
|
|
12
|
+
} from "./reasoning.js";
|
|
3
13
|
export { type ReduceContext, reduceStatus, type StatusInputEvent } from "./state-machine.js";
|
|
4
|
-
export { type BuildSummaryInput, buildSummary } from "./summary.js";
|
|
14
|
+
export { type BuildSummaryInput, buildSummary, emptyRunTally, foldMessageIntoRunTally } from "./summary.js";
|
|
5
15
|
export {
|
|
6
16
|
type AgentStatus,
|
|
7
17
|
type AgentStatusChangedPayload,
|
|
8
18
|
type AgentStatusEvent,
|
|
9
19
|
INITIAL_STATUS,
|
|
10
20
|
type ReasoningTokenProvenance,
|
|
21
|
+
type RunTally,
|
|
11
22
|
type StatusPhase,
|
|
12
23
|
type TurnStopReason,
|
|
13
24
|
type TurnSummary,
|