diff --git a/src/cli/App.tsx b/src/cli/App.tsx
index 2134a0d..c2f069e 100644
--- a/src/cli/App.tsx
+++ b/src/cli/App.tsx
@@ -1,7 +1,6 @@
// src/cli/App.tsx
import { existsSync, readFileSync, writeFileSync } from "node:fs";
-
import { APIError, APIUserAbortError } from "@letta-ai/letta-client/core/error";
import type {
AgentState,
@@ -32,6 +31,7 @@ import { type AgentProvenance, createAgent } from "../agent/create";
import { sendMessageStream } from "../agent/message";
import { getModelDisplayName, getModelInfo } from "../agent/model";
import { SessionStats } from "../agent/stats";
+import { INTERRUPTED_BY_USER } from "../constants";
import type { ApprovalContext } from "../permissions/analyzer";
import { type PermissionMode, permissionMode } from "../permissions/mode";
import {
@@ -144,6 +144,7 @@ import {
import {
clearCompletedSubagents,
clearSubagentsByIds,
+ interruptActiveSubagents,
} from "./helpers/subagentState";
import { getRandomThinkingVerb } from "./helpers/thinkingMessages";
import {
@@ -2559,6 +2560,9 @@ export default function App({
(buffersRef.current.abortGeneration || 0) + 1;
const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current);
+ // Mark any running subagents as interrupted
+ interruptActiveSubagents(INTERRUPTED_BY_USER);
+
// Show interrupt feedback (yellow message if no tools were cancelled)
if (!toolsCancelled) {
appendError(INTERRUPT_MESSAGE, true);
@@ -2605,6 +2609,9 @@ export default function App({
(buffersRef.current.abortGeneration || 0) + 1;
const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current);
+ // Mark any running subagents as interrupted
+ interruptActiveSubagents(INTERRUPTED_BY_USER);
+
// NOW abort the stream - interrupted flag is already set
if (abortControllerRef.current) {
abortControllerRef.current.abort();
diff --git a/src/cli/components/SubagentGroupDisplay.tsx b/src/cli/components/SubagentGroupDisplay.tsx
index c7acd9a..605d2b1 100644
--- a/src/cli/components/SubagentGroupDisplay.tsx
+++ b/src/cli/components/SubagentGroupDisplay.tsx
@@ -7,7 +7,7 @@
*
* Features:
* - Real-time updates via useSyncExternalStore
- * - Blinking dots for running agents
+ * - Single blinking dot in header while running
* - Expand/collapse tool calls (ctrl+o)
* - Shows "Running N subagents..." while active
*
@@ -64,24 +64,9 @@ interface AgentRowProps {
const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
const { treeChar, continueChar } = getTreeChars(isLast);
const columns = useTerminalWidth();
- const gutterWidth = 7; // continueChar (3) + " ⎿ " (4)
+ const gutterWidth = 8; // indent (3) + continueChar (2) + status indent (3)
const contentWidth = Math.max(0, columns - gutterWidth);
- const getDotElement = () => {
- switch (agent.status) {
- case "pending":
- return ;
- case "running":
- return ;
- case "completed":
- return ●;
- case "error":
- return ●;
- default:
- return ●;
- }
- };
-
const isRunning = agent.status === "pending" || agent.status === "running";
const stats = formatStats(
agent.toolCalls.length,
@@ -94,19 +79,30 @@ const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
{/* Main row: tree char + description + type + model + stats */}
- {treeChar}
- {getDotElement()}
- {agent.description}
- · {agent.type.toLowerCase()}
- {agent.model && · {agent.model}}
- · {stats}
+
+
+ {" "}
+ {treeChar}{" "}
+
+ {agent.description}
+
+ {" · "}
+ {agent.type.toLowerCase()}
+ {agent.model ? ` · ${agent.model}` : ""}
+ {" · "}
+ {stats}
+
+
{/* Subagent URL */}
{agent.agentURL && (
- {continueChar}
- {" ⎿ Subagent: "}
+
+ {" "}
+ {continueChar} ⎿{" "}
+
+ {"Subagent: "}
{agent.agentURL}
)}
@@ -117,7 +113,10 @@ const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
const formattedArgs = formatToolArgs(tc.args);
return (
- {continueChar}
+
+ {" "}
+ {continueChar}
+
{" "}
{tc.name}({formattedArgs})
@@ -130,15 +129,21 @@ const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
{agent.status === "completed" ? (
<>
- {continueChar}
- {" ⎿ Done"}
+
+ {" "}
+ {continueChar}
+
+ {" Done"}
>
) : agent.status === "error" ? (
<>
- {continueChar}
- {" ⎿ "}
+
+ {" "}
+ {continueChar}
+
+ {" "}
@@ -149,16 +154,22 @@ const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
>
) : lastTool ? (
<>
- {continueChar}
+
+ {" "}
+ {continueChar}
+
- {" ⎿ "}
+ {" "}
{lastTool.name}
>
) : (
<>
- {continueChar}
- {" ⎿ Starting..."}
+
+ {" "}
+ {continueChar}
+
+ {" Starting..."}
>
)}
diff --git a/src/cli/components/SubagentGroupStatic.tsx b/src/cli/components/SubagentGroupStatic.tsx
index d49e6a8..a074b31 100644
--- a/src/cli/components/SubagentGroupStatic.tsx
+++ b/src/cli/components/SubagentGroupStatic.tsx
@@ -51,33 +51,39 @@ interface AgentRowProps {
const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
const { treeChar, continueChar } = getTreeChars(isLast);
const columns = useTerminalWidth();
- const gutterWidth = 7; // continueChar (3) + " ⎿ " (4)
+ const gutterWidth = 8; // indent (3) + continueChar (2) + status indent (3)
const contentWidth = Math.max(0, columns - gutterWidth);
- const dotColor =
- agent.status === "completed"
- ? colors.subagent.completed
- : colors.subagent.error;
-
const stats = formatStats(agent.toolCount, agent.totalTokens);
return (
{/* Main row: tree char + description + type + model + stats */}
- {treeChar}
- ●
- {agent.description}
- · {agent.type.toLowerCase()}
- {agent.model && · {agent.model}}
- · {stats}
+
+
+ {" "}
+ {treeChar}{" "}
+
+ {agent.description}
+
+ {" · "}
+ {agent.type.toLowerCase()}
+ {agent.model ? ` · ${agent.model}` : ""}
+ {" · "}
+ {stats}
+
+
{/* Subagent URL */}
{agent.agentURL && (
- {continueChar}
- {" ⎿ Subagent: "}
+
+ {" "}
+ {continueChar} ⎿{" "}
+
+ {"Subagent: "}
{agent.agentURL}
)}
@@ -86,15 +92,21 @@ const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
{agent.status === "completed" ? (
<>
- {continueChar}
- {" ⎿ Done"}
+
+ {" "}
+ {continueChar}
+
+ {" Done"}
>
) : (
<>
- {continueChar}
- {" ⎿ "}
+
+ {" "}
+ {continueChar}
+
+ {" "}
diff --git a/src/cli/components/colors.ts b/src/cli/components/colors.ts
index 76ddb5f..57daeb9 100644
--- a/src/cli/components/colors.ts
+++ b/src/cli/components/colors.ts
@@ -127,7 +127,7 @@ export const colors = {
running: brandColors.statusWarning,
completed: brandColors.statusSuccess,
error: brandColors.statusError,
- treeChar: brandColors.textDisabled,
+ treeChar: brandColors.textSecondary,
hint: brandColors.textDisabled,
},
diff --git a/src/cli/helpers/subagentDisplay.ts b/src/cli/helpers/subagentDisplay.ts
index 66fe22e..72db8f0 100644
--- a/src/cli/helpers/subagentDisplay.ts
+++ b/src/cli/helpers/subagentDisplay.ts
@@ -43,6 +43,6 @@ export function getTreeChars(isLast: boolean): {
} {
return {
treeChar: isLast ? "└─" : "├─",
- continueChar: isLast ? " " : "│ ",
+ continueChar: isLast ? " " : "│ ",
};
}
diff --git a/src/cli/helpers/subagentState.ts b/src/cli/helpers/subagentState.ts
index 51be0da..790d22a 100644
--- a/src/cli/helpers/subagentState.ts
+++ b/src/cli/helpers/subagentState.ts
@@ -274,6 +274,29 @@ export function hasActiveSubagents(): boolean {
return false;
}
+/**
+ * Mark all running/pending subagents as interrupted
+ * Called when user presses ESC to interrupt execution
+ */
+export function interruptActiveSubagents(errorMessage: string): void {
+ let anyInterrupted = false;
+ for (const [id, agent] of store.agents.entries()) {
+ if (agent.status === "pending" || agent.status === "running") {
+ const updatedAgent: SubagentState = {
+ ...agent,
+ status: "error",
+ error: errorMessage,
+ durationMs: Date.now() - agent.startTime,
+ };
+ store.agents.set(id, updatedAgent);
+ anyInterrupted = true;
+ }
+ }
+ if (anyInterrupted) {
+ notifyListeners();
+ }
+}
+
// ============================================================================
// React Integration (useSyncExternalStore compatible)
// ============================================================================