@code-yeongyu/senpi-codemode 2026.9.23-2 → 2026.9.23-4
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 +30 -0
- package/README.md +10 -8
- package/package.json +4 -4
- package/src/tool/display-code.ts +145 -0
- package/src/tool/eval-request.ts +7 -12
- package/src/tool/eval-tool.ts +2 -2
- package/src/tool/render.ts +38 -35
- package/src/tool/types.ts +1 -4
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,36 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.23-4] - 2026-09-23
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Eval cards no longer display truncation or full-output artifact footer warnings. Model-facing eval text and grouping are unchanged; content explicitly addressed only to the model is omitted from the text fallback. ([#2041](https://github.com/code-yeongyu/senpi/issues/2041))
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
## [2026.9.23-3] - 2026-09-23
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Dense one-line JavaScript eval cells are previewed in the TUI broken at statement, block, and long-array boundaries with indentation, keeping the original tokens and comments. Unparseable, non-JavaScript, and already readable cells are shown as sent, and the code that runs is unchanged. ([#2050](https://github.com/code-yeongyu/senpi/issues/2050))
|
|
38
|
+
- The `eval` `summary` guide asks for a progress update saying what the agent is doing and why, in the language the user writes in, and summaries no longer have a length limit (the 80-character truncation is gone). A collapsed eval block shows the first three summary lines; expanding it shows the rest. ([#2050](https://github.com/code-yeongyu/senpi/issues/2050))
|
|
39
|
+
- Codemode CI tests use at most two fork workers, matching the coding-agent suite's interpreter-heavy test scheduling. Test deadlines, assertions, and local worker defaults are unchanged. ([#2039](https://github.com/code-yeongyu/senpi/issues/2039))
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
15
45
|
## [2026.9.23-2] - 2026-09-23
|
|
16
46
|
|
|
17
47
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -29,7 +29,9 @@ task-tool names are known.
|
|
|
29
29
|
session-adjacent spill files for large streams.
|
|
30
30
|
- TUI and HTML-export rendering for syntax-highlighted cells, status rows,
|
|
31
31
|
task progress, structured display values, truncation warnings, and image
|
|
32
|
-
fallbacks.
|
|
32
|
+
fallbacks. A JavaScript cell sent as dense one-line code is previewed broken
|
|
33
|
+
at statement, block, and long-array boundaries; the cell itself runs exactly
|
|
34
|
+
as sent.
|
|
33
35
|
- Runtime identity badges in eval headers — `eval py (3.14.7, ~/.venv/bin/python3)`,
|
|
34
36
|
`eval js (node 26.7.0, /opt/…/bin/node)` — with the same `runtime` info on
|
|
35
37
|
`EvalToolDetails` and its `cells` for RPC consumers; interpreter detection
|
|
@@ -207,13 +209,13 @@ same name/spec/mode; no worker state is reconstructed in the prelude.
|
|
|
207
209
|
|
|
208
210
|
## Required summary
|
|
209
211
|
|
|
210
|
-
Every `eval` run call MUST include a `summary` — one line in the
|
|
211
|
-
|
|
212
|
-
a
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
Every `eval` run call MUST include a `summary` — one line in the language the
|
|
213
|
+
user writes in: a progress update saying what the agent is doing and why, not
|
|
214
|
+
a label for the code. The
|
|
215
|
+
summary is shown in the TUI while the cell runs and in the finished result, so
|
|
216
|
+
you can always tell what is running and why. It has no length limit; a
|
|
217
|
+
collapsed block shows its first three lines. A run request without a
|
|
218
|
+
`summary` fails with a teaching error.
|
|
217
219
|
|
|
218
220
|
## Detached cells
|
|
219
221
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.9.23-
|
|
3
|
+
"version": "2026.9.23-4",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@babel/parser": "8.0.4",
|
|
33
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.23-
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.23-4",
|
|
34
34
|
"typebox": "1.3.34"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.9.23-
|
|
37
|
+
"@code-yeongyu/senpi": "2026.9.23-4"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.9.23-
|
|
40
|
+
"@code-yeongyu/senpi": "2026.9.23-4"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import type { EvalLanguage } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
// Display-only: models often send a JS cell as one long line of joined statements. The preview
|
|
5
|
+
// breaks it at statement, block, and long-array boundaries and keeps every source token and
|
|
6
|
+
// comment verbatim. The tool arguments are never touched (senpi#1472).
|
|
7
|
+
const DENSE_LINE_LENGTH = 100;
|
|
8
|
+
const ARRAY_BREAK_LENGTH = 60;
|
|
9
|
+
const INDENT = " ";
|
|
10
|
+
const CACHE_LIMIT = 64;
|
|
11
|
+
|
|
12
|
+
type SourceNode = { readonly type: string; readonly start: number; readonly end: number };
|
|
13
|
+
|
|
14
|
+
// A whitespace/comma/comment region between two tokens that becomes a line break.
|
|
15
|
+
type Gap = {
|
|
16
|
+
readonly start: number;
|
|
17
|
+
readonly end: number;
|
|
18
|
+
readonly contentDepth: number;
|
|
19
|
+
readonly nextDepth: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const cache = new Map<string, string>();
|
|
23
|
+
|
|
24
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
25
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sourceNode(value: unknown): SourceNode | undefined {
|
|
29
|
+
if (!isRecord(value)) return undefined;
|
|
30
|
+
const { type, start, end } = value;
|
|
31
|
+
if (typeof type !== "string" || typeof start !== "number" || typeof end !== "number") return undefined;
|
|
32
|
+
return { type, start, end };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sourceNodes(value: unknown): SourceNode[] {
|
|
36
|
+
if (!Array.isArray(value)) return [];
|
|
37
|
+
return value.flatMap((item) => sourceNode(item) ?? []);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isDense(code: string): boolean {
|
|
41
|
+
return code.split("\n").some((line) => line.length > DENSE_LINE_LENGTH);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseProgram(code: string): unknown {
|
|
45
|
+
try {
|
|
46
|
+
return parse(code, {
|
|
47
|
+
sourceType: "module",
|
|
48
|
+
allowAwaitOutsideFunction: true,
|
|
49
|
+
allowReturnOutsideFunction: true,
|
|
50
|
+
allowImportExportEverywhere: true,
|
|
51
|
+
}).program;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof SyntaxError) return undefined;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function siblingGaps(children: readonly SourceNode[], depth: number): Gap[] {
|
|
59
|
+
return children.slice(1).map((child, index) => ({
|
|
60
|
+
start: children[index]?.end ?? child.start,
|
|
61
|
+
end: child.start,
|
|
62
|
+
contentDepth: depth,
|
|
63
|
+
nextDepth: depth,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Gaps for a bracketed container whose children move onto their own indented lines.
|
|
68
|
+
function containerGaps(container: SourceNode, children: readonly SourceNode[], depth: number): Gap[] {
|
|
69
|
+
const first = children[0];
|
|
70
|
+
const last = children.at(-1);
|
|
71
|
+
if (first === undefined || last === undefined) return [];
|
|
72
|
+
const inner = depth + 1;
|
|
73
|
+
return [
|
|
74
|
+
{ start: container.start + 1, end: first.start, contentDepth: inner, nextDepth: inner },
|
|
75
|
+
...siblingGaps(children, inner),
|
|
76
|
+
{ start: last.end, end: container.end - 1, contentDepth: inner, nextDepth: depth },
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function breakableChildren(node: SourceNode, record: Readonly<Record<string, unknown>>, code: string) {
|
|
81
|
+
if (node.type === "BlockStatement" || node.type === "StaticBlock") return sourceNodes(record.body);
|
|
82
|
+
if (node.type !== "ArrayExpression") return undefined;
|
|
83
|
+
const elements = sourceNodes(record.elements);
|
|
84
|
+
const text = code.slice(node.start, node.end);
|
|
85
|
+
if (elements.length < 2 || text.length <= ARRAY_BREAK_LENGTH || text.includes("\n")) return undefined;
|
|
86
|
+
return elements;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function collectGaps(value: unknown, depth: number, code: string, gaps: Gap[]): void {
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
for (const item of value) collectGaps(item, depth, code, gaps);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!isRecord(value)) return;
|
|
95
|
+
const node = sourceNode(value);
|
|
96
|
+
const children = node === undefined ? undefined : breakableChildren(node, value, code);
|
|
97
|
+
const breaks = node !== undefined && children !== undefined && children.length > 0;
|
|
98
|
+
if (breaks) gaps.push(...containerGaps(node, children, depth));
|
|
99
|
+
for (const [key, child] of Object.entries(value)) {
|
|
100
|
+
if (key === "loc" || key === "extra" || key.endsWith("Comments")) continue;
|
|
101
|
+
if (child !== null && typeof child === "object") collectGaps(child, breaks ? depth + 1 : depth, code, gaps);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderGap(text: string, gap: Gap): string {
|
|
106
|
+
let rest = text.trim();
|
|
107
|
+
let attached = "";
|
|
108
|
+
if (rest.startsWith(",") || rest.startsWith(";")) {
|
|
109
|
+
attached = rest.slice(0, 1);
|
|
110
|
+
rest = rest.slice(1).trim();
|
|
111
|
+
}
|
|
112
|
+
const contentIndent = INDENT.repeat(gap.contentDepth);
|
|
113
|
+
const content = rest
|
|
114
|
+
.split("\n")
|
|
115
|
+
.map((line) => line.trim())
|
|
116
|
+
.filter((line) => line.length > 0)
|
|
117
|
+
.map((line) => `\n${contentIndent}${line}`)
|
|
118
|
+
.join("");
|
|
119
|
+
return `${attached}${content}\n${INDENT.repeat(gap.nextDepth)}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function reformat(code: string): string {
|
|
123
|
+
const program = parseProgram(code);
|
|
124
|
+
if (!isRecord(program)) return code;
|
|
125
|
+
const gaps = siblingGaps(sourceNodes(program.body), 0);
|
|
126
|
+
collectGaps(program.body, 0, code, gaps);
|
|
127
|
+
gaps.sort((left, right) => left.start - right.start);
|
|
128
|
+
let output = "";
|
|
129
|
+
let cursor = 0;
|
|
130
|
+
for (const gap of gaps) {
|
|
131
|
+
output += code.slice(cursor, gap.start) + renderGap(code.slice(gap.start, gap.end), gap);
|
|
132
|
+
cursor = gap.end;
|
|
133
|
+
}
|
|
134
|
+
return (output + code.slice(cursor)).trim();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function displayCode(code: string, language: EvalLanguage): string {
|
|
138
|
+
if (language !== "js" || !isDense(code)) return code;
|
|
139
|
+
const cached = cache.get(code);
|
|
140
|
+
if (cached !== undefined) return cached;
|
|
141
|
+
const formatted = reformat(code);
|
|
142
|
+
if (cache.size >= CACHE_LIMIT) cache.delete(cache.keys().next().value ?? "");
|
|
143
|
+
cache.set(code, formatted);
|
|
144
|
+
return formatted;
|
|
145
|
+
}
|
package/src/tool/eval-request.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
|
-
import {
|
|
2
|
+
import type { EvalControlInput, EvalToolInput, EvalToolRequest } from "./types.ts";
|
|
3
3
|
|
|
4
4
|
const NON_INTERACTIVE_MODES = new Set(["print", "json"]);
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// over-limit value is force-truncated here (prepareArguments runs before schema validation)
|
|
10
|
-
// instead of failing the call.
|
|
11
|
-
export function clampEvalSummary(value: unknown): string | undefined {
|
|
6
|
+
// A summary is one line of any length: whitespace (including newlines) collapses to single
|
|
7
|
+
// spaces, and a blank value counts as absent. The TUI bounds how much of it a collapsed block shows.
|
|
8
|
+
export function normalizeEvalSummary(value: unknown): string | undefined {
|
|
12
9
|
if (typeof value !== "string") return undefined;
|
|
13
10
|
const normalized = value.trim().replace(/\s+/gu, " ");
|
|
14
|
-
|
|
15
|
-
if (normalized.length <= EVAL_SUMMARY_MAX_LENGTH) return normalized;
|
|
16
|
-
return `${normalized.slice(0, EVAL_SUMMARY_MAX_LENGTH - ELLIPSIS.length)}${ELLIPSIS}`;
|
|
11
|
+
return normalized.length === 0 ? undefined : normalized;
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
export function parseEvalRequest(params: unknown): EvalToolRequest {
|
|
@@ -28,10 +23,10 @@ export function parseEvalRequest(params: unknown): EvalToolRequest {
|
|
|
28
23
|
throw new TypeError(`Unknown eval action "${String(params.action)}"`);
|
|
29
24
|
if (!isEvalLanguage(params.language)) throw new TypeError("eval run requires language");
|
|
30
25
|
if (typeof params.code !== "string") throw new TypeError("eval run requires code");
|
|
31
|
-
const summary =
|
|
26
|
+
const summary = normalizeEvalSummary(params.summary);
|
|
32
27
|
if (summary === undefined)
|
|
33
28
|
throw new TypeError(
|
|
34
|
-
"eval run requires summary — one line in the user's language: what
|
|
29
|
+
"eval run requires summary — one line in the user's language: what you are working on and for what purpose",
|
|
35
30
|
);
|
|
36
31
|
if (params.on_timeout !== undefined && params.on_timeout !== "detach" && params.on_timeout !== "error")
|
|
37
32
|
throw new TypeError(`Unknown eval on_timeout value "${String(params.on_timeout)}"`);
|
package/src/tool/eval-tool.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
|
|
9
9
|
import { EvalDetachedCellManager } from "./detached-cell-manager.ts";
|
|
10
10
|
import { executeEvalControl } from "./detached-eval-result.ts";
|
|
11
|
-
import {
|
|
11
|
+
import { isEvalControlRequest, normalizeEvalSummary, parseEvalRequest } from "./eval-request.ts";
|
|
12
12
|
import type { CreateEvalToolOptions } from "./eval-tool-options.ts";
|
|
13
13
|
import { runEvalCell } from "./run-eval-cell.ts";
|
|
14
14
|
import {
|
|
@@ -109,7 +109,7 @@ export function createEvalTool(options: CreateEvalToolOptions) {
|
|
|
109
109
|
const record = args as Record<string, unknown>;
|
|
110
110
|
if (record.action === "peek" || record.action === "stop" || record.action === "list")
|
|
111
111
|
return args as EvalToolRequest;
|
|
112
|
-
const summary =
|
|
112
|
+
const summary = normalizeEvalSummary(record.summary);
|
|
113
113
|
if (summary === undefined) delete record.summary;
|
|
114
114
|
else record.summary = summary;
|
|
115
115
|
return args as EvalToolRequest;
|
package/src/tool/render.ts
CHANGED
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
type ToolRenderResultOptions,
|
|
10
10
|
truncateToVisualLines,
|
|
11
11
|
} from "@code-yeongyu/senpi";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import type { TruncationMeta } from "../output/output-meta.ts";
|
|
13
|
+
import { displayCode } from "./display-code.ts";
|
|
14
|
+
import { normalizeEvalSummary } from "./eval-request.ts";
|
|
14
15
|
import {
|
|
15
16
|
JSON_TREE_MAX_DEPTH_COLLAPSED,
|
|
16
17
|
JSON_TREE_MAX_DEPTH_EXPANDED,
|
|
@@ -65,6 +66,7 @@ type RenderBlock =
|
|
|
65
66
|
| { readonly kind: "dynamic"; readonly render: (width: number) => readonly string[] };
|
|
66
67
|
|
|
67
68
|
const CODE_PREVIEW_LINES = 4;
|
|
69
|
+
const SUMMARY_PREVIEW_LINES = 3;
|
|
68
70
|
const OUTPUT_PREVIEW_LINES = 8;
|
|
69
71
|
const STATUS_PREVIEW_COUNT = 3;
|
|
70
72
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
@@ -183,6 +185,22 @@ function renderAllVisualLines(text: string, width: number): string[] {
|
|
|
183
185
|
return truncateToVisualLines(text, Number.POSITIVE_INFINITY, width).visualLines.map((line) => line.trimEnd());
|
|
184
186
|
}
|
|
185
187
|
|
|
188
|
+
// A summary has no length limit, so a collapsed block shows its first lines and marks the cut.
|
|
189
|
+
function summaryVisualLines(summary: string, width: number, expanded: boolean): string[] {
|
|
190
|
+
const lines = renderAllVisualLines(summary, width);
|
|
191
|
+
if (expanded || lines.length <= SUMMARY_PREVIEW_LINES) return lines;
|
|
192
|
+
const kept = renderAllVisualLines(summary, Math.max(1, width - 1)).slice(0, SUMMARY_PREVIEW_LINES);
|
|
193
|
+
kept[SUMMARY_PREVIEW_LINES - 1] = `${kept[SUMMARY_PREVIEW_LINES - 1] ?? ""}\u2026`;
|
|
194
|
+
return kept;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function summaryBlock(summary: string, theme: Theme | undefined, expanded: boolean): RenderBlock {
|
|
198
|
+
return {
|
|
199
|
+
kind: "dynamic",
|
|
200
|
+
render: (width) => summaryVisualLines(summary, width, expanded).map((line) => style(theme, "muted", line)),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
186
204
|
function renderTextBlock(block: Extract<RenderBlock, { kind: "text" }>, width: number): string[] {
|
|
187
205
|
if (block.maxVisualLines === undefined) return renderAllVisualLines(block.text, width);
|
|
188
206
|
const result = truncateToVisualLines(block.text, block.maxVisualLines, width);
|
|
@@ -287,7 +305,7 @@ function languageForHighlighter(language: EvalLanguage): "python" | "javascript"
|
|
|
287
305
|
|
|
288
306
|
function highlightedCode(code: string, language: EvalLanguage, theme: Theme | undefined): string {
|
|
289
307
|
const normalizedCode = code.trim().length > 0 ? code : "...";
|
|
290
|
-
const lines = highlightCode(normalizedCode, languageForHighlighter(language));
|
|
308
|
+
const lines = highlightCode(displayCode(normalizedCode, language), languageForHighlighter(language));
|
|
291
309
|
return (theme === undefined ? lines.map((line) => line.replace(/\u001b\[[0-9;]*m/gu, "")) : lines).join("\n");
|
|
292
310
|
}
|
|
293
311
|
|
|
@@ -619,11 +637,9 @@ function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges
|
|
|
619
637
|
if (cell.summary !== undefined) {
|
|
620
638
|
appendLines(
|
|
621
639
|
lines,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
color: "muted",
|
|
626
|
-
}),
|
|
640
|
+
summaryVisualLines(cell.summary, Math.max(1, environment.width - 2), environment.expanded).map(
|
|
641
|
+
(line) => `${style(environment.theme, "muted", "│ ")}${style(environment.theme, "muted", line)}`,
|
|
642
|
+
),
|
|
627
643
|
);
|
|
628
644
|
}
|
|
629
645
|
const innerWidth = Math.max(1, environment.width - 2);
|
|
@@ -645,7 +661,7 @@ function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges
|
|
|
645
661
|
for (const line of codePreview.lines) {
|
|
646
662
|
appendLines(lines, renderPrefixed(line, environment, { prefix: "│ ", continuation: "│ ", color: "borderMuted" }));
|
|
647
663
|
}
|
|
648
|
-
const output =
|
|
664
|
+
const output = cell.output.trimEnd();
|
|
649
665
|
if (output.length > 0) {
|
|
650
666
|
appendLines(lines, renderPrefixed("output", environment, { prefix: "├─ ", continuation: "│ ", color: "dim" }));
|
|
651
667
|
const outputColor: ThemeColor = cell.status === "error" ? "error" : "toolOutput";
|
|
@@ -757,24 +773,13 @@ function renderDetailedLines(
|
|
|
757
773
|
context.environment.width,
|
|
758
774
|
),
|
|
759
775
|
);
|
|
760
|
-
if (details.notice !== undefined)
|
|
761
|
-
appendLines(
|
|
762
|
-
lines,
|
|
763
|
-
renderAllVisualLines(style(context.environment.theme, "dim", details.notice), context.environment.width),
|
|
764
|
-
);
|
|
765
|
-
const warning = formatTruncationWarning(details.meta) ?? (details.truncated ? "[eval output truncated]" : null);
|
|
766
|
-
if (warning !== null)
|
|
767
|
-
appendLines(
|
|
768
|
-
lines,
|
|
769
|
-
renderAllVisualLines(style(context.environment.theme, "warning", warning), context.environment.width),
|
|
770
|
-
);
|
|
771
776
|
return lines;
|
|
772
777
|
}
|
|
773
778
|
|
|
774
779
|
function textOutput(result: AgentToolResult<EvalResultDetails>, showImageFallback: boolean): string {
|
|
775
780
|
const lines: string[] = [];
|
|
776
781
|
for (const part of result.content) {
|
|
777
|
-
if (part.type === "text") lines.push(part.text);
|
|
782
|
+
if (part.type === "text" && part.audience !== "model") lines.push(part.text);
|
|
778
783
|
else if (showImageFallback && part.type === "image") {
|
|
779
784
|
lines.push(`[image: ${sanitizeTerminalLabel(part.mimeType)}]`);
|
|
780
785
|
}
|
|
@@ -884,11 +889,11 @@ function resultMetadata(
|
|
|
884
889
|
return [{ kind: "text", text: style(theme, "muted", metadata.join(" | ")) }];
|
|
885
890
|
}
|
|
886
891
|
|
|
887
|
-
// The call renderer reads the assistant message's raw arguments, which
|
|
888
|
-
// original summary (senpi#1472 detached preparation from the message)
|
|
889
|
-
//
|
|
892
|
+
// The call renderer reads the assistant message's raw arguments, which keep the provider's
|
|
893
|
+
// original summary (senpi#1472 detached preparation from the message), so it normalizes here,
|
|
894
|
+
// idempotently: an already-normalized value is returned unchanged.
|
|
890
895
|
function displaySummary(summary: string | undefined): string | undefined {
|
|
891
|
-
return
|
|
896
|
+
return normalizeEvalSummary(summary);
|
|
892
897
|
}
|
|
893
898
|
|
|
894
899
|
export function renderEvalCall(
|
|
@@ -915,10 +920,14 @@ export function renderEvalCall(
|
|
|
915
920
|
{ kind: "text", text: style(theme, "toolTitle", `eval ${args.language}${reset}${timeout}`) },
|
|
916
921
|
...(displaySummary(args.summary) === undefined
|
|
917
922
|
? []
|
|
918
|
-
: [
|
|
923
|
+
: [summaryBlock(displaySummary(args.summary) ?? "", theme, context.expanded)]),
|
|
919
924
|
{
|
|
920
925
|
kind: "text",
|
|
921
|
-
text: style(
|
|
926
|
+
text: style(
|
|
927
|
+
theme,
|
|
928
|
+
"mdCodeBlock",
|
|
929
|
+
args.code.trim().length > 0 ? displayCode(args.code, args.language) : "...",
|
|
930
|
+
),
|
|
922
931
|
maxVisualLines: context.expanded ? undefined : CODE_PREVIEW_LINES,
|
|
923
932
|
collapseKind: "code",
|
|
924
933
|
theme,
|
|
@@ -1006,14 +1015,12 @@ export function renderEvalResult(
|
|
|
1006
1015
|
const status = resultStatus(details, options, context.isError);
|
|
1007
1016
|
const blocks: RenderBlock[] = [
|
|
1008
1017
|
{ kind: "text", text: resultHeader(details, status, theme) },
|
|
1009
|
-
...(details?.summary === undefined
|
|
1010
|
-
? []
|
|
1011
|
-
: [{ kind: "text" as const, text: style(theme, "muted", details.summary) }]),
|
|
1018
|
+
...(details?.summary === undefined ? [] : [summaryBlock(details.summary, theme, expanded)]),
|
|
1012
1019
|
...resultMetadata(details, options, theme, status),
|
|
1013
1020
|
{ kind: "blank" },
|
|
1014
1021
|
];
|
|
1015
1022
|
const rawOutput = textOutput(result, context.showImages && imageProtocol === null);
|
|
1016
|
-
const output =
|
|
1023
|
+
const output = rawOutput.trimEnd();
|
|
1017
1024
|
const hasRenderedImage =
|
|
1018
1025
|
context.showImages && imageProtocol !== null && result.content.some((part) => part.type === "image");
|
|
1019
1026
|
if (output.length > 0) {
|
|
@@ -1070,10 +1077,6 @@ export function renderEvalResult(
|
|
|
1070
1077
|
const calls = toolCallRows(details);
|
|
1071
1078
|
const nestedCalls = nestedToolCallBlock(details, theme, context.cwd, expanded);
|
|
1072
1079
|
if (calls.length > 0) blocks.push({ kind: "blank" }, nestedCalls ?? { kind: "toolCalls", calls, expanded, theme });
|
|
1073
|
-
if (details?.notice !== undefined)
|
|
1074
|
-
blocks.push({ kind: "blank" }, { kind: "text", text: style(theme, "dim", details.notice) });
|
|
1075
|
-
const warning = formatTruncationWarning(details?.meta) ?? (details?.truncated ? "[eval output truncated]" : null);
|
|
1076
|
-
if (warning !== null) blocks.push({ kind: "blank" }, { kind: "text", text: style(theme, "warning", warning) });
|
|
1077
1080
|
component.setBlocks(blocks);
|
|
1078
1081
|
return component;
|
|
1079
1082
|
}
|
package/src/tool/types.ts
CHANGED
|
@@ -17,8 +17,6 @@ export function enabledLanguageList(enabled: EnabledEvalLanguages): EvalLanguage
|
|
|
17
17
|
return evalLanguageOrder.filter((language) => enabled[language]);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export const EVAL_SUMMARY_MAX_LENGTH = 80;
|
|
21
|
-
|
|
22
20
|
/** The deadlines the schema teaches the model; every number comes from the resolved settings. */
|
|
23
21
|
export interface EvalDeadlineSeconds {
|
|
24
22
|
readonly runBudgetSeconds: number;
|
|
@@ -79,9 +77,8 @@ function evalInputProperties<Language extends TSchema>(languageSchema: Language,
|
|
|
79
77
|
code: Type.Optional(Type.String({ description: "Cell body, verbatim." })),
|
|
80
78
|
summary: Type.Optional(
|
|
81
79
|
Type.String({
|
|
82
|
-
maxLength: EVAL_SUMMARY_MAX_LENGTH,
|
|
83
80
|
description:
|
|
84
|
-
"REQUIRED for run.
|
|
81
|
+
"REQUIRED for run. One line in the language the user writes in: a progress update saying what you are doing and why, not a label for the code; shown in the TUI while the cell runs.",
|
|
85
82
|
}),
|
|
86
83
|
),
|
|
87
84
|
timeout: Type.Optional(Type.Number({ minimum: 1, description: timeoutFieldDescription(deadlines) })),
|