@oh-my-pi/pi-coding-agent 16.1.14 → 16.1.16
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 -0
- package/dist/cli.js +3705 -4015
- package/dist/types/advisor/runtime.d.ts +3 -0
- package/dist/types/cli/args.d.ts +2 -5
- package/dist/types/cli/flag-tables.d.ts +2 -2
- package/dist/types/cli/session-picker.d.ts +4 -2
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +32 -1
- package/dist/types/eval/agent-bridge.d.ts +19 -0
- package/dist/types/eval/js/shared/helpers.d.ts +1 -13
- package/dist/types/eval/js/shared/types.d.ts +1 -1
- package/dist/types/eval/js/worker-protocol.d.ts +1 -1
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/export/share.d.ts +8 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/transports/stdio.d.ts +12 -1
- package/dist/types/modes/components/plugin-settings.d.ts +5 -0
- package/dist/types/modes/components/session-selector.d.ts +25 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -3
- package/dist/types/modes/components/status-line/types.d.ts +1 -0
- package/dist/types/secrets/obfuscator.d.ts +3 -3
- package/dist/types/task/isolation-runner.d.ts +128 -0
- package/dist/types/task/worktree.d.ts +14 -1
- package/dist/types/thinking.d.ts +15 -0
- package/dist/types/tools/eval-render.d.ts +3 -0
- package/dist/types/tools/eval.d.ts +11 -17
- package/dist/types/tools/todo.d.ts +26 -28
- package/dist/types/tui/output-block.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/shell-snapshot.d.ts +10 -0
- package/dist/types/web/search/providers/exa.d.ts +2 -0
- package/dist/types/web/search/providers/perplexity.d.ts +17 -3
- package/package.json +12 -12
- package/scripts/build-binary.ts +18 -4
- package/src/advisor/__tests__/advisor.test.ts +114 -0
- package/src/advisor/runtime.ts +129 -1
- package/src/cli/args.ts +4 -5
- package/src/cli/flag-tables.ts +3 -3
- package/src/cli/gallery-fixtures/interaction.ts +6 -9
- package/src/cli/gallery-fixtures/shell.ts +15 -23
- package/src/cli/session-picker.ts +17 -3
- package/src/commands/launch.ts +3 -3
- package/src/config/model-registry.ts +12 -4
- package/src/config/settings-schema.ts +37 -1
- package/src/edit/renderer.ts +34 -12
- package/src/eval/__tests__/agent-bridge.test.ts +462 -3
- package/src/eval/__tests__/helpers-local-roots.test.ts +2 -5
- package/src/eval/__tests__/julia-prelude.test.ts +1 -30
- package/src/eval/__tests__/prelude-agent.test.ts +42 -8
- package/src/eval/agent-bridge.ts +301 -71
- package/src/eval/jl/prelude.jl +32 -227
- package/src/eval/jl/runner.jl +38 -12
- package/src/eval/js/shared/helpers.ts +1 -114
- package/src/eval/js/shared/prelude.txt +13 -27
- package/src/eval/js/shared/runtime.ts +0 -6
- package/src/eval/js/shared/types.ts +1 -1
- package/src/eval/js/worker-protocol.ts +1 -1
- package/src/eval/py/__tests__/prelude.test.ts +13 -0
- package/src/eval/py/executor.ts +1 -1
- package/src/eval/py/prelude.py +47 -105
- package/src/eval/py/runner.py +0 -6
- package/src/eval/rb/prelude.rb +21 -189
- package/src/eval/rb/runner.rb +116 -9
- package/src/exec/bash-executor.ts +44 -0
- package/src/export/html/tool-views.generated.js +29 -29
- package/src/export/share.ts +51 -28
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/local-protocol.ts +100 -53
- package/src/main.ts +15 -4
- package/src/mcp/transports/stdio.ts +20 -4
- package/src/modes/acp/acp-event-mapper.ts +7 -2
- package/src/modes/components/custom-editor.test.ts +22 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/footer.ts +4 -3
- package/src/modes/components/plugin-settings.ts +7 -1
- package/src/modes/components/session-selector.ts +143 -29
- package/src/modes/components/status-line/component.ts +5 -1
- package/src/modes/components/status-line/context-thresholds.ts +11 -3
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/controllers/command-controller.ts +6 -0
- package/src/modes/rpc/rpc-mode.ts +6 -0
- package/src/modes/utils/copy-targets.ts +7 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/system/workflow-notice.md +3 -3
- package/src/prompts/tools/bash.md +18 -2
- package/src/prompts/tools/eval.md +19 -19
- package/src/prompts/tools/search.md +1 -0
- package/src/prompts/tools/todo.md +1 -1
- package/src/secrets/obfuscator.ts +3 -9
- package/src/session/agent-session.ts +264 -52
- package/src/slash-commands/builtin-registry.ts +2 -1
- package/src/task/index.ts +61 -207
- package/src/task/isolation-runner.ts +354 -0
- package/src/task/worktree.ts +46 -9
- package/src/thinking.ts +20 -0
- package/src/tools/ask.ts +44 -38
- package/src/tools/bash.ts +9 -2
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/eval-render.ts +34 -27
- package/src/tools/eval.ts +100 -103
- package/src/tools/index.ts +8 -1
- package/src/tools/read.ts +136 -60
- package/src/tools/todo.ts +60 -64
- package/src/tui/code-cell.ts +1 -1
- package/src/tui/output-block.ts +11 -0
- package/src/utils/image-resize.ts +30 -0
- package/src/utils/shell-snapshot.ts +63 -1
- package/src/web/search/providers/exa.ts +85 -1
- package/src/web/search/providers/perplexity.ts +18 -6
package/src/task/worktree.ts
CHANGED
|
@@ -194,6 +194,19 @@ export async function captureDeltaPatch(isolationDir: string, baseline: Worktree
|
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* Apply nested repo patches directly to their working directories after parent merge.
|
|
197
|
+
*
|
|
198
|
+
* Pre-existing dirty state in a nested repo is stashed before the patch is
|
|
199
|
+
* applied and popped back (with `--index` so staged WIP stays staged) after
|
|
200
|
+
* the commit, so unrelated user edits never get folded into the agent's
|
|
201
|
+
* commit. A failing `git stash pop` (e.g. user edits collide with the patched
|
|
202
|
+
* lines) leaves the stash entry intact, emits a `logger.warn`, and is
|
|
203
|
+
* returned to the caller as a human-readable warning string — the agent
|
|
204
|
+
* commit already landed, so this is a partial success the workflow needs to
|
|
205
|
+
* see, not a thrown failure.
|
|
206
|
+
*
|
|
207
|
+
* Returns the collected stash-restore warnings (empty when every nested repo
|
|
208
|
+
* was restored cleanly). Throws when the patch apply itself fails.
|
|
209
|
+
*
|
|
197
210
|
* @param commitMessage Optional async function to generate a commit message from the combined diff.
|
|
198
211
|
* If omitted or returns null, falls back to a generic message.
|
|
199
212
|
*/
|
|
@@ -201,7 +214,8 @@ export async function applyNestedPatches(
|
|
|
201
214
|
repoRoot: string,
|
|
202
215
|
patches: NestedRepoPatch[],
|
|
203
216
|
commitMessage?: (diff: string) => Promise<string | null>,
|
|
204
|
-
): Promise<
|
|
217
|
+
): Promise<string[]> {
|
|
218
|
+
const warnings: string[] = [];
|
|
205
219
|
// Group patches by target repo to apply all at once and commit
|
|
206
220
|
const byRepo = new Map<string, NestedRepoPatch[]>();
|
|
207
221
|
for (const p of patches) {
|
|
@@ -220,17 +234,40 @@ export async function applyNestedPatches(
|
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
const combinedDiff = repoPatches.map(p => p.patch).join("\n");
|
|
223
|
-
for (const { patch } of repoPatches) {
|
|
224
|
-
await git.patch.applyText(nestedDir, patch);
|
|
225
|
-
}
|
|
226
237
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
await git.
|
|
231
|
-
|
|
238
|
+
// Preserve any pre-existing dirty state (tracked + untracked) so we
|
|
239
|
+
// commit only the agent delta, not the user's in-flight work.
|
|
240
|
+
const stashed =
|
|
241
|
+
(await git.status(nestedDir)).trim().length > 0
|
|
242
|
+
? await git.stash.push(nestedDir, `omp-isolation-${Snowflake.next()}`)
|
|
243
|
+
: false;
|
|
244
|
+
try {
|
|
245
|
+
for (const { patch } of repoPatches) {
|
|
246
|
+
await git.patch.applyText(nestedDir, patch);
|
|
247
|
+
}
|
|
248
|
+
if ((await git.status(nestedDir)).trim().length > 0) {
|
|
249
|
+
const msg = (await commitMessage?.(combinedDiff)) ?? "changes from isolated task(s)";
|
|
250
|
+
await git.stage.files(nestedDir);
|
|
251
|
+
await git.commit(nestedDir, msg);
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
if (stashed) {
|
|
255
|
+
try {
|
|
256
|
+
await git.stash.pop(nestedDir, { index: true });
|
|
257
|
+
} catch (popErr) {
|
|
258
|
+
const message = popErr instanceof Error ? popErr.message : String(popErr);
|
|
259
|
+
logger.warn("Pre-existing nested-repo dirty state could not be auto-restored", {
|
|
260
|
+
nestedDir,
|
|
261
|
+
error: message,
|
|
262
|
+
});
|
|
263
|
+
warnings.push(
|
|
264
|
+
`Pre-existing dirty state in nested repo \`${relativePath}\` could not be auto-restored after the agent commit; stash entry preserved (${message}).`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
232
268
|
}
|
|
233
269
|
}
|
|
270
|
+
return warnings;
|
|
234
271
|
}
|
|
235
272
|
|
|
236
273
|
// ═══════════════════════════════════════════════════════════════════════════
|
package/src/thinking.ts
CHANGED
|
@@ -153,6 +153,26 @@ export function getConfiguredThinkingLevelMetadata(level: ConfiguredThinkingLeve
|
|
|
153
153
|
return level === AUTO_THINKING ? AUTO_THINKING_METADATA : getThinkingLevelMetadata(level);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Thinking selectors accepted by the `--thinking` CLI flag, in display order:
|
|
158
|
+
* `off`, every concrete effort (`minimal`..`xhigh`), then `auto`. Single source
|
|
159
|
+
* for the flag's `options` list, shell completions, and the "invalid level"
|
|
160
|
+
* warning so all three stay in sync.
|
|
161
|
+
*/
|
|
162
|
+
export const CLI_THINKING_LEVELS: readonly string[] = [ThinkingLevel.Off, ...THINKING_EFFORTS, AUTO_THINKING];
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parses a `--thinking` CLI value. Accepts every {@link parseConfiguredThinkingLevel}
|
|
166
|
+
* selector (`off`, `auto`, `minimal`..`xhigh`, plus the `max` alias) but rejects
|
|
167
|
+
* `inherit`: an explicit `inherit` on the command line would suppress the
|
|
168
|
+
* settings/scoped-model fallback during startup resolution only to resolve back
|
|
169
|
+
* to the provider default, which is never what the user means.
|
|
170
|
+
*/
|
|
171
|
+
export function parseCliThinkingLevel(value: string | null | undefined): ConfiguredThinkingLevel | undefined {
|
|
172
|
+
const level = parseConfiguredThinkingLevel(value);
|
|
173
|
+
return level === ThinkingLevel.Inherit ? undefined : level;
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
/**
|
|
157
177
|
* Resolves an auto-classified effort against the active model's supported
|
|
158
178
|
* range. Unlike {@link clampThinkingLevelForModel}, `auto` never resolves below
|
package/src/tools/ask.ts
CHANGED
|
@@ -308,7 +308,7 @@ async function askSingleQuestion(
|
|
|
308
308
|
}
|
|
309
309
|
const customResult = await promptForCustomInput();
|
|
310
310
|
if (customResult.input === undefined) {
|
|
311
|
-
|
|
311
|
+
continue;
|
|
312
312
|
}
|
|
313
313
|
customInput = customResult.input;
|
|
314
314
|
break;
|
|
@@ -332,51 +332,57 @@ async function askSingleQuestion(
|
|
|
332
332
|
}
|
|
333
333
|
selectedOptions = Array.from(selected);
|
|
334
334
|
} else {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
335
|
+
while (true) {
|
|
336
|
+
const displayOptions = addRecommendedSuffix(questionOptions, recommended);
|
|
337
|
+
const optionsWithNavigation: ExtensionUISelectItem[] = [...displayOptions, OTHER_OPTION];
|
|
338
|
+
|
|
339
|
+
let initialIndex = recommended;
|
|
340
|
+
const previouslySelected = selectedOptions[0];
|
|
341
|
+
if (previouslySelected) {
|
|
342
|
+
const selectedIndex = questionOptions.findIndex(option => option.label === previouslySelected);
|
|
343
|
+
if (selectedIndex >= 0) initialIndex = selectedIndex;
|
|
344
|
+
} else if (customInput !== undefined) {
|
|
345
|
+
initialIndex = displayOptions.length;
|
|
346
|
+
}
|
|
347
|
+
if (initialIndex !== undefined) {
|
|
348
|
+
const maxIndex = Math.max(optionsWithNavigation.length - 1, 0);
|
|
349
|
+
initialIndex = Math.max(0, Math.min(initialIndex, maxIndex));
|
|
350
|
+
}
|
|
350
351
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
352
|
+
const {
|
|
353
|
+
choice,
|
|
354
|
+
timedOut: selectTimedOut,
|
|
355
|
+
navigation: arrowNavigation,
|
|
356
|
+
} = await selectOption(promptWithProgress, optionsWithNavigation, initialIndex, {
|
|
357
|
+
selectionMarker: "radio",
|
|
358
|
+
markableCount: displayOptions.length,
|
|
359
|
+
});
|
|
360
|
+
timedOut = selectTimedOut;
|
|
360
361
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
362
|
+
if (arrowNavigation) {
|
|
363
|
+
return { selectedOptions, customInput, timedOut, navigation: arrowNavigation };
|
|
364
|
+
}
|
|
365
|
+
if (choice === undefined) {
|
|
366
|
+
if (!timedOut) {
|
|
367
|
+
return { selectedOptions, customInput, timedOut, cancelled: true };
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
367
370
|
}
|
|
368
|
-
|
|
369
|
-
|
|
371
|
+
if (choice === OTHER_OPTION) {
|
|
372
|
+
if (selectTimedOut) {
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
370
375
|
const customResult = await promptForCustomInput();
|
|
371
|
-
if (customResult.input
|
|
372
|
-
|
|
373
|
-
selectedOptions = [];
|
|
376
|
+
if (customResult.input === undefined) {
|
|
377
|
+
continue;
|
|
374
378
|
}
|
|
375
|
-
|
|
379
|
+
customInput = customResult.input;
|
|
380
|
+
selectedOptions = [];
|
|
381
|
+
break;
|
|
376
382
|
}
|
|
377
|
-
} else {
|
|
378
383
|
selectedOptions = [stripRecommendedSuffix(choice)];
|
|
379
384
|
customInput = undefined;
|
|
385
|
+
break;
|
|
380
386
|
}
|
|
381
387
|
if (navigation?.allowForward) {
|
|
382
388
|
return { selectedOptions, customInput, timedOut, navigation: "forward" };
|
package/src/tools/bash.ts
CHANGED
|
@@ -19,7 +19,7 @@ import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
|
19
19
|
import type { ClientBridgeTerminalExitStatus, ClientBridgeTerminalOutput } from "../session/client-bridge";
|
|
20
20
|
import { DEFAULT_MAX_BYTES, enforceInlineByteCap, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
21
21
|
import { renderStatusLine } from "../tui";
|
|
22
|
-
import { CachedOutputBlock, markFramedBlockComponent } from "../tui/output-block";
|
|
22
|
+
import { CachedOutputBlock, markFramedBlockComponent, outputBlockContentWidth } from "../tui/output-block";
|
|
23
23
|
import { getSixelLineMask } from "../utils/sixel";
|
|
24
24
|
import type { ToolSession } from ".";
|
|
25
25
|
import { truncateForPrompt } from "./approval";
|
|
@@ -1334,7 +1334,14 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1334
1334
|
.map(line => uiTheme.fg("toolOutput", replaceTabs(line)))
|
|
1335
1335
|
.join("\n");
|
|
1336
1336
|
const textContent = styledOutput;
|
|
1337
|
-
|
|
1337
|
+
// Cap the collapsed/streaming output to a viewport-sized tail and
|
|
1338
|
+
// measure it at the box's INNER width. Otherwise a growing tail
|
|
1339
|
+
// window scrolls its (mutating) rows above the live-region window
|
|
1340
|
+
// and the engine re-commits a fresh snapshot every frame —
|
|
1341
|
+
// spraying duplicate "… ctrl+o to expand" banners into native
|
|
1342
|
+
// scrollback (the box never overflows the viewport now).
|
|
1343
|
+
const previewBudget = Math.min(previewLines, previewWindow);
|
|
1344
|
+
const result = truncateToVisualLines(textContent, previewBudget, outputBlockContentWidth(width));
|
|
1338
1345
|
if (result.skippedCount > 0) {
|
|
1339
1346
|
outputLines.push(
|
|
1340
1347
|
uiTheme.fg(
|
|
@@ -800,7 +800,7 @@ export class WorkerCore {
|
|
|
800
800
|
displays.push({ type: "text", text: safeJsonStringify(output.data) });
|
|
801
801
|
return;
|
|
802
802
|
}
|
|
803
|
-
// status — surface as compact JSON so helper side effects (read/write/
|
|
803
|
+
// status — surface as compact JSON so helper side effects (read/write/env) appear in
|
|
804
804
|
// the cell result alongside explicit display() output.
|
|
805
805
|
displays.push({ type: "text", text: safeJsonStringify(output.event) });
|
|
806
806
|
}
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
|
18
18
|
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
19
19
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
20
20
|
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
21
|
-
import { markFramedBlockComponent, renderCodeCell } from "../tui";
|
|
21
|
+
import { markFramedBlockComponent, outputBlockContentWidth, renderCodeCell } from "../tui";
|
|
22
22
|
import {
|
|
23
23
|
JSON_TREE_MAX_DEPTH_COLLAPSED,
|
|
24
24
|
JSON_TREE_MAX_DEPTH_EXPANDED,
|
|
@@ -56,6 +56,9 @@ interface EvalRenderCellArg {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
interface EvalRenderArgs {
|
|
59
|
+
language?: string;
|
|
60
|
+
code?: string;
|
|
61
|
+
title?: string;
|
|
59
62
|
cells?: EvalRenderCellArg[];
|
|
60
63
|
__partialJson?: string;
|
|
61
64
|
}
|
|
@@ -81,8 +84,8 @@ function normalizeRenderLanguage(value: string | undefined): EvalLanguage {
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
if (!args) return [];
|
|
88
|
+
const raw = Array.isArray(args.cells) ? args.cells : typeof args.code === "string" ? [args] : [];
|
|
86
89
|
const out: EvalRenderCell[] = [];
|
|
87
90
|
for (const cell of raw) {
|
|
88
91
|
if (!cell || typeof cell !== "object") continue;
|
|
@@ -238,14 +241,12 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
238
241
|
const opIcons: Record<string, AvailableIcon> = {
|
|
239
242
|
read: "icon.file",
|
|
240
243
|
write: "icon.file",
|
|
241
|
-
append: "icon.file",
|
|
242
244
|
cat: "icon.file",
|
|
243
245
|
touch: "icon.file",
|
|
244
246
|
ls: "icon.folder",
|
|
245
247
|
cd: "icon.folder",
|
|
246
248
|
pwd: "icon.folder",
|
|
247
249
|
mkdir: "icon.folder",
|
|
248
|
-
tree: "icon.folder",
|
|
249
250
|
git_status: "icon.git",
|
|
250
251
|
git_diff: "icon.git",
|
|
251
252
|
git_log: "icon.git",
|
|
@@ -277,7 +278,6 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
277
278
|
if (data.path) parts.push(`from ${shortenPath(String(data.path))}`);
|
|
278
279
|
break;
|
|
279
280
|
case "write":
|
|
280
|
-
case "append":
|
|
281
281
|
parts.push(`${data.chars ?? data.bytes ?? 0} chars`);
|
|
282
282
|
if (data.path) parts.push(`to ${shortenPath(String(data.path))}`);
|
|
283
283
|
break;
|
|
@@ -316,13 +316,6 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
316
316
|
parts.push(`${data.lines} line${(data.lines as number) !== 1 ? "s" : ""}`);
|
|
317
317
|
if (data.staged) parts.push("(staged)");
|
|
318
318
|
break;
|
|
319
|
-
case "diff":
|
|
320
|
-
if (data.identical) {
|
|
321
|
-
parts.push("files identical");
|
|
322
|
-
} else {
|
|
323
|
-
parts.push("files differ");
|
|
324
|
-
}
|
|
325
|
-
break;
|
|
326
319
|
case "batch":
|
|
327
320
|
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""} processed`);
|
|
328
321
|
break;
|
|
@@ -412,8 +405,6 @@ function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string
|
|
|
412
405
|
case "cat":
|
|
413
406
|
case "head":
|
|
414
407
|
case "tail":
|
|
415
|
-
case "tree":
|
|
416
|
-
case "diff":
|
|
417
408
|
case "git_diff":
|
|
418
409
|
case "sh":
|
|
419
410
|
if (data.preview) addPreview(String(data.preview));
|
|
@@ -468,23 +459,33 @@ function formatCellOutputLines(
|
|
|
468
459
|
return { lines: [], hiddenCount: 0 };
|
|
469
460
|
}
|
|
470
461
|
|
|
462
|
+
// Cell output lands in renderCodeCell → renderOutputBlock, which re-wraps it
|
|
463
|
+
// at the box's inner content width. Bound the collapsed tail by VISUAL rows
|
|
464
|
+
// at that width so a long-line tail can't wrap into more rows than budgeted
|
|
465
|
+
// and scroll its mutating preview above the live-region window — the
|
|
466
|
+
// duplicate "ctrl+o to expand" scrollback spray.
|
|
467
|
+
const innerWidth = outputBlockContentWidth(width);
|
|
468
|
+
|
|
471
469
|
if (cell.hasMarkdown && cell.status !== "error") {
|
|
472
470
|
const md = new Markdown(cell.output, 0, 0, getMarkdownTheme());
|
|
473
|
-
const allLines = md.render(
|
|
471
|
+
const allLines = md.render(innerWidth);
|
|
474
472
|
const displayLines = expanded ? allLines : allLines.slice(-previewLines);
|
|
475
473
|
const hiddenCount = allLines.length - displayLines.length;
|
|
476
474
|
return { lines: displayLines, hiddenCount };
|
|
477
475
|
}
|
|
478
476
|
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
477
|
+
const styledOutput = cell.output
|
|
478
|
+
.split("\n")
|
|
479
|
+
.map(line => {
|
|
480
|
+
const cleaned = replaceTabs(line);
|
|
481
|
+
return cell.status === "error" ? theme.fg("error", cleaned) : theme.fg("toolOutput", cleaned);
|
|
482
|
+
})
|
|
483
|
+
.join("\n");
|
|
484
|
+
if (expanded) {
|
|
485
|
+
return { lines: styledOutput.split("\n"), hiddenCount: 0 };
|
|
486
|
+
}
|
|
487
|
+
const { visualLines, skippedCount } = truncateToVisualLines(styledOutput, previewLines, innerWidth);
|
|
488
|
+
return { lines: visualLines, hiddenCount: skippedCount };
|
|
488
489
|
}
|
|
489
490
|
|
|
490
491
|
export const evalToolRenderer = {
|
|
@@ -586,7 +587,10 @@ export const evalToolRenderer = {
|
|
|
586
587
|
return markFramedBlockComponent({
|
|
587
588
|
render: (width: number): readonly string[] => {
|
|
588
589
|
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
589
|
-
const previewLines =
|
|
590
|
+
const previewLines = Math.min(
|
|
591
|
+
options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES,
|
|
592
|
+
previewWindowRows(),
|
|
593
|
+
);
|
|
590
594
|
const key = `${expanded}|${previewLines}|${options.spinnerFrame}|${previewWindowRows()}`;
|
|
591
595
|
if (cached && cached.key === key && cached.width === width) {
|
|
592
596
|
return cached.result;
|
|
@@ -717,7 +721,10 @@ export const evalToolRenderer = {
|
|
|
717
721
|
|
|
718
722
|
return {
|
|
719
723
|
render: (width: number): readonly string[] => {
|
|
720
|
-
const previewLines =
|
|
724
|
+
const previewLines = Math.min(
|
|
725
|
+
options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES,
|
|
726
|
+
previewWindowRows(),
|
|
727
|
+
);
|
|
721
728
|
if (cachedLines === undefined || cachedWidth !== width || cachedPreviewLines !== previewLines) {
|
|
722
729
|
const result = truncateToVisualLines(textContent, previewLines, width);
|
|
723
730
|
cachedLines = result.visualLines;
|