@oh-my-pi/pi-coding-agent 15.7.3 → 15.7.6
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 +69 -1
- package/dist/types/config/settings-schema.d.ts +12 -22
- package/dist/types/eval/__tests__/heartbeat.test.d.ts +1 -0
- package/dist/types/eval/heartbeat.d.ts +45 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +24 -5
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/local-protocol.d.ts +19 -9
- package/dist/types/internal-urls/types.d.ts +14 -0
- package/dist/types/lsp/client.d.ts +3 -0
- package/dist/types/lsp/diagnostics-ledger.d.ts +10 -0
- package/dist/types/lsp/index.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +4 -0
- package/dist/types/mcp/manager.d.ts +14 -5
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/custom-editor.d.ts +0 -1
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/controllers/command-controller.d.ts +2 -3
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +10 -7
- package/dist/types/session/shake-types.d.ts +3 -3
- package/dist/types/task/repair-args.d.ts +52 -0
- package/dist/types/tiny/models.d.ts +0 -14
- package/dist/types/tiny/title-client.d.ts +28 -2
- package/dist/types/tiny/title-protocol.d.ts +8 -9
- package/dist/types/tools/ask.d.ts +8 -6
- package/dist/types/tools/eval-backends.d.ts +12 -0
- package/dist/types/tools/eval-render.d.ts +52 -0
- package/dist/types/tools/eval.d.ts +2 -35
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/index.d.ts +4 -11
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tui/output-block.d.ts +11 -10
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/package.json +9 -9
- package/scripts/build-binary.ts +0 -1
- package/src/cli.ts +59 -0
- package/src/config/model-registry.ts +33 -4
- package/src/config/settings-schema.ts +13 -24
- package/src/config/settings.ts +10 -0
- package/src/discovery/claude.ts +41 -22
- package/src/edit/index.ts +23 -3
- package/src/eval/__tests__/agent-bridge.test.ts +90 -0
- package/src/eval/__tests__/heartbeat.test.ts +84 -0
- package/src/eval/__tests__/llm-bridge.test.ts +30 -0
- package/src/eval/agent-bridge.ts +44 -38
- package/src/eval/heartbeat.ts +74 -0
- package/src/eval/js/executor.ts +13 -9
- package/src/eval/llm-bridge.ts +20 -14
- package/src/eval/py/executor.ts +14 -18
- package/src/exec/bash-executor.ts +31 -5
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +16 -18
- package/src/extensibility/extensions/runner.ts +22 -17
- package/src/extensibility/extensions/types.ts +39 -5
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/local-protocol.ts +23 -11
- package/src/internal-urls/types.ts +15 -0
- package/src/lsp/client.ts +28 -5
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/index.ts +9 -22
- package/src/lsp/utils.ts +21 -0
- package/src/mcp/manager.ts +87 -4
- package/src/modes/acp/acp-agent.ts +8 -4
- package/src/modes/components/assistant-message.ts +28 -1
- package/src/modes/components/custom-editor.ts +9 -7
- package/src/modes/components/hook-selector.ts +159 -32
- package/src/modes/components/tool-execution.ts +20 -4
- package/src/modes/controllers/command-controller.ts +7 -39
- package/src/modes/controllers/event-controller.ts +38 -28
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +17 -6
- package/src/modes/theme/theme-schema.json +30 -0
- package/src/modes/theme/theme.ts +39 -2
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +5 -2
- package/src/prompts/system/project-prompt.md +3 -2
- package/src/prompts/system/subagent-system-prompt.md +12 -8
- package/src/prompts/system/system-prompt.md +8 -6
- package/src/prompts/tools/ask.md +2 -1
- package/src/prompts/tools/eval.md +1 -1
- package/src/session/agent-session.ts +75 -103
- package/src/session/shake-types.ts +4 -5
- package/src/slash-commands/builtin-registry.ts +2 -4
- package/src/task/executor.ts +14 -4
- package/src/task/index.ts +3 -2
- package/src/task/repair-args.ts +117 -0
- package/src/tiny/models.ts +0 -28
- package/src/tiny/title-client.ts +133 -43
- package/src/tiny/title-protocol.ts +11 -16
- package/src/tiny/worker.ts +6 -61
- package/src/tools/ask.ts +74 -32
- package/src/tools/ast-edit.ts +3 -0
- package/src/tools/ast-grep.ts +3 -0
- package/src/tools/eval-backends.ts +38 -0
- package/src/tools/eval-render.ts +750 -0
- package/src/tools/eval.ts +27 -754
- package/src/tools/find.ts +20 -6
- package/src/tools/gh.ts +1 -0
- package/src/tools/index.ts +7 -37
- package/src/tools/path-utils.ts +13 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/renderers.ts +1 -1
- package/src/tools/search.ts +12 -1
- package/src/tools/write.ts +9 -1
- package/src/tui/output-block.ts +42 -79
- package/src/utils/git.ts +9 -3
package/src/tools/eval.ts
CHANGED
|
@@ -1,56 +1,26 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import
|
|
4
|
-
import { Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import { formatNumber, prompt } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
6
4
|
import * as z from "zod/v4";
|
|
7
|
-
import { settings } from "../config/settings";
|
|
8
5
|
import { jsBackend, pythonBackend } from "../eval";
|
|
9
6
|
import type { ExecutorBackend, ExecutorBackendResult } from "../eval/backend";
|
|
7
|
+
import { EVAL_HEARTBEAT_OP } from "../eval/heartbeat";
|
|
10
8
|
import { IdleTimeout } from "../eval/idle-timeout";
|
|
11
9
|
import { defaultEvalSessionId } from "../eval/session-id";
|
|
12
10
|
import type { EvalCellResult, EvalDisplayOutput, EvalLanguage, EvalStatusEvent, EvalToolDetails } from "../eval/types";
|
|
13
|
-
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
14
|
-
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
15
|
-
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
16
|
-
import { shimmerEnabled } from "../modes/theme/shimmer";
|
|
17
|
-
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
18
11
|
import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
|
|
19
12
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
|
|
20
|
-
import { borderShimmerTick, renderCodeCell } from "../tui";
|
|
21
13
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
22
|
-
import {
|
|
14
|
+
import type { ToolSession } from ".";
|
|
23
15
|
import { truncateForPrompt } from "./approval";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
JSON_TREE_MAX_LINES_COLLAPSED,
|
|
28
|
-
JSON_TREE_MAX_LINES_EXPANDED,
|
|
29
|
-
JSON_TREE_SCALAR_LEN_COLLAPSED,
|
|
30
|
-
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
31
|
-
renderJsonTreeLines,
|
|
32
|
-
} from "./json-tree";
|
|
33
|
-
import {
|
|
34
|
-
formatStyledTruncationWarning,
|
|
35
|
-
resolveOutputMaxColumns,
|
|
36
|
-
resolveOutputSinkHeadBytes,
|
|
37
|
-
stripOutputNotice,
|
|
38
|
-
} from "./output-meta";
|
|
39
|
-
import {
|
|
40
|
-
formatBadge,
|
|
41
|
-
formatDuration,
|
|
42
|
-
formatStatusIcon,
|
|
43
|
-
formatTitle,
|
|
44
|
-
replaceTabs,
|
|
45
|
-
shortenPath,
|
|
46
|
-
truncateToWidth,
|
|
47
|
-
wrapBrackets,
|
|
48
|
-
} from "./render-utils";
|
|
16
|
+
import { resolveEvalBackends } from "./eval-backends";
|
|
17
|
+
import { upsertStatusEvent } from "./eval-render";
|
|
18
|
+
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "./output-meta";
|
|
49
19
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
50
20
|
import { toolResult } from "./tool-result";
|
|
51
21
|
import { clampTimeout } from "./tool-timeouts";
|
|
52
22
|
|
|
53
|
-
export
|
|
23
|
+
export { EVAL_DEFAULT_PREVIEW_LINES, evalToolRenderer } from "./eval-render";
|
|
54
24
|
|
|
55
25
|
/**
|
|
56
26
|
* Per-cell input. Each cell runs in order; state persists within a language
|
|
@@ -155,10 +125,6 @@ function detailsNotice(cells: ResolvedEvalCell[]): string | undefined {
|
|
|
155
125
|
return notices.length > 0 ? notices.join(" ") : undefined;
|
|
156
126
|
}
|
|
157
127
|
|
|
158
|
-
function languageForHighlighter(language: EvalLanguage | undefined): "python" | "javascript" {
|
|
159
|
-
return language === "js" ? "javascript" : "python";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
128
|
function timeoutSecondsFromMs(timeoutMs: number): number {
|
|
163
129
|
return clampTimeout("eval", timeoutMs / 1000);
|
|
164
130
|
}
|
|
@@ -347,15 +313,16 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
347
313
|
for (let i = 0; i < cells.length; i++) {
|
|
348
314
|
const cell = cells[i];
|
|
349
315
|
const backend = cell.resolved.backend;
|
|
350
|
-
// The per-cell `timeout` is
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
// completion
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
//
|
|
316
|
+
// The per-cell `timeout` is a wall-clock budget on the cell's *own*
|
|
317
|
+
// work, but it is paused while a host-side `agent()`/`llm()` bridge
|
|
318
|
+
// call is in flight: those calls pump a heartbeat (see
|
|
319
|
+
// `withBridgeHeartbeat`) that re-arms the watchdog, so a long fanout
|
|
320
|
+
// or a slow completion runs to completion. Nothing else re-arms it —
|
|
321
|
+
// compute, stdout, `log()`/`phase()`, and ordinary tool calls all
|
|
322
|
+
// count against the budget — so a cell that is not delegating to an
|
|
323
|
+
// agent/llm is bounded by a plain wall-clock timeout. The watchdog
|
|
324
|
+
// drives `combinedSignal`; we pass no wall-clock deadline downstream
|
|
325
|
+
// so the backends never arm a competing fixed timer.
|
|
359
326
|
const idleTimeoutMs = timeoutSecondsFromMs(cell.timeoutMs) * 1000;
|
|
360
327
|
const idle = new IdleTimeout(idleTimeoutMs);
|
|
361
328
|
const combinedSignal = signal
|
|
@@ -388,7 +355,16 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
388
355
|
outputSink!.push(chunk);
|
|
389
356
|
},
|
|
390
357
|
onStatus: event => {
|
|
391
|
-
|
|
358
|
+
// Only a bridge heartbeat re-arms the watchdog: it is the
|
|
359
|
+
// keepalive `agent()`/`llm()` pump while a host-side call is
|
|
360
|
+
// in flight, so those calls effectively pause the budget. It
|
|
361
|
+
// carries no payload — bump and drop it. Every other event
|
|
362
|
+
// (compute helpers, log()/phase(), tool results) renders but
|
|
363
|
+
// counts against the plain wall-clock budget.
|
|
364
|
+
if (event.op === EVAL_HEARTBEAT_OP) {
|
|
365
|
+
idle.bump();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
392
368
|
cellResult.statusEvents ??= [];
|
|
393
369
|
upsertStatusEvent(cellResult.statusEvents, event);
|
|
394
370
|
pushUpdate();
|
|
@@ -594,706 +570,3 @@ async function summarizeFinal(
|
|
|
594
570
|
artifactId: rawSummary.artifactId,
|
|
595
571
|
};
|
|
596
572
|
}
|
|
597
|
-
|
|
598
|
-
interface EvalRenderCellArg {
|
|
599
|
-
language?: string;
|
|
600
|
-
code?: string;
|
|
601
|
-
title?: string;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
interface EvalRenderArgs {
|
|
605
|
-
cells?: EvalRenderCellArg[];
|
|
606
|
-
__partialJson?: string;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
interface EvalRenderContext {
|
|
610
|
-
output?: string;
|
|
611
|
-
expanded?: boolean;
|
|
612
|
-
previewLines?: number;
|
|
613
|
-
timeout?: number;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
interface EvalRenderCell {
|
|
617
|
-
language: EvalLanguage;
|
|
618
|
-
code: string;
|
|
619
|
-
title?: string;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function normalizeRenderLanguage(value: string | undefined): EvalLanguage {
|
|
623
|
-
return value === "js" ? "js" : "python";
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
627
|
-
const raw = args?.cells;
|
|
628
|
-
if (!Array.isArray(raw)) return [];
|
|
629
|
-
const out: EvalRenderCell[] = [];
|
|
630
|
-
for (const cell of raw) {
|
|
631
|
-
if (!cell || typeof cell !== "object") continue;
|
|
632
|
-
const code = typeof cell.code === "string" ? cell.code : "";
|
|
633
|
-
out.push({
|
|
634
|
-
language: normalizeRenderLanguage(typeof cell.language === "string" ? cell.language : undefined),
|
|
635
|
-
code,
|
|
636
|
-
title: typeof cell.title === "string" ? cell.title : undefined,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
return out;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
type AgentEventStatus = "pending" | "running" | "completed" | "failed" | "aborted";
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* Append or replace a status event. `agent` events are progress snapshots keyed
|
|
646
|
-
* by `id`, so they coalesce in place (preserving first-seen order); every other
|
|
647
|
-
* op is a discrete action and simply appends. Keeps the persisted event list
|
|
648
|
-
* bounded even when a subagent emits hundreds of throttled progress ticks.
|
|
649
|
-
*/
|
|
650
|
-
function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void {
|
|
651
|
-
if (event.op === "agent" && typeof event.id === "string") {
|
|
652
|
-
const id = event.id;
|
|
653
|
-
const idx = events.findIndex(e => e.op === "agent" && e.id === id);
|
|
654
|
-
if (idx >= 0) {
|
|
655
|
-
events[idx] = event;
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
events.push(event);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
function eventString(value: unknown): string | undefined {
|
|
663
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
function eventNumber(value: unknown): number {
|
|
667
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function agentEventStatus(value: unknown): AgentEventStatus {
|
|
671
|
-
switch (value) {
|
|
672
|
-
case "pending":
|
|
673
|
-
case "running":
|
|
674
|
-
case "completed":
|
|
675
|
-
case "failed":
|
|
676
|
-
case "aborted":
|
|
677
|
-
return value;
|
|
678
|
-
default:
|
|
679
|
-
return "running";
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/** Append the toolCount · context · cost · model stat run, mirroring the task tool. */
|
|
684
|
-
function formatAgentStats(event: EvalStatusEvent, theme: Theme): string {
|
|
685
|
-
let line = "";
|
|
686
|
-
const toolCount = eventNumber(event.toolCount);
|
|
687
|
-
if (toolCount > 0) {
|
|
688
|
-
line += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(toolCount)} ${theme.icon.extensionTool}`)}`;
|
|
689
|
-
}
|
|
690
|
-
const contextTokens = eventNumber(event.contextTokens);
|
|
691
|
-
if (contextTokens > 0) {
|
|
692
|
-
const contextWindow = eventNumber(event.contextWindow);
|
|
693
|
-
const ctx =
|
|
694
|
-
contextWindow > 0
|
|
695
|
-
? formatContextUsage((contextTokens / contextWindow) * 100, contextWindow)
|
|
696
|
-
: formatNumber(contextTokens);
|
|
697
|
-
line += `${theme.sep.dot}${theme.fg("dim", ctx)}`;
|
|
698
|
-
}
|
|
699
|
-
const cost = eventNumber(event.cost);
|
|
700
|
-
if (cost > 0) {
|
|
701
|
-
line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${cost.toFixed(2)}`)}`;
|
|
702
|
-
}
|
|
703
|
-
const model = eventString(event.model);
|
|
704
|
-
if (model && settings.get("task.showResolvedModelBadge")) {
|
|
705
|
-
line += `${theme.sep.dot}${theme.fg("dim", truncateToWidth(replaceTabs(model), 30))}`;
|
|
706
|
-
}
|
|
707
|
-
return line;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
/**
|
|
711
|
-
* Render coalesced `agent()` progress as a Task-tool-style tree, one entry per
|
|
712
|
-
* subagent: a status line (icon · id · stats) plus, while running, the current
|
|
713
|
-
* tool/intent. Drawn below the cell box so progress streams live.
|
|
714
|
-
*/
|
|
715
|
-
function renderAgentProgressEvents(events: EvalStatusEvent[], theme: Theme, spinnerFrame?: number): string[] {
|
|
716
|
-
const lines: string[] = [];
|
|
717
|
-
for (let i = 0; i < events.length; i++) {
|
|
718
|
-
const event = events[i];
|
|
719
|
-
const isLast = i === events.length - 1;
|
|
720
|
-
const prefix = theme.fg("dim", isLast ? theme.tree.last : theme.tree.branch);
|
|
721
|
-
const cont = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
722
|
-
|
|
723
|
-
const status = agentEventStatus(event.status);
|
|
724
|
-
const iconStatus =
|
|
725
|
-
status === "completed"
|
|
726
|
-
? "success"
|
|
727
|
-
: status === "failed"
|
|
728
|
-
? "error"
|
|
729
|
-
: status === "aborted"
|
|
730
|
-
? "aborted"
|
|
731
|
-
: status === "pending"
|
|
732
|
-
? "pending"
|
|
733
|
-
: "running";
|
|
734
|
-
const iconColor =
|
|
735
|
-
status === "completed" ? "success" : status === "failed" || status === "aborted" ? "error" : "accent";
|
|
736
|
-
const icon = formatStatusIcon(iconStatus, theme, status === "running" ? spinnerFrame : undefined);
|
|
737
|
-
|
|
738
|
-
const id = eventString(event.id) ?? "agent";
|
|
739
|
-
let line = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", theme.bold(id))}`;
|
|
740
|
-
|
|
741
|
-
if (status === "failed" || status === "aborted") {
|
|
742
|
-
line += ` ${formatBadge(status, iconColor, theme)}`;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
const currentTool = eventString(event.currentTool);
|
|
746
|
-
const lastIntent = eventString(event.lastIntent);
|
|
747
|
-
if (status === "running" && !currentTool && !lastIntent) {
|
|
748
|
-
const preview = eventString(event.taskPreview);
|
|
749
|
-
if (preview) line += ` ${theme.fg("muted", truncateToWidth(replaceTabs(preview), 48))}`;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
line += formatAgentStats(event, theme);
|
|
753
|
-
if (status === "completed" || status === "failed" || status === "aborted") {
|
|
754
|
-
const durationMs = eventNumber(event.durationMs);
|
|
755
|
-
if (durationMs > 0) line += `${theme.sep.dot}${theme.fg("dim", formatDuration(durationMs))}`;
|
|
756
|
-
}
|
|
757
|
-
lines.push(line);
|
|
758
|
-
|
|
759
|
-
if (status === "running") {
|
|
760
|
-
if (currentTool) {
|
|
761
|
-
let toolLine = `${cont}${theme.tree.hook} ${theme.fg("muted", currentTool)}`;
|
|
762
|
-
const detail = lastIntent ?? eventString(event.currentToolArgs);
|
|
763
|
-
if (detail) toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(detail), 48))}`;
|
|
764
|
-
lines.push(toolLine);
|
|
765
|
-
} else if (lastIntent) {
|
|
766
|
-
lines.push(`${cont}${theme.tree.hook} ${theme.fg("dim", truncateToWidth(replaceTabs(lastIntent), 48))}`);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
return lines;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/** Format a status event as a single line for display. */
|
|
774
|
-
function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
775
|
-
const { op, ...data } = event;
|
|
776
|
-
|
|
777
|
-
type AvailableIcon = "icon.file" | "icon.folder" | "icon.git" | "icon.package";
|
|
778
|
-
const opIcons: Record<string, AvailableIcon> = {
|
|
779
|
-
read: "icon.file",
|
|
780
|
-
write: "icon.file",
|
|
781
|
-
append: "icon.file",
|
|
782
|
-
cat: "icon.file",
|
|
783
|
-
touch: "icon.file",
|
|
784
|
-
ls: "icon.folder",
|
|
785
|
-
cd: "icon.folder",
|
|
786
|
-
pwd: "icon.folder",
|
|
787
|
-
mkdir: "icon.folder",
|
|
788
|
-
tree: "icon.folder",
|
|
789
|
-
git_status: "icon.git",
|
|
790
|
-
git_diff: "icon.git",
|
|
791
|
-
git_log: "icon.git",
|
|
792
|
-
git_show: "icon.git",
|
|
793
|
-
git_branch: "icon.git",
|
|
794
|
-
git_file_at: "icon.git",
|
|
795
|
-
git_has_changes: "icon.git",
|
|
796
|
-
run: "icon.package",
|
|
797
|
-
sh: "icon.package",
|
|
798
|
-
env: "icon.package",
|
|
799
|
-
batch: "icon.package",
|
|
800
|
-
llm: "icon.package",
|
|
801
|
-
log: "icon.package",
|
|
802
|
-
phase: "icon.package",
|
|
803
|
-
};
|
|
804
|
-
|
|
805
|
-
const iconKey = opIcons[op] ?? "icon.file";
|
|
806
|
-
const icon = theme.styledSymbol(iconKey, "muted");
|
|
807
|
-
|
|
808
|
-
const parts: string[] = [];
|
|
809
|
-
|
|
810
|
-
if (data.error) {
|
|
811
|
-
return `${icon} ${theme.fg("warning", op)}: ${theme.fg("dim", String(data.error))}`;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
switch (op) {
|
|
815
|
-
case "read":
|
|
816
|
-
parts.push(`${data.chars ?? data.bytes ?? 0} chars`);
|
|
817
|
-
if (data.path) parts.push(`from ${shortenPath(String(data.path))}`);
|
|
818
|
-
break;
|
|
819
|
-
case "write":
|
|
820
|
-
case "append":
|
|
821
|
-
parts.push(`${data.chars ?? data.bytes ?? 0} chars`);
|
|
822
|
-
if (data.path) parts.push(`to ${shortenPath(String(data.path))}`);
|
|
823
|
-
break;
|
|
824
|
-
case "cat":
|
|
825
|
-
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""}`);
|
|
826
|
-
parts.push(`${data.chars} chars`);
|
|
827
|
-
break;
|
|
828
|
-
case "ls":
|
|
829
|
-
parts.push(`${data.count} entr${(data.count as number) !== 1 ? "ies" : "y"}`);
|
|
830
|
-
break;
|
|
831
|
-
case "env":
|
|
832
|
-
if (data.action === "set") {
|
|
833
|
-
parts.push(`set ${data.key}=${truncateToWidth(String(data.value ?? ""), 30)}`);
|
|
834
|
-
} else if (data.action === "get") {
|
|
835
|
-
parts.push(`${data.key}=${truncateToWidth(String(data.value ?? ""), 30)}`);
|
|
836
|
-
} else {
|
|
837
|
-
parts.push(`${data.count} variable${(data.count as number) !== 1 ? "s" : ""}`);
|
|
838
|
-
}
|
|
839
|
-
break;
|
|
840
|
-
case "git_status":
|
|
841
|
-
if (data.clean) {
|
|
842
|
-
parts.push("clean");
|
|
843
|
-
} else {
|
|
844
|
-
const statusParts: string[] = [];
|
|
845
|
-
if (data.staged) statusParts.push(`${data.staged} staged`);
|
|
846
|
-
if (data.modified) statusParts.push(`${data.modified} modified`);
|
|
847
|
-
if (data.untracked) statusParts.push(`${data.untracked} untracked`);
|
|
848
|
-
parts.push(statusParts.join(", ") || "unknown");
|
|
849
|
-
}
|
|
850
|
-
if (data.branch) parts.push(`on ${data.branch}`);
|
|
851
|
-
break;
|
|
852
|
-
case "git_log":
|
|
853
|
-
parts.push(`${data.commits} commit${(data.commits as number) !== 1 ? "s" : ""}`);
|
|
854
|
-
break;
|
|
855
|
-
case "git_diff":
|
|
856
|
-
parts.push(`${data.lines} line${(data.lines as number) !== 1 ? "s" : ""}`);
|
|
857
|
-
if (data.staged) parts.push("(staged)");
|
|
858
|
-
break;
|
|
859
|
-
case "diff":
|
|
860
|
-
if (data.identical) {
|
|
861
|
-
parts.push("files identical");
|
|
862
|
-
} else {
|
|
863
|
-
parts.push("files differ");
|
|
864
|
-
}
|
|
865
|
-
break;
|
|
866
|
-
case "batch":
|
|
867
|
-
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""} processed`);
|
|
868
|
-
break;
|
|
869
|
-
case "llm":
|
|
870
|
-
if (data.model) parts.push(String(data.model));
|
|
871
|
-
if (data.tier && data.tier !== data.model) parts.push(`(${data.tier})`);
|
|
872
|
-
parts.push(`${data.chars ?? 0} chars`);
|
|
873
|
-
break;
|
|
874
|
-
case "wc":
|
|
875
|
-
parts.push(`${data.lines}L ${data.words}W ${data.chars}C`);
|
|
876
|
-
break;
|
|
877
|
-
case "cd":
|
|
878
|
-
case "pwd":
|
|
879
|
-
case "mkdir":
|
|
880
|
-
case "touch":
|
|
881
|
-
if (data.path) parts.push(shortenPath(String(data.path)));
|
|
882
|
-
break;
|
|
883
|
-
case "log":
|
|
884
|
-
parts.push(String(data.message ?? ""));
|
|
885
|
-
break;
|
|
886
|
-
case "phase":
|
|
887
|
-
parts.push(String(data.title ?? ""));
|
|
888
|
-
break;
|
|
889
|
-
default:
|
|
890
|
-
if (data.count !== undefined) {
|
|
891
|
-
parts.push(String(data.count));
|
|
892
|
-
}
|
|
893
|
-
if (data.path) {
|
|
894
|
-
parts.push(shortenPath(String(data.path)));
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
const desc = parts.length > 0 ? parts.join(" · ") : "";
|
|
899
|
-
return `${icon} ${theme.fg("muted", op)}${desc ? ` ${theme.fg("dim", desc)}` : ""}`;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
/** Format status event with expanded detail lines. */
|
|
903
|
-
function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string[] {
|
|
904
|
-
const lines: string[] = [];
|
|
905
|
-
const { op, ...data } = event;
|
|
906
|
-
|
|
907
|
-
lines.push(formatStatusEvent(event, theme));
|
|
908
|
-
|
|
909
|
-
const addItems = (items: unknown[], formatter: (item: unknown) => string, max = 5) => {
|
|
910
|
-
const arr = Array.isArray(items) ? items : [];
|
|
911
|
-
for (let i = 0; i < Math.min(arr.length, max); i++) {
|
|
912
|
-
lines.push(` ${theme.fg("dim", formatter(arr[i]))}`);
|
|
913
|
-
}
|
|
914
|
-
if (arr.length > max) {
|
|
915
|
-
lines.push(` ${theme.fg("dim", `… ${arr.length - max} more`)}`);
|
|
916
|
-
}
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
const addPreview = (preview: string, maxLines = 3) => {
|
|
920
|
-
const previewLines = String(preview).split("\n").slice(0, maxLines);
|
|
921
|
-
for (const line of previewLines) {
|
|
922
|
-
lines.push(` ${theme.fg("toolOutput", truncateToWidth(replaceTabs(line), 80))}`);
|
|
923
|
-
}
|
|
924
|
-
const totalLines = String(preview).split("\n").length;
|
|
925
|
-
if (totalLines > maxLines) {
|
|
926
|
-
lines.push(` ${theme.fg("dim", `… ${totalLines - maxLines} more lines`)}`);
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
switch (op) {
|
|
931
|
-
case "ls":
|
|
932
|
-
if (data.items) addItems(data.items as unknown[], m => String(m));
|
|
933
|
-
break;
|
|
934
|
-
case "env":
|
|
935
|
-
if (data.keys) addItems(data.keys as unknown[], k => String(k), 10);
|
|
936
|
-
break;
|
|
937
|
-
case "git_log":
|
|
938
|
-
if (data.entries) {
|
|
939
|
-
addItems(data.entries as unknown[], e => {
|
|
940
|
-
const entry = e as { sha: string; subject: string };
|
|
941
|
-
return `${entry.sha} ${truncateToWidth(entry.subject, 50)}`;
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
break;
|
|
945
|
-
case "git_status":
|
|
946
|
-
if (data.files) addItems(data.files as unknown[], f => String(f));
|
|
947
|
-
break;
|
|
948
|
-
case "git_branch":
|
|
949
|
-
if (data.branches) addItems(data.branches as unknown[], b => String(b));
|
|
950
|
-
break;
|
|
951
|
-
case "read":
|
|
952
|
-
case "cat":
|
|
953
|
-
case "head":
|
|
954
|
-
case "tail":
|
|
955
|
-
case "tree":
|
|
956
|
-
case "diff":
|
|
957
|
-
case "git_diff":
|
|
958
|
-
case "sh":
|
|
959
|
-
if (data.preview) addPreview(String(data.preview));
|
|
960
|
-
break;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
return lines;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
/** Render status events as tree lines. */
|
|
967
|
-
function renderStatusEvents(events: EvalStatusEvent[], theme: Theme, expanded: boolean): string[] {
|
|
968
|
-
if (events.length === 0) return [];
|
|
969
|
-
|
|
970
|
-
const maxCollapsed = 3;
|
|
971
|
-
const maxExpanded = 10;
|
|
972
|
-
const displayCount = expanded ? Math.min(events.length, maxExpanded) : Math.min(events.length, maxCollapsed);
|
|
973
|
-
|
|
974
|
-
const lines: string[] = [];
|
|
975
|
-
for (let i = 0; i < displayCount; i++) {
|
|
976
|
-
const isLast = i === displayCount - 1 && (expanded || events.length <= maxCollapsed);
|
|
977
|
-
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
978
|
-
|
|
979
|
-
if (expanded) {
|
|
980
|
-
const eventLines = formatStatusEventExpanded(events[i], theme);
|
|
981
|
-
lines.push(`${theme.fg("dim", branch)} ${eventLines[0]}`);
|
|
982
|
-
const continueBranch = isLast ? " " : `${theme.tree.vertical} `;
|
|
983
|
-
for (let j = 1; j < eventLines.length; j++) {
|
|
984
|
-
lines.push(`${theme.fg("dim", continueBranch)}${eventLines[j]}`);
|
|
985
|
-
}
|
|
986
|
-
} else {
|
|
987
|
-
lines.push(`${theme.fg("dim", branch)} ${formatStatusEvent(events[i], theme)}`);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
if (!expanded && events.length > maxCollapsed) {
|
|
992
|
-
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", `… ${events.length - maxCollapsed} more`)}`);
|
|
993
|
-
} else if (expanded && events.length > maxExpanded) {
|
|
994
|
-
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", `… ${events.length - maxExpanded} more`)}`);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
return lines;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
function formatCellOutputLines(
|
|
1001
|
-
cell: EvalCellResult,
|
|
1002
|
-
expanded: boolean,
|
|
1003
|
-
previewLines: number,
|
|
1004
|
-
theme: Theme,
|
|
1005
|
-
width: number,
|
|
1006
|
-
): { lines: string[]; hiddenCount: number } {
|
|
1007
|
-
if (!cell.output) {
|
|
1008
|
-
return { lines: [], hiddenCount: 0 };
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
if (cell.hasMarkdown && cell.status !== "error") {
|
|
1012
|
-
const md = new Markdown(cell.output, 0, 0, getMarkdownTheme());
|
|
1013
|
-
const allLines = md.render(width);
|
|
1014
|
-
const displayLines = expanded ? allLines : allLines.slice(-previewLines);
|
|
1015
|
-
const hiddenCount = allLines.length - displayLines.length;
|
|
1016
|
-
return { lines: displayLines, hiddenCount };
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
const rawLines = cell.output.split("\n");
|
|
1020
|
-
const displayLines = expanded ? rawLines : rawLines.slice(-previewLines);
|
|
1021
|
-
const hiddenCount = rawLines.length - displayLines.length;
|
|
1022
|
-
const outputLines = displayLines.map(line => {
|
|
1023
|
-
const cleaned = replaceTabs(line);
|
|
1024
|
-
return cell.status === "error" ? theme.fg("error", cleaned) : theme.fg("toolOutput", cleaned);
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
|
-
return { lines: outputLines, hiddenCount };
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
export const evalToolRenderer = {
|
|
1031
|
-
renderCall(args: EvalRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1032
|
-
const cells = getRenderCells(args);
|
|
1033
|
-
|
|
1034
|
-
if (cells.length === 0) {
|
|
1035
|
-
const promptSym = uiTheme.fg("accent", ">>>");
|
|
1036
|
-
const text = formatTitle(`${promptSym} …`, uiTheme);
|
|
1037
|
-
return new Text(text, 0, 0);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
1041
|
-
|
|
1042
|
-
return {
|
|
1043
|
-
render: (width: number): string[] => {
|
|
1044
|
-
const animate = options.isPartial && shimmerEnabled();
|
|
1045
|
-
const key = `${animate ? borderShimmerTick() : 0}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
|
|
1046
|
-
if (cached && cached.key === key && cached.width === width) {
|
|
1047
|
-
return cached.result;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
const lines: string[] = [];
|
|
1051
|
-
for (let i = 0; i < cells.length; i++) {
|
|
1052
|
-
const cell = cells[i];
|
|
1053
|
-
const cellLines = renderCodeCell(
|
|
1054
|
-
{
|
|
1055
|
-
code: cell.code,
|
|
1056
|
-
language: languageForHighlighter(cell.language),
|
|
1057
|
-
index: i,
|
|
1058
|
-
total: cells.length,
|
|
1059
|
-
title: cell.title,
|
|
1060
|
-
status: "pending",
|
|
1061
|
-
width,
|
|
1062
|
-
codeMaxLines: EVAL_DEFAULT_PREVIEW_LINES,
|
|
1063
|
-
expanded: true,
|
|
1064
|
-
animate,
|
|
1065
|
-
},
|
|
1066
|
-
uiTheme,
|
|
1067
|
-
);
|
|
1068
|
-
lines.push(...cellLines);
|
|
1069
|
-
if (i < cells.length - 1) {
|
|
1070
|
-
lines.push("");
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
cached = { key, width, result: lines };
|
|
1074
|
-
return lines;
|
|
1075
|
-
},
|
|
1076
|
-
invalidate: () => {
|
|
1077
|
-
cached = undefined;
|
|
1078
|
-
},
|
|
1079
|
-
};
|
|
1080
|
-
},
|
|
1081
|
-
|
|
1082
|
-
renderResult(
|
|
1083
|
-
result: { content: Array<{ type: string; text?: string }>; details?: EvalToolDetails },
|
|
1084
|
-
options: RenderResultOptions & { renderContext?: EvalRenderContext },
|
|
1085
|
-
uiTheme: Theme,
|
|
1086
|
-
_args?: EvalRenderArgs,
|
|
1087
|
-
): Component {
|
|
1088
|
-
const details = result.details;
|
|
1089
|
-
|
|
1090
|
-
const rawOutput =
|
|
1091
|
-
options.renderContext?.output ?? (result.content?.find(c => c.type === "text")?.text ?? "").trimEnd();
|
|
1092
|
-
// Strip the LLM-facing notice (appended by wrappedExecute) before display;
|
|
1093
|
-
// the styled `warningLine` below carries the same text in ⟨…⟩ form.
|
|
1094
|
-
const output = stripOutputNotice(rawOutput, details?.meta).trimEnd();
|
|
1095
|
-
|
|
1096
|
-
const jsonOutputs = details?.jsonOutputs ?? [];
|
|
1097
|
-
const treeExpanded = options.renderContext?.expanded ?? options.expanded;
|
|
1098
|
-
const treeDepth = treeExpanded ? JSON_TREE_MAX_DEPTH_EXPANDED : JSON_TREE_MAX_DEPTH_COLLAPSED;
|
|
1099
|
-
const treeLineCap = treeExpanded ? JSON_TREE_MAX_LINES_EXPANDED : JSON_TREE_MAX_LINES_COLLAPSED;
|
|
1100
|
-
const treeScalarLen = treeExpanded ? JSON_TREE_SCALAR_LEN_EXPANDED : JSON_TREE_SCALAR_LEN_COLLAPSED;
|
|
1101
|
-
const labelOutputs = jsonOutputs.length > 1;
|
|
1102
|
-
const jsonLines = jsonOutputs.flatMap((value, index) => {
|
|
1103
|
-
const tree = renderJsonTreeLines(value, uiTheme, treeDepth, treeLineCap, treeScalarLen);
|
|
1104
|
-
const body = tree.truncated ? [...tree.lines, uiTheme.fg("dim", "…")] : tree.lines;
|
|
1105
|
-
return labelOutputs ? [uiTheme.fg("dim", `display[${index + 1}]`), ...body] : body;
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
const timeoutSeconds = options.renderContext?.timeout;
|
|
1109
|
-
const timeoutLine =
|
|
1110
|
-
typeof timeoutSeconds === "number"
|
|
1111
|
-
? uiTheme.fg("dim", wrapBrackets(`Timeout: ${timeoutSeconds}s`, uiTheme))
|
|
1112
|
-
: undefined;
|
|
1113
|
-
let warningLine: string | undefined;
|
|
1114
|
-
if (details?.meta?.truncation) {
|
|
1115
|
-
warningLine = formatStyledTruncationWarning(details.meta, uiTheme) ?? undefined;
|
|
1116
|
-
}
|
|
1117
|
-
const noticeLine = details?.notice ? uiTheme.fg("dim", wrapBrackets(details.notice, uiTheme)) : undefined;
|
|
1118
|
-
|
|
1119
|
-
const cellResults = details?.cells;
|
|
1120
|
-
if (cellResults && cellResults.length > 0) {
|
|
1121
|
-
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
1122
|
-
|
|
1123
|
-
return {
|
|
1124
|
-
render: (width: number): string[] => {
|
|
1125
|
-
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
1126
|
-
const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
|
|
1127
|
-
const animate = options.isPartial && shimmerEnabled();
|
|
1128
|
-
const key = `${expanded}|${previewLines}|${options.spinnerFrame}|${animate ? borderShimmerTick() : 0}`;
|
|
1129
|
-
if (cached && cached.key === key && cached.width === width) {
|
|
1130
|
-
return cached.result;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
const lines: string[] = [];
|
|
1134
|
-
for (let i = 0; i < cellResults.length; i++) {
|
|
1135
|
-
const cell = cellResults[i];
|
|
1136
|
-
const allEvents = cell.statusEvents ?? [];
|
|
1137
|
-
const agentEvents = allEvents.filter(e => e.op === "agent");
|
|
1138
|
-
const otherEvents = agentEvents.length > 0 ? allEvents.filter(e => e.op !== "agent") : allEvents;
|
|
1139
|
-
const statusLines = renderStatusEvents(otherEvents, uiTheme, expanded);
|
|
1140
|
-
const outputContent = formatCellOutputLines(cell, expanded, previewLines, uiTheme, width);
|
|
1141
|
-
const outputLines = [...outputContent.lines];
|
|
1142
|
-
if (!expanded && outputContent.hiddenCount > 0) {
|
|
1143
|
-
outputLines.push(
|
|
1144
|
-
uiTheme.fg("dim", `… ${outputContent.hiddenCount} more lines (ctrl+o to expand)`),
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
if (statusLines.length > 0) {
|
|
1148
|
-
if (outputLines.length > 0) {
|
|
1149
|
-
outputLines.push(uiTheme.fg("dim", "Status"));
|
|
1150
|
-
}
|
|
1151
|
-
outputLines.push(...statusLines);
|
|
1152
|
-
}
|
|
1153
|
-
const cellLines = renderCodeCell(
|
|
1154
|
-
{
|
|
1155
|
-
code: cell.code,
|
|
1156
|
-
language: languageForHighlighter(cell.language ?? details?.language),
|
|
1157
|
-
index: i,
|
|
1158
|
-
total: cellResults.length,
|
|
1159
|
-
title: cell.title,
|
|
1160
|
-
status: cell.status,
|
|
1161
|
-
spinnerFrame: options.spinnerFrame,
|
|
1162
|
-
duration: cell.durationMs,
|
|
1163
|
-
output: outputLines.length > 0 ? outputLines.join("\n") : undefined,
|
|
1164
|
-
outputMaxLines: outputLines.length,
|
|
1165
|
-
codeMaxLines: expanded ? Number.POSITIVE_INFINITY : EVAL_DEFAULT_PREVIEW_LINES,
|
|
1166
|
-
expanded,
|
|
1167
|
-
width,
|
|
1168
|
-
animate,
|
|
1169
|
-
},
|
|
1170
|
-
uiTheme,
|
|
1171
|
-
);
|
|
1172
|
-
lines.push(...cellLines);
|
|
1173
|
-
if (agentEvents.length > 0) {
|
|
1174
|
-
lines.push(...renderAgentProgressEvents(agentEvents, uiTheme, options.spinnerFrame));
|
|
1175
|
-
}
|
|
1176
|
-
if (i < cellResults.length - 1) {
|
|
1177
|
-
lines.push("");
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
if (jsonLines.length > 0) {
|
|
1181
|
-
if (lines.length > 0) {
|
|
1182
|
-
lines.push("");
|
|
1183
|
-
}
|
|
1184
|
-
lines.push(...jsonLines);
|
|
1185
|
-
}
|
|
1186
|
-
if (timeoutLine) {
|
|
1187
|
-
lines.push(timeoutLine);
|
|
1188
|
-
}
|
|
1189
|
-
if (noticeLine) {
|
|
1190
|
-
lines.push(noticeLine);
|
|
1191
|
-
}
|
|
1192
|
-
if (warningLine) {
|
|
1193
|
-
lines.push(warningLine);
|
|
1194
|
-
}
|
|
1195
|
-
cached = { key, width, result: lines };
|
|
1196
|
-
return lines;
|
|
1197
|
-
},
|
|
1198
|
-
invalidate: () => {
|
|
1199
|
-
cached = undefined;
|
|
1200
|
-
},
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
const displayOutput = output;
|
|
1205
|
-
const combinedOutput = [displayOutput, ...jsonLines].filter(Boolean).join("\n");
|
|
1206
|
-
|
|
1207
|
-
const statusEvents = details?.statusEvents ?? [];
|
|
1208
|
-
const statusLines = renderStatusEvents(
|
|
1209
|
-
statusEvents,
|
|
1210
|
-
uiTheme,
|
|
1211
|
-
options.renderContext?.expanded ?? options.expanded,
|
|
1212
|
-
);
|
|
1213
|
-
|
|
1214
|
-
if (!combinedOutput && statusLines.length === 0) {
|
|
1215
|
-
const lines = [timeoutLine, noticeLine, warningLine].filter(Boolean) as string[];
|
|
1216
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
if (!combinedOutput && statusLines.length > 0) {
|
|
1220
|
-
const lines = [uiTheme.fg("dim", "Status"), ...statusLines, timeoutLine, noticeLine, warningLine].filter(
|
|
1221
|
-
Boolean,
|
|
1222
|
-
) as string[];
|
|
1223
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
if (options.renderContext?.expanded ?? options.expanded) {
|
|
1227
|
-
const styledOutput = combinedOutput
|
|
1228
|
-
.split("\n")
|
|
1229
|
-
.map(line => uiTheme.fg("toolOutput", line))
|
|
1230
|
-
.join("\n");
|
|
1231
|
-
const lines = [
|
|
1232
|
-
styledOutput,
|
|
1233
|
-
...(statusLines.length > 0 ? [uiTheme.fg("dim", "Status"), ...statusLines] : []),
|
|
1234
|
-
timeoutLine,
|
|
1235
|
-
noticeLine,
|
|
1236
|
-
warningLine,
|
|
1237
|
-
].filter(Boolean) as string[];
|
|
1238
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
const styledOutput = combinedOutput
|
|
1242
|
-
.split("\n")
|
|
1243
|
-
.map(line => uiTheme.fg("toolOutput", line))
|
|
1244
|
-
.join("\n");
|
|
1245
|
-
const textContent = `\n${styledOutput}`;
|
|
1246
|
-
|
|
1247
|
-
let cachedWidth: number | undefined;
|
|
1248
|
-
let cachedLines: string[] | undefined;
|
|
1249
|
-
let cachedSkipped: number | undefined;
|
|
1250
|
-
let cachedPreviewLines: number | undefined;
|
|
1251
|
-
|
|
1252
|
-
return {
|
|
1253
|
-
render: (width: number): string[] => {
|
|
1254
|
-
const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
|
|
1255
|
-
if (cachedLines === undefined || cachedWidth !== width || cachedPreviewLines !== previewLines) {
|
|
1256
|
-
const result = truncateToVisualLines(textContent, previewLines, width);
|
|
1257
|
-
cachedLines = result.visualLines;
|
|
1258
|
-
cachedSkipped = result.skippedCount;
|
|
1259
|
-
cachedWidth = width;
|
|
1260
|
-
cachedPreviewLines = previewLines;
|
|
1261
|
-
}
|
|
1262
|
-
const outputLines: string[] = [];
|
|
1263
|
-
if (cachedSkipped && cachedSkipped > 0) {
|
|
1264
|
-
outputLines.push("");
|
|
1265
|
-
const skippedLine = uiTheme.fg(
|
|
1266
|
-
"dim",
|
|
1267
|
-
`… (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (ctrl+o to expand)`,
|
|
1268
|
-
);
|
|
1269
|
-
outputLines.push(truncateToWidth(skippedLine, width));
|
|
1270
|
-
}
|
|
1271
|
-
outputLines.push(...cachedLines);
|
|
1272
|
-
if (statusLines.length > 0) {
|
|
1273
|
-
outputLines.push(truncateToWidth(uiTheme.fg("dim", "Status"), width));
|
|
1274
|
-
for (const statusLine of statusLines) {
|
|
1275
|
-
outputLines.push(truncateToWidth(statusLine, width));
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
if (timeoutLine) {
|
|
1279
|
-
outputLines.push(truncateToWidth(timeoutLine, width));
|
|
1280
|
-
}
|
|
1281
|
-
if (noticeLine) {
|
|
1282
|
-
outputLines.push(truncateToWidth(noticeLine, width));
|
|
1283
|
-
}
|
|
1284
|
-
if (warningLine) {
|
|
1285
|
-
outputLines.push(truncateToWidth(warningLine, width));
|
|
1286
|
-
}
|
|
1287
|
-
return outputLines;
|
|
1288
|
-
},
|
|
1289
|
-
invalidate: () => {
|
|
1290
|
-
cachedWidth = undefined;
|
|
1291
|
-
cachedLines = undefined;
|
|
1292
|
-
cachedSkipped = undefined;
|
|
1293
|
-
cachedPreviewLines = undefined;
|
|
1294
|
-
},
|
|
1295
|
-
};
|
|
1296
|
-
},
|
|
1297
|
-
mergeCallAndResult: true,
|
|
1298
|
-
inline: true,
|
|
1299
|
-
};
|