mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): apply outputPad to all transcript blocks (#10557)
This commit is contained in:
parent
76f6c06daf
commit
18336987ad
20 changed files with 223 additions and 49 deletions
|
|
@ -5,11 +5,17 @@
|
|||
### Added
|
||||
|
||||
- Added `+name` and `-name` entries to `--tools`, which change the default tool selection instead of replacing it, for example `pi -t +codemode`
|
||||
- Added `outputPad` to the tool render context ([#10557](https://github.com/earendil-works/pi/pull/10557) by [@rwachtler](https://github.com/rwachtler))
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed `outputPad` to also apply to `!` command output, tool output, and summary blocks ([#9946](https://github.com/earendil-works/pi/issues/9946), [#10557](https://github.com/earendil-works/pi/pull/10557) by [@rwachtler](https://github.com/rwachtler))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed managed installs keeping every old release; `pi update` now keeps only the new release and the one it updated from ([#10392](https://github.com/earendil-works/pi/issues/10392))
|
||||
- Fixed standalone binaries loading `.env`, `.env.local`, and `.env.development` from the launch directory into Pi's environment ([#10473](https://github.com/earendil-works/pi/issues/10473))
|
||||
- Fixed `!!` command headers losing their dim color once output arrives ([#10557](https://github.com/earendil-works/pi/pull/10557) by [@rwachtler](https://github.com/rwachtler))
|
||||
|
||||
## [1.0.4] - 2026-10-05
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ See [Compaction Reference](compaction.md) for trigger, summarization, and valida
|
|||
| `fullscreenCopyOnSelect` | boolean | `true` | Copy selected text automatically in fullscreen mode. |
|
||||
| `fullscreenWheelScrollLines` | `"auto"` \| number | `"auto"` | Lines per mouse-wheel event in fullscreen mode, from 1 to 100. `"auto"` moves one line per event in local macOS terminals, which already accelerate wheel and trackpad input; elsewhere, and over SSH, it speeds up fast wheel spins to at most 6 lines per event. Alt+wheel moves five times as far. |
|
||||
| `editorPaddingX` | number | `0` | Horizontal editor padding from 0 to 3 cells. |
|
||||
| `outputPad` | `0 \| 1` | `1` | Horizontal transcript padding. |
|
||||
| `outputPad` | `0 \| 1` | `1` | Horizontal transcript padding for messages, tool output, `!` command output, and summary blocks. |
|
||||
| `autocompleteMaxVisible` | number | `5` | Visible autocomplete entries, from 3 to 20. |
|
||||
| `showHardwareCursor` | boolean | `false` | Show the terminal cursor while Pi positions it for input methods. |
|
||||
| `terminal.showImages` | boolean | `true` | Display inline images when supported. |
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRend
|
|||
expanded,
|
||||
showImages: false,
|
||||
isError,
|
||||
outputPad: 1,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -489,6 +489,8 @@ export interface ToolRenderContext<TState = any, TArgs = any> {
|
|||
showImages: boolean;
|
||||
/** Whether the current result is an error. */
|
||||
isError: boolean;
|
||||
/** Horizontal padding configured by the outputPad setting. Renderers with `renderShell: "self"` apply it themselves. */
|
||||
outputPad: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export interface Settings {
|
|||
treeFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; // Default filter when opening /tree
|
||||
thinkingBudgets?: ThinkingBudgetsSettings; // Custom token budgets for thinking levels
|
||||
editorPaddingX?: number; // Horizontal padding for input editor (default: 0)
|
||||
outputPad?: 0 | 1; // Horizontal padding for chat message output (default: 1)
|
||||
outputPad?: 0 | 1; // Horizontal padding for transcript content (default: 1)
|
||||
autocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5)
|
||||
showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME
|
||||
markdown?: MarkdownSettings;
|
||||
|
|
|
|||
|
|
@ -133,8 +133,10 @@ function buildEditCallComponent(
|
|||
args: RenderableEditArgs | undefined,
|
||||
theme: Theme,
|
||||
cwd: string,
|
||||
outputPad: number,
|
||||
): EditCallRenderComponent {
|
||||
component.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));
|
||||
component.setPaddingX(outputPad);
|
||||
component.clear();
|
||||
component.addChild(new Text(formatEditCall(args, theme, cwd), 0, 0));
|
||||
|
||||
|
|
@ -192,7 +194,13 @@ export const editRenderers: Pick<ToolDefinition<any, any>, "renderCall" | "rende
|
|||
});
|
||||
}
|
||||
|
||||
return buildEditCallComponent(component, args as RenderableEditArgs | undefined, theme, context.cwd);
|
||||
return buildEditCallComponent(
|
||||
component,
|
||||
args as RenderableEditArgs | undefined,
|
||||
theme,
|
||||
context.cwd,
|
||||
context.outputPad,
|
||||
);
|
||||
},
|
||||
renderResult(result, _options, theme, context) {
|
||||
const callComponent = context.state.callComponent;
|
||||
|
|
@ -215,7 +223,13 @@ export const editRenderers: Pick<ToolDefinition<any, any>, "renderCall" | "rende
|
|||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
buildEditCallComponent(callComponent, context.args as RenderableEditArgs | undefined, theme, context.cwd);
|
||||
buildEditCallComponent(
|
||||
callComponent,
|
||||
context.args as RenderableEditArgs | undefined,
|
||||
theme,
|
||||
context.cwd,
|
||||
context.outputPad,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +246,7 @@ export const editRenderers: Pick<ToolDefinition<any, any>, "renderCall" | "rende
|
|||
return component;
|
||||
}
|
||||
component.addChild(new Spacer(1));
|
||||
component.addChild(new Text(output, 1, 0));
|
||||
component.addChild(new Text(output, context.outputPad, 0));
|
||||
return component;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,14 +28,16 @@ export class BashExecutionComponent extends Container {
|
|||
private fullOutputPath?: string;
|
||||
private expanded = false;
|
||||
private contentContainer: Container;
|
||||
/** `dim` marks `!!` commands, whose output is excluded from the model context. */
|
||||
private readonly colorKey: "dim" | "bashMode";
|
||||
private outputPad: number;
|
||||
|
||||
constructor(command: string, ui: TUI, excludeFromContext = false) {
|
||||
constructor(command: string, ui: TUI, excludeFromContext = false, outputPad = 1) {
|
||||
super();
|
||||
this.command = command;
|
||||
|
||||
// Use dim border for excluded-from-context commands (!! prefix)
|
||||
const colorKey = excludeFromContext ? "dim" : "bashMode";
|
||||
const borderColor = (str: string) => theme.fg(colorKey, str);
|
||||
this.colorKey = excludeFromContext ? "dim" : "bashMode";
|
||||
this.outputPad = outputPad;
|
||||
const borderColor = (str: string) => theme.fg(this.colorKey, str);
|
||||
|
||||
// Add spacer
|
||||
this.addChild(new Spacer(1));
|
||||
|
|
@ -47,21 +49,17 @@ export class BashExecutionComponent extends Container {
|
|||
this.contentContainer = new Container();
|
||||
this.addChild(this.contentContainer);
|
||||
|
||||
// Command header
|
||||
const header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);
|
||||
this.contentContainer.addChild(header);
|
||||
|
||||
// Loader
|
||||
this.loader = new Loader(
|
||||
ui,
|
||||
(spinner) => theme.fg(colorKey, spinner),
|
||||
(spinner) => theme.fg(this.colorKey, spinner),
|
||||
(text) => theme.fg("muted", text),
|
||||
`Running... (${keyText("tui.select.cancel")} to cancel)`, // Plain text for loader
|
||||
);
|
||||
this.contentContainer.addChild(this.loader);
|
||||
|
||||
// Bottom border
|
||||
this.addChild(new DynamicBorder(borderColor));
|
||||
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,6 +70,11 @@ export class BashExecutionComponent extends Container {
|
|||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setOutputPad(outputPad: number): void {
|
||||
this.outputPad = outputPad;
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
override invalidate(): void {
|
||||
super.invalidate();
|
||||
this.updateDisplay();
|
||||
|
|
@ -135,7 +138,7 @@ export class BashExecutionComponent extends Container {
|
|||
this.contentContainer.clear();
|
||||
|
||||
// Command header
|
||||
const header = new Text(theme.fg("bashMode", theme.bold(`$ ${this.command}`)), 1, 0);
|
||||
const header = new Text(theme.fg(this.colorKey, theme.bold(`$ ${this.command}`)), this.outputPad, 0);
|
||||
this.contentContainer.addChild(header);
|
||||
|
||||
// Output
|
||||
|
|
@ -143,7 +146,7 @@ export class BashExecutionComponent extends Container {
|
|||
if (this.expanded) {
|
||||
// Show all lines
|
||||
const displayText = availableLines.map((line) => theme.fg("muted", line)).join("\n");
|
||||
this.contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
|
||||
this.contentContainer.addChild(new Text(`\n${displayText}`, this.outputPad, 0));
|
||||
} else {
|
||||
// Use shared visual truncation utility with width-aware caching
|
||||
const styledOutput = previewLogicalLines.map((line) => theme.fg("muted", line)).join("\n");
|
||||
|
|
@ -153,7 +156,7 @@ export class BashExecutionComponent extends Container {
|
|||
this.contentContainer.addChild({
|
||||
render: (width: number) => {
|
||||
if (cachedLines === undefined || cachedWidth !== width) {
|
||||
const result = truncateToVisualLines(styledInput, PREVIEW_LINES, width, 1);
|
||||
const result = truncateToVisualLines(styledInput, PREVIEW_LINES, width, this.outputPad);
|
||||
cachedLines = result.visualLines;
|
||||
cachedWidth = width;
|
||||
}
|
||||
|
|
@ -199,7 +202,7 @@ export class BashExecutionComponent extends Container {
|
|||
}
|
||||
|
||||
if (statusParts.length > 0) {
|
||||
this.contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, 1, 0));
|
||||
this.contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, this.outputPad, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export class BranchSummaryMessageComponent extends Box {
|
|||
private message: BranchSummaryMessage;
|
||||
private markdownTheme: MarkdownTheme;
|
||||
|
||||
constructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
||||
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
||||
constructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme(), outputPad = 1) {
|
||||
super(outputPad, 1, (t) => theme.bg("customMessageBg", t));
|
||||
this.message = message;
|
||||
this.markdownTheme = markdownTheme;
|
||||
this.updateDisplay();
|
||||
|
|
@ -24,6 +24,10 @@ export class BranchSummaryMessageComponent extends Box {
|
|||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setOutputPad(outputPad: number): void {
|
||||
this.setPaddingX(outputPad);
|
||||
}
|
||||
|
||||
override invalidate(): void {
|
||||
super.invalidate();
|
||||
this.updateDisplay();
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export class CompactionSummaryMessageComponent extends Box {
|
|||
private message: CompactionSummaryMessage;
|
||||
private markdownTheme: MarkdownTheme;
|
||||
|
||||
constructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
||||
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
||||
constructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme(), outputPad = 1) {
|
||||
super(outputPad, 1, (t) => theme.bg("customMessageBg", t));
|
||||
this.message = message;
|
||||
this.markdownTheme = markdownTheme;
|
||||
this.updateDisplay();
|
||||
|
|
@ -24,6 +24,10 @@ export class CompactionSummaryMessageComponent extends Box {
|
|||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setOutputPad(outputPad: number): void {
|
||||
this.setPaddingX(outputPad);
|
||||
}
|
||||
|
||||
override invalidate(): void {
|
||||
super.invalidate();
|
||||
this.updateDisplay();
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ export class CustomEntryComponent extends Container {
|
|||
private renderer: EntryRenderer;
|
||||
private customComponent?: Component;
|
||||
private _expanded = false;
|
||||
private outputPad: number;
|
||||
|
||||
constructor(entry: CustomEntry<unknown>, renderer: EntryRenderer) {
|
||||
constructor(entry: CustomEntry<unknown>, renderer: EntryRenderer, outputPad = 1) {
|
||||
super();
|
||||
this.entry = entry;
|
||||
this.renderer = renderer;
|
||||
this.outputPad = outputPad;
|
||||
this.rebuild();
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +34,11 @@ export class CustomEntryComponent extends Container {
|
|||
}
|
||||
}
|
||||
|
||||
setOutputPad(outputPad: number): void {
|
||||
this.outputPad = outputPad;
|
||||
this.rebuild();
|
||||
}
|
||||
|
||||
override invalidate(): void {
|
||||
super.invalidate();
|
||||
this.rebuild();
|
||||
|
|
@ -46,7 +53,7 @@ export class CustomEntryComponent extends Container {
|
|||
component = this.renderer(this.entry, { expanded: this._expanded }, theme);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
||||
const box = new Box(this.outputPad, 1, (text) => theme.bg("customMessageBg", text));
|
||||
box.addChild(new Text(theme.fg("error", `[${this.entry.customType}] renderer failed: ${message}`), 0, 0));
|
||||
component = box;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ export class CustomMessageComponent extends Container {
|
|||
// Default rendering uses our box
|
||||
this.addChild(this.box);
|
||||
this.box.clear();
|
||||
this.box.setPaddingX(this.outputPad);
|
||||
|
||||
// Default rendering: label + content
|
||||
const label = theme.fg("customMessageLabel", `\x1b[1m[${this.message.customType}]\x1b[22m`);
|
||||
|
|
|
|||
|
|
@ -827,7 +827,7 @@ export class SettingsSelectorComponent extends Container {
|
|||
items.splice(editorPaddingIndex + 1, 0, {
|
||||
id: "output-padding",
|
||||
label: "Output padding",
|
||||
description: "Horizontal padding for user messages, assistant messages, and thinking",
|
||||
description: "Horizontal padding for messages, tool output, and command output",
|
||||
currentValue: String(config.outputPad),
|
||||
values: ["0", "1"],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ export class SkillInvocationMessageComponent extends Box {
|
|||
private skillBlock: ParsedSkillBlock;
|
||||
private markdownTheme: MarkdownTheme;
|
||||
|
||||
constructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
||||
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
||||
constructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme(), outputPad = 1) {
|
||||
super(outputPad, 1, (t) => theme.bg("customMessageBg", t));
|
||||
this.skillBlock = skillBlock;
|
||||
this.markdownTheme = markdownTheme;
|
||||
this.updateDisplay();
|
||||
|
|
@ -25,6 +25,10 @@ export class SkillInvocationMessageComponent extends Box {
|
|||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setOutputPad(outputPad: number): void {
|
||||
this.setPaddingX(outputPad);
|
||||
}
|
||||
|
||||
override invalidate(): void {
|
||||
super.invalidate();
|
||||
this.updateDisplay();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const FALLBACK_PREVIEW_LINES = 10;
|
|||
export interface ToolExecutionOptions {
|
||||
showImages?: boolean;
|
||||
imageWidthCells?: number;
|
||||
outputPad?: number;
|
||||
}
|
||||
|
||||
export class ToolExecutionComponent extends Container {
|
||||
|
|
@ -46,6 +47,7 @@ export class ToolExecutionComponent extends Container {
|
|||
private expanded = false;
|
||||
private showImages: boolean;
|
||||
private imageWidthCells: number;
|
||||
private outputPad: number;
|
||||
private isPartial = true;
|
||||
private toolDefinition?: ToolRenderers;
|
||||
private ui: TUI;
|
||||
|
|
@ -75,6 +77,7 @@ export class ToolExecutionComponent extends Container {
|
|||
this.toolDefinition = toolDefinition;
|
||||
this.showImages = options.showImages ?? true;
|
||||
this.imageWidthCells = options.imageWidthCells ?? 60;
|
||||
this.outputPad = options.outputPad ?? 1;
|
||||
this.ui = ui;
|
||||
this.cwd = cwd;
|
||||
|
||||
|
|
@ -130,6 +133,7 @@ export class ToolExecutionComponent extends Container {
|
|||
expanded: this.expanded,
|
||||
showImages: this.showImages,
|
||||
isError: this.result?.isError ?? false,
|
||||
outputPad: this.outputPad,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -196,6 +200,11 @@ export class ToolExecutionComponent extends Container {
|
|||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setOutputPad(outputPad: number): void {
|
||||
this.outputPad = outputPad;
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setShowImages(show: boolean): void {
|
||||
this.showImages = show;
|
||||
this.updateDisplay();
|
||||
|
|
@ -267,6 +276,7 @@ export class ToolExecutionComponent extends Container {
|
|||
const renderContainer = this.getRenderShell() === "self" ? this.selfRenderContainer : this.contentBox;
|
||||
if (renderContainer instanceof Box) {
|
||||
renderContainer.setBgFn(bgFn);
|
||||
renderContainer.setPaddingX(this.outputPad);
|
||||
}
|
||||
renderContainer.clear();
|
||||
|
||||
|
|
@ -318,6 +328,7 @@ export class ToolExecutionComponent extends Container {
|
|||
}
|
||||
} else {
|
||||
this.contentText.setCustomBgFn(bgFn);
|
||||
this.contentText.setPaddingX(this.outputPad);
|
||||
this.contentText.setText(this.formatToolExecution());
|
||||
hasContent = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3515,6 +3515,7 @@ export class InteractiveMode {
|
|||
{
|
||||
showImages: this.settingsManager.getShowImages(),
|
||||
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
||||
outputPad: this.outputPad,
|
||||
},
|
||||
this.getRegisteredToolDefinition(content.name),
|
||||
this.ui,
|
||||
|
|
@ -3593,6 +3594,7 @@ export class InteractiveMode {
|
|||
{
|
||||
showImages: this.settingsManager.getShowImages(),
|
||||
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
||||
outputPad: this.outputPad,
|
||||
},
|
||||
this.getRegisteredToolDefinition(event.toolName),
|
||||
this.ui,
|
||||
|
|
@ -3825,7 +3827,7 @@ export class InteractiveMode {
|
|||
if (!renderer) {
|
||||
return;
|
||||
}
|
||||
const component = new CustomEntryComponent(entry, renderer);
|
||||
const component = new CustomEntryComponent(entry, renderer, this.outputPad);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
if (!component.hasContent()) {
|
||||
return;
|
||||
|
|
@ -3845,7 +3847,12 @@ export class InteractiveMode {
|
|||
private addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void {
|
||||
switch (message.role) {
|
||||
case "bashExecution": {
|
||||
const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
|
||||
const component = new BashExecutionComponent(
|
||||
message.command,
|
||||
this.ui,
|
||||
message.excludeFromContext,
|
||||
this.outputPad,
|
||||
);
|
||||
if (message.output) {
|
||||
component.appendOutput(message.output);
|
||||
}
|
||||
|
|
@ -3874,14 +3881,22 @@ export class InteractiveMode {
|
|||
}
|
||||
case "compactionSummary": {
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
||||
const component = new CompactionSummaryMessageComponent(
|
||||
message,
|
||||
this.getMarkdownThemeWithSettings(),
|
||||
this.outputPad,
|
||||
);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
this.chatContainer.addChild(component);
|
||||
break;
|
||||
}
|
||||
case "branchSummary": {
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
||||
const component = new BranchSummaryMessageComponent(
|
||||
message,
|
||||
this.getMarkdownThemeWithSettings(),
|
||||
this.outputPad,
|
||||
);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
this.chatContainer.addChild(component);
|
||||
break;
|
||||
|
|
@ -3900,6 +3915,7 @@ export class InteractiveMode {
|
|||
const component = new SkillInvocationMessageComponent(
|
||||
skillBlock,
|
||||
this.getMarkdownThemeWithSettings(),
|
||||
this.outputPad,
|
||||
);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
this.chatContainer.addChild(component);
|
||||
|
|
@ -3996,6 +4012,7 @@ export class InteractiveMode {
|
|||
{
|
||||
showImages: this.settingsManager.getShowImages(),
|
||||
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
||||
outputPad: this.outputPad,
|
||||
},
|
||||
this.getRegisteredToolDefinition(content.name),
|
||||
this.ui,
|
||||
|
|
@ -5040,23 +5057,14 @@ export class InteractiveMode {
|
|||
onOutputPadChange: (padding) => {
|
||||
this.settingsManager.setOutputPad(padding);
|
||||
this.outputPad = padding;
|
||||
if (this.streamingComponent || this.session.isStreaming) {
|
||||
for (const child of this.chatContainer.children) {
|
||||
if (
|
||||
child instanceof AssistantMessageComponent ||
|
||||
child instanceof CustomMessageComponent ||
|
||||
child instanceof UserMessageComponent
|
||||
) {
|
||||
for (const container of [this.chatContainer, this.pendingMessagesContainer]) {
|
||||
for (const child of container.children) {
|
||||
if ("setOutputPad" in child && typeof child.setOutputPad === "function") {
|
||||
child.setOutputPad(padding);
|
||||
}
|
||||
}
|
||||
if (this.streamingComponent) {
|
||||
this.streamingComponent.setOutputPad(padding);
|
||||
}
|
||||
this.ui.requestRender();
|
||||
return;
|
||||
}
|
||||
this.rebuildChatFromMessages();
|
||||
this.ui.requestRender();
|
||||
},
|
||||
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
||||
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
||||
|
|
@ -6971,7 +6979,7 @@ export class InteractiveMode {
|
|||
const result = eventResult.result;
|
||||
|
||||
// Create UI component for display
|
||||
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
||||
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext, this.outputPad);
|
||||
if (this.session.isStreaming) {
|
||||
this.pendingMessagesContainer.addChild(this.bashComponent);
|
||||
this.pendingBashComponents.push(this.bashComponent);
|
||||
|
|
@ -6999,7 +7007,7 @@ export class InteractiveMode {
|
|||
|
||||
// Normal execution path (possibly with custom operations)
|
||||
const isDeferred = this.session.isStreaming;
|
||||
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
||||
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext, this.outputPad);
|
||||
|
||||
if (isDeferred) {
|
||||
// Show in pending area when agent is streaming
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ function render(
|
|||
expanded,
|
||||
showImages: false,
|
||||
isError,
|
||||
outputPad: 1,
|
||||
} satisfies ToolRenderContext;
|
||||
const component = codemodeRenderers.renderResult?.(
|
||||
result,
|
||||
|
|
|
|||
94
packages/coding-agent/test/output-pad.test.ts
Normal file
94
packages/coding-agent/test/output-pad.test.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { type Component, TuiMainScreen } from "@earendil-works/pi-tui";
|
||||
import { Type } from "typebox";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
||||
import { VirtualTerminal } from "../../tui/test/virtual-terminal.ts";
|
||||
import type { ToolDefinition } from "../src/core/extensions/types.ts";
|
||||
import { createEditToolDefinition } from "../src/core/tools/edit.ts";
|
||||
import { BashExecutionComponent } from "../src/modes/interactive/components/bash-execution.ts";
|
||||
import { CompactionSummaryMessageComponent } from "../src/modes/interactive/components/compaction-summary-message.ts";
|
||||
import { ToolExecutionComponent } from "../src/modes/interactive/components/tool-execution.ts";
|
||||
import { initTheme } from "../src/modes/interactive/theme/theme.ts";
|
||||
import { stripAnsi } from "../src/utils/ansi.ts";
|
||||
|
||||
const ui = new TuiMainScreen(new VirtualTerminal(80, 24));
|
||||
|
||||
const tool: ToolDefinition = {
|
||||
name: "custom_tool",
|
||||
label: "custom_tool",
|
||||
description: "custom tool",
|
||||
parameters: Type.Any(),
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
||||
};
|
||||
|
||||
type OutputPaddedComponent = Component & { setOutputPad(outputPad: number): void };
|
||||
|
||||
/** Text lines without ANSI codes or trailing fill. Blank lines and full-width borders are skipped. */
|
||||
function renderLines(component: Component): string[] {
|
||||
return component
|
||||
.render(60)
|
||||
.map((line) => stripAnsi(line).trimEnd())
|
||||
.filter((line) => /[\w$(]/.test(line));
|
||||
}
|
||||
|
||||
function createTool(definition: ToolDefinition | undefined, outputPad: number): ToolExecutionComponent {
|
||||
const component = new ToolExecutionComponent("custom_tool", "id", {}, { outputPad }, definition, ui, "/");
|
||||
component.updateResult({ content: [{ type: "text", text: "ok" }], isError: false });
|
||||
return component;
|
||||
}
|
||||
|
||||
const components: Array<{ name: string; create: (outputPad: number) => OutputPaddedComponent }> = [
|
||||
{
|
||||
name: "bash execution",
|
||||
create: (outputPad) => {
|
||||
const component = new BashExecutionComponent("pwd", ui, false, outputPad);
|
||||
component.appendOutput("/tmp");
|
||||
component.setComplete(1, false);
|
||||
return component;
|
||||
},
|
||||
},
|
||||
{ name: "tool execution", create: (outputPad) => createTool(tool, outputPad) },
|
||||
{ name: "tool execution without a definition", create: (outputPad) => createTool(undefined, outputPad) },
|
||||
{
|
||||
name: "self-rendered edit result",
|
||||
create: (outputPad) => {
|
||||
const component = new ToolExecutionComponent(
|
||||
"edit",
|
||||
"id",
|
||||
{ path: "file.txt", edits: [{ oldText: "old", newText: "new" }] },
|
||||
{ outputPad },
|
||||
createEditToolDefinition("/"),
|
||||
ui,
|
||||
"/",
|
||||
);
|
||||
component.updateResult({ content: [{ type: "text", text: "Could not find old text" }], isError: true });
|
||||
return component;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compaction summary",
|
||||
create: (outputPad) =>
|
||||
new CompactionSummaryMessageComponent(
|
||||
{ role: "compactionSummary", summary: "summary", tokensBefore: 10, timestamp: 0 },
|
||||
undefined,
|
||||
outputPad,
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
describe("outputPad", () => {
|
||||
beforeAll(() => {
|
||||
initTheme("dark");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
ui.stop();
|
||||
});
|
||||
|
||||
test.each(components)("$name renders at outputPad 0 and 1", ({ create }) => {
|
||||
const component = create(0);
|
||||
const lines = renderLines(component);
|
||||
expect(lines.filter((line) => line.startsWith(" "))).toEqual([]);
|
||||
component.setOutputPad(1);
|
||||
expect(renderLines(component)).toEqual(lines.map((line) => ` ${line}`));
|
||||
});
|
||||
});
|
||||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Box.setPaddingX(...)` and `Text.setPaddingX(...)` ([#10557](https://github.com/earendil-works/pi/pull/10557) by [@rwachtler](https://github.com/rwachtler))
|
||||
|
||||
## [1.0.4] - 2026-10-05
|
||||
|
||||
## [1.0.3] - 2026-10-05
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ export class Box implements Component {
|
|||
// Don't invalidate here - we'll detect bgFn changes by sampling output
|
||||
}
|
||||
|
||||
setPaddingX(paddingX: number): void {
|
||||
this.paddingX = paddingX;
|
||||
this.invalidateCache();
|
||||
}
|
||||
|
||||
private invalidateCache(): void {
|
||||
this.cache = undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ export class Text implements Component {
|
|||
this.cachedLines = undefined;
|
||||
}
|
||||
|
||||
setPaddingX(paddingX: number): void {
|
||||
this.paddingX = paddingX;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.cachedText = undefined;
|
||||
this.cachedWidth = undefined;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue