@bubblebrain-ai/bubble 0.0.29 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/goal/format.d.ts +1 -1
- package/dist/goal/format.js +1 -1
- package/dist/slash-commands/commands.js +1 -119
- package/dist/slash-commands/types.d.ts +1 -11
- package/dist/tui-ink/app.d.ts +0 -18
- package/dist/tui-ink/app.js +91 -231
- package/dist/tui-ink/input-box.d.ts +3 -0
- package/dist/tui-ink/input-box.js +8 -1
- package/dist/tui-ink/message-list.d.ts +1 -2
- package/dist/tui-ink/message-list.js +13 -14
- package/dist/tui-ink/subagent-inspector.d.ts +1 -1
- package/dist/tui-ink/subagent-inspector.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,6 @@ Bubble ships with a catalog of built-in providers. Configure them inside the app
|
|
|
66
66
|
| --- | --- |
|
|
67
67
|
| `/login` | OAuth sign-in for ChatGPT; unlocks the OpenAI Codex models without an API key. |
|
|
68
68
|
| `/provider` | Open a picker to connect, switch, add, or remove a provider. |
|
|
69
|
-
| `/key <provider> <key>` | Set the API key for a provider. |
|
|
70
69
|
| `/model` | Pick the active model and reasoning effort. |
|
|
71
70
|
|
|
72
71
|
Built-in providers include OpenAI, Anthropic, Google, DeepSeek, Moonshot (CN and international), Kimi for Coding, Zhipu AI, Z.AI, Alibaba DashScope, Doubao (Volcengine Ark), MiniMax, StepFun, Groq, Together AI, Fireworks, and a `local` profile for any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio, etc.).
|
|
@@ -155,7 +154,7 @@ Rules use a simple pattern syntax, for example:
|
|
|
155
154
|
| --- | --- |
|
|
156
155
|
| `/help` | List available commands. |
|
|
157
156
|
| `/model` | Switch model and reasoning effort. |
|
|
158
|
-
| `/provider`, `/login`, `/logout
|
|
157
|
+
| `/provider`, `/login`, `/logout` | Connect and manage providers. |
|
|
159
158
|
| `/session`, `/rewind`, `/clear` | Manage conversation history. |
|
|
160
159
|
| `/skills` | Open the searchable skills picker. |
|
|
161
160
|
| `/mcp` | List or reconnect MCP servers. |
|
|
@@ -163,7 +162,7 @@ Rules use a simple pattern syntax, for example:
|
|
|
163
162
|
| `/permissions` | View or edit allow/deny rules. |
|
|
164
163
|
| `/context`, `/stats`, `/compact` | Inspect context usage, model stats, and compact the session. |
|
|
165
164
|
| `/lsp`, `/hooks` | Manage language servers and lifecycle hooks. |
|
|
166
|
-
| `/theme
|
|
165
|
+
| `/theme` | Adjust the interface theme. |
|
|
167
166
|
| `/feedback` | Send feedback or report a bug. |
|
|
168
167
|
|
|
169
168
|
## Non-interactive mode
|
package/dist/goal/format.d.ts
CHANGED
|
@@ -14,5 +14,5 @@ export declare function goalSummaryText(goal: GoalState): string;
|
|
|
14
14
|
* update_goal tool can't report this — see goal/tools.ts).
|
|
15
15
|
*/
|
|
16
16
|
export declare function goalCompleteNotice(goal: GoalState): string;
|
|
17
|
-
/** Compact single-line indicator for
|
|
17
|
+
/** Compact single-line indicator for status surfaces. */
|
|
18
18
|
export declare function goalIndicatorLine(goal: GoalState, maxObjective?: number): string;
|
package/dist/goal/format.js
CHANGED
|
@@ -95,7 +95,7 @@ function completionTokenUsagePhrase(goal) {
|
|
|
95
95
|
? `${formatTokensCompact(goal.tokensUsed)}/${formatTokensCompact(goal.tokenBudget)} tok used`
|
|
96
96
|
: `${formatTokensCompact(goal.tokensUsed)} tok used`;
|
|
97
97
|
}
|
|
98
|
-
/** Compact single-line indicator for
|
|
98
|
+
/** Compact single-line indicator for status surfaces. */
|
|
99
99
|
export function goalIndicatorLine(goal, maxObjective = 48) {
|
|
100
100
|
const segments = [`goal: ${goalStatusLabel(goal.status)}`, `${goal.turnsSpent} turns`];
|
|
101
101
|
const tokens = tokensPart(goal);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UserConfig
|
|
1
|
+
import { UserConfig } from "../config.js";
|
|
2
2
|
import { formatContextUsage } from "../context/usage.js";
|
|
3
3
|
import { formatDiagnostics } from "../lsp/index.js";
|
|
4
4
|
import { normalizeNameForMCP } from "../mcp/name.js";
|
|
@@ -8,7 +8,6 @@ import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingL
|
|
|
8
8
|
import { SessionManager } from "../session.js";
|
|
9
9
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
10
10
|
import { normalizeSingleLine } from "../text-display.js";
|
|
11
|
-
import { copyToClipboard } from "../clipboard.js";
|
|
12
11
|
import { formatRelativeTime } from "../tui/recent-activity.js";
|
|
13
12
|
import { HOOK_EVENT_NAMES, isHookEventName } from "../hooks/index.js";
|
|
14
13
|
import { isThinkingLevel } from "../variant/thinking-level.js";
|
|
@@ -287,17 +286,6 @@ async function handleMemoryCommand(args, ctx) {
|
|
|
287
286
|
}
|
|
288
287
|
return "Usage: /memory [status|search|compact|summarize|refresh|reset]";
|
|
289
288
|
}
|
|
290
|
-
function parseKeyArgs(args, ctx) {
|
|
291
|
-
const trimmed = args.trim();
|
|
292
|
-
const [first, ...rest] = trimmed.split(/\s+/);
|
|
293
|
-
const explicitProvider = first
|
|
294
|
-
? ctx.registry.getConfigured().find((provider) => provider.id === first)
|
|
295
|
-
: undefined;
|
|
296
|
-
if (explicitProvider) {
|
|
297
|
-
return { provider: explicitProvider, apiKey: rest.join(" ") };
|
|
298
|
-
}
|
|
299
|
-
return { provider: ctx.registry.getDefault(), apiKey: trimmed };
|
|
300
|
-
}
|
|
301
289
|
const builtinSlashCommandEntries = [
|
|
302
290
|
{
|
|
303
291
|
name: "skills",
|
|
@@ -306,13 +294,6 @@ const builtinSlashCommandEntries = [
|
|
|
306
294
|
ctx.openPicker("skill");
|
|
307
295
|
},
|
|
308
296
|
},
|
|
309
|
-
{
|
|
310
|
-
name: "agents",
|
|
311
|
-
description: "Inspect spawned subagents and their working traces (also Ctrl+G)",
|
|
312
|
-
async handler(_args, ctx) {
|
|
313
|
-
ctx.openPicker("agents");
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
297
|
{
|
|
317
298
|
name: "help",
|
|
318
299
|
description: "Show available slash commands",
|
|
@@ -370,33 +351,6 @@ const builtinSlashCommandEntries = [
|
|
|
370
351
|
return `Theme set to ${arg}${arg === "auto" ? ` (resolved to ${resolved})` : ""}.`;
|
|
371
352
|
},
|
|
372
353
|
},
|
|
373
|
-
{
|
|
374
|
-
name: "sidebar",
|
|
375
|
-
description: "Toggle the right sidebar. Usage: /sidebar [open|close|auto]",
|
|
376
|
-
async handler(args, ctx) {
|
|
377
|
-
if (!ctx.toggleSidebar || !ctx.setSidebarMode) {
|
|
378
|
-
return "Sidebar control is only available inside the TUI.";
|
|
379
|
-
}
|
|
380
|
-
const arg = args.trim().toLowerCase();
|
|
381
|
-
if (!arg) {
|
|
382
|
-
ctx.toggleSidebar();
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
if (["open", "show", "expand", "expanded", "on"].includes(arg)) {
|
|
386
|
-
ctx.setSidebarMode("expanded");
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (["close", "hide", "collapse", "collapsed", "off"].includes(arg)) {
|
|
390
|
-
ctx.setSidebarMode("collapsed");
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
if (arg === "auto") {
|
|
394
|
-
ctx.setSidebarMode("auto");
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
return "Usage: /sidebar [open|close|auto]";
|
|
398
|
-
},
|
|
399
|
-
},
|
|
400
354
|
{
|
|
401
355
|
name: "clear",
|
|
402
356
|
description: "Clear the current conversation history",
|
|
@@ -410,27 +364,6 @@ const builtinSlashCommandEntries = [
|
|
|
410
364
|
ctx.clearMessages();
|
|
411
365
|
},
|
|
412
366
|
},
|
|
413
|
-
{
|
|
414
|
-
name: "copy",
|
|
415
|
-
description: "Copy the last assistant message to the system clipboard",
|
|
416
|
-
async handler(args, ctx) {
|
|
417
|
-
const lastAssistant = [...ctx.agent.messages]
|
|
418
|
-
.reverse()
|
|
419
|
-
.find((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim().length > 0);
|
|
420
|
-
if (!lastAssistant || typeof lastAssistant.content !== "string") {
|
|
421
|
-
return "No assistant message to copy yet.";
|
|
422
|
-
}
|
|
423
|
-
const text = lastAssistant.content;
|
|
424
|
-
try {
|
|
425
|
-
await copyToClipboard(text);
|
|
426
|
-
}
|
|
427
|
-
catch (err) {
|
|
428
|
-
return `Failed to copy to clipboard: ${err?.message || String(err)}`;
|
|
429
|
-
}
|
|
430
|
-
const chars = text.length;
|
|
431
|
-
return `Copied last assistant message to clipboard (${chars} character${chars === 1 ? "" : "s"}).`;
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
367
|
{
|
|
435
368
|
name: "rewind",
|
|
436
369
|
description: "Rewind conversation and/or file edits to before an earlier message. Usage: /rewind [n] [--code|--chat]",
|
|
@@ -674,31 +607,6 @@ const builtinSlashCommandEntries = [
|
|
|
674
607
|
return `Model switched to ${displaySelectedModel(next, ctx.agent.thinking)}.`;
|
|
675
608
|
},
|
|
676
609
|
},
|
|
677
|
-
{
|
|
678
|
-
name: "key",
|
|
679
|
-
description: "Set API key for the current or a specific provider. Usage: /key [provider-id] <key>",
|
|
680
|
-
async handler(args, ctx) {
|
|
681
|
-
if (!args) {
|
|
682
|
-
ctx.openPicker("key");
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
const { provider, apiKey } = parseKeyArgs(args, ctx);
|
|
686
|
-
if (!provider) {
|
|
687
|
-
return "No provider configured. Use /provider --add <id> first.";
|
|
688
|
-
}
|
|
689
|
-
if (!apiKey) {
|
|
690
|
-
return `Usage: /key ${provider.id} <key>`;
|
|
691
|
-
}
|
|
692
|
-
if (ctx.registry.getModelConfig().hasProvider(provider.id)) {
|
|
693
|
-
return `API key for ${provider.name} is managed in ~/.bubble/models.json. Please edit that file directly.`;
|
|
694
|
-
}
|
|
695
|
-
ctx.registry.updateProviderKey(provider.id, apiKey);
|
|
696
|
-
ctx.registry.setDefault(provider.id);
|
|
697
|
-
ctx.agent.setProvider(ctx.createProvider(provider.id, apiKey, provider.baseURL));
|
|
698
|
-
ctx.agent.providerId = provider.id;
|
|
699
|
-
return `API key updated for ${provider.name} to ${maskKey(apiKey)}.`;
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
610
|
{
|
|
703
611
|
name: "logout",
|
|
704
612
|
description: "Remove OAuth credentials for a provider. Usage: /logout [openai]",
|
|
@@ -739,32 +647,6 @@ const builtinSlashCommandEntries = [
|
|
|
739
647
|
: "Exited plan mode.";
|
|
740
648
|
},
|
|
741
649
|
},
|
|
742
|
-
{
|
|
743
|
-
name: "todos",
|
|
744
|
-
description: "Show the current todo list. Use /todos clear to reset it.",
|
|
745
|
-
async handler(args, ctx) {
|
|
746
|
-
const sub = args.trim();
|
|
747
|
-
if (sub === "clear") {
|
|
748
|
-
const previous = ctx.agent.getTodos().length;
|
|
749
|
-
if (previous === 0) {
|
|
750
|
-
return "Todo list is already empty.";
|
|
751
|
-
}
|
|
752
|
-
ctx.agent.setTodos([]);
|
|
753
|
-
return `Cleared ${previous} todo item${previous === 1 ? "" : "s"}.`;
|
|
754
|
-
}
|
|
755
|
-
const todos = ctx.agent.getTodos();
|
|
756
|
-
if (todos.length === 0) {
|
|
757
|
-
return "No todos yet. The assistant will create some when working on multi-step tasks.";
|
|
758
|
-
}
|
|
759
|
-
const glyph = (status) => status === "completed" ? "✔" : status === "in_progress" ? "▶" : "○";
|
|
760
|
-
const lines = ["Todos:"];
|
|
761
|
-
for (const todo of todos) {
|
|
762
|
-
const label = todo.status === "in_progress" ? (todo.activeForm || todo.content) : todo.content;
|
|
763
|
-
lines.push(` ${glyph(todo.status)} ${label}`);
|
|
764
|
-
}
|
|
765
|
-
return lines.join("\n");
|
|
766
|
-
},
|
|
767
|
-
},
|
|
768
650
|
{
|
|
769
651
|
name: "permissions",
|
|
770
652
|
description: "Inspect or edit allow/deny rules. Subcommands: add <scope> <list> <rule>, remove <scope> <list> <rule>, clear (session allowlist), reload.",
|
|
@@ -10,7 +10,6 @@ import type { LspService } from "../lsp/index.js";
|
|
|
10
10
|
import type { MemoryScope } from "../memory/index.js";
|
|
11
11
|
import type { ThemeMode } from "../config.js";
|
|
12
12
|
import type { ExternalHookController } from "../hooks/controller.js";
|
|
13
|
-
export type SidebarMode = "auto" | "expanded" | "collapsed";
|
|
14
13
|
/**
|
|
15
14
|
* Live progress for a manual `/compact` run, pushed to the TUI so it can render
|
|
16
15
|
* a progress bar. `phase` advances collecting → summarizing → applying;
|
|
@@ -21,11 +20,6 @@ export interface CompactionProgress {
|
|
|
21
20
|
phase: "collecting" | "summarizing" | "applying";
|
|
22
21
|
streamedChars: number;
|
|
23
22
|
}
|
|
24
|
-
export interface SidebarCommandState {
|
|
25
|
-
mode: SidebarMode;
|
|
26
|
-
visible: boolean;
|
|
27
|
-
active: boolean;
|
|
28
|
-
}
|
|
29
23
|
export interface SlashCommandContext {
|
|
30
24
|
agent: Agent;
|
|
31
25
|
addMessage: (role: "user" | "assistant" | "error", content: string) => void;
|
|
@@ -34,7 +28,7 @@ export interface SlashCommandContext {
|
|
|
34
28
|
exit: () => void;
|
|
35
29
|
sessionManager?: SessionManager;
|
|
36
30
|
createProvider: (providerId: string, apiKey: string, baseURL: string) => Provider;
|
|
37
|
-
openPicker: (mode: "model" | "key" | "provider" | "provider-add" | "login" | "logout" | "skill" | "feishu-setup"
|
|
31
|
+
openPicker: (mode: "model" | "key" | "provider" | "provider-add" | "login" | "logout" | "skill" | "feishu-setup", providerId?: string) => void;
|
|
38
32
|
registry: ProviderRegistry;
|
|
39
33
|
skillRegistry: SkillRegistry;
|
|
40
34
|
bashAllowlist?: BashAllowlist;
|
|
@@ -52,10 +46,6 @@ export interface SlashCommandContext {
|
|
|
52
46
|
getResolvedTheme?: () => "light" | "dark";
|
|
53
47
|
/** Persist a new theme mode AND apply it to the running TUI. */
|
|
54
48
|
setThemeMode?: (mode: ThemeMode) => void;
|
|
55
|
-
/** Toggle the right session sidebar in the running TUI. */
|
|
56
|
-
toggleSidebar?: () => SidebarCommandState;
|
|
57
|
-
/** Set the right session sidebar mode in the running TUI. */
|
|
58
|
-
setSidebarMode?: (mode: SidebarMode) => SidebarCommandState;
|
|
59
49
|
/** Open the feedback dialog. `initialDescription` prefills the description field. */
|
|
60
50
|
openFeedback?: (initialDescription: string) => void;
|
|
61
51
|
/** Open the interactive rewind picker. When absent, /rewind falls back to a text listing. */
|
package/dist/tui-ink/app.d.ts
CHANGED
|
@@ -60,26 +60,8 @@ export interface ExitSummary {
|
|
|
60
60
|
wallMs: number;
|
|
61
61
|
}
|
|
62
62
|
export declare const INK_LOCAL_SLASH_COMMANDS: readonly [{
|
|
63
|
-
readonly name: "thinking";
|
|
64
|
-
readonly description: "Toggle thinking block visibility";
|
|
65
|
-
}, {
|
|
66
|
-
readonly name: "toggle-thinking";
|
|
67
|
-
readonly description: "Toggle thinking block visibility";
|
|
68
|
-
}, {
|
|
69
63
|
readonly name: "goal";
|
|
70
64
|
readonly description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)";
|
|
71
|
-
}, {
|
|
72
|
-
readonly name: "trace";
|
|
73
|
-
readonly description: "Toggle verbose trace output";
|
|
74
|
-
}, {
|
|
75
|
-
readonly name: "verbose";
|
|
76
|
-
readonly description: "Toggle verbose trace output";
|
|
77
|
-
}, {
|
|
78
|
-
readonly name: "debug";
|
|
79
|
-
readonly description: "Toggle verbose trace output";
|
|
80
|
-
}, {
|
|
81
|
-
readonly name: "write-previews";
|
|
82
|
-
readonly description: "Toggle write preview expansion";
|
|
83
65
|
}];
|
|
84
66
|
export declare function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
85
67
|
export {};
|
package/dist/tui-ink/app.js
CHANGED
|
@@ -6,7 +6,7 @@ import { isHiddenToolMetadata } from "../agent/discovery-barrier.js";
|
|
|
6
6
|
import { SessionManager } from "../session.js";
|
|
7
7
|
import { registry as slashRegistry } from "../slash-commands/index.js";
|
|
8
8
|
import { UserConfig, maskKey } from "../config.js";
|
|
9
|
-
import { InputBox, isCtrlCInput, } from "./input-box.js";
|
|
9
|
+
import { InputBox, isCtrlCInput, isCtrlLetterInput, } from "./input-box.js";
|
|
10
10
|
import { MessageList } from "./message-list.js";
|
|
11
11
|
import { isMultiplexedTerminal } from "./terminal-env.js";
|
|
12
12
|
import { appendTextPart, appendToolPart, compactDisplayMessages, contentFromParts, latestCompactionSummary, moveStatusMessageToEnd, nextDisplayMessageKey, setUserInputStatus, snapshotDisplayParts, stripInterruptedAssistantMarker, toolCallsFromParts, } from "./display-history.js";
|
|
@@ -227,34 +227,10 @@ function withMessageKey(message) {
|
|
|
227
227
|
// 40ms keeps perceived latency invisible while capping layout work at 25fps.
|
|
228
228
|
const STREAMING_FLUSH_INTERVAL_MS = 40;
|
|
229
229
|
export const INK_LOCAL_SLASH_COMMANDS = [
|
|
230
|
-
{
|
|
231
|
-
name: "thinking",
|
|
232
|
-
description: "Toggle thinking block visibility",
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
name: "toggle-thinking",
|
|
236
|
-
description: "Toggle thinking block visibility",
|
|
237
|
-
},
|
|
238
230
|
{
|
|
239
231
|
name: "goal",
|
|
240
232
|
description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)",
|
|
241
233
|
},
|
|
242
|
-
{
|
|
243
|
-
name: "trace",
|
|
244
|
-
description: "Toggle verbose trace output",
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: "verbose",
|
|
248
|
-
description: "Toggle verbose trace output",
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: "debug",
|
|
252
|
-
description: "Toggle verbose trace output",
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
name: "write-previews",
|
|
256
|
-
description: "Toggle write preview expansion",
|
|
257
|
-
},
|
|
258
234
|
];
|
|
259
235
|
export function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, onExit }) {
|
|
260
236
|
const [sessionManager, setSessionManager] = useState(initialSessionManager);
|
|
@@ -281,8 +257,8 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
281
257
|
const [streamingReasoning, setStreamingReasoning] = useState("");
|
|
282
258
|
const [streamingTools, setStreamingTools] = useState([]);
|
|
283
259
|
const [streamingParts, setStreamingParts] = useState([]);
|
|
284
|
-
// Live subagent groups for the
|
|
285
|
-
// reflects members as their events stream into the transcript.
|
|
260
|
+
// Live subagent groups for the inspector opened from the subagent entry line;
|
|
261
|
+
// recomputed each render so it reflects members as their events stream into the transcript.
|
|
286
262
|
const subagentGroups = useMemo(() => collectSubagentGroups(messages, streamingTools), [messages, streamingTools]);
|
|
287
263
|
const subagentMembers = useMemo(() => subagentGroups.flatMap((g) => g.members), [subagentGroups]);
|
|
288
264
|
// Down-arrow from the composer focuses the subagent entry line; Enter then
|
|
@@ -320,9 +296,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
320
296
|
const [composerDraft, setComposerDraft] = useState(null);
|
|
321
297
|
const [keyProviderId, setKeyProviderId] = useState(null);
|
|
322
298
|
const [showThinking, setShowThinking] = useState(false);
|
|
323
|
-
const [expandedToolOutput, setExpandedToolOutput] = useState(false);
|
|
324
299
|
const [verboseTrace, setVerboseTrace] = useState(false);
|
|
325
|
-
const [sidebarMode, setSidebarMode] = useState("collapsed");
|
|
326
300
|
const startedWithVisibleHistoryRef = useRef(messages.some((message) => message.syntheticKind !== "ui_summary"));
|
|
327
301
|
const { columns: terminalColumns, rows: terminalRows } = useTerminalSize();
|
|
328
302
|
const showWelcome = shouldShowWelcomeBanner({
|
|
@@ -338,6 +312,12 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
338
312
|
// MessageList so Ink discards its already-printed rows and re-prints the
|
|
339
313
|
// rebuilt list onto a freshly-cleared screen instead of appending duplicates.
|
|
340
314
|
const [staticGeneration, setStaticGeneration] = useState(0);
|
|
315
|
+
const reprintTranscript = useCallback(() => {
|
|
316
|
+
if (process.stdout.isTTY) {
|
|
317
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
318
|
+
}
|
|
319
|
+
setStaticGeneration((generation) => generation + 1);
|
|
320
|
+
}, []);
|
|
341
321
|
// Steer/queue while the agent runs:
|
|
342
322
|
// Enter steers the current run via the agent's input controller; Tab (or an
|
|
343
323
|
// ineligible input) queues for the next turn. Both render placeholder user
|
|
@@ -567,7 +547,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
567
547
|
setSubagentEntryFocused(false);
|
|
568
548
|
return;
|
|
569
549
|
}
|
|
570
|
-
if (
|
|
550
|
+
if (isCtrlLetterInput(input, key, "p") && !pickerMode && !activeAbortRef.current) {
|
|
571
551
|
setStatsPanel(null);
|
|
572
552
|
setPickerMode("slash");
|
|
573
553
|
return;
|
|
@@ -582,7 +562,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
582
562
|
}
|
|
583
563
|
return;
|
|
584
564
|
}
|
|
585
|
-
if (
|
|
565
|
+
if (isCtrlLetterInput(input, key, "t") && !pickerMode) {
|
|
586
566
|
setShowThinking((current) => {
|
|
587
567
|
const next = !current;
|
|
588
568
|
addMessage("assistant", next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
@@ -590,12 +570,13 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
590
570
|
});
|
|
591
571
|
return;
|
|
592
572
|
}
|
|
593
|
-
if (
|
|
573
|
+
if (isCtrlLetterInput(input, key, "o") && !pickerMode) {
|
|
594
574
|
setVerboseTrace((v) => !v);
|
|
575
|
+
reprintTranscript();
|
|
595
576
|
return;
|
|
596
577
|
}
|
|
597
578
|
// Ctrl+R: cycle thinking level (formerly Shift+Tab)
|
|
598
|
-
if (
|
|
579
|
+
if (isCtrlLetterInput(input, key, "r") && !pickerMode) {
|
|
599
580
|
const modelParts = agent.model.includes(":")
|
|
600
581
|
? agent.model.split(":")
|
|
601
582
|
: [agent.providerId || safeRegistry.getDefault()?.id || "openai", agent.model];
|
|
@@ -639,12 +620,9 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
639
620
|
// un-printed, so we wipe the screen + scrollback and bump the Static key:
|
|
640
621
|
// Ink then re-prints the rebuilt list fresh instead of appending duplicates.
|
|
641
622
|
const resetTranscript = useCallback((updater) => {
|
|
642
|
-
|
|
643
|
-
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
644
|
-
}
|
|
645
|
-
setStaticGeneration((generation) => generation + 1);
|
|
623
|
+
reprintTranscript();
|
|
646
624
|
updateDisplayMessages(updater);
|
|
647
|
-
}, [updateDisplayMessages]);
|
|
625
|
+
}, [reprintTranscript, updateDisplayMessages]);
|
|
648
626
|
const addMessage = useCallback((role, content) => {
|
|
649
627
|
updateDisplayMessages((prev) => [...prev, withMessageKey({ role, content })]);
|
|
650
628
|
}, [updateDisplayMessages]);
|
|
@@ -759,21 +737,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
759
737
|
const { description: _drop, ...rest } = base;
|
|
760
738
|
setPendingFeedback({ base: rest, initialDescription });
|
|
761
739
|
}, [agent]);
|
|
762
|
-
const sidebarFits = terminalColumns > 120;
|
|
763
|
-
const sidebarVisible = sidebarMode === "expanded" ? sidebarFits : sidebarMode === "auto" && sidebarFits;
|
|
764
|
-
const currentSidebarCommandState = useCallback((mode = sidebarMode) => {
|
|
765
|
-
const visible = mode === "expanded" ? sidebarFits : mode === "auto" && sidebarFits;
|
|
766
|
-
return { mode, visible, active: visible };
|
|
767
|
-
}, [sidebarFits, sidebarMode]);
|
|
768
|
-
const toggleSidebar = useCallback(() => {
|
|
769
|
-
const next = sidebarVisible ? "collapsed" : "expanded";
|
|
770
|
-
setSidebarMode(next);
|
|
771
|
-
return currentSidebarCommandState(next);
|
|
772
|
-
}, [currentSidebarCommandState, sidebarVisible]);
|
|
773
|
-
const applySidebarMode = useCallback((mode) => {
|
|
774
|
-
setSidebarMode(mode);
|
|
775
|
-
return currentSidebarCommandState(mode);
|
|
776
|
-
}, [currentSidebarCommandState]);
|
|
777
740
|
const openSessionPicker = useCallback(() => {
|
|
778
741
|
if (activeAbortRef.current) {
|
|
779
742
|
addMessage("error", "Stop the current run before switching sessions.");
|
|
@@ -1553,30 +1516,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1553
1516
|
requestExit();
|
|
1554
1517
|
return;
|
|
1555
1518
|
}
|
|
1556
|
-
if (/^\/(?:thinking|toggle-thinking)(?:\s|$)/.test(input.trim())) {
|
|
1557
|
-
setShowThinking((current) => {
|
|
1558
|
-
const next = !current;
|
|
1559
|
-
addMessage("assistant", next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
1560
|
-
return next;
|
|
1561
|
-
});
|
|
1562
|
-
return;
|
|
1563
|
-
}
|
|
1564
|
-
if (/^\/(?:trace|verbose|debug)(?:\s|$)/.test(input.trim())) {
|
|
1565
|
-
setVerboseTrace((current) => {
|
|
1566
|
-
const next = !current;
|
|
1567
|
-
addMessage("assistant", next ? "Verbose trace visible" : "Compact trace visible");
|
|
1568
|
-
return next;
|
|
1569
|
-
});
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
if (/^\/write-previews(?:\s|$)/.test(input.trim())) {
|
|
1573
|
-
setExpandedToolOutput((current) => {
|
|
1574
|
-
const next = !current;
|
|
1575
|
-
addMessage("assistant", next ? "Write previews expanded" : "Write previews collapsed");
|
|
1576
|
-
return next;
|
|
1577
|
-
});
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
1519
|
if (/^\/goal(?:\s|$)/.test(input.trim())) {
|
|
1581
1520
|
await handleGoalCommand(input);
|
|
1582
1521
|
return;
|
|
@@ -1615,8 +1554,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1615
1554
|
getThemeMode: () => themeMode,
|
|
1616
1555
|
getResolvedTheme: () => themeResolved,
|
|
1617
1556
|
setThemeMode: applyThemeMode,
|
|
1618
|
-
toggleSidebar,
|
|
1619
|
-
setSidebarMode: applySidebarMode,
|
|
1620
1557
|
openStats: openStatsPanel,
|
|
1621
1558
|
compactionProgress: setCompaction,
|
|
1622
1559
|
});
|
|
@@ -1682,7 +1619,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1682
1619
|
setStartingSubmit(null);
|
|
1683
1620
|
}
|
|
1684
1621
|
}
|
|
1685
|
-
}, [addMessage, agent, args.cwd, openPicker, openSessionPicker, openRewindPicker, openStatsPanel, createProvider, currentSessionFile, fillComposer, prepareSubmitDisplay, safeRegistry, safeSkillRegistry, updateDisplayMessages, queueInput, submitSteer, requestExit,
|
|
1622
|
+
}, [addMessage, agent, args.cwd, openPicker, openSessionPicker, openRewindPicker, openStatsPanel, createProvider, currentSessionFile, fillComposer, prepareSubmitDisplay, safeRegistry, safeSkillRegistry, updateDisplayMessages, queueInput, submitSteer, requestExit, setStartingSubmit]);
|
|
1686
1623
|
// Drain the queue once the run ends and no modal needs the user first.
|
|
1687
1624
|
// The placeholder row is removed right before resubmitting — handleSubmit
|
|
1688
1625
|
// renders the message again as a regular user row.
|
|
@@ -1747,81 +1684,80 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1747
1684
|
// tail, pickers, composer, footer) occupies the live region. Letting it size
|
|
1748
1685
|
// to its content keeps the composer pinned just below the latest output the
|
|
1749
1686
|
// way ordinary shell programs do.
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
}, disabled: !!pendingPlan || !!pendingApproval || !!pendingQuestion || !!pendingFeedback || !!statsPanel || subagentEntryFocused, cursorResetEpoch: cursorResetEpoch, draftText: composerDraft?.text, draftEpoch: composerDraft?.epoch, onDraftApplied: clearComposerDraft, skillRegistry: safeSkillRegistry, localSlashCommands: [...INK_LOCAL_SLASH_COMMANDS], terminalColumns: mainWidth, cwd: args.cwd, sessionFile: currentSessionFile(), nextImageLabelStart: nextImageDisplayLabelStartRef.current }) })), !isExiting && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && subagentMembers.length > 0 && (_jsxs(Box, { paddingX: 1, flexShrink: 0, backgroundColor: palette.background, children: [_jsx(Text, { bold: subagentEntryFocused, color: subagentEntryFocused ? palette.accent : palette.toolName, children: subagentEntryFocused ? "> ↳ " : " ↳ " }), _jsxs(Text, { color: subagentEntryFocused ? palette.accent : palette.muted, children: [subagentMembers.length, " subagent", subagentMembers.length === 1 ? "" : "s", " \u00B7 ", subagentSummary(subagentMembers), " \u00B7 "] }), _jsx(Text, { color: palette.accent, children: subagentEntryFocused ? "Enter open · Esc back" : "↓ to inspect traces" })] })), !isExiting && (_jsx(Box, { flexShrink: 0, children: _jsx(FooterBar, { data: buildFooterData({ mode: permissionMode, goalLine }) }) }))] }), sidebarVisible && (_jsx(InkSidebar, { width: sidebarWidth, agent: agent, sessionManager: sessionManager, cwd: args.cwd, mode: permissionMode, goalLine: goalLine, todos: todos, mcpManager: mcpManager, lspService: lspService }))] }) }));
|
|
1687
|
+
const mainWidth = Math.max(40, terminalColumns);
|
|
1688
|
+
return (_jsx(ThemeProvider, { value: palette, children: _jsxs(Box, { flexDirection: "column", width: mainWidth, backgroundColor: palette.background, children: [_jsx(MessageList, { messages: messages, streamingContent: streamingContent, streamingReasoning: streamingReasoning, streamingTools: streamingTools, streamingParts: streamingParts, terminalColumns: mainWidth, showThinking: showThinking, verboseTrace: verboseTrace, pendingApproval: approvalHint, nowTick: nowTick, welcomeBanner: welcomeBannerNode, staticGeneration: staticGeneration, paddingX: 1, maxStreamRows: Math.max(6, terminalRows - 10) }), pickerMode === "model" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ModelPicker, { registry: safeRegistry, current: agent.model, currentThinkingLevel: thinkingLevel, recent: userConfig.getRecentModels(), onSelect: handleModelSelect, onCancel: closePicker }) })), pickerMode === "provider" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
1689
|
+
.filter((p) => isUserVisibleProvider(p.id))
|
|
1690
|
+
.map((p) => {
|
|
1691
|
+
const configured = safeRegistry.getConfigured().find((item) => item.id === p.id);
|
|
1692
|
+
const configuredLabel = configured?.apiKey ? "configured" : "needs key";
|
|
1693
|
+
return {
|
|
1694
|
+
id: p.id,
|
|
1695
|
+
name: `${p.name} [${configuredLabel}]`,
|
|
1696
|
+
enabled: true,
|
|
1697
|
+
};
|
|
1698
|
+
}), current: currentProviderId, onSelect: handleProviderSelect, onCancel: closePicker }) })), pickerMode === "provider-add" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
1699
|
+
.filter((p) => isUserVisibleProvider(p.id))
|
|
1700
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleProviderAddSelect, onCancel: closePicker, title: "Add Provider" }) })), pickerMode === "login" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
1701
|
+
.filter((p) => isUserVisibleProvider(p.id) && safeRegistry.supportsOAuth(p.id))
|
|
1702
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLoginProviderSelect, onCancel: closePicker, title: "Select Login Provider" }) })), pickerMode === "logout" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: safeRegistry.getConfigured()
|
|
1703
|
+
.filter((p) => safeRegistry.getAuthStorage().has(p.id))
|
|
1704
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLogoutProviderSelect, onCancel: closePicker, title: "Select Logout Provider" }) })), pickerMode === "key" && keyTarget && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(KeyPicker, { providerName: keyTarget.name, onSubmit: handleKeySubmit, onCancel: () => {
|
|
1705
|
+
closePicker();
|
|
1706
|
+
setKeyProviderId(null);
|
|
1707
|
+
} }) })), pickerMode === "skill" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SkillPicker, { skills: safeSkillRegistry.summaries(), onSelect: (name) => {
|
|
1708
|
+
fillComposer(`/${name} `);
|
|
1709
|
+
closePicker();
|
|
1710
|
+
}, onCancel: closePicker }) })), pickerMode === "slash" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(CommandPalette, { items: commandPaletteItems, terminalColumns: mainWidth, terminalRows: terminalRows, onSelect: (item) => {
|
|
1711
|
+
closePicker();
|
|
1712
|
+
if (item.action === "insert-skill") {
|
|
1713
|
+
fillComposer(`/${item.value} `);
|
|
1714
|
+
}
|
|
1715
|
+
else {
|
|
1716
|
+
void handleSubmit(item.command);
|
|
1717
|
+
}
|
|
1718
|
+
}, onCancel: closePicker }) })), pickerMode === "mcp-reconnect" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(McpReconnectPicker, { items: mcpReconnectItems, terminalColumns: mainWidth, terminalRows: terminalRows, onSelect: (item) => {
|
|
1719
|
+
closePicker();
|
|
1720
|
+
void handleSubmit(item.command);
|
|
1721
|
+
}, onCancel: closePicker }) })), pickerMode === "session" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SessionPicker, { currentCwd: args.cwd, currentSessions: SessionManager.summarizeSessionsForCwd(args.cwd), allSessions: SessionManager.listAllSessions(), onSelect: handleSessionSelect, onCancel: closePicker }) })), pickerMode === "agents" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SubagentInspector, { groups: subagentGroups, onCancel: closePicker }) })), pickerMode === "rewind" && sessionManager && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(RewindPicker, { sessionManager: sessionManager, terminalColumns: mainWidth, terminalRows: terminalRows, onSelect: (command) => {
|
|
1722
|
+
closePicker();
|
|
1723
|
+
void handleSubmit(command);
|
|
1724
|
+
}, onCancel: closePicker }) })), pickerMode === "feishu-setup" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(FeishuSetupPicker, { onComplete: (summary) => {
|
|
1725
|
+
closePicker();
|
|
1726
|
+
addMessage("assistant", summary);
|
|
1727
|
+
}, onCancel: () => {
|
|
1728
|
+
closePicker();
|
|
1729
|
+
addMessage("assistant", "已取消 Feishu setup。");
|
|
1730
|
+
} }) })), statsPanel && !pickerMode && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(StatsPanel, { panel: statsPanel, terminalColumns: mainWidth, terminalRows: terminalRows, onRangeChange: (range) => setStatsPanel((current) => current ? { ...current, range } : current), onCancel: closeStatsPanel }) })), todos.length > 0 && !pickerMode && !statsPanel && !pendingPlan && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(TodosPanel, { todos: todos, terminalColumns: terminalColumns }) })), pendingPlan && !pickerMode && !statsPanel && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(PlanConfirm, { initialPlan: pendingPlan.plan, onApprove: (finalPlan) => {
|
|
1731
|
+
const resolve = pendingPlan.resolve;
|
|
1732
|
+
setPendingPlan(null);
|
|
1733
|
+
resolve({ action: "approve", plan: finalPlan });
|
|
1734
|
+
}, onReject: (reason) => {
|
|
1735
|
+
const resolve = pendingPlan.resolve;
|
|
1736
|
+
setPendingPlan(null);
|
|
1737
|
+
resolve({ action: "reject", reason });
|
|
1738
|
+
} }) })), pendingApproval && !pickerMode && !statsPanel && !pendingPlan && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ApprovalDialog, { request: pendingApproval.request, onDecision: (decision) => {
|
|
1739
|
+
const resolve = pendingApproval.resolve;
|
|
1740
|
+
setPendingApproval(null);
|
|
1741
|
+
resolve(decision);
|
|
1742
|
+
}, onAllowBashPrefix: (prefix) => {
|
|
1743
|
+
bashAllowlist?.add(prefix);
|
|
1744
|
+
} }) })), pendingQuestion && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingFeedback && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(QuestionDialog, { request: pendingQuestion, onSubmit: (answers) => {
|
|
1745
|
+
questionController?.reply(pendingQuestion.id, answers);
|
|
1746
|
+
setPendingQuestion(null);
|
|
1747
|
+
}, onCancel: () => {
|
|
1748
|
+
questionController?.reject(pendingQuestion.id);
|
|
1749
|
+
setPendingQuestion(null);
|
|
1750
|
+
} }) })), pendingFeedback && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(FeedbackDialog, { base: pendingFeedback.base, initialDescription: pendingFeedback.initialDescription, onDismiss: () => setPendingFeedback(null), onResult: (result) => {
|
|
1751
|
+
if (result.kind === "success") {
|
|
1752
|
+
addMessage("assistant", `Feedback submitted: ${result.url}`);
|
|
1753
|
+
}
|
|
1754
|
+
else if (result.kind === "error") {
|
|
1755
|
+
addMessage("error", `Feedback failed: ${result.message}`);
|
|
1756
|
+
}
|
|
1757
|
+
} }) })), !isExiting && compaction && (_jsx(Box, { flexShrink: 0, backgroundColor: palette.background, children: _jsx(CompactionProgressCard, { progress: compaction }) })), !isExiting && isRunning && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && (_jsx(Box, { paddingX: 1, paddingBottom: 1, flexShrink: 0, backgroundColor: palette.background, children: _jsx(WaitingIndicator, { tools: streamingTools, hasStreamingText: streamingContent.length > 0, hasStreamingReasoning: streamingReasoning.length > 0, streamedChars: streamingContent.length + streamingReasoning.length, nowTick: nowTick, pendingSteerCount: pendingSteerCount, queuedCount: queuedCount }) })), !isExiting && !pickerMode && !statsPanel && (_jsx(Box, { paddingBottom: 1, flexShrink: 0, backgroundColor: palette.background, children: _jsx(InputBox, { onSubmit: handleSubmit, onQueue: isRunning ? queueInput : undefined, onArrowDownAtBottom: () => {
|
|
1758
|
+
if (subagentMembers.length > 0 && !pickerMode)
|
|
1759
|
+
setSubagentEntryFocused(true);
|
|
1760
|
+
}, disabled: !!pendingPlan || !!pendingApproval || !!pendingQuestion || !!pendingFeedback || !!statsPanel || subagentEntryFocused, cursorResetEpoch: cursorResetEpoch, draftText: composerDraft?.text, draftEpoch: composerDraft?.epoch, onDraftApplied: clearComposerDraft, skillRegistry: safeSkillRegistry, localSlashCommands: [...INK_LOCAL_SLASH_COMMANDS], terminalColumns: mainWidth, cwd: args.cwd, sessionFile: currentSessionFile(), nextImageLabelStart: nextImageDisplayLabelStartRef.current }) })), !isExiting && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && subagentMembers.length > 0 && (_jsxs(Box, { paddingX: 1, flexShrink: 0, backgroundColor: palette.background, children: [_jsx(Text, { bold: subagentEntryFocused, color: subagentEntryFocused ? palette.accent : palette.toolName, children: subagentEntryFocused ? "> ↳ " : " ↳ " }), _jsxs(Text, { color: subagentEntryFocused ? palette.accent : palette.muted, children: [subagentMembers.length, " subagent", subagentMembers.length === 1 ? "" : "s", " \u00B7 ", subagentSummary(subagentMembers), " \u00B7 "] }), _jsx(Text, { color: palette.accent, children: subagentEntryFocused ? "Enter open · Esc back" : "↓ to inspect traces" })] })), !isExiting && (_jsx(Box, { flexShrink: 0, children: _jsx(FooterBar, { data: buildFooterData({ mode: permissionMode, goalLine }) }) }))] }) }));
|
|
1825
1761
|
}
|
|
1826
1762
|
function buildCommandPaletteItems(skillRegistry) {
|
|
1827
1763
|
const items = new Map();
|
|
@@ -2109,82 +2045,6 @@ function StatsPanel({ panel, terminalColumns, terminalRows, onRangeChange, onCan
|
|
|
2109
2045
|
return (_jsx(Text, { color: heading ? theme.accent : undefined, bold: heading, children: line || " " }, key));
|
|
2110
2046
|
}) }), maxScroll > 0 && (_jsxs(Text, { color: theme.muted, children: [scroll + 1, "-", Math.min(lines.length, scroll + maxVisible), " of ", lines.length] }))] }));
|
|
2111
2047
|
}
|
|
2112
|
-
function summarizeMcpStates(states) {
|
|
2113
|
-
const summary = { connected: 0, starting: 0, failed: 0, disabled: 0, tools: 0 };
|
|
2114
|
-
for (const state of states) {
|
|
2115
|
-
if (state.status.kind === "connected") {
|
|
2116
|
-
summary.connected += 1;
|
|
2117
|
-
summary.tools += state.status.tools.length;
|
|
2118
|
-
}
|
|
2119
|
-
else if (state.status.kind === "failed") {
|
|
2120
|
-
summary.failed += 1;
|
|
2121
|
-
}
|
|
2122
|
-
else {
|
|
2123
|
-
summary.disabled += 1;
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
return summary;
|
|
2127
|
-
}
|
|
2128
|
-
function summarizeLspStatuses(statuses) {
|
|
2129
|
-
const summary = { connected: 0, starting: 0, failed: 0, disabled: 0 };
|
|
2130
|
-
for (const status of statuses) {
|
|
2131
|
-
if (status.status === "connected")
|
|
2132
|
-
summary.connected += 1;
|
|
2133
|
-
else if (status.status === "starting")
|
|
2134
|
-
summary.starting += 1;
|
|
2135
|
-
else
|
|
2136
|
-
summary.failed += 1;
|
|
2137
|
-
}
|
|
2138
|
-
return summary;
|
|
2139
|
-
}
|
|
2140
|
-
function formatStatusCount(summary) {
|
|
2141
|
-
const parts = [];
|
|
2142
|
-
if (summary.connected > 0)
|
|
2143
|
-
parts.push(`${summary.connected} up`);
|
|
2144
|
-
if (summary.starting > 0)
|
|
2145
|
-
parts.push(`${summary.starting} starting`);
|
|
2146
|
-
if (summary.failed > 0)
|
|
2147
|
-
parts.push(`${summary.failed} failed`);
|
|
2148
|
-
if (summary.disabled > 0)
|
|
2149
|
-
parts.push(`${summary.disabled} disabled`);
|
|
2150
|
-
return parts.join(" · ") || "none";
|
|
2151
|
-
}
|
|
2152
|
-
function SidebarSection({ title, children }) {
|
|
2153
|
-
const theme = useTheme();
|
|
2154
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: theme.accent, bold: true, children: title }), children] }));
|
|
2155
|
-
}
|
|
2156
|
-
function SidebarRow({ label, value, color, }) {
|
|
2157
|
-
const theme = useTheme();
|
|
2158
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.muted, children: [label, ": "] }), _jsx(Text, { color: color ?? theme.userMessageText, children: value })] }));
|
|
2159
|
-
}
|
|
2160
|
-
function InkSidebar({ width, agent, sessionManager, cwd, mode, goalLine, todos, mcpManager, lspService, }) {
|
|
2161
|
-
const theme = useTheme();
|
|
2162
|
-
const innerWidth = Math.max(12, width - 4);
|
|
2163
|
-
const todoCounts = todos.reduce((acc, todo) => {
|
|
2164
|
-
acc[todo.status] = (acc[todo.status] ?? 0) + 1;
|
|
2165
|
-
return acc;
|
|
2166
|
-
}, {});
|
|
2167
|
-
const todoSummary = todos.length === 0
|
|
2168
|
-
? "none"
|
|
2169
|
-
: [
|
|
2170
|
-
todoCounts.in_progress ? `${todoCounts.in_progress} active` : "",
|
|
2171
|
-
todoCounts.pending ? `${todoCounts.pending} pending` : "",
|
|
2172
|
-
todoCounts.completed ? `${todoCounts.completed} done` : "",
|
|
2173
|
-
].filter(Boolean).join(" · ");
|
|
2174
|
-
const mcpStates = mcpManager?.getStates() ?? [];
|
|
2175
|
-
const mcpSummary = summarizeMcpStates(mcpStates);
|
|
2176
|
-
const lspSummary = lspService?.isDisabled()
|
|
2177
|
-
? { connected: 0, starting: 0, failed: 0, disabled: 1 }
|
|
2178
|
-
: summarizeLspStatuses(lspService?.status() ?? []);
|
|
2179
|
-
const latestMcpFailure = mcpStates.find((state) => state.status.kind === "failed");
|
|
2180
|
-
const latestLspFailure = lspService?.status().find((status) => status.status === "error");
|
|
2181
|
-
const sessionTitle = truncate(sessionDisplayName(sessionManager), innerWidth);
|
|
2182
|
-
const modelLabel = agent.model ? displayModel(agent.model) : "not selected";
|
|
2183
|
-
const route = agent.providerId
|
|
2184
|
-
? `${agent.providerId}/${modelLabel}`
|
|
2185
|
-
: modelLabel;
|
|
2186
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: "100%", borderStyle: "single", borderColor: theme.border, paddingX: 1, paddingY: 1, flexShrink: 0, children: [_jsx(Text, { color: theme.borderActive, bold: true, children: "Session" }), _jsx(Text, { color: theme.userMessageText, children: sessionTitle }), _jsx(Text, { color: theme.muted, children: truncate(friendlyCwd(cwd), innerWidth) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(SidebarSection, { title: "Runtime", children: [_jsx(SidebarRow, { label: "model", value: truncate(route, innerWidth - 7) }), _jsx(SidebarRow, { label: "mode", value: mode, color: mode === "bypassPermissions" ? theme.warning : theme.userMessageText }), _jsx(SidebarRow, { label: "thinking", value: agent.thinking || "off" })] }), goalLine && (_jsx(SidebarSection, { title: "Goal", children: _jsx(Text, { color: theme.userMessageText, children: truncate(goalLine, innerWidth) }) })), _jsx(SidebarSection, { title: "Todos", children: _jsx(Text, { color: todos.length > 0 ? theme.userMessageText : theme.muted, children: truncate(todoSummary, innerWidth) }) }), _jsxs(SidebarSection, { title: "MCP", children: [_jsx(Text, { color: mcpSummary.failed > 0 ? theme.warning : theme.userMessageText, children: truncate(`${formatStatusCount(mcpSummary)}${mcpSummary.tools > 0 ? ` · ${mcpSummary.tools} tools` : ""}`, innerWidth) }), latestMcpFailure?.status.kind === "failed" && (_jsx(Text, { color: theme.muted, children: truncate(latestMcpFailure.status.error, innerWidth) }))] }), _jsxs(SidebarSection, { title: "LSP", children: [_jsx(Text, { color: lspSummary.failed > 0 ? theme.warning : theme.userMessageText, children: truncate(formatStatusCount(lspSummary), innerWidth) }), latestLspFailure?.message && (_jsx(Text, { color: theme.muted, children: truncate(latestLspFailure.message, innerWidth) }))] })] })] }));
|
|
2187
|
-
}
|
|
2188
2048
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
2189
2049
|
const GENERIC_PHRASES = [
|
|
2190
2050
|
"mapping the workspace",
|
|
@@ -47,6 +47,9 @@ export declare function resolveCursorRowCompensation(input: {
|
|
|
47
47
|
viewportRows: number;
|
|
48
48
|
previousOutputHeight: number | null;
|
|
49
49
|
}): number;
|
|
50
|
+
export declare function isCtrlLetterInput(input: string, key: {
|
|
51
|
+
ctrl?: boolean;
|
|
52
|
+
}, letter: string): boolean;
|
|
50
53
|
export declare function isCtrlCInput(input: string, key: {
|
|
51
54
|
ctrl?: boolean;
|
|
52
55
|
}): boolean;
|
|
@@ -37,8 +37,15 @@ export function resolveCursorRowCompensation(input) {
|
|
|
37
37
|
return input.previousRowCompensation;
|
|
38
38
|
return needsCursorRowCompensation(input.nextOutputHeight, input.viewportRows, input.previousOutputHeight) ? 1 : 0;
|
|
39
39
|
}
|
|
40
|
+
export function isCtrlLetterInput(input, key, letter) {
|
|
41
|
+
const normalized = letter.toLowerCase();
|
|
42
|
+
if (!/^[a-z]$/.test(normalized))
|
|
43
|
+
return false;
|
|
44
|
+
const rawControlInput = String.fromCharCode(normalized.charCodeAt(0) - 96);
|
|
45
|
+
return input === rawControlInput || (key.ctrl === true && input.toLowerCase() === normalized);
|
|
46
|
+
}
|
|
40
47
|
export function isCtrlCInput(input, key) {
|
|
41
|
-
return input
|
|
48
|
+
return isCtrlLetterInput(input, key, "c");
|
|
42
49
|
}
|
|
43
50
|
export function shouldUseLineComposerFrame(_background) {
|
|
44
51
|
return true;
|
|
@@ -18,7 +18,6 @@ interface MessageListProps {
|
|
|
18
18
|
streamingParts: DisplayMessagePart[];
|
|
19
19
|
terminalColumns: number;
|
|
20
20
|
showThinking?: boolean;
|
|
21
|
-
expandedToolOutput?: boolean;
|
|
22
21
|
verboseTrace: boolean;
|
|
23
22
|
pendingApproval?: PendingApprovalHint | null;
|
|
24
23
|
/** Animation tick used to refresh in-progress elapsed counters. */
|
|
@@ -42,5 +41,5 @@ interface MessageListProps {
|
|
|
42
41
|
*/
|
|
43
42
|
maxStreamRows?: number;
|
|
44
43
|
}
|
|
45
|
-
export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking,
|
|
44
|
+
export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration, paddingX, maxStreamRows, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
46
45
|
export {};
|
|
@@ -12,7 +12,7 @@ import { latestSubagentNote, sortSubagents, subagentDescriptor, subagentLabel, s
|
|
|
12
12
|
import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
|
|
13
13
|
import { splitImageDisplayContent } from "../tui/image-display.js";
|
|
14
14
|
const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
|
|
15
|
-
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false,
|
|
15
|
+
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration = 0, paddingX = 1, maxStreamRows, }) {
|
|
16
16
|
const theme = useTheme();
|
|
17
17
|
const hasStreaming = !!(streamingContent ||
|
|
18
18
|
streamingReasoning ||
|
|
@@ -47,8 +47,8 @@ export function MessageList({ messages, streamingContent, streamingReasoning, st
|
|
|
47
47
|
if (item.kind === "welcome") {
|
|
48
48
|
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, children: welcomeBanner }, item.key));
|
|
49
49
|
}
|
|
50
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, children: _jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, showThinking: showThinking,
|
|
51
|
-
} }, `transcript-${staticGeneration}`), hasDynamic && (_jsxs(DynamicClamp, { maxRows: clampDynamic ? maxStreamRows : undefined, paddingX: paddingX, children: [hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, showThinking: showThinking,
|
|
50
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, children: _jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, showThinking: showThinking, verboseTrace: verboseTrace, showExpandHint: item.showExpandHint, separateFromPrevious: item.separateFromPrevious }) }, item.key));
|
|
51
|
+
} }, `transcript-${staticGeneration}`), hasDynamic && (_jsxs(DynamicClamp, { maxRows: clampDynamic ? maxStreamRows : undefined, paddingX: paddingX, children: [hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, showThinking: showThinking, verboseTrace: verboseTrace, pendingApproval: pendingApproval, nowTick: nowTick })), pendingSteerMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: pendingSteerMessages, terminalColumns: terminalColumns, title: "Messages to steer at next model call", hint: "applies before the next provider request", bulletColor: theme.warning })), queuedInputMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: queuedInputMessages, terminalColumns: terminalColumns, title: "Messages queued for next turn", hint: "runs after the current answer", bulletColor: theme.muted }))] }))] }));
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Bounds the live (in-progress turn) region to at most `maxRows` rows, pinned
|
|
@@ -99,7 +99,7 @@ function DynamicClamp({ maxRows, paddingX, children, }) {
|
|
|
99
99
|
// append-only (compaction reuses already-compacted instances), keys are
|
|
100
100
|
// stable, and nowTick is only threaded to the last row, so memo hits for all
|
|
101
101
|
// settled history rows.
|
|
102
|
-
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, showThinking,
|
|
102
|
+
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, showThinking, verboseTrace, showExpandHint, separateFromPrevious, nowTick, }) {
|
|
103
103
|
const theme = useTheme();
|
|
104
104
|
if (message.role === "user") {
|
|
105
105
|
return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus, separateFromPrevious: separateFromPrevious }));
|
|
@@ -139,9 +139,9 @@ const MessageItem = React.memo(function MessageItem({ message, terminalColumns,
|
|
|
139
139
|
message.taskElapsedMs !== undefined;
|
|
140
140
|
if (!hasVisibleAssistantContent)
|
|
141
141
|
return null;
|
|
142
|
-
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns,
|
|
142
|
+
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), visibleContent.trim() && _jsx(MarkdownContent, { content: visibleContent })] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
|
|
143
143
|
});
|
|
144
|
-
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking,
|
|
144
|
+
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking, verboseTrace, pendingApproval, nowTick, }) {
|
|
145
145
|
const deferredContent = React.useDeferredValue(content);
|
|
146
146
|
const deferredReasoning = React.useDeferredValue(reasoning);
|
|
147
147
|
const deferredParts = React.useDeferredValue(parts);
|
|
@@ -155,16 +155,16 @@ function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, s
|
|
|
155
155
|
// turn commits — no spacing jump at finalize time. (The old marginTop=0
|
|
156
156
|
// was a flicker mitigation for the main-screen <Static> renderer; the
|
|
157
157
|
// alt-screen viewport repaints frames atomically, so it's obsolete.)
|
|
158
|
-
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns,
|
|
158
|
+
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
159
159
|
}
|
|
160
|
-
function MessageParts({ parts, terminalColumns,
|
|
160
|
+
function MessageParts({ parts, terminalColumns, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
161
161
|
const lastToolsPartIndex = findLastToolsPartIndex(parts);
|
|
162
162
|
const lastTextPartIndex = findLastTextPartIndex(parts);
|
|
163
163
|
return (_jsx(Box, { flexDirection: "column", children: parts.map((part, idx) => {
|
|
164
164
|
if (part.type === "text") {
|
|
165
165
|
return (_jsx(TimelineText, { content: part.content, compactTop: idx === 0, terminalColumns: terminalColumns, streaming: streaming && idx === lastTextPartIndex }, `text-${idx}`));
|
|
166
166
|
}
|
|
167
|
-
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns,
|
|
167
|
+
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
|
|
168
168
|
}) }));
|
|
169
169
|
}
|
|
170
170
|
function findLastTextPartIndex(parts) {
|
|
@@ -195,17 +195,16 @@ function TimelineText({ content, compactTop, terminalColumns, streaming = false,
|
|
|
195
195
|
const trimmed = visible.trim();
|
|
196
196
|
return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\u25CF " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: streaming ? (_jsx(StreamingMarkdown, { content: trimmed, maxWidth: available })) : (_jsx(MarkdownContent, { content: trimmed, maxWidth: available })) })] }));
|
|
197
197
|
}
|
|
198
|
-
function ToolsPart({ toolCalls, terminalColumns,
|
|
198
|
+
function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
|
|
199
199
|
if (toolCalls.length === 0)
|
|
200
200
|
return null;
|
|
201
|
-
|
|
202
|
-
if (!expandTools) {
|
|
201
|
+
if (!verboseTrace) {
|
|
203
202
|
return (_jsx(TraceGroupList, { toolCalls: toolCalls, terminalColumns: terminalColumns, pendingApproval: pendingApproval, nowTick: nowTick, compactTop: compactTop, showActivity: showActivity }));
|
|
204
203
|
}
|
|
205
204
|
const lastIdx = toolCalls.length - 1;
|
|
206
205
|
return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
|
|
207
206
|
const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
|
|
208
|
-
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose:
|
|
207
|
+
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: verboseTrace, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
|
|
209
208
|
}) }));
|
|
210
209
|
}
|
|
211
210
|
function fallbackStreamingParts(content, tools) {
|
|
@@ -553,7 +552,7 @@ function SubagentToolDisplay({ toolCall, verbose, terminalColumns, compactTop, }
|
|
|
553
552
|
const descriptor = padVisual(truncateVisual(subagentDescriptor(subagent), descriptorWidth), descriptorWidth);
|
|
554
553
|
const note = truncateVisual(latestSubagentNote(subagent), Math.max(12, detailWidth - 16 - descriptorWidth - 10));
|
|
555
554
|
return (_jsxs(Box, { children: [_jsx(Text, { color: subagentStatusColor(status, theme), children: label }), _jsxs(Text, { color: theme.traceAction, children: [" ", descriptor] }), _jsxs(Text, { color: subagentStatusColor(status, theme), children: [" ", padVisual(status, 9)] }), note && _jsxs(Text, { color: subagent.error ? theme.error : theme.traceDetail, children: [" ", note] })] }, subagent.subAgentId ?? `${subagentLabel(subagent)}-${index}`));
|
|
556
|
-
}), omitted > 0 && (_jsxs(Text, { color: theme.muted, children: ["... ", omitted, " more \u00B7 Ctrl+O to expand \u00B7
|
|
555
|
+
}), omitted > 0 && (_jsxs(Text, { color: theme.muted, children: ["... ", omitted, " more \u00B7 Ctrl+O to expand \u00B7 \u2193 then Enter to inspect traces"] }))] })), subagents.length === 0 && toolCall.result && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: hasError ? theme.error : theme.muted, children: summarizeToolResult(toolCall) }) }))] }));
|
|
557
556
|
}
|
|
558
557
|
function TruncationHint({ remaining, verbose, showExpandHint, }) {
|
|
559
558
|
const theme = useTheme();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Full-screen subagent inspector
|
|
2
|
+
* Full-screen subagent inspector opened from the subagent entry line.
|
|
3
3
|
*
|
|
4
4
|
* Two-level drill-in modeled on Claude Code's workflow view: a grouped list of
|
|
5
5
|
* subagents (each spawn_agent is one member; each agent_team/agent_batch is a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Full-screen subagent inspector
|
|
3
|
+
* Full-screen subagent inspector opened from the subagent entry line.
|
|
4
4
|
*
|
|
5
5
|
* Two-level drill-in modeled on Claude Code's workflow view: a grouped list of
|
|
6
6
|
* subagents (each spawn_agent is one member; each agent_team/agent_batch is a
|