mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(coding-agent): add configurable chat padding
This commit is contained in:
parent
1d48616328
commit
5a64f09b41
22 changed files with 292 additions and 88 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `transcriptIndent` and `toolIndent` settings to configure interactive transcript and tool/message indentation.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `--session` and `SessionManager.open()` to reject non-empty invalid session files without overwriting them ([#6002](https://github.com/earendil-works/pi/issues/6002)).
|
- Fixed `--session` and `SessionManager.open()` to reject non-empty invalid session files without overwriting them ([#6002](https://github.com/earendil-works/pi/issues/6002)).
|
||||||
|
|
|
||||||
|
|
@ -2074,6 +2074,7 @@ pi.registerTool({
|
||||||
- `lastComponent` - the previously returned component for that slot, if any
|
- `lastComponent` - the previously returned component for that slot, if any
|
||||||
- `invalidate()` - request a rerender of this tool row
|
- `invalidate()` - request a rerender of this tool row
|
||||||
- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`
|
- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`
|
||||||
|
- `toolIndent` - current tool/message indentation in terminal columns; default shells apply it automatically, self-shell renderers can opt in (use `context.toolIndent ?? 1`)
|
||||||
|
|
||||||
Use `context.state` for cross-slot shared state. Keep slot-local caches on the returned component instance when you want to reuse and mutate the same component across renders.
|
Use `context.state` for cross-slot shared state. Keep slot-local caches on the returned component instance when you want to reuse and mutate the same component across renders.
|
||||||
|
|
||||||
|
|
@ -2154,6 +2155,7 @@ Custom editors and `ctx.ui.custom()` components receive `keybindings: Keybinding
|
||||||
#### Best Practices
|
#### Best Practices
|
||||||
|
|
||||||
- Use `Text` with padding `(0, 0)`. The default Box handles padding.
|
- Use `Text` with padding `(0, 0)`. The default Box handles padding.
|
||||||
|
- Use `context.toolIndent` only when `renderShell: "self"` needs to align with the user's tool indentation setting.
|
||||||
- Use `\n` for multi-line content.
|
- Use `\n` for multi-line content.
|
||||||
- Handle `isPartial` for streaming progress.
|
- Handle `isPartial` for streaming progress.
|
||||||
- Support `expanded` for detail on demand.
|
- Support `expanded` for detail on demand.
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
|
||||||
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
|
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
|
||||||
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
|
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
|
||||||
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
|
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
|
||||||
|
| `transcriptIndent` | number | `1` | Horizontal padding for assistant transcript text (0-4) |
|
||||||
|
| `toolIndent` | number | `1` | Horizontal padding for tool and message blocks (0-4) |
|
||||||
| `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
|
| `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
|
||||||
| `showHardwareCursor` | boolean | `false` | Show the terminal cursor while TUI positions it for IME support |
|
| `showHardwareCursor` | boolean | `false` | Show the terminal cursor while TUI positions it for IME support |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3034,6 +3034,7 @@ export class AgentSession {
|
||||||
getToolDefinition: (name) => this.getToolDefinition(name),
|
getToolDefinition: (name) => this.getToolDefinition(name),
|
||||||
theme,
|
theme,
|
||||||
cwd: this.sessionManager.getCwd(),
|
cwd: this.sessionManager.getCwd(),
|
||||||
|
toolIndent: this.settingsManager.getToolIndent(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return await exportSessionToHtml(this.sessionManager, this.state, {
|
return await exportSessionToHtml(this.sessionManager, this.state, {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ export interface ToolHtmlRendererDeps {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
/** Terminal width for rendering (default: 100) */
|
/** Terminal width for rendering (default: 100) */
|
||||||
width?: number;
|
width?: number;
|
||||||
|
/** Tool indentation to expose to custom tool renderers (default: 1) */
|
||||||
|
toolIndent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolHtmlRenderer {
|
export interface ToolHtmlRenderer {
|
||||||
|
|
@ -56,7 +58,7 @@ function trimRenderedResultLines(lines: string[]): string[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRenderer {
|
export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRenderer {
|
||||||
const { getToolDefinition, theme, cwd, width = 100 } = deps;
|
const { getToolDefinition, theme, cwd, width = 100, toolIndent = 1 } = deps;
|
||||||
|
|
||||||
const renderedCallComponents = new Map<string, Component>();
|
const renderedCallComponents = new Map<string, Component>();
|
||||||
const renderedResultComponents = new Map<string, Component>();
|
const renderedResultComponents = new Map<string, Component>();
|
||||||
|
|
@ -91,6 +93,7 @@ export function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRend
|
||||||
isPartial,
|
isPartial,
|
||||||
expanded,
|
expanded,
|
||||||
showImages: false,
|
showImages: false,
|
||||||
|
toolIndent,
|
||||||
isError,
|
isError,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -425,6 +425,8 @@ export interface ToolRenderContext<TState = any, TArgs = any> {
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
/** Whether inline images are currently shown in the TUI. */
|
/** Whether inline images are currently shown in the TUI. */
|
||||||
showImages: boolean;
|
showImages: boolean;
|
||||||
|
/** Current tool/message indentation in terminal columns. Core-owned shells apply it automatically. */
|
||||||
|
toolIndent?: number;
|
||||||
/** Whether the current result is an error. */
|
/** Whether the current result is an error. */
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,15 @@ export type DefaultProjectTrust = "ask" | "always" | "never";
|
||||||
|
|
||||||
export type TransportSetting = Transport;
|
export type TransportSetting = Transport;
|
||||||
|
|
||||||
|
export const INDENT_VALUES = [0, 1, 2, 3, 4] as const;
|
||||||
|
|
||||||
|
function normalizeIndent(value: unknown): number {
|
||||||
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return INDENT_VALUES.includes(value as (typeof INDENT_VALUES)[number]) ? value : 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package source for npm/git packages.
|
* Package source for npm/git packages.
|
||||||
* - String form: load all resources from the package
|
* - String form: load all resources from the package
|
||||||
|
|
@ -114,6 +123,8 @@ export interface Settings {
|
||||||
editorPaddingX?: number; // Horizontal padding for input editor (default: 0)
|
editorPaddingX?: number; // Horizontal padding for input editor (default: 0)
|
||||||
autocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5)
|
autocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5)
|
||||||
showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME
|
showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME
|
||||||
|
transcriptIndent?: number; // Horizontal padding for assistant transcript text (default: 1; allowed: 0-4)
|
||||||
|
toolIndent?: number; // Horizontal padding for tool and message blocks (default: 1; allowed: 0-4)
|
||||||
markdown?: MarkdownSettings;
|
markdown?: MarkdownSettings;
|
||||||
warnings?: WarningSettings;
|
warnings?: WarningSettings;
|
||||||
sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag)
|
sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag)
|
||||||
|
|
@ -1169,6 +1180,26 @@ export class SettingsManager {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTranscriptIndent(): number {
|
||||||
|
return normalizeIndent(this.settings.transcriptIndent);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTranscriptIndent(indent: number): void {
|
||||||
|
this.globalSettings.transcriptIndent = normalizeIndent(indent);
|
||||||
|
this.markModified("transcriptIndent");
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
getToolIndent(): number {
|
||||||
|
return normalizeIndent(this.settings.toolIndent);
|
||||||
|
}
|
||||||
|
|
||||||
|
setToolIndent(indent: number): void {
|
||||||
|
this.globalSettings.toolIndent = normalizeIndent(indent);
|
||||||
|
this.markModified("toolIndent");
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
getAutocompleteMaxVisible(): number {
|
getAutocompleteMaxVisible(): number {
|
||||||
return this.settings.autocompleteMaxVisible ?? 5;
|
return this.settings.autocompleteMaxVisible ?? 5;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,8 +144,8 @@ type EditCallRenderComponent = Box & {
|
||||||
settledError?: boolean;
|
settledError?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createEditCallRenderComponent(): EditCallRenderComponent {
|
function createEditCallRenderComponent(toolIndent: number): EditCallRenderComponent {
|
||||||
return Object.assign(new Box(1, 1, (text: string) => text), {
|
return Object.assign(new Box(toolIndent, 1, (text: string) => text), {
|
||||||
preview: undefined as EditPreview | undefined,
|
preview: undefined as EditPreview | undefined,
|
||||||
previewArgsKey: undefined as string | undefined,
|
previewArgsKey: undefined as string | undefined,
|
||||||
previewPending: false,
|
previewPending: false,
|
||||||
|
|
@ -153,7 +153,11 @@ function createEditCallRenderComponent(): EditCallRenderComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEditCallRenderComponent(state: EditRenderState, lastComponent: unknown): EditCallRenderComponent {
|
function getEditCallRenderComponent(
|
||||||
|
state: EditRenderState,
|
||||||
|
lastComponent: unknown,
|
||||||
|
toolIndent: number,
|
||||||
|
): EditCallRenderComponent {
|
||||||
if (lastComponent instanceof Box) {
|
if (lastComponent instanceof Box) {
|
||||||
const component = lastComponent as EditCallRenderComponent;
|
const component = lastComponent as EditCallRenderComponent;
|
||||||
state.callComponent = component;
|
state.callComponent = component;
|
||||||
|
|
@ -162,7 +166,7 @@ function getEditCallRenderComponent(state: EditRenderState, lastComponent: unkno
|
||||||
if (state.callComponent) {
|
if (state.callComponent) {
|
||||||
return state.callComponent;
|
return state.callComponent;
|
||||||
}
|
}
|
||||||
const component = createEditCallRenderComponent();
|
const component = createEditCallRenderComponent(toolIndent);
|
||||||
state.callComponent = component;
|
state.callComponent = component;
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
@ -361,7 +365,8 @@ export function createEditToolDefinition(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
renderCall(args, theme, context) {
|
renderCall(args, theme, context) {
|
||||||
const component = getEditCallRenderComponent(context.state, context.lastComponent);
|
const toolIndent = context.toolIndent ?? 1;
|
||||||
|
const component = getEditCallRenderComponent(context.state, context.lastComponent, toolIndent);
|
||||||
const previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);
|
const previewInput = getRenderablePreviewInput(args as RenderableEditArgs | undefined);
|
||||||
const argsKey = previewInput
|
const argsKey = previewInput
|
||||||
? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })
|
? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })
|
||||||
|
|
@ -426,7 +431,7 @@ export function createEditToolDefinition(
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
component.addChild(new Spacer(1));
|
component.addChild(new Spacer(1));
|
||||||
component.addChild(new Text(output, 1, 0));
|
component.addChild(new Text(output, context.toolIndent ?? 1, 0));
|
||||||
return component;
|
return component;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,11 @@ export class ArminComponent implements Component {
|
||||||
private cachedWidth = 0;
|
private cachedWidth = 0;
|
||||||
private gridVersion = 0;
|
private gridVersion = 0;
|
||||||
private cachedVersion = -1;
|
private cachedVersion = -1;
|
||||||
|
private toolIndent: number;
|
||||||
|
|
||||||
constructor(ui: TUI) {
|
constructor(ui: TUI, toolIndent = 1) {
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
|
this.toolIndent = toolIndent;
|
||||||
this.effect = EFFECTS[Math.floor(Math.random() * EFFECTS.length)];
|
this.effect = EFFECTS[Math.floor(Math.random() * EFFECTS.length)];
|
||||||
this.finalGrid = buildFinalGrid();
|
this.finalGrid = buildFinalGrid();
|
||||||
this.currentGrid = this.createEmptyGrid();
|
this.currentGrid = this.createEmptyGrid();
|
||||||
|
|
@ -88,20 +90,21 @@ export class ArminComponent implements Component {
|
||||||
return this.cachedLines;
|
return this.cachedLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
const padding = 1;
|
const padding = this.toolIndent;
|
||||||
const availableWidth = width - padding;
|
const leftPadding = " ".repeat(padding);
|
||||||
|
const availableWidth = Math.max(0, width - padding);
|
||||||
|
|
||||||
this.cachedLines = this.currentGrid.map((row) => {
|
this.cachedLines = this.currentGrid.map((row) => {
|
||||||
// Clip row to available width before applying color
|
// Clip row to available width before applying color
|
||||||
const clipped = row.slice(0, availableWidth).join("");
|
const clipped = row.slice(0, availableWidth).join("");
|
||||||
const padRight = Math.max(0, width - padding - clipped.length);
|
const padRight = Math.max(0, width - padding - clipped.length);
|
||||||
return ` ${theme.fg("accent", clipped)}${" ".repeat(padRight)}`;
|
return `${leftPadding}${theme.fg("accent", clipped)}${" ".repeat(padRight)}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add "ARMIN SAYS HI" at the end
|
// Add "ARMIN SAYS HI" at the end
|
||||||
const message = "ARMIN SAYS HI";
|
const message = "ARMIN SAYS HI";
|
||||||
const msgPadRight = Math.max(0, width - padding - message.length);
|
const msgPadRight = Math.max(0, width - padding - message.length);
|
||||||
this.cachedLines.push(` ${theme.fg("accent", message)}${" ".repeat(msgPadRight)}`);
|
this.cachedLines.push(`${leftPadding}${theme.fg("accent", message)}${" ".repeat(msgPadRight)}`);
|
||||||
|
|
||||||
this.cachedWidth = width;
|
this.cachedWidth = width;
|
||||||
this.cachedVersion = this.gridVersion;
|
this.cachedVersion = this.gridVersion;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export class AssistantMessageComponent extends Container {
|
||||||
private hideThinkingBlock: boolean;
|
private hideThinkingBlock: boolean;
|
||||||
private markdownTheme: MarkdownTheme;
|
private markdownTheme: MarkdownTheme;
|
||||||
private hiddenThinkingLabel: string;
|
private hiddenThinkingLabel: string;
|
||||||
|
private transcriptIndent: number;
|
||||||
private lastMessage?: AssistantMessage;
|
private lastMessage?: AssistantMessage;
|
||||||
private hasToolCalls = false;
|
private hasToolCalls = false;
|
||||||
|
|
||||||
|
|
@ -22,12 +23,14 @@ export class AssistantMessageComponent extends Container {
|
||||||
hideThinkingBlock = false,
|
hideThinkingBlock = false,
|
||||||
markdownTheme: MarkdownTheme = getMarkdownTheme(),
|
markdownTheme: MarkdownTheme = getMarkdownTheme(),
|
||||||
hiddenThinkingLabel = "Thinking...",
|
hiddenThinkingLabel = "Thinking...",
|
||||||
|
transcriptIndent = 1,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.hideThinkingBlock = hideThinkingBlock;
|
this.hideThinkingBlock = hideThinkingBlock;
|
||||||
this.markdownTheme = markdownTheme;
|
this.markdownTheme = markdownTheme;
|
||||||
this.hiddenThinkingLabel = hiddenThinkingLabel;
|
this.hiddenThinkingLabel = hiddenThinkingLabel;
|
||||||
|
this.transcriptIndent = transcriptIndent;
|
||||||
|
|
||||||
// Container for text/thinking content
|
// Container for text/thinking content
|
||||||
this.contentContainer = new Container();
|
this.contentContainer = new Container();
|
||||||
|
|
@ -90,7 +93,9 @@ export class AssistantMessageComponent extends Container {
|
||||||
if (content.type === "text" && content.text.trim()) {
|
if (content.type === "text" && content.text.trim()) {
|
||||||
// Assistant text messages with no background - trim the text
|
// Assistant text messages with no background - trim the text
|
||||||
// Set paddingY=0 to avoid extra spacing before tool executions
|
// Set paddingY=0 to avoid extra spacing before tool executions
|
||||||
this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));
|
this.contentContainer.addChild(
|
||||||
|
new Markdown(content.text.trim(), this.transcriptIndent, 0, this.markdownTheme),
|
||||||
|
);
|
||||||
} else if (content.type === "thinking" && content.thinking.trim()) {
|
} else if (content.type === "thinking" && content.thinking.trim()) {
|
||||||
// Add spacing only when another visible assistant content block follows.
|
// Add spacing only when another visible assistant content block follows.
|
||||||
// This avoids a superfluous blank line before separately-rendered tool execution blocks.
|
// This avoids a superfluous blank line before separately-rendered tool execution blocks.
|
||||||
|
|
@ -101,7 +106,7 @@ export class AssistantMessageComponent extends Container {
|
||||||
if (this.hideThinkingBlock) {
|
if (this.hideThinkingBlock) {
|
||||||
// Show static thinking label when hidden
|
// Show static thinking label when hidden
|
||||||
this.contentContainer.addChild(
|
this.contentContainer.addChild(
|
||||||
new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), 1, 0),
|
new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), this.transcriptIndent, 0),
|
||||||
);
|
);
|
||||||
if (hasVisibleContentAfter) {
|
if (hasVisibleContentAfter) {
|
||||||
this.contentContainer.addChild(new Spacer(1));
|
this.contentContainer.addChild(new Spacer(1));
|
||||||
|
|
@ -109,7 +114,7 @@ export class AssistantMessageComponent extends Container {
|
||||||
} else {
|
} else {
|
||||||
// Thinking traces in thinkingText color, italic
|
// Thinking traces in thinkingText color, italic
|
||||||
this.contentContainer.addChild(
|
this.contentContainer.addChild(
|
||||||
new Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {
|
new Markdown(content.thinking.trim(), this.transcriptIndent, 0, this.markdownTheme, {
|
||||||
color: (text: string) => theme.fg("thinkingText", text),
|
color: (text: string) => theme.fg("thinkingText", text),
|
||||||
italic: true,
|
italic: true,
|
||||||
}),
|
}),
|
||||||
|
|
@ -134,7 +139,7 @@ export class AssistantMessageComponent extends Container {
|
||||||
"error",
|
"error",
|
||||||
"Error: Model stopped because it reached the maximum output token limit. The response may be incomplete.",
|
"Error: Model stopped because it reached the maximum output token limit. The response may be incomplete.",
|
||||||
),
|
),
|
||||||
1,
|
this.transcriptIndent,
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -145,11 +150,11 @@ export class AssistantMessageComponent extends Container {
|
||||||
? message.errorMessage
|
? message.errorMessage
|
||||||
: "Operation aborted";
|
: "Operation aborted";
|
||||||
this.contentContainer.addChild(new Spacer(1));
|
this.contentContainer.addChild(new Spacer(1));
|
||||||
this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
|
this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), this.transcriptIndent, 0));
|
||||||
} else if (message.stopReason === "error") {
|
} else if (message.stopReason === "error") {
|
||||||
const errorMsg = message.errorMessage || "Unknown error";
|
const errorMsg = message.errorMessage || "Unknown error";
|
||||||
this.contentContainer.addChild(new Spacer(1));
|
this.contentContainer.addChild(new Spacer(1));
|
||||||
this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), this.transcriptIndent, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,12 @@ export class BashExecutionComponent extends Container {
|
||||||
private fullOutputPath?: string;
|
private fullOutputPath?: string;
|
||||||
private expanded = false;
|
private expanded = false;
|
||||||
private contentContainer: Container;
|
private contentContainer: Container;
|
||||||
|
private toolIndent: number;
|
||||||
|
|
||||||
constructor(command: string, ui: TUI, excludeFromContext = false) {
|
constructor(command: string, ui: TUI, excludeFromContext = false, toolIndent = 1) {
|
||||||
super();
|
super();
|
||||||
this.command = command;
|
this.command = command;
|
||||||
|
this.toolIndent = toolIndent;
|
||||||
|
|
||||||
// Use dim border for excluded-from-context commands (!! prefix)
|
// Use dim border for excluded-from-context commands (!! prefix)
|
||||||
const colorKey = excludeFromContext ? "dim" : "bashMode";
|
const colorKey = excludeFromContext ? "dim" : "bashMode";
|
||||||
|
|
@ -48,7 +50,7 @@ export class BashExecutionComponent extends Container {
|
||||||
this.addChild(this.contentContainer);
|
this.addChild(this.contentContainer);
|
||||||
|
|
||||||
// Command header
|
// Command header
|
||||||
const header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);
|
const header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), this.toolIndent, 0);
|
||||||
this.contentContainer.addChild(header);
|
this.contentContainer.addChild(header);
|
||||||
|
|
||||||
// Loader
|
// Loader
|
||||||
|
|
@ -135,7 +137,7 @@ export class BashExecutionComponent extends Container {
|
||||||
this.contentContainer.clear();
|
this.contentContainer.clear();
|
||||||
|
|
||||||
// Command header
|
// Command header
|
||||||
const header = new Text(theme.fg("bashMode", theme.bold(`$ ${this.command}`)), 1, 0);
|
const header = new Text(theme.fg("bashMode", theme.bold(`$ ${this.command}`)), this.toolIndent, 0);
|
||||||
this.contentContainer.addChild(header);
|
this.contentContainer.addChild(header);
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
|
|
@ -143,7 +145,7 @@ export class BashExecutionComponent extends Container {
|
||||||
if (this.expanded) {
|
if (this.expanded) {
|
||||||
// Show all lines
|
// Show all lines
|
||||||
const displayText = availableLines.map((line) => theme.fg("muted", line)).join("\n");
|
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.toolIndent, 0));
|
||||||
} else {
|
} else {
|
||||||
// Use shared visual truncation utility with width-aware caching
|
// Use shared visual truncation utility with width-aware caching
|
||||||
const styledOutput = previewLogicalLines.map((line) => theme.fg("muted", line)).join("\n");
|
const styledOutput = previewLogicalLines.map((line) => theme.fg("muted", line)).join("\n");
|
||||||
|
|
@ -153,7 +155,7 @@ export class BashExecutionComponent extends Container {
|
||||||
this.contentContainer.addChild({
|
this.contentContainer.addChild({
|
||||||
render: (width: number) => {
|
render: (width: number) => {
|
||||||
if (cachedLines === undefined || cachedWidth !== width) {
|
if (cachedLines === undefined || cachedWidth !== width) {
|
||||||
const result = truncateToVisualLines(styledInput, PREVIEW_LINES, width, 1);
|
const result = truncateToVisualLines(styledInput, PREVIEW_LINES, width, this.toolIndent);
|
||||||
cachedLines = result.visualLines;
|
cachedLines = result.visualLines;
|
||||||
cachedWidth = width;
|
cachedWidth = width;
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +201,7 @@ export class BashExecutionComponent extends Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusParts.length > 0) {
|
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.toolIndent, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ export class BranchSummaryMessageComponent extends Box {
|
||||||
private message: BranchSummaryMessage;
|
private message: BranchSummaryMessage;
|
||||||
private markdownTheme: MarkdownTheme;
|
private markdownTheme: MarkdownTheme;
|
||||||
|
|
||||||
constructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
constructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme(), toolIndent = 1) {
|
||||||
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
super(toolIndent, 1, (t) => theme.bg("customMessageBg", t));
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.markdownTheme = markdownTheme;
|
this.markdownTheme = markdownTheme;
|
||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ export class CompactionSummaryMessageComponent extends Box {
|
||||||
private message: CompactionSummaryMessage;
|
private message: CompactionSummaryMessage;
|
||||||
private markdownTheme: MarkdownTheme;
|
private markdownTheme: MarkdownTheme;
|
||||||
|
|
||||||
constructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
constructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme(), toolIndent = 1) {
|
||||||
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
super(toolIndent, 1, (t) => theme.bg("customMessageBg", t));
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.markdownTheme = markdownTheme;
|
this.markdownTheme = markdownTheme;
|
||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export class CustomMessageComponent extends Container {
|
||||||
message: CustomMessage<unknown>,
|
message: CustomMessage<unknown>,
|
||||||
customRenderer?: MessageRenderer,
|
customRenderer?: MessageRenderer,
|
||||||
markdownTheme: MarkdownTheme = getMarkdownTheme(),
|
markdownTheme: MarkdownTheme = getMarkdownTheme(),
|
||||||
|
toolIndent = 1,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
@ -30,7 +31,7 @@ export class CustomMessageComponent extends Container {
|
||||||
this.addChild(new Spacer(1));
|
this.addChild(new Spacer(1));
|
||||||
|
|
||||||
// Create box with purple background (used for default rendering)
|
// Create box with purple background (used for default rendering)
|
||||||
this.box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
|
this.box = new Box(toolIndent, 1, (t) => theme.bg("customMessageBg", t));
|
||||||
|
|
||||||
this.rebuild();
|
this.rebuild();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,17 @@ function loadImageBase64(): string | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EarendilAnnouncementComponent extends Container {
|
export class EarendilAnnouncementComponent extends Container {
|
||||||
constructor() {
|
constructor(toolIndent = 1) {
|
||||||
super();
|
super();
|
||||||
|
const addText = (content: string) => {
|
||||||
|
this.addChild(new Text(content, toolIndent, 0));
|
||||||
|
};
|
||||||
|
|
||||||
this.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
|
this.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
|
||||||
this.addChild(new Text(theme.bold(theme.fg("accent", "pi has joined Earendil")), 1, 0));
|
addText(theme.bold(theme.fg("accent", "pi has joined Earendil")));
|
||||||
this.addChild(new Spacer(1));
|
this.addChild(new Spacer(1));
|
||||||
this.addChild(new Text(theme.fg("muted", "Read the blog post:"), 1, 0));
|
addText(theme.fg("muted", "Read the blog post:"));
|
||||||
this.addChild(new Text(theme.fg("mdLink", BLOG_URL), 1, 0));
|
addText(theme.fg("mdLink", BLOG_URL));
|
||||||
this.addChild(new Spacer(1));
|
this.addChild(new Spacer(1));
|
||||||
|
|
||||||
const imageBase64 = loadImageBase64();
|
const imageBase64 = loadImageBase64();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
Text,
|
Text,
|
||||||
} from "@earendil-works/pi-tui";
|
} from "@earendil-works/pi-tui";
|
||||||
import { formatHttpIdleTimeoutMs, HTTP_IDLE_TIMEOUT_CHOICES } from "../../../core/http-dispatcher.ts";
|
import { formatHttpIdleTimeoutMs, HTTP_IDLE_TIMEOUT_CHOICES } from "../../../core/http-dispatcher.ts";
|
||||||
import type { DefaultProjectTrust, WarningSettings } from "../../../core/settings-manager.ts";
|
import { type DefaultProjectTrust, INDENT_VALUES, type WarningSettings } from "../../../core/settings-manager.ts";
|
||||||
import {
|
import {
|
||||||
getSelectListTheme,
|
getSelectListTheme,
|
||||||
getSettingsListTheme,
|
getSettingsListTheme,
|
||||||
|
|
@ -71,6 +71,8 @@ export interface SettingsConfig {
|
||||||
treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
||||||
showHardwareCursor: boolean;
|
showHardwareCursor: boolean;
|
||||||
editorPaddingX: number;
|
editorPaddingX: number;
|
||||||
|
transcriptIndent: number;
|
||||||
|
toolIndent: number;
|
||||||
autocompleteMaxVisible: number;
|
autocompleteMaxVisible: number;
|
||||||
quietStartup: boolean;
|
quietStartup: boolean;
|
||||||
defaultProjectTrust: DefaultProjectTrust;
|
defaultProjectTrust: DefaultProjectTrust;
|
||||||
|
|
@ -100,6 +102,8 @@ export interface SettingsCallbacks {
|
||||||
onTreeFilterModeChange: (mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all") => void;
|
onTreeFilterModeChange: (mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all") => void;
|
||||||
onShowHardwareCursorChange: (enabled: boolean) => void;
|
onShowHardwareCursorChange: (enabled: boolean) => void;
|
||||||
onEditorPaddingXChange: (padding: number) => void;
|
onEditorPaddingXChange: (padding: number) => void;
|
||||||
|
onTranscriptIndentChange: (indent: number) => void;
|
||||||
|
onToolIndentChange: (indent: number) => void;
|
||||||
onAutocompleteMaxVisibleChange: (maxVisible: number) => void;
|
onAutocompleteMaxVisibleChange: (maxVisible: number) => void;
|
||||||
onQuietStartupChange: (enabled: boolean) => void;
|
onQuietStartupChange: (enabled: boolean) => void;
|
||||||
onDefaultProjectTrustChange: (defaultProjectTrust: DefaultProjectTrust) => void;
|
onDefaultProjectTrustChange: (defaultProjectTrust: DefaultProjectTrust) => void;
|
||||||
|
|
@ -676,9 +680,29 @@ export class SettingsSelectorComponent extends Container {
|
||||||
values: ["0", "1", "2", "3"],
|
values: ["0", "1", "2", "3"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Autocomplete max visible toggle (insert after editor-padding)
|
// Transcript indent toggle (insert after editor-padding)
|
||||||
const editorPaddingIndex = items.findIndex((item) => item.id === "editor-padding");
|
const editorPaddingIndex = items.findIndex((item) => item.id === "editor-padding");
|
||||||
items.splice(editorPaddingIndex + 1, 0, {
|
items.splice(editorPaddingIndex + 1, 0, {
|
||||||
|
id: "transcript-indent",
|
||||||
|
label: "Transcript indent",
|
||||||
|
description: "Horizontal padding for assistant transcript text (0-4)",
|
||||||
|
currentValue: String(config.transcriptIndent),
|
||||||
|
values: INDENT_VALUES.map((value) => String(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tool indent toggle (insert after transcript-indent)
|
||||||
|
const transcriptIndentIndex = items.findIndex((item) => item.id === "transcript-indent");
|
||||||
|
items.splice(transcriptIndentIndex + 1, 0, {
|
||||||
|
id: "tool-indent",
|
||||||
|
label: "Tool indent",
|
||||||
|
description: "Horizontal padding for tool and message blocks (0-4)",
|
||||||
|
currentValue: String(config.toolIndent),
|
||||||
|
values: INDENT_VALUES.map((value) => String(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Autocomplete max visible toggle (insert after tool-indent)
|
||||||
|
const toolIndentIndex = items.findIndex((item) => item.id === "tool-indent");
|
||||||
|
items.splice(toolIndentIndex + 1, 0, {
|
||||||
id: "autocomplete-max-visible",
|
id: "autocomplete-max-visible",
|
||||||
label: "Autocomplete max items",
|
label: "Autocomplete max items",
|
||||||
description: "Max visible items in autocomplete dropdown (3-20)",
|
description: "Max visible items in autocomplete dropdown (3-20)",
|
||||||
|
|
@ -782,6 +806,12 @@ export class SettingsSelectorComponent extends Container {
|
||||||
case "editor-padding":
|
case "editor-padding":
|
||||||
callbacks.onEditorPaddingXChange(parseInt(newValue, 10));
|
callbacks.onEditorPaddingXChange(parseInt(newValue, 10));
|
||||||
break;
|
break;
|
||||||
|
case "transcript-indent":
|
||||||
|
callbacks.onTranscriptIndentChange(parseInt(newValue, 10));
|
||||||
|
break;
|
||||||
|
case "tool-indent":
|
||||||
|
callbacks.onToolIndentChange(parseInt(newValue, 10));
|
||||||
|
break;
|
||||||
case "autocomplete-max-visible":
|
case "autocomplete-max-visible":
|
||||||
callbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));
|
callbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ export class SkillInvocationMessageComponent extends Box {
|
||||||
private skillBlock: ParsedSkillBlock;
|
private skillBlock: ParsedSkillBlock;
|
||||||
private markdownTheme: MarkdownTheme;
|
private markdownTheme: MarkdownTheme;
|
||||||
|
|
||||||
constructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
constructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme(), toolIndent = 1) {
|
||||||
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
super(toolIndent, 1, (t) => theme.bg("customMessageBg", t));
|
||||||
this.skillBlock = skillBlock;
|
this.skillBlock = skillBlock;
|
||||||
this.markdownTheme = markdownTheme;
|
this.markdownTheme = markdownTheme;
|
||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { theme } from "../theme/theme.ts";
|
||||||
export interface ToolExecutionOptions {
|
export interface ToolExecutionOptions {
|
||||||
showImages?: boolean;
|
showImages?: boolean;
|
||||||
imageWidthCells?: number;
|
imageWidthCells?: number;
|
||||||
|
toolIndent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ToolExecutionComponent extends Container {
|
export class ToolExecutionComponent extends Container {
|
||||||
|
|
@ -25,6 +26,7 @@ export class ToolExecutionComponent extends Container {
|
||||||
private expanded = false;
|
private expanded = false;
|
||||||
private showImages: boolean;
|
private showImages: boolean;
|
||||||
private imageWidthCells: number;
|
private imageWidthCells: number;
|
||||||
|
private toolIndent: number;
|
||||||
private isPartial = true;
|
private isPartial = true;
|
||||||
private toolDefinition?: ToolDefinition<any, any>;
|
private toolDefinition?: ToolDefinition<any, any>;
|
||||||
private builtInToolDefinition?: ToolDefinition<any, any>;
|
private builtInToolDefinition?: ToolDefinition<any, any>;
|
||||||
|
|
@ -57,6 +59,7 @@ export class ToolExecutionComponent extends Container {
|
||||||
this.builtInToolDefinition = createAllToolDefinitions(cwd)[toolName as ToolName];
|
this.builtInToolDefinition = createAllToolDefinitions(cwd)[toolName as ToolName];
|
||||||
this.showImages = options.showImages ?? true;
|
this.showImages = options.showImages ?? true;
|
||||||
this.imageWidthCells = options.imageWidthCells ?? 60;
|
this.imageWidthCells = options.imageWidthCells ?? 60;
|
||||||
|
this.toolIndent = options.toolIndent ?? 1;
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
this.cwd = cwd;
|
this.cwd = cwd;
|
||||||
|
|
||||||
|
|
@ -65,8 +68,8 @@ export class ToolExecutionComponent extends Container {
|
||||||
// Always create all shell variants. contentBox is used for default renderer-based composition.
|
// Always create all shell variants. contentBox is used for default renderer-based composition.
|
||||||
// selfRenderContainer is used when the tool renders its own framing.
|
// selfRenderContainer is used when the tool renders its own framing.
|
||||||
// contentText is reserved for generic fallback rendering when no tool definition exists.
|
// contentText is reserved for generic fallback rendering when no tool definition exists.
|
||||||
this.contentBox = new Box(1, 1, (text: string) => theme.bg("toolPendingBg", text));
|
this.contentBox = new Box(this.toolIndent, 1, (text: string) => theme.bg("toolPendingBg", text));
|
||||||
this.contentText = new Text("", 1, 1, (text: string) => theme.bg("toolPendingBg", text));
|
this.contentText = new Text("", this.toolIndent, 1, (text: string) => theme.bg("toolPendingBg", text));
|
||||||
this.selfRenderContainer = new Container();
|
this.selfRenderContainer = new Container();
|
||||||
|
|
||||||
if (this.hasRendererDefinition()) {
|
if (this.hasRendererDefinition()) {
|
||||||
|
|
@ -128,6 +131,7 @@ export class ToolExecutionComponent extends Container {
|
||||||
isPartial: this.isPartial,
|
isPartial: this.isPartial,
|
||||||
expanded: this.expanded,
|
expanded: this.expanded,
|
||||||
showImages: this.showImages,
|
showImages: this.showImages,
|
||||||
|
toolIndent: this.toolIndent,
|
||||||
isError: this.result?.isError ?? false,
|
isError: this.result?.isError ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
||||||
export class UserMessageComponent extends Container {
|
export class UserMessageComponent extends Container {
|
||||||
private contentBox: Box;
|
private contentBox: Box;
|
||||||
|
|
||||||
constructor(text: string, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
|
constructor(text: string, markdownTheme: MarkdownTheme = getMarkdownTheme(), toolIndent = 1) {
|
||||||
super();
|
super();
|
||||||
this.contentBox = new Box(1, 1, (content: string) => theme.bg("userMessageBg", content));
|
this.contentBox = new Box(toolIndent, 1, (content: string) => theme.bg("userMessageBg", content));
|
||||||
this.contentBox.addChild(
|
this.contentBox.addChild(
|
||||||
new Markdown(
|
new Markdown(
|
||||||
text,
|
text,
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ import {
|
||||||
import type {
|
import type {
|
||||||
AutocompleteItem,
|
AutocompleteItem,
|
||||||
AutocompleteProvider,
|
AutocompleteProvider,
|
||||||
|
DefaultTextStyle,
|
||||||
EditorComponent,
|
EditorComponent,
|
||||||
Keybinding,
|
Keybinding,
|
||||||
KeyId,
|
KeyId,
|
||||||
|
MarkdownOptions,
|
||||||
MarkdownTheme,
|
MarkdownTheme,
|
||||||
OverlayHandle,
|
OverlayHandle,
|
||||||
OverlayOptions,
|
OverlayOptions,
|
||||||
|
|
@ -598,13 +600,11 @@ export class InteractiveMode {
|
||||||
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
||||||
const latestVersion = versionMatch ? versionMatch[1] : this.version;
|
const latestVersion = versionMatch ? versionMatch[1] : this.version;
|
||||||
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
||||||
this.chatContainer.addChild(new Text(condensedText, 1, 0));
|
this.chatContainer.addChild(this.createToolText(condensedText));
|
||||||
} else {
|
} else {
|
||||||
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.bold(theme.fg("accent", "What's New"))));
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(this.createToolMarkdown(this.changelogMarkdown.trim()));
|
||||||
new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()),
|
|
||||||
);
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
this.chatContainer.addChild(new DynamicBorder());
|
this.chatContainer.addChild(new DynamicBorder());
|
||||||
|
|
@ -960,6 +960,38 @@ export class InteractiveMode {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTranscriptIndent(): number {
|
||||||
|
return this.settingsManager.getTranscriptIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getToolIndent(): number {
|
||||||
|
return this.settingsManager.getToolIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createToolText(text: string, paddingY = 0, customBgFn?: (text: string) => string): Text {
|
||||||
|
return new Text(text, this.getToolIndent(), paddingY, customBgFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createToolMarkdown(
|
||||||
|
text: string,
|
||||||
|
paddingY = 0,
|
||||||
|
defaultTextStyle?: DefaultTextStyle,
|
||||||
|
options?: MarkdownOptions,
|
||||||
|
): Markdown {
|
||||||
|
return new Markdown(
|
||||||
|
text,
|
||||||
|
this.getToolIndent(),
|
||||||
|
paddingY,
|
||||||
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
defaultTextStyle,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createToolTruncatedText(text: string, paddingY = 0): TruncatedText {
|
||||||
|
return new TruncatedText(text, this.getToolIndent(), paddingY);
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Extension System
|
// Extension System
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -2426,7 +2458,7 @@ export class InteractiveMode {
|
||||||
*/
|
*/
|
||||||
private showExtensionError(extensionPath: string, error: string, stack?: string): void {
|
private showExtensionError(extensionPath: string, error: string, stack?: string): void {
|
||||||
const errorMsg = `Extension "${extensionPath}" error: ${error}`;
|
const errorMsg = `Extension "${extensionPath}" error: ${error}`;
|
||||||
const errorText = new Text(theme.fg("error", errorMsg), 1, 0);
|
const errorText = this.createToolText(theme.fg("error", errorMsg));
|
||||||
this.chatContainer.addChild(errorText);
|
this.chatContainer.addChild(errorText);
|
||||||
if (stack) {
|
if (stack) {
|
||||||
// Show stack trace in dim color, indented
|
// Show stack trace in dim color, indented
|
||||||
|
|
@ -2436,7 +2468,7 @@ export class InteractiveMode {
|
||||||
.map((line) => theme.fg("dim", ` ${line.trim()}`))
|
.map((line) => theme.fg("dim", ` ${line.trim()}`))
|
||||||
.join("\n");
|
.join("\n");
|
||||||
if (stackLines) {
|
if (stackLines) {
|
||||||
this.chatContainer.addChild(new Text(stackLines, 1, 0));
|
this.chatContainer.addChild(this.createToolText(stackLines));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
|
|
@ -2793,6 +2825,7 @@ export class InteractiveMode {
|
||||||
this.hideThinkingBlock,
|
this.hideThinkingBlock,
|
||||||
this.getMarkdownThemeWithSettings(),
|
this.getMarkdownThemeWithSettings(),
|
||||||
this.hiddenThinkingLabel,
|
this.hiddenThinkingLabel,
|
||||||
|
this.getTranscriptIndent(),
|
||||||
);
|
);
|
||||||
this.streamingMessage = event.message;
|
this.streamingMessage = event.message;
|
||||||
this.chatContainer.addChild(this.streamingComponent);
|
this.chatContainer.addChild(this.streamingComponent);
|
||||||
|
|
@ -2816,6 +2849,7 @@ export class InteractiveMode {
|
||||||
{
|
{
|
||||||
showImages: this.settingsManager.getShowImages(),
|
showImages: this.settingsManager.getShowImages(),
|
||||||
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
||||||
|
toolIndent: this.getToolIndent(),
|
||||||
},
|
},
|
||||||
this.getRegisteredToolDefinition(content.name),
|
this.getRegisteredToolDefinition(content.name),
|
||||||
this.ui,
|
this.ui,
|
||||||
|
|
@ -2885,6 +2919,7 @@ export class InteractiveMode {
|
||||||
{
|
{
|
||||||
showImages: this.settingsManager.getShowImages(),
|
showImages: this.settingsManager.getShowImages(),
|
||||||
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
||||||
|
toolIndent: this.getToolIndent(),
|
||||||
},
|
},
|
||||||
this.getRegisteredToolDefinition(event.toolName),
|
this.getRegisteredToolDefinition(event.toolName),
|
||||||
this.ui,
|
this.ui,
|
||||||
|
|
@ -3000,7 +3035,7 @@ export class InteractiveMode {
|
||||||
this.showError(event.errorMessage);
|
this.showError(event.errorMessage);
|
||||||
} else {
|
} else {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.fg("error", event.errorMessage)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
||||||
|
|
@ -3094,7 +3129,7 @@ export class InteractiveMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
const spacer = new Spacer(1);
|
const spacer = new Spacer(1);
|
||||||
const text = new Text(theme.fg("dim", message), 1, 0);
|
const text = this.createToolText(theme.fg("dim", message));
|
||||||
this.chatContainer.addChild(spacer);
|
this.chatContainer.addChild(spacer);
|
||||||
this.chatContainer.addChild(text);
|
this.chatContainer.addChild(text);
|
||||||
this.lastStatusSpacer = spacer;
|
this.lastStatusSpacer = spacer;
|
||||||
|
|
@ -3105,7 +3140,12 @@ export class InteractiveMode {
|
||||||
private addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void {
|
private addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void {
|
||||||
switch (message.role) {
|
switch (message.role) {
|
||||||
case "bashExecution": {
|
case "bashExecution": {
|
||||||
const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
|
const component = new BashExecutionComponent(
|
||||||
|
message.command,
|
||||||
|
this.ui,
|
||||||
|
message.excludeFromContext,
|
||||||
|
this.getToolIndent(),
|
||||||
|
);
|
||||||
if (message.output) {
|
if (message.output) {
|
||||||
component.appendOutput(message.output);
|
component.appendOutput(message.output);
|
||||||
}
|
}
|
||||||
|
|
@ -3121,7 +3161,12 @@ export class InteractiveMode {
|
||||||
case "custom": {
|
case "custom": {
|
||||||
if (message.display) {
|
if (message.display) {
|
||||||
const renderer = this.session.extensionRunner.getMessageRenderer(message.customType);
|
const renderer = this.session.extensionRunner.getMessageRenderer(message.customType);
|
||||||
const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
|
const component = new CustomMessageComponent(
|
||||||
|
message,
|
||||||
|
renderer,
|
||||||
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
this.getToolIndent(),
|
||||||
|
);
|
||||||
component.setExpanded(this.toolOutputExpanded);
|
component.setExpanded(this.toolOutputExpanded);
|
||||||
this.chatContainer.addChild(component);
|
this.chatContainer.addChild(component);
|
||||||
}
|
}
|
||||||
|
|
@ -3129,14 +3174,22 @@ export class InteractiveMode {
|
||||||
}
|
}
|
||||||
case "compactionSummary": {
|
case "compactionSummary": {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
const component = new CompactionSummaryMessageComponent(
|
||||||
|
message,
|
||||||
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
this.getToolIndent(),
|
||||||
|
);
|
||||||
component.setExpanded(this.toolOutputExpanded);
|
component.setExpanded(this.toolOutputExpanded);
|
||||||
this.chatContainer.addChild(component);
|
this.chatContainer.addChild(component);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "branchSummary": {
|
case "branchSummary": {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
const component = new BranchSummaryMessageComponent(
|
||||||
|
message,
|
||||||
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
this.getToolIndent(),
|
||||||
|
);
|
||||||
component.setExpanded(this.toolOutputExpanded);
|
component.setExpanded(this.toolOutputExpanded);
|
||||||
this.chatContainer.addChild(component);
|
this.chatContainer.addChild(component);
|
||||||
break;
|
break;
|
||||||
|
|
@ -3153,6 +3206,7 @@ export class InteractiveMode {
|
||||||
const component = new SkillInvocationMessageComponent(
|
const component = new SkillInvocationMessageComponent(
|
||||||
skillBlock,
|
skillBlock,
|
||||||
this.getMarkdownThemeWithSettings(),
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
this.getToolIndent(),
|
||||||
);
|
);
|
||||||
component.setExpanded(this.toolOutputExpanded);
|
component.setExpanded(this.toolOutputExpanded);
|
||||||
this.chatContainer.addChild(component);
|
this.chatContainer.addChild(component);
|
||||||
|
|
@ -3162,11 +3216,16 @@ export class InteractiveMode {
|
||||||
const userComponent = new UserMessageComponent(
|
const userComponent = new UserMessageComponent(
|
||||||
skillBlock.userMessage,
|
skillBlock.userMessage,
|
||||||
this.getMarkdownThemeWithSettings(),
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
this.getToolIndent(),
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(userComponent);
|
this.chatContainer.addChild(userComponent);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
const userComponent = new UserMessageComponent(
|
||||||
|
textContent,
|
||||||
|
this.getMarkdownThemeWithSettings(),
|
||||||
|
this.getToolIndent(),
|
||||||
|
);
|
||||||
this.chatContainer.addChild(userComponent);
|
this.chatContainer.addChild(userComponent);
|
||||||
}
|
}
|
||||||
if (options?.populateHistory) {
|
if (options?.populateHistory) {
|
||||||
|
|
@ -3181,6 +3240,7 @@ export class InteractiveMode {
|
||||||
this.hideThinkingBlock,
|
this.hideThinkingBlock,
|
||||||
this.getMarkdownThemeWithSettings(),
|
this.getMarkdownThemeWithSettings(),
|
||||||
this.hiddenThinkingLabel,
|
this.hiddenThinkingLabel,
|
||||||
|
this.getTranscriptIndent(),
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(assistantComponent);
|
this.chatContainer.addChild(assistantComponent);
|
||||||
break;
|
break;
|
||||||
|
|
@ -3227,6 +3287,7 @@ export class InteractiveMode {
|
||||||
{
|
{
|
||||||
showImages: this.settingsManager.getShowImages(),
|
showImages: this.settingsManager.getShowImages(),
|
||||||
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
||||||
|
toolIndent: this.getToolIndent(),
|
||||||
},
|
},
|
||||||
this.getRegisteredToolDefinition(content.name),
|
this.getRegisteredToolDefinition(content.name),
|
||||||
this.ui,
|
this.ui,
|
||||||
|
|
@ -3298,13 +3359,11 @@ export class InteractiveMode {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Text(
|
this.createToolText(
|
||||||
theme.fg(
|
theme.fg(
|
||||||
"warning",
|
"warning",
|
||||||
`This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`,
|
`This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`,
|
||||||
),
|
),
|
||||||
1,
|
|
||||||
0,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -3713,13 +3772,13 @@ export class InteractiveMode {
|
||||||
|
|
||||||
showError(errorMessage: string): void {
|
showError(errorMessage: string): void {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.fg("error", `Error: ${errorMessage}`)));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
showWarning(warningMessage: string): void {
|
showWarning(warningMessage: string): void {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.fg("warning", `Warning: ${warningMessage}`)));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3736,18 +3795,18 @@ export class InteractiveMode {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0),
|
this.createToolText(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`),
|
||||||
);
|
);
|
||||||
if (note) {
|
if (note) {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Markdown(note, 1, 0, this.getMarkdownThemeWithSettings(), {
|
this.createToolMarkdown(note, 0, {
|
||||||
color: (text) => theme.fg("muted", text),
|
color: (text) => theme.fg("muted", text),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
}
|
}
|
||||||
this.chatContainer.addChild(new Text(changelogLine, 1, 0));
|
this.chatContainer.addChild(this.createToolText(changelogLine));
|
||||||
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
@ -3760,10 +3819,8 @@ export class InteractiveMode {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Text(
|
this.createToolText(
|
||||||
`${theme.bold(theme.fg("warning", "Package Updates Available"))}\n${updateInstruction}\n${theme.fg("muted", "Packages:")}\n${packageLines}`,
|
`${theme.bold(theme.fg("warning", "Package Updates Available"))}\n${updateInstruction}\n${theme.fg("muted", "Packages:")}\n${packageLines}`,
|
||||||
1,
|
|
||||||
0,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
||||||
|
|
@ -3813,15 +3870,15 @@ export class InteractiveMode {
|
||||||
this.pendingMessagesContainer.addChild(new Spacer(1));
|
this.pendingMessagesContainer.addChild(new Spacer(1));
|
||||||
for (const message of steeringMessages) {
|
for (const message of steeringMessages) {
|
||||||
const text = theme.fg("dim", `Steering: ${message}`);
|
const text = theme.fg("dim", `Steering: ${message}`);
|
||||||
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
this.pendingMessagesContainer.addChild(this.createToolTruncatedText(text));
|
||||||
}
|
}
|
||||||
for (const message of followUpMessages) {
|
for (const message of followUpMessages) {
|
||||||
const text = theme.fg("dim", `Follow-up: ${message}`);
|
const text = theme.fg("dim", `Follow-up: ${message}`);
|
||||||
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
this.pendingMessagesContainer.addChild(this.createToolTruncatedText(text));
|
||||||
}
|
}
|
||||||
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
|
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
|
||||||
const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
|
const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
|
||||||
this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
this.pendingMessagesContainer.addChild(this.createToolTruncatedText(hintText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3998,6 +4055,8 @@ export class InteractiveMode {
|
||||||
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
||||||
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
||||||
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
||||||
|
transcriptIndent: this.settingsManager.getTranscriptIndent(),
|
||||||
|
toolIndent: this.settingsManager.getToolIndent(),
|
||||||
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
||||||
quietStartup: this.settingsManager.getQuietStartup(),
|
quietStartup: this.settingsManager.getQuietStartup(),
|
||||||
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
||||||
|
|
@ -4063,12 +4122,6 @@ export class InteractiveMode {
|
||||||
onHideThinkingBlockChange: (hidden) => {
|
onHideThinkingBlockChange: (hidden) => {
|
||||||
this.hideThinkingBlock = hidden;
|
this.hideThinkingBlock = hidden;
|
||||||
this.settingsManager.setHideThinkingBlock(hidden);
|
this.settingsManager.setHideThinkingBlock(hidden);
|
||||||
for (const child of this.chatContainer.children) {
|
|
||||||
if (child instanceof AssistantMessageComponent) {
|
|
||||||
child.setHideThinkingBlock(hidden);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.chatContainer.clear();
|
|
||||||
this.rebuildChatFromMessages();
|
this.rebuildChatFromMessages();
|
||||||
},
|
},
|
||||||
onCollapseChangelogChange: (collapsed) => {
|
onCollapseChangelogChange: (collapsed) => {
|
||||||
|
|
@ -4100,6 +4153,14 @@ export class InteractiveMode {
|
||||||
this.editor.setPaddingX(padding);
|
this.editor.setPaddingX(padding);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onTranscriptIndentChange: (indent) => {
|
||||||
|
this.settingsManager.setTranscriptIndent(indent);
|
||||||
|
this.rebuildChatFromMessages();
|
||||||
|
},
|
||||||
|
onToolIndentChange: (indent) => {
|
||||||
|
this.settingsManager.setToolIndent(indent);
|
||||||
|
this.rebuildChatFromMessages();
|
||||||
|
},
|
||||||
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
||||||
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
||||||
this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
|
this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
|
||||||
|
|
@ -5349,7 +5410,7 @@ export class InteractiveMode {
|
||||||
const currentName = this.sessionManager.getSessionName();
|
const currentName = this.sessionManager.getSessionName();
|
||||||
if (currentName) {
|
if (currentName) {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name: ${currentName}`), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.fg("dim", `Session name: ${currentName}`)));
|
||||||
} else {
|
} else {
|
||||||
this.showWarning("Usage: /name <name>");
|
this.showWarning("Usage: /name <name>");
|
||||||
}
|
}
|
||||||
|
|
@ -5363,7 +5424,7 @@ export class InteractiveMode {
|
||||||
this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
|
this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
|
||||||
}
|
}
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${sessionName ?? name}`), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.fg("dim", `Session name set: ${sessionName ?? name}`)));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5400,7 +5461,7 @@ export class InteractiveMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(info, 1, 0));
|
this.chatContainer.addChild(this.createToolText(info));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5418,9 +5479,9 @@ export class InteractiveMode {
|
||||||
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new DynamicBorder());
|
this.chatContainer.addChild(new DynamicBorder());
|
||||||
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.bold(theme.fg("accent", "What's New"))));
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.getMarkdownThemeWithSettings()));
|
this.chatContainer.addChild(this.createToolMarkdown(changelogMarkdown, 1));
|
||||||
this.chatContainer.addChild(new DynamicBorder());
|
this.chatContainer.addChild(new DynamicBorder());
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
@ -5547,9 +5608,9 @@ export class InteractiveMode {
|
||||||
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new DynamicBorder());
|
this.chatContainer.addChild(new DynamicBorder());
|
||||||
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
|
this.chatContainer.addChild(this.createToolText(theme.bold(theme.fg("accent", "Keyboard Shortcuts"))));
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.getMarkdownThemeWithSettings()));
|
this.chatContainer.addChild(this.createToolMarkdown(hotkeys.trim(), 1));
|
||||||
this.chatContainer.addChild(new DynamicBorder());
|
this.chatContainer.addChild(new DynamicBorder());
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
@ -5566,7 +5627,7 @@ export class InteractiveMode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
this.chatContainer.addChild(this.createToolText(`${theme.fg("accent", "✓ New session started")}`, 1));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await this.handleFatalRuntimeError("Failed to create session", error);
|
await this.handleFatalRuntimeError("Failed to create session", error);
|
||||||
|
|
@ -5601,20 +5662,20 @@ export class InteractiveMode {
|
||||||
|
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Text(`${theme.fg("accent", "✓ Debug log written")}\n${theme.fg("muted", debugLogPath)}`, 1, 1),
|
this.createToolText(`${theme.fg("accent", "✓ Debug log written")}\n${theme.fg("muted", debugLogPath)}`, 1),
|
||||||
);
|
);
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleArminSaysHi(): void {
|
private handleArminSaysHi(): void {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new ArminComponent(this.ui));
|
this.chatContainer.addChild(new ArminComponent(this.ui, this.getToolIndent()));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDementedDelves(): void {
|
private handleDementedDelves(): void {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(new EarendilAnnouncementComponent());
|
this.chatContainer.addChild(new EarendilAnnouncementComponent(this.getToolIndent()));
|
||||||
this.ui.requestRender();
|
this.ui.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5646,7 +5707,7 @@ export class InteractiveMode {
|
||||||
const result = eventResult.result;
|
const result = eventResult.result;
|
||||||
|
|
||||||
// Create UI component for display
|
// Create UI component for display
|
||||||
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext, this.getToolIndent());
|
||||||
if (this.session.isStreaming) {
|
if (this.session.isStreaming) {
|
||||||
this.pendingMessagesContainer.addChild(this.bashComponent);
|
this.pendingMessagesContainer.addChild(this.bashComponent);
|
||||||
this.pendingBashComponents.push(this.bashComponent);
|
this.pendingBashComponents.push(this.bashComponent);
|
||||||
|
|
@ -5674,7 +5735,7 @@ export class InteractiveMode {
|
||||||
|
|
||||||
// Normal execution path (possibly with custom operations)
|
// Normal execution path (possibly with custom operations)
|
||||||
const isDeferred = this.session.isStreaming;
|
const isDeferred = this.session.isStreaming;
|
||||||
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext, this.getToolIndent());
|
||||||
|
|
||||||
if (isDeferred) {
|
if (isDeferred) {
|
||||||
// Show in pending area when agent is streaming
|
// Show in pending area when agent is streaming
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,31 @@ describe("SettingsManager", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should normalize and save transcript/tool indents", async () => {
|
||||||
|
const settingsPath = join(agentDir, "settings.json");
|
||||||
|
let manager = SettingsManager.create(projectDir, agentDir);
|
||||||
|
expect(manager.getTranscriptIndent()).toBe(1);
|
||||||
|
expect(manager.getToolIndent()).toBe(1);
|
||||||
|
|
||||||
|
writeFileSync(settingsPath, JSON.stringify({ transcriptIndent: 0, toolIndent: 4 }));
|
||||||
|
manager = SettingsManager.create(projectDir, agentDir);
|
||||||
|
expect(manager.getTranscriptIndent()).toBe(0);
|
||||||
|
expect(manager.getToolIndent()).toBe(4);
|
||||||
|
|
||||||
|
writeFileSync(settingsPath, JSON.stringify({ transcriptIndent: 5, toolIndent: "2" }));
|
||||||
|
manager = SettingsManager.create(projectDir, agentDir);
|
||||||
|
expect(manager.getTranscriptIndent()).toBe(1);
|
||||||
|
expect(manager.getToolIndent()).toBe(1);
|
||||||
|
|
||||||
|
manager.setTranscriptIndent(2);
|
||||||
|
manager.setToolIndent(3);
|
||||||
|
await manager.flush();
|
||||||
|
|
||||||
|
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
||||||
|
expect(savedSettings.transcriptIndent).toBe(2);
|
||||||
|
expect(savedSettings.toolIndent).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
describe("shellCommandPrefix", () => {
|
describe("shellCommandPrefix", () => {
|
||||||
it("should load shellCommandPrefix from settings", () => {
|
it("should load shellCommandPrefix from settings", () => {
|
||||||
const settingsPath = join(agentDir, "settings.json");
|
const settingsPath = join(agentDir, "settings.json");
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,26 @@ describe("ToolExecutionComponent parity", () => {
|
||||||
expect(rendered).toContain("custom result");
|
expect(rendered).toContain("custom result");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("applies tool indentation to default tool shells and render contexts", () => {
|
||||||
|
const toolDefinition: ToolDefinition = {
|
||||||
|
...createBaseToolDefinition(),
|
||||||
|
renderCall: (_args, _theme, context) => new Text(`indent:${context.toolIndent}`, 0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
const component = new ToolExecutionComponent(
|
||||||
|
"custom_tool",
|
||||||
|
"tool-indent",
|
||||||
|
{},
|
||||||
|
{ toolIndent: 4 },
|
||||||
|
toolDefinition,
|
||||||
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const contentLine = stripAnsi(component.render(40).find((line) => line.includes("indent:")) ?? "").trimEnd();
|
||||||
|
expect(contentLine).toBe(" indent:4");
|
||||||
|
});
|
||||||
|
|
||||||
test("self-rendered empty tool rows take no layout space", () => {
|
test("self-rendered empty tool rows take no layout space", () => {
|
||||||
const toolDefinition: ToolDefinition = {
|
const toolDefinition: ToolDefinition = {
|
||||||
...createBaseToolDefinition(),
|
...createBaseToolDefinition(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue