Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

- Fixed URLs not opening on click in fullscreen mode on terminals such as Ghostty; clicking a link in the transcript, dock, or overlays now opens it in the browser.
- Fixed ctrl+p ("Toggle agent message expansion") only toggling received agent messages; it now expands and collapses sent agent messages together with received ones.

## [0.7.2] - 2026-08-11

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function buildConversationComponents(
options.cwd,
);
tool.setExpanded(expanded);
tool.setAgentMessagesExpanded(agentMessagesExpanded);
tool.markExecutionStarted();
tool.setArgsComplete();
selectLatestToolExpandHint(components, tool);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface IPythonCellState {
isPartial?: boolean;
isError?: boolean;
expanded?: boolean;
agentMessagesExpanded?: boolean;
showExpandHint?: boolean;
executionStarted?: boolean;
argsComplete?: boolean;
Expand Down Expand Up @@ -643,7 +644,7 @@ export class IPythonCellComponent implements Component {
for (const message of messages) {
const label = message.deliveryStatus === "delivered" ? "Agent message sent" : "Agent message queued";
const recipient = formatAgentMessageParticipant("sent", message.receiverRole, message.target);
if (this.state.expanded) {
if (this.state.agentMessagesExpanded) {
this.addBlank(lines, width);
this.addPlain(
lines,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class ToolExecutionComponent extends Container {
private toolCallId: string;
private args: any;
private expanded = false;
private agentMessagesExpanded = false;
private showExpandHint = true;
private showImages: boolean;
private includeImageDimensions: boolean;
Expand Down Expand Up @@ -267,6 +268,14 @@ export class ToolExecutionComponent extends Container {
this.updateDisplay();
}

setAgentMessagesExpanded(expanded: boolean): void {
if (this.agentMessagesExpanded === expanded) {
return;
}
this.agentMessagesExpanded = expanded;
this.updateDisplay();
}

setShowExpandHint(show: boolean): void {
if (this.showExpandHint === show) {
return;
Expand Down Expand Up @@ -331,6 +340,7 @@ export class ToolExecutionComponent extends Container {
isPartial: this.isPartial,
isError: this.result?.isError ?? false,
expanded: this.expanded,
agentMessagesExpanded: this.agentMessagesExpanded,
executionStarted: this.executionStarted,
argsComplete: this.argsComplete,
showExpandHint: this.showExpandHint,
Expand Down
18 changes: 18 additions & 0 deletions packages/coding-agent/src/modes/interactive/interactive-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,19 @@ function isExpandable(obj: unknown): obj is Expandable {
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
}

interface AgentMessagesExpandable {
setAgentMessagesExpanded(expanded: boolean): void;
}

function hasAgentMessagesExpansion(obj: unknown): obj is AgentMessagesExpandable {
return (
typeof obj === "object" &&
obj !== null &&
"setAgentMessagesExpanded" in obj &&
typeof (obj as AgentMessagesExpandable).setAgentMessagesExpanded === "function"
);
}

class ExpandableText extends Text implements Expandable {
constructor(
private readonly getCollapsedText: () => string,
Expand Down Expand Up @@ -3018,6 +3031,7 @@ export class InteractiveMode {
this.getCurrentCwd(),
);
component.setExpanded(this.toolOutputExpanded);
component.setAgentMessagesExpanded(this.agentMessagesExpanded);
if (this.startedToolCalls.has(latestToolCall.id)) {
component.markExecutionStarted();
}
Expand Down Expand Up @@ -6499,6 +6513,7 @@ export class InteractiveMode {
this.getCurrentCwd(),
);
component.setExpanded(this.toolOutputExpanded);
component.setAgentMessagesExpanded(this.agentMessagesExpanded);
selectLatestToolExpandHint(this.chatContainer.children, component);
this.chatContainer.addChild(component);
this.registerIpythonToolComponent(content.name, content.id, component);
Expand Down Expand Up @@ -7268,6 +7283,9 @@ export class InteractiveMode {
if (isExpandable(child)) {
child.setExpanded(this.expansionStateFor(child));
}
if (hasAgentMessagesExpansion(child)) {
child.setAgentMessagesExpanded(this.agentMessagesExpanded);
}
}
// Expanding/collapsing changes blocks above the viewport, which would
// otherwise force a full redraw that scrolls to the top and replays the
Expand Down
7 changes: 6 additions & 1 deletion packages/coding-agent/test/interactive-mode-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4234,6 +4234,7 @@ describe("InteractiveMode.setToolsExpanded", () => {

test("toggles agent messages separately from tools", () => {
const toolChild = { setExpanded: vi.fn() };
const ipythonChild = { setExpanded: vi.fn(), setAgentMessagesExpanded: vi.fn() };
const messageChild = new AgentMessageComponent({
role: "custom",
customType: "agent_message",
Expand All @@ -4243,19 +4244,23 @@ describe("InteractiveMode.setToolsExpanded", () => {
timestamp: 123,
} as any);
const messageSetExpanded = vi.spyOn(messageChild, "setExpanded");
const fakeThis = createExpansionFakeThis([toolChild, messageChild]);
const fakeThis = createExpansionFakeThis([toolChild, ipythonChild, messageChild]);

fakeThis.toggleAgentMessageExpansion();

expect(fakeThis.agentMessagesExpanded).toBe(true);
expect(fakeThis.toolOutputExpanded).toBe(false);
expect(messageSetExpanded).toHaveBeenCalledWith(true);
expect(toolChild.setExpanded).toHaveBeenCalledWith(false);
expect(ipythonChild.setExpanded).toHaveBeenCalledWith(false);
expect(ipythonChild.setAgentMessagesExpanded).toHaveBeenCalledWith(true);

fakeThis.setToolsExpanded(true);

expect(toolChild.setExpanded).toHaveBeenCalledWith(true);
expect(messageSetExpanded).toHaveBeenLastCalledWith(true);
expect(ipythonChild.setExpanded).toHaveBeenCalledWith(true);
expect(ipythonChild.setAgentMessagesExpanded).toHaveBeenLastCalledWith(true);
expect(fakeThis.agentMessagesExpanded).toBe(true);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ describe("ENG-4531 agent message UI", () => {
executionStarted: true,
argsComplete: true,
expanded: true,
agentMessagesExpanded: true,
details: {
status: "ok",
result: receipt,
Expand Down Expand Up @@ -593,6 +594,7 @@ describe("ENG-4531 agent message UI", () => {
executionStarted: true,
argsComplete: true,
expanded: true,
agentMessagesExpanded: true,
details: {
status: "ok",
result: receipts,
Expand Down Expand Up @@ -623,6 +625,7 @@ describe("ENG-4531 agent message UI", () => {
executionStarted: true,
argsComplete: true,
expanded: true,
agentMessagesExpanded: true,
details: {
status: "ok",
result: "{'referenced_message': 'agentmsg_4531_ref', 'answer': 42}",
Expand Down Expand Up @@ -652,6 +655,7 @@ describe("ENG-4531 agent message UI", () => {
executionStarted: true,
argsComplete: true,
expanded: true,
agentMessagesExpanded: true,
details: {
status: "ok",
result: "'done'",
Expand All @@ -676,4 +680,41 @@ describe("ENG-4531 agent message UI", () => {
expect(rendered).toContain(" ╰─ Ping.");
expect(rendered).toContain("done");
});

it("decouples sent-message expansion from tool-output expansion", () => {
const sentAgentMessage = {
id: "agentmsg_4531_decoupled",
message: "Decouple me.",
deliveryStatus: "delivered",
receiverRole: "parent",
target: {
activeSessionId: "worker-active",
sessionId: "worker-session",
sessionName: "Worker",
},
};
const baseState = {
code: 'await agent_message.send("Decouple me.", receiver_role="parent")',
executionStarted: true,
argsComplete: true,
details: { status: "ok", sentAgentMessages: [sentAgentMessage] },
};

const agentExpanded = stripAnsi(
new IPythonCellComponent({ ...baseState, expanded: false, agentMessagesExpanded: true })
.render(120)
.join("\n"),
);
expect(agentExpanded).toContain(" ◆ Agent message sent · to parent Worker");
expect(agentExpanded).toContain(" ╰─ Decouple me.");
expect(agentExpanded).not.toContain("· Decouple me.");

const toolExpanded = stripAnsi(
new IPythonCellComponent({ ...baseState, expanded: true, agentMessagesExpanded: false })
.render(120)
.join("\n"),
);
expect(toolExpanded).toContain(" ◆ Agent message sent · to parent Worker · Decouple me.");
expect(toolExpanded).not.toContain("╰─");
});
});