fix(coding-agent): centralize hidden activity state
This commit is contained in:
@@ -54,7 +54,7 @@ import { EvalExecutionComponent } from "./eval-execution";
|
||||
import { type LateDiagnosticsFile, LateDiagnosticsMessageComponent } from "./late-diagnostics-message";
|
||||
import { groupedReadUsageCallIds, ReadToolGroupComponent, readArgsCollapseIntoGroup } from "./read-tool-group";
|
||||
import { SkillMessageComponent } from "./skill-message";
|
||||
import { ToolActivityContainer } from "./tool-activity-container";
|
||||
import { ToolActivityContainer } from "./tool-activity";
|
||||
import { ToolExecutionComponent } from "./tool-execution";
|
||||
import { TranscriptContainer } from "./transcript-container";
|
||||
import { createUsageRowBlock } from "./usage-row";
|
||||
@@ -97,7 +97,9 @@ export class ChatTranscriptBuilder {
|
||||
#expandables: Array<{ setExpanded(expanded: boolean): void }> = [];
|
||||
#expanded = false;
|
||||
|
||||
constructor(private readonly deps: ChatTranscriptBuilderDeps) {}
|
||||
constructor(private readonly deps: ChatTranscriptBuilderDeps) {
|
||||
this.container.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
}
|
||||
|
||||
/** Whether the transcript currently holds any rendered rows. */
|
||||
get isEmpty(): boolean {
|
||||
@@ -194,7 +196,6 @@ 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);
|
||||
}
|
||||
@@ -409,7 +410,6 @@ export class ChatTranscriptBuilder {
|
||||
this.deps.cwd,
|
||||
content.id,
|
||||
);
|
||||
component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
this.#trackExpandable(component);
|
||||
this.container.addChild(component);
|
||||
|
||||
@@ -470,14 +470,12 @@ export class ChatTranscriptBuilder {
|
||||
if (!message.display) return;
|
||||
if (message.customType === "async-result") {
|
||||
const component = buildAsyncResultBlock(message);
|
||||
component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
this.container.addChild(component);
|
||||
return;
|
||||
}
|
||||
if (message.customType === LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE) {
|
||||
const details = (message as CustomMessage<{ files?: LateDiagnosticsFile[] }>).details;
|
||||
const component = new LateDiagnosticsMessageComponent(details?.files ?? []);
|
||||
component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
this.#trackExpandable(component);
|
||||
this.container.addChild(component);
|
||||
return;
|
||||
@@ -512,7 +510,6 @@ export class ChatTranscriptBuilder {
|
||||
);
|
||||
this.#trackExpandable(messageComponent);
|
||||
const component = new ToolActivityContainer(messageComponent);
|
||||
component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
||||
this.container.addChild(component);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { type Component, Container } from "@oh-my-pi/pi-tui";
|
||||
import type { ToolActivityComponent } from "./tool-activity";
|
||||
|
||||
/** Keeps model/tool activity mounted while making its rows reversible. */
|
||||
export class ToolActivityContainer extends Container implements ToolActivityComponent {
|
||||
#visible = true;
|
||||
|
||||
constructor(component: Component | Component[]) {
|
||||
super();
|
||||
if (Array.isArray(component)) {
|
||||
for (const child of component) this.addChild(child);
|
||||
} else {
|
||||
this.addChild(component);
|
||||
}
|
||||
}
|
||||
|
||||
setToolActivityVisible(visible: boolean): void {
|
||||
if (this.#visible === visible) return;
|
||||
this.#visible = visible;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
override render(width: number): readonly string[] {
|
||||
if (!this.#visible) return [];
|
||||
return super.render(width);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import type { ToolActivityComponent } from "./tool-activity";
|
||||
|
||||
/** A tool-originated warning that follows the reversible activity toggle. */
|
||||
export class ToolActivityWarningComponent extends Text implements ToolActivityComponent {
|
||||
#visible = true;
|
||||
|
||||
constructor(message: string) {
|
||||
super(message, 1, 0);
|
||||
}
|
||||
|
||||
setToolActivityVisible(visible: boolean): void {
|
||||
if (this.#visible === visible) return;
|
||||
this.#visible = visible;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
override render(width: number): readonly string[] {
|
||||
if (!this.#visible) return [];
|
||||
return super.render(width);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,33 @@
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { type Component, Container } from "@oh-my-pi/pi-tui";
|
||||
|
||||
/** A transcript component whose rows belong to model/tool activity. */
|
||||
export interface ToolActivityComponent {
|
||||
setToolActivityVisible(visible: boolean): void;
|
||||
}
|
||||
|
||||
/** Narrows mounted transcript children to the reversible tool-activity contract. */
|
||||
export function isToolActivityComponent(component: Component): component is Component & ToolActivityComponent {
|
||||
return typeof (component as Partial<ToolActivityComponent>).setToolActivityVisible === "function";
|
||||
}
|
||||
|
||||
export class ToolActivityContainer extends Container implements ToolActivityComponent {
|
||||
#visible = true;
|
||||
|
||||
constructor(component: Component | Component[]) {
|
||||
super();
|
||||
if (Array.isArray(component)) {
|
||||
for (const child of component) this.addChild(child);
|
||||
} else {
|
||||
this.addChild(component);
|
||||
}
|
||||
}
|
||||
|
||||
setToolActivityVisible(visible: boolean): void {
|
||||
if (this.#visible === visible) return;
|
||||
this.#visible = visible;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
override render(width: number): readonly string[] {
|
||||
if (!this.#visible) return [];
|
||||
return super.render(width);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type RenderStablePrefix,
|
||||
type ViewportTailProvider,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { isToolActivityComponent } from "./tool-activity";
|
||||
|
||||
/**
|
||||
* A transcript block that is still mutating (a foreground tool awaiting its
|
||||
@@ -160,6 +161,7 @@ export class TranscriptContainer
|
||||
extends Container
|
||||
implements NativeScrollbackLiveRegion, NativeScrollbackCommittedRows, RenderStablePrefix, ViewportTailProvider
|
||||
{
|
||||
#toolActivityVisible = true;
|
||||
// Bumped to retire every block segment at once (theme change / clear); a
|
||||
// segment is only reused when its stored generation matches.
|
||||
#generation = 0;
|
||||
@@ -182,6 +184,20 @@ export class TranscriptContainer
|
||||
// consumes the report and re-bases the baseline). Out-of-band renders
|
||||
// between engine frames lower it; they can never inflate it.
|
||||
#stableRowsFloor = 0;
|
||||
override addChild(component: Component): void {
|
||||
if (isToolActivityComponent(component)) component.setToolActivityVisible(this.#toolActivityVisible);
|
||||
super.addChild(component);
|
||||
}
|
||||
|
||||
setToolActivityVisible(visible: boolean): void {
|
||||
if (this.#toolActivityVisible === visible) return;
|
||||
this.#toolActivityVisible = visible;
|
||||
for (const child of this.children) {
|
||||
if (isToolActivityComponent(child)) child.setToolActivityVisible(visible);
|
||||
}
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
override invalidate(): void {
|
||||
// Theme/global invalidation: retire every diff snapshot so stale styling
|
||||
// is not diffed against the recolored render.
|
||||
|
||||
@@ -345,7 +345,6 @@ 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;
|
||||
}
|
||||
@@ -1131,7 +1130,6 @@ 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);
|
||||
@@ -1384,7 +1382,6 @@ export class EventController {
|
||||
);
|
||||
component.setArgsComplete(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);
|
||||
@@ -1624,13 +1621,13 @@ export class EventController {
|
||||
// This text can be a provider error copied verbatim off the wire (the
|
||||
// Cursor todo bridge forwards the server's string), so it may carry
|
||||
// ANSI escapes, other C0/C1 controls, tabs, newlines, or a line far
|
||||
// wider than the terminal. `showToolActivityWarning` renders through
|
||||
// a reversible activity component, so hiding tool activity does not
|
||||
// lose this warning. `sanitizeText` drops the control sequences (and
|
||||
// returns the same reference when there are none), then `previewLine`
|
||||
// collapses the remaining whitespace and bounds the width. Sanitizing
|
||||
// first matters: truncating before stripping can cut an escape
|
||||
// mid-sequence and leave a dangling introducer.
|
||||
// wider than the terminal. `showToolActivityWarning` renders through a
|
||||
// plain `Text`, which strips none of that — an escape reaches the terminal
|
||||
// and can repaint outside the row. `sanitizeText` drops the control
|
||||
// sequences (and returns the same reference when there are none),
|
||||
// then `previewLine` collapses the remaining whitespace and bounds
|
||||
// the width. Sanitizing first matters: truncating before stripping
|
||||
// can cut an escape mid-sequence and leave a dangling introducer.
|
||||
//
|
||||
// This is the render boundary, not the persisted result: the stored
|
||||
// error stays full-fidelity for the transcript and for replays.
|
||||
@@ -2019,14 +2016,12 @@ export class EventController {
|
||||
}
|
||||
const component = new TtsrNotificationComponent(event.rules);
|
||||
component.setExpanded(this.ctx.toolOutputExpanded);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.present(component);
|
||||
this.#lastTtsrNotification = component;
|
||||
}
|
||||
|
||||
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
||||
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.present(component);
|
||||
}
|
||||
async #handleTodoAutoClear(_event: Extract<AgentSessionEvent, { type: "todo_auto_clear" }>): Promise<void> {
|
||||
|
||||
@@ -11,7 +11,6 @@ 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 { isToolActivityComponent } from "../../modes/components/tool-activity";
|
||||
import { ToolExecutionComponent } from "../../modes/components/tool-execution";
|
||||
import { TreeSelectorComponent } from "../../modes/components/tree-selector";
|
||||
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
||||
@@ -1925,18 +1924,16 @@ export class InputController {
|
||||
}
|
||||
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
if (isToolActivityComponent(child)) {
|
||||
if (
|
||||
!this.ctx.hideToolActivity &&
|
||||
(child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent)
|
||||
) {
|
||||
child.setExpanded(false);
|
||||
}
|
||||
child.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
if (
|
||||
!this.ctx.hideToolActivity &&
|
||||
(child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent)
|
||||
) {
|
||||
child.setExpanded(false);
|
||||
} else if (child instanceof AssistantMessageComponent) {
|
||||
child.setToolResultImagesVisible(!this.ctx.hideToolActivity);
|
||||
}
|
||||
}
|
||||
this.ctx.chatContainer.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
|
||||
if (this.ctx.hideToolActivity) this.ctx.ui.clearInlineImages();
|
||||
this.ctx.ui.resetDisplay();
|
||||
|
||||
@@ -98,7 +98,6 @@ import { renderSegmentTrack } from "../components/segment-track";
|
||||
import { SessionAccountSelectorComponent } from "../components/session-account-selector";
|
||||
import { SessionSelectorComponent, type SessionSelectorOptions } from "../components/session-selector";
|
||||
import { SettingsSelectorComponent } from "../components/settings-selector";
|
||||
import { isToolActivityComponent } from "../components/tool-activity";
|
||||
import { ToolExecutionComponent } from "../components/tool-execution";
|
||||
import { TranscriptBlock } from "../components/transcript-container";
|
||||
import { TreeSelectorComponent } from "../components/tree-selector";
|
||||
@@ -495,15 +494,13 @@ export class SelectorController {
|
||||
this.ctx.hideToolActivity = hidden;
|
||||
if (!hidden) this.ctx.toolOutputExpanded = false;
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
if (isToolActivityComponent(child)) {
|
||||
if (!hidden && (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent)) {
|
||||
child.setExpanded(false);
|
||||
}
|
||||
child.setToolActivityVisible(!hidden);
|
||||
if (!hidden && (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent)) {
|
||||
child.setExpanded(false);
|
||||
} else if (child instanceof AssistantMessageComponent) {
|
||||
child.setToolResultImagesVisible(!hidden);
|
||||
}
|
||||
}
|
||||
this.ctx.chatContainer.setToolActivityVisible(!hidden);
|
||||
if (hidden) this.ctx.ui.clearInlineImages();
|
||||
this.ctx.ui.resetDisplay();
|
||||
break;
|
||||
|
||||
@@ -838,6 +838,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
this.editor.setTopBorderProvider(availableWidth => this.statusLine.getTopBorder(availableWidth));
|
||||
|
||||
this.hideToolActivity = settings.get("display.hideToolActivity");
|
||||
this.chatContainer.setToolActivityVisible(!this.hideToolActivity);
|
||||
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
||||
this.proseOnlyThinking = settings.get("proseOnlyThinking");
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { createIrcMessageCard } from "../../tools/hub";
|
||||
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
||||
import { canonicalizeMessage } from "../../utils/thinking-display";
|
||||
import { ToolActivityContainer } from "../components/tool-activity-container";
|
||||
import { ToolActivityContainer } from "../components/tool-activity";
|
||||
import { TranscriptBlock } from "../components/transcript-container";
|
||||
import { theme } from "../theme/theme";
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ import {
|
||||
} from "../../modes/components/read-tool-group";
|
||||
import { SkillMessageComponent } from "../../modes/components/skill-message";
|
||||
import { StrippedToolCallsPlaceholder } from "../../modes/components/stripped-tool-calls-placeholder";
|
||||
import { ToolActivityContainer } from "../../modes/components/tool-activity-container";
|
||||
import { ToolActivityWarningComponent } from "../../modes/components/tool-activity-warning";
|
||||
import { ToolActivityContainer } from "../../modes/components/tool-activity";
|
||||
import { ToolExecutionComponent } from "../../modes/components/tool-execution";
|
||||
import { TranscriptBlock } from "../../modes/components/transcript-container";
|
||||
import { createUsageRowBlock } from "../../modes/components/usage-row";
|
||||
@@ -164,7 +163,6 @@ export class UiHelpers {
|
||||
if (message.display) {
|
||||
if (message.customType === "async-result") {
|
||||
const component = buildAsyncResultBlock(message);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
break;
|
||||
}
|
||||
@@ -176,7 +174,6 @@ export class UiHelpers {
|
||||
).details;
|
||||
const component = new LateDiagnosticsMessageComponent(details?.files ?? []);
|
||||
component.setExpanded(this.ctx.toolOutputExpanded);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
break;
|
||||
}
|
||||
@@ -187,7 +184,6 @@ export class UiHelpers {
|
||||
);
|
||||
messageComponent.setExpanded(this.ctx.toolOutputExpanded);
|
||||
const component = new ToolActivityContainer(messageComponent);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
break;
|
||||
}
|
||||
@@ -462,7 +458,6 @@ 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);
|
||||
@@ -477,7 +472,6 @@ 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);
|
||||
@@ -531,7 +525,6 @@ export class UiHelpers {
|
||||
content.id,
|
||||
);
|
||||
component.setExpanded(this.ctx.toolOutputExpanded);
|
||||
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
||||
this.ctx.chatContainer.addChild(component);
|
||||
|
||||
if (hasErrorStop && errorMessage) {
|
||||
@@ -591,7 +584,6 @@ 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);
|
||||
@@ -757,9 +749,7 @@ export class UiHelpers {
|
||||
this.ctx.present([new Spacer(1), text]);
|
||||
}
|
||||
showToolActivityWarning(warningMessage: string): void {
|
||||
const text = new ToolActivityWarningComponent(`Warning: ${warningMessage}`).setStyleFn(t =>
|
||||
theme.fg("warning", t),
|
||||
);
|
||||
const text = new Text(`Warning: ${warningMessage}`, 1, 0).setStyleFn(t => theme.fg("warning", t));
|
||||
this.ctx.present(new ToolActivityContainer([new Spacer(1), text]));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ import { beforeEach, describe, expect, it } from "bun:test";
|
||||
import { stripVTControlCharacters } from "node:util";
|
||||
import type { Rule } from "@oh-my-pi/pi-coding-agent/capability/rule";
|
||||
import { TodoReminderComponent } from "@oh-my-pi/pi-coding-agent/modes/components/todo-reminder";
|
||||
import { ToolActivityContainer } from "@oh-my-pi/pi-coding-agent/modes/components/tool-activity-container";
|
||||
import { ToolActivityWarningComponent } from "@oh-my-pi/pi-coding-agent/modes/components/tool-activity-warning";
|
||||
import { ToolActivityContainer } from "@oh-my-pi/pi-coding-agent/modes/components/tool-activity";
|
||||
import { TranscriptContainer } from "@oh-my-pi/pi-coding-agent/modes/components/transcript-container";
|
||||
import { TtsrNotificationComponent } from "@oh-my-pi/pi-coding-agent/modes/components/ttsr-notification";
|
||||
import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
|
||||
const darkTheme = await getThemeByName("dark");
|
||||
|
||||
@@ -15,7 +16,7 @@ describe("tool activity visibility", () => {
|
||||
setThemeInstance(darkTheme);
|
||||
});
|
||||
|
||||
it("hides and restores mounted activity blocks without losing their content", () => {
|
||||
it("applies visibility to mounted and subsequently added activity blocks", () => {
|
||||
const rule: Rule = {
|
||||
name: "ts-no-tiny-functions",
|
||||
path: "/rules/ts-no-tiny-functions.md",
|
||||
@@ -27,24 +28,26 @@ describe("tool activity visibility", () => {
|
||||
level: "project",
|
||||
},
|
||||
};
|
||||
const components = [
|
||||
{ component: new TtsrNotificationComponent([rule]), text: "ts-no-tiny-functions" },
|
||||
{
|
||||
component: new TodoReminderComponent([{ content: "finish the task", status: "in_progress" }], 1, 3),
|
||||
text: "finish the task",
|
||||
},
|
||||
{
|
||||
component: new ToolActivityContainer(new ToolActivityWarningComponent("Warning: tool failed")),
|
||||
text: "tool failed",
|
||||
},
|
||||
] as const;
|
||||
const transcript = new TranscriptContainer();
|
||||
transcript.addChild(new TtsrNotificationComponent([rule]));
|
||||
transcript.addChild(new TodoReminderComponent([{ content: "finish the task", status: "in_progress" }], 1, 3));
|
||||
transcript.addChild(new ToolActivityContainer(new Text("tool warning", 1, 0)));
|
||||
|
||||
for (const { component, text } of components) {
|
||||
expect(stripVTControlCharacters(component.render(120).join("\n"))).toContain(text);
|
||||
component.setToolActivityVisible(false);
|
||||
expect(stripVTControlCharacters(component.render(120).join("\n"))).toBe("");
|
||||
component.setToolActivityVisible(true);
|
||||
expect(stripVTControlCharacters(component.render(120).join("\n"))).toContain(text);
|
||||
}
|
||||
const visible = stripVTControlCharacters(transcript.render(120).join("\n"));
|
||||
expect(visible).toContain("ts-no-tiny-functions");
|
||||
expect(visible).toContain("finish the task");
|
||||
expect(visible).toContain("tool warning");
|
||||
|
||||
transcript.setToolActivityVisible(false);
|
||||
expect(stripVTControlCharacters(transcript.render(120).join("\n"))).toBe("");
|
||||
transcript.addChild(new ToolActivityContainer(new Text("late activity", 1, 0)));
|
||||
expect(stripVTControlCharacters(transcript.render(120).join("\n"))).toBe("");
|
||||
|
||||
transcript.setToolActivityVisible(true);
|
||||
const restored = stripVTControlCharacters(transcript.render(120).join("\n"));
|
||||
expect(restored).toContain("ts-no-tiny-functions");
|
||||
expect(restored).toContain("finish the task");
|
||||
expect(restored).toContain("tool warning");
|
||||
expect(restored).toContain("late activity");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { AssistantMessage, ImageContent, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { kStreamingPartialJson } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
||||
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { AssistantMessageComponent } from "@oh-my-pi/pi-coding-agent/modes/components/assistant-message";
|
||||
import { StrippedToolCallsPlaceholder } from "@oh-my-pi/pi-coding-agent/modes/components/stripped-tool-calls-placeholder";
|
||||
import { TranscriptContainer } from "@oh-my-pi/pi-coding-agent/modes/components/transcript-container";
|
||||
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
|
||||
import { UiHelpers } from "@oh-my-pi/pi-coding-agent/modes/utils/ui-helpers";
|
||||
@@ -142,8 +142,9 @@ function makeRenderCtx(
|
||||
transcript: SessionContext,
|
||||
showImages = true,
|
||||
hideToolActivity = false,
|
||||
): { ctx: InteractiveModeContext; chatContainer: Container } {
|
||||
const chatContainer = new Container();
|
||||
): { ctx: InteractiveModeContext; chatContainer: TranscriptContainer } {
|
||||
const chatContainer = new TranscriptContainer();
|
||||
chatContainer.setToolActivityVisible(!hideToolActivity);
|
||||
let helpers: UiHelpers;
|
||||
const ctx = {
|
||||
chatContainer,
|
||||
@@ -157,6 +158,10 @@ function makeRenderCtx(
|
||||
updateEditorTopBorder: vi.fn(),
|
||||
ui: { requestRender: vi.fn(), imageBudget: undefined },
|
||||
resetTranscript: () => chatContainer.clear(),
|
||||
present: (content: Component | readonly Component[]) => {
|
||||
const components = Array.isArray(content) ? content : [content];
|
||||
for (const component of components) chatContainer.addChild(component);
|
||||
},
|
||||
// Rebuild paths honor terminal.showImages since the native-image work;
|
||||
// keep it on so the image-replay contracts below stay meaningful.
|
||||
settings: {
|
||||
@@ -487,6 +492,16 @@ describe("UiHelpers.renderInitialMessages — hidden tool activity", () => {
|
||||
expect(visibleRender).toContain("LAUNCH_COMPLETION_MARKER");
|
||||
});
|
||||
|
||||
it("hides tool-originated warnings when activity is hidden", () => {
|
||||
const hidden = makeRenderCtx(makeEmptyContext(), true, true);
|
||||
new UiHelpers(hidden.ctx).showToolActivityWarning("TODO_WARNING_MARKER");
|
||||
expect(Bun.stripANSI(hidden.chatContainer.render(120).join("\n"))).not.toContain("TODO_WARNING_MARKER");
|
||||
|
||||
const visible = makeRenderCtx(makeEmptyContext(), true, false);
|
||||
new UiHelpers(visible.ctx).showToolActivityWarning("TODO_WARNING_MARKER");
|
||||
expect(Bun.stripANSI(visible.chatContainer.render(120).join("\n"))).toContain("TODO_WARNING_MARKER");
|
||||
});
|
||||
|
||||
it("hides the stripped-tool-calls placeholder with tool activity and restores it on reveal", () => {
|
||||
const strippedAssistant: AgentMessage & StrippedToolCallsMarker = {
|
||||
role: "assistant",
|
||||
@@ -508,9 +523,7 @@ describe("UiHelpers.renderInitialMessages — hidden tool activity", () => {
|
||||
);
|
||||
|
||||
// A live reveal must restore the placeholder without a transcript rebuild.
|
||||
for (const child of hidden.chatContainer.children) {
|
||||
if (child instanceof StrippedToolCallsPlaceholder) child.setToolActivityVisible(true);
|
||||
}
|
||||
hidden.chatContainer.setToolActivityVisible(true);
|
||||
expect(Bun.stripANSI(hidden.chatContainer.render(120).join("\n"))).toContain(
|
||||
"2 tool calls elided — no result on this branch",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user