Merge PR #7342: feat(tui): add hidden tool activity mode (@dannyboy-ai)
This commit is contained in:
@@ -61,6 +61,9 @@
|
||||
- Fixed `skill://` resolution ignoring explicitly configured `skills.customDirectories` entries when a same-named skill existed in a default discovery path: the custom-directory skill now wins as the higher-priority source ([#7190](https://github.com/can1357/oh-my-pi/issues/7190)).
|
||||
- Fixed image paste failing on Wayland-only Linux sessions by reading PNG clipboard payloads through `wl-paste` before falling back to the native bridge ([#7316](https://github.com/can1357/oh-my-pi/issues/7316)).
|
||||
- Fixed prewalk switching to the fast model during read-only investigation: `xd://` devices are dispatched through the `write` tool, so a read-only call such as an `lsp` navigation counted as the first edit/write and armed the one-way hand-off mid-planning. Device dispatches now carry the wrapped tool's approval tier and only trigger the switch at a `write`/`exec` tier — read-only `lsp`, `debug` inspection, and internal-URL `ast_edit` calls no longer downgrade the model ([#7312](https://github.com/can1357/oh-my-pi/issues/7312)).
|
||||
### Added
|
||||
|
||||
- Added `display.hideToolActivity` and the configurable `Ctrl+Shift+O` shortcut to hide model-initiated tool calls, results, failures, and result images while keeping assistant text visible; hidden activity remains persisted and can be revealed later in collapsed form.
|
||||
|
||||
## [17.2.4] - 2026-08-01
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ interface AppKeybindings {
|
||||
"app.model.select": true;
|
||||
"app.model.selectTemporary": true;
|
||||
"app.tools.expand": true;
|
||||
"app.tools.toggleVisibility": true;
|
||||
"app.editor.external": true;
|
||||
"app.message.followUp": true;
|
||||
"app.retry": true;
|
||||
@@ -124,6 +125,10 @@ export const KEYBINDINGS = {
|
||||
defaultKeys: "ctrl+o",
|
||||
description: "Expand tools",
|
||||
},
|
||||
"app.tools.toggleVisibility": {
|
||||
defaultKeys: "ctrl+shift+o",
|
||||
description: "Show or hide tool activity",
|
||||
},
|
||||
"app.editor.external": {
|
||||
defaultKeys: "ctrl+g",
|
||||
description: "Open external editor",
|
||||
|
||||
@@ -1018,6 +1018,17 @@ export const SETTINGS_SCHEMA = {
|
||||
},
|
||||
},
|
||||
|
||||
"display.hideToolActivity": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
ui: {
|
||||
tab: "appearance",
|
||||
group: "Display",
|
||||
label: "Hide Tool Activity",
|
||||
description: "Hide model-initiated tool calls and results from the transcript",
|
||||
},
|
||||
},
|
||||
|
||||
"display.showTokenUsage": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
|
||||
@@ -185,6 +185,7 @@ export class AssistantMessageComponent extends Container {
|
||||
#toolImagesByCallId = new Map<string, ImageContent[]>();
|
||||
#convertedKittyImages = new Map<string, ImageContent>();
|
||||
#showImages = true;
|
||||
#showToolResultImages = true;
|
||||
#kittyConversionsInFlight = new Set<string>();
|
||||
#transcriptBlockFinalized: boolean;
|
||||
/**
|
||||
@@ -578,6 +579,15 @@ export class AssistantMessageComponent extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
|
||||
setToolResultImagesVisible(visible: boolean): void {
|
||||
if (this.#showToolResultImages === visible) return;
|
||||
this.#showToolResultImages = visible;
|
||||
if (this.#lastMessage) {
|
||||
this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
|
||||
}
|
||||
}
|
||||
|
||||
setToolResultImages(toolCallId: string, images: ImageContent[]): void {
|
||||
if (!toolCallId) return;
|
||||
const validImages = images.filter(img => img.type === "image" && img.data && img.mimeType);
|
||||
@@ -649,6 +659,7 @@ export class AssistantMessageComponent extends Container {
|
||||
}
|
||||
|
||||
#renderToolImages(): void {
|
||||
if (!this.#showToolResultImages) return;
|
||||
const entries = Array.from(this.#toolImagesByCallId.entries()).flatMap(([toolCallId, images]) =>
|
||||
images.map((image, index) => ({ image, key: `${toolCallId}:${index}` })),
|
||||
);
|
||||
|
||||
@@ -190,6 +190,7 @@ export class ChatTranscriptBuilder {
|
||||
this.#readGroup = new ReadToolGroupComponent({
|
||||
showContentPreview: settings.get("read.toolResultPreview"),
|
||||
});
|
||||
this.#readGroup.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
this.#trackExpandable(this.#readGroup);
|
||||
this.container.addChild(this.#readGroup);
|
||||
}
|
||||
@@ -321,6 +322,7 @@ export class ChatTranscriptBuilder {
|
||||
proseOnlyThinking,
|
||||
);
|
||||
assistantComponent.setImagesVisible(settings.get("terminal.showImages"));
|
||||
assistantComponent.setToolResultImagesVisible(!settings.get("display.hideToolActivity"));
|
||||
this.#trackExpandable(assistantComponent);
|
||||
this.container.addChild(assistantComponent);
|
||||
|
||||
@@ -353,6 +355,7 @@ export class ChatTranscriptBuilder {
|
||||
proseOnlyThinking,
|
||||
);
|
||||
component.setImagesVisible(settings.get("terminal.showImages"));
|
||||
component.setToolResultImagesVisible(!settings.get("display.hideToolActivity"));
|
||||
this.#trackExpandable(component);
|
||||
this.container.addChild(component);
|
||||
};
|
||||
@@ -401,6 +404,7 @@ export class ChatTranscriptBuilder {
|
||||
this.deps.cwd,
|
||||
content.id,
|
||||
);
|
||||
component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
this.#trackExpandable(component);
|
||||
this.container.addChild(component);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ type ConfigurableEditorAction = Extract<
|
||||
| "app.model.select"
|
||||
| "app.model.selectTemporary"
|
||||
| "app.tools.expand"
|
||||
| "app.tools.toggleVisibility"
|
||||
| "app.thinking.toggle"
|
||||
| "app.editor.external"
|
||||
| "app.history.search"
|
||||
@@ -53,6 +54,7 @@ const DEFAULT_ACTION_KEYS: Record<ConfigurableEditorAction, KeyId[]> = {
|
||||
"app.model.select": ["alt+m"],
|
||||
"app.model.selectTemporary": ["alt+p"],
|
||||
"app.tools.expand": ["ctrl+o"],
|
||||
"app.tools.toggleVisibility": ["ctrl+shift+o"],
|
||||
"app.thinking.toggle": ["ctrl+t"],
|
||||
"app.editor.external": ["ctrl+g"],
|
||||
"app.history.search": ["ctrl+r"],
|
||||
@@ -550,6 +552,7 @@ export class CustomEditor extends Editor {
|
||||
onCycleModelBackward?: () => void;
|
||||
onSelectModel?: () => void;
|
||||
onExpandTools?: () => void;
|
||||
onToggleToolActivity?: () => void;
|
||||
onToggleThinking?: () => void;
|
||||
onExternalEditor?: () => void;
|
||||
onHistorySearch?: () => void;
|
||||
@@ -910,6 +913,12 @@ export class CustomEditor extends Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
// Intercept configured tool activity visibility toggle
|
||||
if (this.#matchesAction(canonical, "app.tools.toggleVisibility") && this.onToggleToolActivity) {
|
||||
this.onToggleToolActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
// Intercept configured backward model cycling (check before forward cycling)
|
||||
if (this.#matchesAction(canonical, "app.model.cycleBackward") && this.onCycleModelBackward) {
|
||||
this.onCycleModelBackward();
|
||||
|
||||
@@ -332,6 +332,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
||||
#usageBatchByToolCallId = new Map<string, string>();
|
||||
#text: Text;
|
||||
#expanded = false;
|
||||
#toolActivityVisible = true;
|
||||
#showContentPreview: boolean;
|
||||
// A read group accretes entries across multiple assistant completions for as
|
||||
// long as the run of reads is uninterrupted. While it is the active group it
|
||||
@@ -353,6 +354,10 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
||||
this.#updateDisplay();
|
||||
}
|
||||
|
||||
override render(width: number): readonly string[] {
|
||||
if (!this.#toolActivityVisible) return [];
|
||||
return super.render(width);
|
||||
}
|
||||
isTranscriptBlockFinalized(): boolean {
|
||||
if (this.#sealed) return true;
|
||||
if (!this.#finalized) return false;
|
||||
@@ -496,6 +501,11 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
||||
this.#updateDisplay();
|
||||
}
|
||||
|
||||
setToolActivityVisible(visible: boolean): void {
|
||||
this.#toolActivityVisible = visible;
|
||||
super.invalidate();
|
||||
}
|
||||
|
||||
getComponent(): Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ export interface ToolExecutionHandle extends Component {
|
||||
): void;
|
||||
setArgsComplete(toolCallId?: string): void;
|
||||
setExpanded(expanded: boolean): void;
|
||||
setToolActivityVisible(visible: boolean): void;
|
||||
/** Freeze the block as final history: stop spinners and let it commit to scrollback. */
|
||||
seal(): void;
|
||||
}
|
||||
@@ -287,6 +288,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
||||
#toolLabel: string;
|
||||
#args: any;
|
||||
#expanded = false;
|
||||
#toolActivityVisible = true;
|
||||
#showImages: boolean;
|
||||
#editFuzzyThreshold: number | undefined;
|
||||
#editAllowFuzzy: boolean | undefined;
|
||||
@@ -901,6 +903,11 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
||||
this.#updateDisplay();
|
||||
}
|
||||
|
||||
setToolActivityVisible(visible: boolean): void {
|
||||
this.#toolActivityVisible = visible;
|
||||
super.invalidate();
|
||||
}
|
||||
|
||||
setShowImages(show: boolean): void {
|
||||
this.#showImages = show;
|
||||
this.#updateDisplay();
|
||||
@@ -960,6 +967,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
||||
}
|
||||
|
||||
override render(width: number): readonly string[] {
|
||||
if (!this.#toolActivityVisible) return [];
|
||||
const lines = super.render(width);
|
||||
// Update the paint-tracking flags after `super.render(width)` — the
|
||||
// override runs on every compose the parent Container performs, so a
|
||||
|
||||
@@ -276,6 +276,7 @@ export class EventController {
|
||||
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
||||
});
|
||||
group.setExpanded(this.ctx.toolOutputExpanded);
|
||||
group.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(group);
|
||||
this.#lastReadGroup = group;
|
||||
}
|
||||
@@ -927,6 +928,7 @@ export class EventController {
|
||||
content.id,
|
||||
);
|
||||
component.setExpanded(this.ctx.toolOutputExpanded);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
this.ctx.pendingTools.set(content.id, component);
|
||||
this.#toolTimelineComponents.set(content.id, component);
|
||||
@@ -1176,6 +1178,7 @@ export class EventController {
|
||||
event.toolCallId,
|
||||
);
|
||||
component.setExpanded(this.ctx.toolOutputExpanded);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
this.ctx.pendingTools.set(event.toolCallId, component);
|
||||
this.#toolTimelineComponents.set(event.toolCallId, component);
|
||||
|
||||
@@ -8,8 +8,10 @@ import { isSettingsInitialized, settings } from "../../config/settings";
|
||||
import { resolveLocalRoot } from "../../internal-urls";
|
||||
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
||||
import { extractImagePathFromText } from "../../modes/components/custom-editor";
|
||||
import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
|
||||
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
||||
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
||||
import { ToolExecutionComponent } from "../../modes/components/tool-execution";
|
||||
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
||||
import { materializeImageReferenceLinks, shiftImageMarkers } from "../../modes/image-references";
|
||||
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
||||
@@ -455,6 +457,11 @@ export class InputController {
|
||||
this.ctx.editor.onCopyPrompt = () => this.handleCopyPrompt();
|
||||
this.ctx.editor.setActionKeys("app.tools.expand", this.ctx.keybindings.getKeys("app.tools.expand"));
|
||||
this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
|
||||
this.ctx.editor.setActionKeys(
|
||||
"app.tools.toggleVisibility",
|
||||
this.ctx.keybindings.getKeys("app.tools.toggleVisibility"),
|
||||
);
|
||||
this.ctx.editor.onToggleToolActivity = () => this.toggleToolActivityVisibility();
|
||||
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
||||
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
||||
this.ctx.editor.setActionKeys("app.retry", this.ctx.keybindings.getKeys("app.retry"));
|
||||
@@ -1845,9 +1852,37 @@ export class InputController {
|
||||
}
|
||||
|
||||
toggleToolOutputExpansion(): void {
|
||||
if (this.ctx.hideToolActivity) {
|
||||
const visibilityKey = this.ctx.keybindings.getDisplayString("app.tools.toggleVisibility");
|
||||
const visibilityHint = visibilityKey ? `${visibilityKey} or /settings` : "/settings";
|
||||
this.ctx.showStatus(`Tool activity is hidden — show it with ${visibilityHint} before expanding`);
|
||||
return;
|
||||
}
|
||||
this.setToolsExpanded(!this.ctx.toolOutputExpanded);
|
||||
}
|
||||
|
||||
toggleToolActivityVisibility(): void {
|
||||
this.ctx.hideToolActivity = !this.ctx.hideToolActivity;
|
||||
this.ctx.settings.set("display.hideToolActivity", this.ctx.hideToolActivity);
|
||||
|
||||
if (!this.ctx.hideToolActivity) {
|
||||
this.ctx.toolOutputExpanded = false;
|
||||
}
|
||||
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
if (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent) {
|
||||
if (!this.ctx.hideToolActivity) child.setExpanded(false);
|
||||
child.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
} else if (child instanceof AssistantMessageComponent) {
|
||||
child.setToolResultImagesVisible(!this.ctx.hideToolActivity);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ctx.hideToolActivity) this.ctx.ui.clearInlineImages();
|
||||
this.ctx.ui.resetDisplay();
|
||||
this.ctx.showStatus(`Tool activity: ${this.ctx.hideToolActivity ? "hidden" : "visible"}`);
|
||||
}
|
||||
|
||||
setToolsExpanded(expanded: boolean): void {
|
||||
this.ctx.toolOutputExpanded = expanded;
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
|
||||
@@ -92,6 +92,7 @@ import { ModelHubComponent, type ModelRoleSelectionScope } from "../components/m
|
||||
import { ModelPickerComponent } from "../components/model-picker";
|
||||
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
||||
import { PluginSelectorComponent } from "../components/plugin-selector";
|
||||
import { ReadToolGroupComponent } from "../components/read-tool-group";
|
||||
import { ResetUsageSelectorComponent } from "../components/reset-usage-selector";
|
||||
import { renderSegmentTrack } from "../components/segment-track";
|
||||
import { SessionAccountSelectorComponent } from "../components/session-account-selector";
|
||||
@@ -474,6 +475,22 @@ export class SelectorController {
|
||||
break;
|
||||
|
||||
// Settings with UI side effects
|
||||
case "display.hideToolActivity": {
|
||||
const hidden = value as boolean;
|
||||
this.ctx.hideToolActivity = hidden;
|
||||
if (!hidden) this.ctx.toolOutputExpanded = false;
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
if (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent) {
|
||||
if (!hidden) child.setExpanded(false);
|
||||
child.setToolActivityVisible(!hidden);
|
||||
} else if (child instanceof AssistantMessageComponent) {
|
||||
child.setToolResultImagesVisible(!hidden);
|
||||
}
|
||||
}
|
||||
if (hidden) this.ctx.ui.clearInlineImages();
|
||||
this.ctx.ui.resetDisplay();
|
||||
break;
|
||||
}
|
||||
case "terminal.showImages":
|
||||
case "showImages": {
|
||||
const visible = value as boolean;
|
||||
|
||||
@@ -460,6 +460,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
initialChatRendered = false;
|
||||
isBashMode = false;
|
||||
toolOutputExpanded = false;
|
||||
hideToolActivity = false;
|
||||
todoExpanded = false;
|
||||
planModeEnabled = false;
|
||||
planModePaused = false;
|
||||
@@ -778,6 +779,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
// hot path where the render never gets to paint the result.
|
||||
this.editor.setTopBorderProvider(availableWidth => this.statusLine.getTopBorder(availableWidth));
|
||||
|
||||
this.hideToolActivity = settings.get("display.hideToolActivity");
|
||||
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
||||
this.proseOnlyThinking = settings.get("proseOnlyThinking");
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ export interface InteractiveModeContext {
|
||||
initialChatRendered: boolean;
|
||||
isBashMode: boolean;
|
||||
toolOutputExpanded: boolean;
|
||||
hideToolActivity: boolean;
|
||||
todoExpanded: boolean;
|
||||
planModeEnabled: boolean;
|
||||
vibeModeEnabled: boolean;
|
||||
|
||||
@@ -46,6 +46,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
||||
`| \`${appKey(bindings, "app.plan.toggle")}\` | Toggle plan mode |`,
|
||||
`| \`${appKey(bindings, "app.history.search")}\` | Search prompt history |`,
|
||||
`| \`${appKey(bindings, "app.tools.expand")}\` | Toggle tool output expansion |`,
|
||||
`| \`${appKey(bindings, "app.tools.toggleVisibility")}\` | Toggle tool activity visibility |`,
|
||||
`| \`${appKey(bindings, "app.thinking.toggle")}\` | Toggle thinking block visibility |`,
|
||||
`| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
|
||||
`| \`${appKey(bindings, "app.retry")}\` | Retry last failed assistant turn |`,
|
||||
|
||||
@@ -25,6 +25,7 @@ export function createAssistantMessageComponent(
|
||||
ctx.proseOnlyThinking,
|
||||
);
|
||||
component.setImagesVisible(ctx.settings.get("terminal.showImages"));
|
||||
component.setToolResultImagesVisible(!ctx.hideToolActivity);
|
||||
component.setExpanded(ctx.toolOutputExpanded);
|
||||
return component;
|
||||
}
|
||||
|
||||
@@ -444,6 +444,7 @@ export class UiHelpers {
|
||||
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
||||
});
|
||||
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
||||
readGroup.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(readGroup);
|
||||
}
|
||||
readGroup.updateArgs(content.arguments, content.id);
|
||||
@@ -458,6 +459,7 @@ export class UiHelpers {
|
||||
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
||||
});
|
||||
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
||||
readGroup.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(readGroup);
|
||||
}
|
||||
readGroup.updateArgs(content.arguments, content.id);
|
||||
@@ -510,6 +512,7 @@ export class UiHelpers {
|
||||
content.id,
|
||||
);
|
||||
component.setExpanded(this.ctx.toolOutputExpanded);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
|
||||
if (hasErrorStop && errorMessage) {
|
||||
@@ -578,6 +581,7 @@ export class UiHelpers {
|
||||
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
||||
});
|
||||
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
||||
readGroup.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(readGroup);
|
||||
}
|
||||
const args = readToolCallArgs.get(message.toolCallId);
|
||||
|
||||
@@ -19,6 +19,17 @@ describe("CustomEditor keybindings", () => {
|
||||
expect(onRetry).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("routes the configured tool activity visibility chord through handleInput", () => {
|
||||
const editor = new CustomEditor(getEditorTheme());
|
||||
const onToggleToolActivity = vi.fn();
|
||||
|
||||
editor.setActionKeys("app.tools.toggleVisibility", ["alt+h"]);
|
||||
editor.onToggleToolActivity = onToggleToolActivity;
|
||||
editor.handleInput("\x1bh");
|
||||
|
||||
expect(onToggleToolActivity).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("lets custom handlers keep precedence over the default retry chord", () => {
|
||||
const editor = new CustomEditor(getEditorTheme());
|
||||
const onRetry = vi.fn();
|
||||
|
||||
@@ -15,6 +15,9 @@ const TOOL_RESULT_A_MARKER = "TOOL RESULT FROM FIRST TOOL";
|
||||
const MIDDLE_MARKER = "MIDDLE TEXT BETWEEN TOOL CALLS";
|
||||
const TOOL_RESULT_B_MARKER = "TOOL RESULT FROM SECOND TOOL";
|
||||
const FINAL_MARKER = "FINAL ANSWER AFTER SECOND TOOL";
|
||||
const HIDDEN_BASH_COMMAND_MARKER = "HIDDEN BASH COMMAND MARKER";
|
||||
const HIDDEN_BASH_FAILURE_MARKER = "HIDDEN BASH FAILURE MARKER";
|
||||
const HIDDEN_READ_PATH_MARKER = "hidden-tool-activity.ts";
|
||||
|
||||
function zeroUsage(): Usage {
|
||||
return {
|
||||
@@ -48,7 +51,7 @@ function lineContaining(lines: string[], marker: string): number {
|
||||
return index;
|
||||
}
|
||||
|
||||
function createFixture() {
|
||||
function createFixture(hideToolActivity = false) {
|
||||
const chatContainer = new TranscriptContainer();
|
||||
const pendingTools = new Map();
|
||||
const ui = {
|
||||
@@ -72,6 +75,7 @@ function createFixture() {
|
||||
transcriptMessageComponents: new WeakMap(),
|
||||
pendingTools,
|
||||
toolOutputExpanded: false,
|
||||
hideToolActivity,
|
||||
effectiveHideThinkingBlock: false,
|
||||
proseOnlyThinking: true,
|
||||
statusLine: { invalidate: vi.fn() },
|
||||
@@ -211,4 +215,81 @@ describe("EventController mixed assistant text/tool rendering", () => {
|
||||
expect(middleLine).toBeLessThan(toolResultBLine);
|
||||
expect(toolResultBLine).toBeLessThan(finalLine);
|
||||
});
|
||||
|
||||
it("keeps assistant text streaming while hiding bash failures and grouped read activity", async () => {
|
||||
const { controller, chatContainer } = createFixture(true);
|
||||
const bashCall: ToolCall = {
|
||||
type: "toolCall",
|
||||
id: TOOL_CALL_A_ID,
|
||||
name: "bash",
|
||||
arguments: { command: `printf '${HIDDEN_BASH_COMMAND_MARKER}'` },
|
||||
};
|
||||
const readCall: ToolCall = {
|
||||
type: "toolCall",
|
||||
id: TOOL_CALL_B_ID,
|
||||
name: "read",
|
||||
arguments: { path: HIDDEN_READ_PATH_MARKER },
|
||||
};
|
||||
const started = assistantMessage([]);
|
||||
const streaming = assistantMessage([
|
||||
{ type: "text", text: INTRO_MARKER },
|
||||
bashCall,
|
||||
{ type: "text", text: MIDDLE_MARKER },
|
||||
readCall,
|
||||
{ type: "text", text: FINAL_MARKER },
|
||||
]);
|
||||
|
||||
await controller.handleEvent({ type: "message_start", message: started } as Extract<
|
||||
AgentSessionEvent,
|
||||
{ type: "message_start" }
|
||||
>);
|
||||
await controller.handleEvent({
|
||||
type: "message_update",
|
||||
message: streaming,
|
||||
assistantMessageEvent: {
|
||||
type: "toolcall_end",
|
||||
contentIndex: 3,
|
||||
toolCall: readCall,
|
||||
partial: streaming,
|
||||
},
|
||||
} as Extract<AgentSessionEvent, { type: "message_update" }>);
|
||||
await controller.handleEvent({
|
||||
type: "tool_execution_start",
|
||||
toolCallId: TOOL_CALL_A_ID,
|
||||
toolName: "bash",
|
||||
args: bashCall.arguments,
|
||||
} as Extract<AgentSessionEvent, { type: "tool_execution_start" }>);
|
||||
await controller.handleEvent({
|
||||
type: "tool_execution_end",
|
||||
toolCallId: TOOL_CALL_A_ID,
|
||||
toolName: "bash",
|
||||
result: { content: [{ type: "text", text: HIDDEN_BASH_FAILURE_MARKER }] },
|
||||
isError: true,
|
||||
} as Extract<AgentSessionEvent, { type: "tool_execution_end" }>);
|
||||
await controller.handleEvent({
|
||||
type: "tool_execution_start",
|
||||
toolCallId: TOOL_CALL_B_ID,
|
||||
toolName: "read",
|
||||
args: readCall.arguments,
|
||||
} as Extract<AgentSessionEvent, { type: "tool_execution_start" }>);
|
||||
await controller.handleEvent({
|
||||
type: "tool_execution_end",
|
||||
toolCallId: TOOL_CALL_B_ID,
|
||||
toolName: "read",
|
||||
result: { content: [{ type: "text", text: "read result must stay hidden" }] },
|
||||
isError: false,
|
||||
} as Extract<AgentSessionEvent, { type: "tool_execution_end" }>);
|
||||
await controller.handleEvent({ type: "message_end", message: streaming } as Extract<
|
||||
AgentSessionEvent,
|
||||
{ type: "message_end" }
|
||||
>);
|
||||
|
||||
const rendered = Bun.stripANSI(chatContainer.render(120).join("\n"));
|
||||
expect(rendered).toContain(INTRO_MARKER);
|
||||
expect(rendered).toContain(MIDDLE_MARKER);
|
||||
expect(rendered).toContain(FINAL_MARKER);
|
||||
expect(rendered).not.toContain(HIDDEN_BASH_COMMAND_MARKER);
|
||||
expect(rendered).not.toContain(HIDDEN_BASH_FAILURE_MARKER);
|
||||
expect(rendered).not.toContain(HIDDEN_READ_PATH_MARKER);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ type FakeEditor = {
|
||||
onPasteImage?: () => Promise<boolean>;
|
||||
onCopyPrompt?: () => void;
|
||||
onExpandTools?: () => void;
|
||||
onToggleToolActivity?: () => void;
|
||||
onToggleThinking?: () => void;
|
||||
onExternalEditor?: () => void;
|
||||
onRetry?: () => void;
|
||||
@@ -62,6 +63,7 @@ async function createContext() {
|
||||
"app.model.select": ["alt+m"],
|
||||
"app.retry": ["alt+r"],
|
||||
"app.clipboard.pasteImage": ["ctrl+v"],
|
||||
"app.tools.toggleVisibility": ["ctrl+shift+o"],
|
||||
};
|
||||
const customHandlers = new Map<string, () => void>();
|
||||
const setActionKeys = vi.fn();
|
||||
@@ -72,6 +74,7 @@ async function createContext() {
|
||||
customHandlers.clear();
|
||||
});
|
||||
const resetDisplay = vi.fn();
|
||||
const clearInlineImages = vi.fn();
|
||||
const showModelSelector = vi.fn();
|
||||
const requestRender = vi.fn();
|
||||
const showError = vi.fn();
|
||||
@@ -140,6 +143,7 @@ async function createContext() {
|
||||
ui: {
|
||||
requestRender,
|
||||
resetDisplay,
|
||||
clearInlineImages,
|
||||
addInputListener,
|
||||
addStartListener,
|
||||
getFocused: vi.fn(() => focused),
|
||||
@@ -190,6 +194,10 @@ async function createContext() {
|
||||
updatePendingMessagesDisplay,
|
||||
isBashMode: false,
|
||||
isPythonMode: false,
|
||||
hideToolActivity: false,
|
||||
toolOutputExpanded: false,
|
||||
settings: { set: vi.fn() },
|
||||
chatContainer: { children: [] },
|
||||
handleHotkeysCommand: vi.fn(),
|
||||
handlePlanModeCommand: vi.fn(),
|
||||
handleClearCommand: vi.fn(),
|
||||
@@ -228,6 +236,7 @@ async function createContext() {
|
||||
retry,
|
||||
abort,
|
||||
resetDisplay,
|
||||
clearInlineImages,
|
||||
refreshAppearance,
|
||||
resetDisplayAfterAppearanceRefresh,
|
||||
handleBtwBranchKey,
|
||||
@@ -264,6 +273,23 @@ describe("InputController keybinding setup", () => {
|
||||
expect(spies.resetDisplayAfterAppearanceRefresh).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("registers the tool activity visibility action", async () => {
|
||||
const { InputController, ctx, editor, spies } = await createContext();
|
||||
const controller = new InputController(ctx);
|
||||
|
||||
controller.setupKeyHandlers();
|
||||
|
||||
expect(spies.setActionKeys).toHaveBeenCalledWith("app.tools.toggleVisibility", ["ctrl+shift+o"]);
|
||||
expect(editor.onToggleToolActivity).toBeDefined();
|
||||
|
||||
editor.onToggleToolActivity?.();
|
||||
|
||||
expect(ctx.hideToolActivity).toBe(true);
|
||||
expect(ctx.settings.set).toHaveBeenCalledWith("display.hideToolActivity", true);
|
||||
expect(spies.clearInlineImages).toHaveBeenCalledTimes(1);
|
||||
expect(spies.resetDisplay).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not mark pasted shell prompts as Python mode while editing", async () => {
|
||||
const { InputController, ctx, editor } = await createContext();
|
||||
const controller = new InputController(ctx);
|
||||
|
||||
@@ -8,6 +8,7 @@ describe("buildHotkeysMarkdown", () => {
|
||||
"app.clipboard.copyPrompt": "Ctrl+Shift+P",
|
||||
"app.plan.toggle": "Alt+Shift+P",
|
||||
"app.tools.expand": "Ctrl+O",
|
||||
"app.tools.toggleVisibility": "Ctrl+Shift+O",
|
||||
"app.display.reset": "Alt+L",
|
||||
"app.interrupt": "Esc",
|
||||
"app.clear": "Ctrl+C",
|
||||
@@ -43,6 +44,7 @@ describe("buildHotkeysMarkdown", () => {
|
||||
expect(markdown).toContain("| `Ctrl+L` | Start/stop live voice mode (/live) |");
|
||||
expect(markdown).toContain("| `Alt+R` | Retry last failed assistant turn |");
|
||||
expect(markdown).toContain("| `Alt+Shift+P` | Toggle plan mode |");
|
||||
expect(markdown).toContain("| `Ctrl+Shift+O` | Toggle tool activity visibility |");
|
||||
expect(markdown).toContain("| `#<number>` | GitHub issue/PR reference");
|
||||
expect(markdown).toContain("| `#` / `#<text>` | Prompt actions");
|
||||
for (const line of lines) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from "bun:test";
|
||||
import { AssistantMessageComponent } from "@oh-my-pi/pi-coding-agent/modes/components/assistant-message";
|
||||
import { InputController } from "@oh-my-pi/pi-coding-agent/modes/controllers/input-controller";
|
||||
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
|
||||
|
||||
@@ -24,4 +25,83 @@ describe("InputController tool output expansion", () => {
|
||||
expect(resetDisplay).toHaveBeenCalledTimes(1);
|
||||
expect(requestRender).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not expand hidden tool activity and explains why", () => {
|
||||
const expandable = { setExpanded: vi.fn() };
|
||||
const resetDisplay = vi.fn();
|
||||
const showStatus = vi.fn();
|
||||
const ctx = {
|
||||
hideToolActivity: true,
|
||||
toolOutputExpanded: false,
|
||||
chatContainer: { children: [expandable] },
|
||||
keybindings: { getDisplayString: vi.fn(() => "Alt+H") },
|
||||
showStatus,
|
||||
ui: { resetDisplay },
|
||||
} as unknown as InteractiveModeContext;
|
||||
|
||||
new InputController(ctx).toggleToolOutputExpansion();
|
||||
|
||||
expect(ctx.toolOutputExpanded).toBe(false);
|
||||
expect(expandable.setExpanded).not.toHaveBeenCalled();
|
||||
expect(resetDisplay).not.toHaveBeenCalled();
|
||||
expect(showStatus).toHaveBeenCalledWith(expect.stringContaining("Alt+H"));
|
||||
expect(showStatus).toHaveBeenCalledWith(expect.stringContaining("/settings"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("InputController tool activity visibility", () => {
|
||||
it("persists the toggle, preserves transient children, and reveals tools collapsed", () => {
|
||||
const pendingUserMessage = { kind: "pending-user" };
|
||||
const loadingIndicator = { kind: "loading" };
|
||||
const assistant = new AssistantMessageComponent();
|
||||
const setToolResultImagesVisible = vi.spyOn(assistant, "setToolResultImagesVisible");
|
||||
const children = [pendingUserMessage, assistant, loadingIndicator];
|
||||
const clear = vi.fn();
|
||||
const addChild = vi.fn();
|
||||
const rebuildChatFromMessages = vi.fn();
|
||||
const set = vi.fn();
|
||||
const clearInlineImages = vi.fn();
|
||||
const resetDisplay = vi.fn();
|
||||
const showStatus = vi.fn();
|
||||
const ctx = {
|
||||
hideToolActivity: false,
|
||||
toolOutputExpanded: true,
|
||||
settings: { set },
|
||||
chatContainer: { children, clear, addChild },
|
||||
rebuildChatFromMessages,
|
||||
showStatus,
|
||||
ui: { clearInlineImages, resetDisplay },
|
||||
};
|
||||
const controller = new InputController(ctx as unknown as InteractiveModeContext) as unknown as InputController & {
|
||||
toggleToolActivityVisibility(): void;
|
||||
};
|
||||
|
||||
controller.toggleToolActivityVisibility();
|
||||
|
||||
expect(ctx.hideToolActivity).toBe(true);
|
||||
expect(set).toHaveBeenLastCalledWith("display.hideToolActivity", true);
|
||||
expect(ctx.chatContainer.children).toEqual(children);
|
||||
expect(clear).not.toHaveBeenCalled();
|
||||
expect(addChild).not.toHaveBeenCalled();
|
||||
expect(rebuildChatFromMessages).not.toHaveBeenCalled();
|
||||
expect(clearInlineImages).toHaveBeenCalledTimes(1);
|
||||
expect(resetDisplay).toHaveBeenCalledTimes(1);
|
||||
expect(clearInlineImages.mock.invocationCallOrder[0]).toBeLessThan(resetDisplay.mock.invocationCallOrder[0]);
|
||||
expect(showStatus).toHaveBeenLastCalledWith("Tool activity: hidden");
|
||||
expect(setToolResultImagesVisible).toHaveBeenLastCalledWith(false);
|
||||
|
||||
controller.toggleToolActivityVisibility();
|
||||
|
||||
expect(ctx.hideToolActivity).toBe(false);
|
||||
expect(ctx.toolOutputExpanded).toBe(false);
|
||||
expect(set).toHaveBeenLastCalledWith("display.hideToolActivity", false);
|
||||
expect(ctx.chatContainer.children).toEqual(children);
|
||||
expect(clear).not.toHaveBeenCalled();
|
||||
expect(addChild).not.toHaveBeenCalled();
|
||||
expect(rebuildChatFromMessages).not.toHaveBeenCalled();
|
||||
expect(clearInlineImages).toHaveBeenCalledTimes(1);
|
||||
expect(resetDisplay).toHaveBeenCalledTimes(2);
|
||||
expect(showStatus).toHaveBeenLastCalledWith("Tool activity: visible");
|
||||
expect(setToolResultImagesVisible).toHaveBeenLastCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,6 +139,7 @@ function hasImageComponent(component: Component): boolean {
|
||||
function makeRenderCtx(
|
||||
transcript: SessionContext,
|
||||
showImages = true,
|
||||
hideToolActivity = false,
|
||||
): { ctx: InteractiveModeContext; chatContainer: Container } {
|
||||
const chatContainer = new Container();
|
||||
let helpers: UiHelpers;
|
||||
@@ -156,8 +157,15 @@ function makeRenderCtx(
|
||||
resetTranscript: () => chatContainer.clear(),
|
||||
// Rebuild paths honor terminal.showImages since the native-image work;
|
||||
// keep it on so the image-replay contracts below stay meaningful.
|
||||
settings: { get: (key: string) => key === "terminal.showImages" && showImages },
|
||||
settings: {
|
||||
get: (key: string) => {
|
||||
if (key === "terminal.showImages") return showImages;
|
||||
if (key === "display.hideToolActivity") return hideToolActivity;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
toolOutputExpanded: false,
|
||||
hideToolActivity,
|
||||
hideThinkingBlock: false,
|
||||
focusedAgentId: undefined,
|
||||
editor: { addToHistory: vi.fn() },
|
||||
@@ -306,6 +314,33 @@ describe("UiHelpers.renderInitialMessages — image replay", () => {
|
||||
expect(hasImageComponent(chatContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves tool-result images while tool activity is hidden so revealing it can replay the image", async () => {
|
||||
await Settings.init({ inMemory: true, overrides: { "terminal.showImages": true } });
|
||||
setTerminalImageProtocol(ImageProtocol.Sixel);
|
||||
const transcript = transcriptWith([
|
||||
assistantToolCall("read-tool-hidden", "read", { path: "tool-hidden.png" }),
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "read-tool-hidden",
|
||||
toolName: "read",
|
||||
content: [{ type: "text", text: "Read image: tool-hidden.png" }, pngImage],
|
||||
isError: false,
|
||||
timestamp: 2,
|
||||
},
|
||||
]);
|
||||
const { ctx, chatContainer } = makeRenderCtx(transcript, true, true);
|
||||
|
||||
new UiHelpers(ctx).renderInitialMessages();
|
||||
|
||||
expect(hasImageComponent(chatContainer)).toBe(false);
|
||||
const assistant = chatContainer.children.find(
|
||||
(child): child is AssistantMessageComponent => child instanceof AssistantMessageComponent,
|
||||
);
|
||||
expect(assistant).toBeDefined();
|
||||
assistant?.setToolResultImagesVisible(true);
|
||||
expect(hasImageComponent(chatContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it("replays reopened session image blocks through the cold-start rebuild path", async () => {
|
||||
await Settings.init({ inMemory: true, overrides: { "terminal.showImages": true } });
|
||||
setTerminalImageProtocol(ImageProtocol.Sixel);
|
||||
@@ -348,6 +383,57 @@ describe("UiHelpers.renderInitialMessages — image replay", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("UiHelpers.renderInitialMessages — hidden tool activity", () => {
|
||||
it("hides replayed tool cards without discarding them from the persisted transcript", () => {
|
||||
const toolCallId = "replayed-hidden-tool";
|
||||
const toolArgumentMarker = "REPLAYED TOOL ARGUMENT MARKER";
|
||||
const toolResultMarker = "REPLAYED TOOL RESULT MARKER";
|
||||
const narrationMarker = "ASSISTANT NARRATION STAYS VISIBLE";
|
||||
const finalMarker = "FINAL ASSISTANT RESPONSE STAYS VISIBLE";
|
||||
const transcript = transcriptWith([
|
||||
{
|
||||
...assistantToolCall(toolCallId, "contract_probe", { value: toolArgumentMarker }),
|
||||
content: [
|
||||
{ type: "text", text: narrationMarker },
|
||||
{ type: "toolCall", id: toolCallId, name: "contract_probe", arguments: { value: toolArgumentMarker } },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId,
|
||||
toolName: "contract_probe",
|
||||
content: [{ type: "text", text: toolResultMarker }],
|
||||
isError: false,
|
||||
timestamp: 2,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: finalMarker }],
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet",
|
||||
usage: emptyUsage,
|
||||
stopReason: "stop",
|
||||
timestamp: 3,
|
||||
},
|
||||
]);
|
||||
|
||||
const hidden = makeRenderCtx(transcript, true, true);
|
||||
new UiHelpers(hidden.ctx).renderInitialMessages();
|
||||
const hiddenRender = Bun.stripANSI(hidden.chatContainer.render(120).join("\n"));
|
||||
expect(hiddenRender).toContain(narrationMarker);
|
||||
expect(hiddenRender).toContain(finalMarker);
|
||||
expect(hiddenRender).not.toContain(toolArgumentMarker);
|
||||
expect(hiddenRender).not.toContain(toolResultMarker);
|
||||
|
||||
const visible = makeRenderCtx(transcript, true, false);
|
||||
new UiHelpers(visible.ctx).renderInitialMessages();
|
||||
const visibleRender = Bun.stripANSI(visible.chatContainer.render(120).join("\n"));
|
||||
expect(visibleRender).toContain(toolArgumentMarker);
|
||||
expect(visibleRender).toContain(toolResultMarker);
|
||||
});
|
||||
});
|
||||
|
||||
describe("UiHelpers.renderSessionContext — error-stop tool calls", () => {
|
||||
it("keeps the synthetic assistant error result instead of replaying a later tool result", async () => {
|
||||
await Settings.init({ inMemory: true });
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { AssistantMessageComponent } from "@oh-my-pi/pi-coding-agent/modes/components/assistant-message";
|
||||
import { ReadToolGroupComponent } from "@oh-my-pi/pi-coding-agent/modes/components/read-tool-group";
|
||||
import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/components/tool-execution";
|
||||
import { SelectorController } from "@oh-my-pi/pi-coding-agent/modes/controllers/selector-controller";
|
||||
import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
@@ -125,6 +126,50 @@ describe("selector setting side effects", () => {
|
||||
}
|
||||
}
|
||||
|
||||
for (const hidden of [true, false]) {
|
||||
it(`applies display.hideToolActivity=${hidden} to existing tool components`, () => {
|
||||
const setToolVisible = vi.fn();
|
||||
const setToolExpanded = vi.fn();
|
||||
const tool = Object.create(ToolExecutionComponent.prototype) as ToolExecutionComponent;
|
||||
tool.setToolActivityVisible = setToolVisible;
|
||||
tool.setExpanded = setToolExpanded;
|
||||
const setReadVisible = vi.fn();
|
||||
const setReadExpanded = vi.fn();
|
||||
const readGroup = Object.create(ReadToolGroupComponent.prototype) as ReadToolGroupComponent;
|
||||
readGroup.setToolActivityVisible = setReadVisible;
|
||||
readGroup.setExpanded = setReadExpanded;
|
||||
const setToolResultImagesVisible = vi.fn();
|
||||
const assistant = Object.create(AssistantMessageComponent.prototype) as AssistantMessageComponent;
|
||||
assistant.setToolResultImagesVisible = setToolResultImagesVisible;
|
||||
const clearInlineImages = vi.fn();
|
||||
const resetDisplay = vi.fn();
|
||||
const ctx = {
|
||||
hideToolActivity: !hidden,
|
||||
toolOutputExpanded: true,
|
||||
chatContainer: { children: [tool, readGroup, assistant] },
|
||||
ui: { clearInlineImages, resetDisplay },
|
||||
};
|
||||
const controller = new SelectorController(ctx as unknown as InteractiveModeContext);
|
||||
|
||||
controller.handleSettingChange("display.hideToolActivity", hidden);
|
||||
|
||||
expect(ctx.hideToolActivity).toBe(hidden);
|
||||
expect(setToolVisible).toHaveBeenCalledWith(!hidden);
|
||||
expect(setReadVisible).toHaveBeenCalledWith(!hidden);
|
||||
expect(setToolResultImagesVisible).toHaveBeenCalledWith(!hidden);
|
||||
expect(setToolExpanded).toHaveBeenCalledTimes(hidden ? 0 : 1);
|
||||
expect(setReadExpanded).toHaveBeenCalledTimes(hidden ? 0 : 1);
|
||||
expect(ctx.toolOutputExpanded).toBe(hidden);
|
||||
expect(clearInlineImages).toHaveBeenCalledTimes(hidden ? 1 : 0);
|
||||
expect(resetDisplay).toHaveBeenCalledTimes(1);
|
||||
if (hidden) {
|
||||
expect(clearInlineImages.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
resetDisplay.mock.invocationCallOrder[0],
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it("clears stale default role thinking when auto is selected", async () => {
|
||||
const testTheme = await getThemeByName("dark");
|
||||
if (!testTheme) throw new Error("Failed to load dark theme for model selector test");
|
||||
|
||||
@@ -403,6 +403,12 @@ describe("Settings", () => {
|
||||
expect(getDefault("terminal.showProgress")).toBe(false);
|
||||
});
|
||||
|
||||
it("shows tool activity by default", async () => {
|
||||
const settings = await Settings.init({ cwd: projectDir, agentDir });
|
||||
expect(settings.get("display.hideToolActivity")).toBe(false);
|
||||
expect(getDefault("display.hideToolActivity")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the normal startup splash disabled by default", async () => {
|
||||
const settings = await Settings.init({ cwd: projectDir, agentDir });
|
||||
expect(settings.get("startup.showSplash")).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user