更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)

* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files)

纯格式化:移除分号、React Compiler import、import 多行展开。
修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-2): 格式化 commands (79 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-5): 格式化 components其余 + hooks + tools (232 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md

- README.md: 大幅重写,更详细版本历史和配置示例
- Run.ps1: 新增 Windows 启动脚本
- TODO.md: 新增包完成清单
- V6.md: 删除(架构重构规划已不适用)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复以前的问题

* fix: 修复 login 面板的问题

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-04 23:24:27 +08:00
committed by GitHub
parent 02694918b5
commit 5b1a52b8e0
559 changed files with 103807 additions and 101817 deletions

View File

@@ -1,228 +1,200 @@
import { c as _c } from "react/compiler-runtime";
import React, { useMemo } from 'react';
import type { DeepImmutable } from 'src/types/utils.js';
import { useElapsedTime } from '../../hooks/useElapsedTime.js';
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
import { Box, Text, useTheme } from '../../ink.js';
import { useKeybindings } from '../../keybindings/useKeybinding.js';
import { getEmptyToolPermissionContext } from '../../Tool.js';
import type { LocalAgentTaskState } from '../../tasks/LocalAgentTask/LocalAgentTask.js';
import { getTools } from '../../tools.js';
import { formatNumber } from '../../utils/format.js';
import { extractTag } from '../../utils/messages.js';
import { Byline } from '../design-system/Byline.js';
import { Dialog } from '../design-system/Dialog.js';
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
import { UserPlanMessage } from '../messages/UserPlanMessage.js';
import { renderToolActivity } from './renderToolActivity.js';
import { getTaskStatusColor, getTaskStatusIcon } from './taskStatusUtils.js';
import React, { useMemo } from 'react'
import type { DeepImmutable } from 'src/types/utils.js'
import { useElapsedTime } from '../../hooks/useElapsedTime.js'
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
import { Box, Text, useTheme } from '../../ink.js'
import { useKeybindings } from '../../keybindings/useKeybinding.js'
import { getEmptyToolPermissionContext } from '../../Tool.js'
import type { LocalAgentTaskState } from '../../tasks/LocalAgentTask/LocalAgentTask.js'
import { getTools } from '../../tools.js'
import { formatNumber } from '../../utils/format.js'
import { extractTag } from '../../utils/messages.js'
import { Byline } from '../design-system/Byline.js'
import { Dialog } from '../design-system/Dialog.js'
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
import { UserPlanMessage } from '../messages/UserPlanMessage.js'
import { renderToolActivity } from './renderToolActivity.js'
import { getTaskStatusColor, getTaskStatusIcon } from './taskStatusUtils.js'
type Props = {
agent: DeepImmutable<LocalAgentTaskState>;
onDone: () => void;
onKillAgent?: () => void;
onBack?: () => void;
};
export function AsyncAgentDetailDialog(t0) {
const $ = _c(54);
const {
agent,
onDone,
onKillAgent,
onBack
} = t0;
const [theme] = useTheme();
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = getTools(getEmptyToolPermissionContext());
$[0] = t1;
} else {
t1 = $[0];
}
const tools = t1;
const elapsedTime = useElapsedTime(agent.startTime, agent.status === "running", 1000, agent.totalPausedMs ?? 0);
let t2;
if ($[1] !== onDone) {
t2 = {
"confirm:yes": onDone
};
$[1] = onDone;
$[2] = t2;
} else {
t2 = $[2];
}
let t3;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = {
context: "Confirmation"
};
$[3] = t3;
} else {
t3 = $[3];
}
useKeybindings(t2, t3);
let t4;
if ($[4] !== agent.status || $[5] !== onBack || $[6] !== onDone || $[7] !== onKillAgent) {
t4 = e => {
if (e.key === " ") {
e.preventDefault();
onDone();
} else {
if (e.key === "left" && onBack) {
e.preventDefault();
onBack();
} else {
if (e.key === "x" && agent.status === "running" && onKillAgent) {
e.preventDefault();
onKillAgent();
}
}
}
};
$[4] = agent.status;
$[5] = onBack;
$[6] = onDone;
$[7] = onKillAgent;
$[8] = t4;
} else {
t4 = $[8];
}
const handleKeyDown = t4;
let t5;
if ($[9] !== agent.prompt) {
t5 = extractTag(agent.prompt, "plan");
$[9] = agent.prompt;
$[10] = t5;
} else {
t5 = $[10];
}
const planContent = t5;
const displayPrompt = agent.prompt.length > 300 ? agent.prompt.substring(0, 297) + "\u2026" : agent.prompt;
const tokenCount = agent.result?.totalTokens ?? agent.progress?.tokenCount;
const toolUseCount = agent.result?.totalToolUseCount ?? agent.progress?.toolUseCount;
const t6 = agent.selectedAgent?.agentType ?? "agent";
const t7 = agent.description || "Async agent";
let t8;
if ($[11] !== t6 || $[12] !== t7) {
t8 = <Text>{t6} {" "}{t7}</Text>;
$[11] = t6;
$[12] = t7;
$[13] = t8;
} else {
t8 = $[13];
}
const title = t8;
let t9;
if ($[14] !== agent.status) {
t9 = agent.status !== "running" && <Text color={getTaskStatusColor(agent.status)}>{getTaskStatusIcon(agent.status)}{" "}{agent.status === "completed" ? "Completed" : agent.status === "failed" ? "Failed" : "Stopped"}{" \xB7 "}</Text>;
$[14] = agent.status;
$[15] = t9;
} else {
t9 = $[15];
}
let t10;
if ($[16] !== tokenCount) {
t10 = tokenCount !== undefined && tokenCount > 0 && <> · {formatNumber(tokenCount)} tokens</>;
$[16] = tokenCount;
$[17] = t10;
} else {
t10 = $[17];
}
let t11;
if ($[18] !== toolUseCount) {
t11 = toolUseCount !== undefined && toolUseCount > 0 && <>{" "}· {toolUseCount} {toolUseCount === 1 ? "tool" : "tools"}</>;
$[18] = toolUseCount;
$[19] = t11;
} else {
t11 = $[19];
}
let t12;
if ($[20] !== elapsedTime || $[21] !== t10 || $[22] !== t11) {
t12 = <Text dimColor={true}>{elapsedTime}{t10}{t11}</Text>;
$[20] = elapsedTime;
$[21] = t10;
$[22] = t11;
$[23] = t12;
} else {
t12 = $[23];
}
let t13;
if ($[24] !== t12 || $[25] !== t9) {
t13 = <Text>{t9}{t12}</Text>;
$[24] = t12;
$[25] = t9;
$[26] = t13;
} else {
t13 = $[26];
}
const subtitle = t13;
let t14;
if ($[27] !== agent.status || $[28] !== onBack || $[29] !== onKillAgent) {
t14 = exitState => exitState.pending ? <Text>Press {exitState.keyName} again to exit</Text> : <Byline>{onBack && <KeyboardShortcutHint shortcut={"\u2190"} action="go back" />}<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />{agent.status === "running" && onKillAgent && <KeyboardShortcutHint shortcut="x" action="stop" />}</Byline>;
$[27] = agent.status;
$[28] = onBack;
$[29] = onKillAgent;
$[30] = t14;
} else {
t14 = $[30];
}
let t15;
if ($[31] !== agent.progress || $[32] !== agent.status || $[33] !== theme) {
t15 = agent.status === "running" && agent.progress?.recentActivities && agent.progress.recentActivities.length > 0 && <Box flexDirection="column"><Text bold={true} dimColor={true}>Progress</Text>{agent.progress.recentActivities.map((activity, i) => <Text key={i} dimColor={i < agent.progress.recentActivities.length - 1} wrap="truncate-end">{i === agent.progress.recentActivities.length - 1 ? "\u203A " : " "}{renderToolActivity(activity, tools, theme)}</Text>)}</Box>;
$[31] = agent.progress;
$[32] = agent.status;
$[33] = theme;
$[34] = t15;
} else {
t15 = $[34];
}
let t16;
if ($[35] !== displayPrompt || $[36] !== planContent) {
t16 = planContent ? <Box marginTop={1}><UserPlanMessage addMargin={false} planContent={planContent} /></Box> : <Box flexDirection="column" marginTop={1}><Text bold={true} dimColor={true}>Prompt</Text><Text wrap="wrap">{displayPrompt}</Text></Box>;
$[35] = displayPrompt;
$[36] = planContent;
$[37] = t16;
} else {
t16 = $[37];
}
let t17;
if ($[38] !== agent.error || $[39] !== agent.status) {
t17 = agent.status === "failed" && agent.error && <Box flexDirection="column" marginTop={1}><Text bold={true} color="error">Error</Text><Text color="error" wrap="wrap">{agent.error}</Text></Box>;
$[38] = agent.error;
$[39] = agent.status;
$[40] = t17;
} else {
t17 = $[40];
}
let t18;
if ($[41] !== t15 || $[42] !== t16 || $[43] !== t17) {
t18 = <Box flexDirection="column">{t15}{t16}{t17}</Box>;
$[41] = t15;
$[42] = t16;
$[43] = t17;
$[44] = t18;
} else {
t18 = $[44];
}
let t19;
if ($[45] !== onDone || $[46] !== subtitle || $[47] !== t14 || $[48] !== t18 || $[49] !== title) {
t19 = <Dialog title={title} subtitle={subtitle} onCancel={onDone} color="background" inputGuide={t14}>{t18}</Dialog>;
$[45] = onDone;
$[46] = subtitle;
$[47] = t14;
$[48] = t18;
$[49] = title;
$[50] = t19;
} else {
t19 = $[50];
}
let t20;
if ($[51] !== handleKeyDown || $[52] !== t19) {
t20 = <Box flexDirection="column" tabIndex={0} autoFocus={true} onKeyDown={handleKeyDown}>{t19}</Box>;
$[51] = handleKeyDown;
$[52] = t19;
$[53] = t20;
} else {
t20 = $[53];
}
return t20;
agent: DeepImmutable<LocalAgentTaskState>
onDone: () => void
onKillAgent?: () => void
onBack?: () => void
}
export function AsyncAgentDetailDialog({
agent,
onDone,
onKillAgent,
onBack,
}: Props): React.ReactNode {
const [theme] = useTheme()
// Get tools for rendering activity messages
const tools = useMemo(() => getTools(getEmptyToolPermissionContext()), [])
const elapsedTime = useElapsedTime(
agent.startTime,
agent.status === 'running',
1000,
agent.totalPausedMs ?? 0,
)
// Restore confirm:yes (Enter/y) dismissal — Dialog handles confirm:no (Esc)
// internally but does NOT auto-wire confirm:yes.
useKeybindings(
{
'confirm:yes': onDone,
},
{ context: 'Confirmation' },
)
// Component-specific shortcuts shown in UI hints (x=stop) and
// navigation keys (space=dismiss, left=back). These are context-dependent
// actions tied to agent state, not standard dialog keybindings.
// Note: Dialog component already handles ESC via confirm:no keybinding;
// confirm:yes (Enter/y) is handled by useKeybindings above.
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === ' ') {
e.preventDefault()
onDone()
} else if (e.key === 'left' && onBack) {
e.preventDefault()
onBack()
} else if (e.key === 'x' && agent.status === 'running' && onKillAgent) {
e.preventDefault()
onKillAgent()
}
}
// Extract plan from prompt - if present, we show the plan instead of the prompt
const planContent = extractTag(agent.prompt, 'plan')
const displayPrompt =
agent.prompt.length > 300
? agent.prompt.substring(0, 297) + '…'
: agent.prompt
// Get tokens and tool uses (from result if completed, otherwise from progress)
const tokenCount = agent.result?.totalTokens ?? agent.progress?.tokenCount
const toolUseCount =
agent.result?.totalToolUseCount ?? agent.progress?.toolUseCount
const title = (
<Text>
{agent.selectedAgent?.agentType ?? 'agent'} {' '}
{agent.description || 'Async agent'}
</Text>
)
// Build subtitle with status and stats
const subtitle = (
<Text>
{agent.status !== 'running' && (
<Text color={getTaskStatusColor(agent.status)}>
{getTaskStatusIcon(agent.status)}{' '}
{agent.status === 'completed'
? 'Completed'
: agent.status === 'failed'
? 'Failed'
: 'Stopped'}
{' · '}
</Text>
)}
<Text dimColor>
{elapsedTime}
{tokenCount !== undefined && tokenCount > 0 && (
<> · {formatNumber(tokenCount)} tokens</>
)}
{toolUseCount !== undefined && toolUseCount > 0 && (
<>
{' '}
· {toolUseCount} {toolUseCount === 1 ? 'tool' : 'tools'}
</>
)}
</Text>
</Text>
)
return (
<Box
flexDirection="column"
tabIndex={0}
autoFocus
onKeyDown={handleKeyDown}
>
<Dialog
title={title}
subtitle={subtitle}
onCancel={onDone}
color="background"
inputGuide={exitState =>
exitState.pending ? (
<Text>Press {exitState.keyName} again to exit</Text>
) : (
<Byline>
{onBack && <KeyboardShortcutHint shortcut="←" action="go back" />}
<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />
{agent.status === 'running' && onKillAgent && (
<KeyboardShortcutHint shortcut="x" action="stop" />
)}
</Byline>
)
}
>
<Box flexDirection="column">
{/* Recent activities for running agents */}
{agent.status === 'running' &&
agent.progress?.recentActivities &&
agent.progress.recentActivities.length > 0 && (
<Box flexDirection="column">
<Text bold dimColor>
Progress
</Text>
{agent.progress.recentActivities.map((activity, i) => (
<Text
key={i}
dimColor={i < agent.progress!.recentActivities!.length - 1}
wrap="truncate-end"
>
{i === agent.progress!.recentActivities!.length - 1
? ' '
: ' '}
{renderToolActivity(activity, tools, theme)}
</Text>
))}
</Box>
)}
{/* Plan section (if present) - shown instead of prompt */}
{planContent ? (
<Box marginTop={1}>
<UserPlanMessage addMargin={false} planContent={planContent} />
</Box>
) : (
/* Prompt section - only shown when no plan */
<Box flexDirection="column" marginTop={1}>
<Text bold dimColor>
Prompt
</Text>
<Text wrap="wrap">{displayPrompt}</Text>
</Box>
)}
{/* Error details if failed */}
{agent.status === 'failed' && agent.error && (
<Box flexDirection="column" marginTop={1}>
<Text bold color="error">
Error
</Text>
<Text color="error" wrap="wrap">
{agent.error}
</Text>
</Box>
)}
</Box>
</Dialog>
</Box>
)
}

View File

@@ -1,344 +1,146 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Text } from 'src/ink.js';
import type { BackgroundTaskState } from 'src/tasks/types.js';
import type { DeepImmutable } from 'src/types/utils.js';
import { truncate } from 'src/utils/format.js';
import { toInkColor } from 'src/utils/ink.js';
import { plural } from 'src/utils/stringUtils.js';
import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js';
import { RemoteSessionProgress } from './RemoteSessionProgress.js';
import { ShellProgress, TaskStatusText } from './ShellProgress.js';
import { describeTeammateActivity } from './taskStatusUtils.js';
import * as React from 'react'
import { Text } from 'src/ink.js'
import type { BackgroundTaskState } from 'src/tasks/types.js'
import type { DeepImmutable } from 'src/types/utils.js'
import { truncate } from 'src/utils/format.js'
import { toInkColor } from 'src/utils/ink.js'
import { plural } from 'src/utils/stringUtils.js'
import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js'
import { RemoteSessionProgress } from './RemoteSessionProgress.js'
import { ShellProgress, TaskStatusText } from './ShellProgress.js'
import { describeTeammateActivity } from './taskStatusUtils.js'
type Props = {
task: DeepImmutable<BackgroundTaskState>;
maxActivityWidth?: number;
};
export function BackgroundTask(t0) {
const $ = _c(92);
const {
task,
maxActivityWidth
} = t0;
const activityLimit = maxActivityWidth ?? 40;
task: DeepImmutable<BackgroundTaskState>
maxActivityWidth?: number
}
export function BackgroundTask({
task,
maxActivityWidth,
}: Props): React.ReactNode {
const activityLimit = maxActivityWidth ?? 40
switch (task.type) {
case "local_bash":
{
const t1 = task.kind === "monitor" ? task.description : task.command;
let t2;
if ($[0] !== activityLimit || $[1] !== t1) {
t2 = truncate(t1, activityLimit, true);
$[0] = activityLimit;
$[1] = t1;
$[2] = t2;
} else {
t2 = $[2];
}
let t3;
if ($[3] !== task) {
t3 = <ShellProgress shell={task} />;
$[3] = task;
$[4] = t3;
} else {
t3 = $[4];
}
let t4;
if ($[5] !== t2 || $[6] !== t3) {
t4 = <Text>{t2}{" "}{t3}</Text>;
$[5] = t2;
$[6] = t3;
$[7] = t4;
} else {
t4 = $[7];
}
return t4;
}
case "remote_agent":
{
if (task.isRemoteReview) {
let t1;
if ($[8] !== task) {
t1 = <Text><RemoteSessionProgress session={task} /></Text>;
$[8] = task;
$[9] = t1;
} else {
t1 = $[9];
}
return t1;
}
const running = task.status === "running" || task.status === "pending";
const t1 = running ? DIAMOND_OPEN : DIAMOND_FILLED;
let t2;
if ($[10] !== t1) {
t2 = <Text dimColor={true}>{t1} </Text>;
$[10] = t1;
$[11] = t2;
} else {
t2 = $[11];
}
let t3;
if ($[12] !== activityLimit || $[13] !== task.title) {
t3 = truncate(task.title, activityLimit, true);
$[12] = activityLimit;
$[13] = task.title;
$[14] = t3;
} else {
t3 = $[14];
}
let t4;
if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
t4 = <Text dimColor={true}> · </Text>;
$[15] = t4;
} else {
t4 = $[15];
}
let t5;
if ($[16] !== task) {
t5 = <RemoteSessionProgress session={task} />;
$[16] = task;
$[17] = t5;
} else {
t5 = $[17];
}
let t6;
if ($[18] !== t2 || $[19] !== t3 || $[20] !== t5) {
t6 = <Text>{t2}{t3}{t4}{t5}</Text>;
$[18] = t2;
$[19] = t3;
$[20] = t5;
$[21] = t6;
} else {
t6 = $[21];
}
return t6;
}
case "local_agent":
{
let t1;
if ($[22] !== activityLimit || $[23] !== task.description) {
t1 = truncate(task.description, activityLimit, true);
$[22] = activityLimit;
$[23] = task.description;
$[24] = t1;
} else {
t1 = $[24];
}
const t2 = task.status === "completed" ? "done" : undefined;
const t3 = task.status === "completed" && !task.notified ? ", unread" : undefined;
let t4;
if ($[25] !== t2 || $[26] !== t3 || $[27] !== task.status) {
t4 = <TaskStatusText status={task.status} label={t2} suffix={t3} />;
$[25] = t2;
$[26] = t3;
$[27] = task.status;
$[28] = t4;
} else {
t4 = $[28];
}
let t5;
if ($[29] !== t1 || $[30] !== t4) {
t5 = <Text>{t1}{" "}{t4}</Text>;
$[29] = t1;
$[30] = t4;
$[31] = t5;
} else {
t5 = $[31];
}
return t5;
}
case "in_process_teammate":
{
let T0;
let T1;
let t1;
let t2;
let t3;
let t4;
if ($[32] !== activityLimit || $[33] !== task) {
const activity = describeTeammateActivity(task);
T1 = Text;
let t5;
if ($[40] !== task.identity.color) {
t5 = toInkColor(task.identity.color);
$[40] = task.identity.color;
$[41] = t5;
} else {
t5 = $[41];
}
if ($[42] !== t5 || $[43] !== task.identity.agentName) {
t4 = <Text color={t5}>@{task.identity.agentName}</Text>;
$[42] = t5;
$[43] = task.identity.agentName;
$[44] = t4;
} else {
t4 = $[44];
}
T0 = Text;
t1 = true;
t2 = ": ";
t3 = truncate(activity, activityLimit, true);
$[32] = activityLimit;
$[33] = task;
$[34] = T0;
$[35] = T1;
$[36] = t1;
$[37] = t2;
$[38] = t3;
$[39] = t4;
} else {
T0 = $[34];
T1 = $[35];
t1 = $[36];
t2 = $[37];
t3 = $[38];
t4 = $[39];
}
let t5;
if ($[45] !== T0 || $[46] !== t1 || $[47] !== t2 || $[48] !== t3) {
t5 = <T0 dimColor={t1}>{t2}{t3}</T0>;
$[45] = T0;
$[46] = t1;
$[47] = t2;
$[48] = t3;
$[49] = t5;
} else {
t5 = $[49];
}
let t6;
if ($[50] !== T1 || $[51] !== t4 || $[52] !== t5) {
t6 = <T1>{t4}{t5}</T1>;
$[50] = T1;
$[51] = t4;
$[52] = t5;
$[53] = t6;
} else {
t6 = $[53];
}
return t6;
}
case "local_workflow":
{
const t1 = task.workflowName ?? task.summary ?? task.description;
let t2;
if ($[54] !== activityLimit || $[55] !== t1) {
t2 = truncate(t1, activityLimit, true);
$[54] = activityLimit;
$[55] = t1;
$[56] = t2;
} else {
t2 = $[56];
}
let t3;
if ($[57] !== task.agentCount || $[58] !== task.status) {
t3 = task.status === "running" ? `${task.agentCount} ${plural(task.agentCount, "agent")}` : task.status === "completed" ? "done" : undefined;
$[57] = task.agentCount;
$[58] = task.status;
$[59] = t3;
} else {
t3 = $[59];
}
const t4 = task.status === "completed" && !task.notified ? ", unread" : undefined;
let t5;
if ($[60] !== t3 || $[61] !== t4 || $[62] !== task.status) {
t5 = <TaskStatusText status={task.status} label={t3} suffix={t4} />;
$[60] = t3;
$[61] = t4;
$[62] = task.status;
$[63] = t5;
} else {
t5 = $[63];
}
let t6;
if ($[64] !== t2 || $[65] !== t5) {
t6 = <Text>{t2}{" "}{t5}</Text>;
$[64] = t2;
$[65] = t5;
$[66] = t6;
} else {
t6 = $[66];
}
return t6;
}
case "monitor_mcp":
{
let t1;
if ($[67] !== activityLimit || $[68] !== task.description) {
t1 = truncate(task.description, activityLimit, true);
$[67] = activityLimit;
$[68] = task.description;
$[69] = t1;
} else {
t1 = $[69];
}
const t2 = task.status === "completed" ? "done" : undefined;
const t3 = task.status === "completed" && !task.notified ? ", unread" : undefined;
let t4;
if ($[70] !== t2 || $[71] !== t3 || $[72] !== task.status) {
t4 = <TaskStatusText status={task.status} label={t2} suffix={t3} />;
$[70] = t2;
$[71] = t3;
$[72] = task.status;
$[73] = t4;
} else {
t4 = $[73];
}
let t5;
if ($[74] !== t1 || $[75] !== t4) {
t5 = <Text>{t1}{" "}{t4}</Text>;
$[74] = t1;
$[75] = t4;
$[76] = t5;
} else {
t5 = $[76];
}
return t5;
}
case "dream":
{
const n = task.filesTouched.length;
let t1;
if ($[77] !== n || $[78] !== task.phase || $[79] !== task.sessionsReviewing) {
t1 = task.phase === "updating" && n > 0 ? `${n} ${plural(n, "file")}` : `${task.sessionsReviewing} ${plural(task.sessionsReviewing, "session")}`;
$[77] = n;
$[78] = task.phase;
$[79] = task.sessionsReviewing;
$[80] = t1;
} else {
t1 = $[80];
}
const detail = t1;
let t2;
if ($[81] !== detail || $[82] !== task.phase) {
t2 = <Text dimColor={true}>· {task.phase} · {detail}</Text>;
$[81] = detail;
$[82] = task.phase;
$[83] = t2;
} else {
t2 = $[83];
}
const t3 = task.status === "completed" ? "done" : undefined;
const t4 = task.status === "completed" && !task.notified ? ", unread" : undefined;
let t5;
if ($[84] !== t3 || $[85] !== t4 || $[86] !== task.status) {
t5 = <TaskStatusText status={task.status} label={t3} suffix={t4} />;
$[84] = t3;
$[85] = t4;
$[86] = task.status;
$[87] = t5;
} else {
t5 = $[87];
}
let t6;
if ($[88] !== t2 || $[89] !== t5 || $[90] !== task.description) {
t6 = <Text>{task.description}{" "}{t2}{" "}{t5}</Text>;
$[88] = t2;
$[89] = t5;
$[90] = task.description;
$[91] = t6;
} else {
t6 = $[91];
}
return t6;
case 'local_bash':
return (
<Text>
{truncate(
task.kind === 'monitor' ? task.description : task.command,
activityLimit,
true,
)}{' '}
<ShellProgress shell={task} />
</Text>
)
case 'remote_agent': {
// Lite-review renders its own rainbow line (title + live counts),
// so we don't prefix the title — the rainbow already includes it.
if (task.isRemoteReview) {
return (
<Text>
<RemoteSessionProgress session={task} />
</Text>
)
}
const running = task.status === 'running' || task.status === 'pending'
return (
<Text>
<Text dimColor>{running ? DIAMOND_OPEN : DIAMOND_FILLED} </Text>
{truncate(task.title, activityLimit, true)}
<Text dimColor> · </Text>
<RemoteSessionProgress session={task} />
</Text>
)
}
case 'local_agent':
return (
<Text>
{truncate(task.description, activityLimit, true)}{' '}
<TaskStatusText
status={task.status}
label={task.status === 'completed' ? 'done' : undefined}
suffix={
task.status === 'completed' && !task.notified
? ', unread'
: undefined
}
/>
</Text>
)
case 'in_process_teammate': {
const activity = describeTeammateActivity(task)
return (
<Text>
<Text color={toInkColor(task.identity.color)}>
@{task.identity.agentName}
</Text>
<Text dimColor>: {truncate(activity, activityLimit, true)}</Text>
</Text>
)
}
case 'local_workflow':
return (
<Text>
{truncate(
task.workflowName ?? task.summary ?? task.description,
activityLimit,
true,
)}{' '}
<TaskStatusText
status={task.status}
label={
task.status === 'running'
? `${task.agentCount} ${plural(task.agentCount, 'agent')}`
: task.status === 'completed'
? 'done'
: undefined
}
suffix={
task.status === 'completed' && !task.notified
? ', unread'
: undefined
}
/>
</Text>
)
case 'monitor_mcp':
return (
<Text>
{truncate(task.description, activityLimit, true)}{' '}
<TaskStatusText
status={task.status}
label={task.status === 'completed' ? 'done' : undefined}
suffix={
task.status === 'completed' && !task.notified
? ', unread'
: undefined
}
/>
</Text>
)
case 'dream': {
const n = task.filesTouched.length
const detail =
task.phase === 'updating' && n > 0
? `${n} ${plural(n, 'file')}`
: `${task.sessionsReviewing} ${plural(task.sessionsReviewing, 'session')}`
return (
<Text>
{task.description}{' '}
<Text dimColor>
· {task.phase} · {detail}
</Text>{' '}
<TaskStatusText
status={task.status}
label={task.status === 'completed' ? 'done' : undefined}
suffix={
task.status === 'completed' && !task.notified
? ', unread'
: undefined
}
/>
</Text>
)
}
}
}

View File

@@ -1,428 +1,310 @@
import { c as _c } from "react/compiler-runtime";
import figures from 'figures';
import * as React from 'react';
import { useMemo, useState } from 'react';
import { useTerminalSize } from 'src/hooks/useTerminalSize.js';
import { stringWidth } from 'src/ink/stringWidth.js';
import { useAppState, useSetAppState } from 'src/state/AppState.js';
import { enterTeammateView, exitTeammateView } from 'src/state/teammateViewHelpers.js';
import { isPanelAgentTask } from 'src/tasks/LocalAgentTask/LocalAgentTask.js';
import { getPillLabel, pillNeedsCta } from 'src/tasks/pillLabel.js';
import { type BackgroundTaskState, isBackgroundTask, type TaskState } from 'src/tasks/types.js';
import { calculateHorizontalScrollWindow } from 'src/utils/horizontalScroll.js';
import { Box, Text } from '../../ink.js';
import { AGENT_COLOR_TO_THEME_COLOR, AGENT_COLORS, type AgentColorName } from '../../tools/AgentTool/agentColorManager.js';
import type { Theme } from '../../utils/theme.js';
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
import { shouldHideTasksFooter } from './taskStatusUtils.js';
import figures from 'figures'
import * as React from 'react'
import { useMemo, useState } from 'react'
import { useTerminalSize } from 'src/hooks/useTerminalSize.js'
import { stringWidth } from 'src/ink/stringWidth.js'
import { useAppState, useSetAppState } from 'src/state/AppState.js'
import {
enterTeammateView,
exitTeammateView,
} from 'src/state/teammateViewHelpers.js'
import { isPanelAgentTask } from 'src/tasks/LocalAgentTask/LocalAgentTask.js'
import { getPillLabel, pillNeedsCta } from 'src/tasks/pillLabel.js'
import {
type BackgroundTaskState,
isBackgroundTask,
type TaskState,
} from 'src/tasks/types.js'
import { calculateHorizontalScrollWindow } from 'src/utils/horizontalScroll.js'
import { Box, Text } from '../../ink.js'
import {
AGENT_COLOR_TO_THEME_COLOR,
AGENT_COLORS,
type AgentColorName,
} from '../../tools/AgentTool/agentColorManager.js'
import type { Theme } from '../../utils/theme.js'
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
import { shouldHideTasksFooter } from './taskStatusUtils.js'
type Props = {
tasksSelected: boolean;
isViewingTeammate?: boolean;
teammateFooterIndex?: number;
isLeaderIdle?: boolean;
onOpenDialog?: (taskId?: string) => void;
};
export function BackgroundTaskStatus(t0) {
const $ = _c(48);
const {
tasksSelected,
isViewingTeammate,
teammateFooterIndex: t1,
isLeaderIdle: t2,
onOpenDialog
} = t0;
const teammateFooterIndex = t1 === undefined ? 0 : t1;
const isLeaderIdle = t2 === undefined ? false : t2;
const setAppState = useSetAppState();
const {
columns
} = useTerminalSize();
const tasks = useAppState(_temp);
const viewingAgentTaskId = useAppState(_temp2);
let t3;
if ($[0] !== tasks) {
t3 = (Object.values(tasks ?? {}) as TaskState[]).filter(_temp3);
$[0] = tasks;
$[1] = t3;
} else {
t3 = $[1];
}
const runningTasks = t3;
const expandedView = useAppState(_temp4);
const showSpinnerTree = expandedView === "teammates";
const allTeammates = !showSpinnerTree && runningTasks.length > 0 && runningTasks.every(_temp5);
let t4;
if ($[2] !== runningTasks) {
t4 = runningTasks.filter(_temp6).sort(_temp7);
$[2] = runningTasks;
$[3] = t4;
} else {
t4 = $[3];
}
const teammateEntries = t4;
let t5;
if ($[4] !== isLeaderIdle) {
t5 = {
name: "main",
tasksSelected: boolean
isViewingTeammate?: boolean
teammateFooterIndex?: number
isLeaderIdle?: boolean
onOpenDialog?: (taskId?: string) => void
}
export function BackgroundTaskStatus({
tasksSelected,
isViewingTeammate,
teammateFooterIndex = 0,
isLeaderIdle = false,
onOpenDialog,
}: Props): React.ReactNode {
const setAppState = useSetAppState()
const { columns } = useTerminalSize()
const tasks = useAppState(s => s.tasks)
const viewingAgentTaskId = useAppState(s => s.viewingAgentTaskId)
const runningTasks = useMemo(
() =>
(Object.values(tasks ?? {}) as TaskState[]).filter(
t =>
isBackgroundTask(t) &&
!(process.env.USER_TYPE === 'ant' && isPanelAgentTask(t)),
),
[tasks],
)
// Check if all tasks are in-process teammates (team mode)
// In spinner-tree mode, don't show teammate pills (teammates appear in the spinner tree)
const expandedView = useAppState(s => s.expandedView)
const showSpinnerTree = expandedView === 'teammates'
const allTeammates =
!showSpinnerTree &&
runningTasks.length > 0 &&
runningTasks.every(t => t.type === 'in_process_teammate')
// Memoize teammate-related computations at the top level (rules of hooks)
const teammateEntries = useMemo(
() =>
runningTasks
.filter(
(t): t is BackgroundTaskState & { type: 'in_process_teammate' } =>
t.type === 'in_process_teammate',
)
.sort((a, b) =>
a.identity.agentName.localeCompare(b.identity.agentName),
),
[runningTasks],
)
// Build array of all pills with their activity state
// Each pill is "@{name}" and separator is " " (1 char)
// Sort idle agents to the end, but only when not in selection mode
// to avoid reordering while user is arrowing through the list
// "main" always stays first regardless of idle state
const allPills = useMemo(() => {
const mainPill = {
name: 'main',
color: undefined as keyof Theme | undefined,
isIdle: isLeaderIdle,
taskId: undefined as string | undefined
};
$[4] = isLeaderIdle;
$[5] = t5;
} else {
t5 = $[5];
}
const mainPill = t5;
let t6;
if ($[6] !== mainPill || $[7] !== tasksSelected || $[8] !== teammateEntries) {
const teammatePills = teammateEntries.map(_temp8);
taskId: undefined as string | undefined,
}
const teammatePills = teammateEntries.map(t => ({
name: t.identity.agentName,
color: getAgentThemeColor(t.identity.color),
isIdle: t.isIdle,
taskId: t.id,
}))
// Only sort teammates when not selecting to avoid reordering during navigation
if (!tasksSelected) {
teammatePills.sort(_temp9);
teammatePills.sort((a, b) => {
// Active agents first, idle agents last
if (a.isIdle !== b.isIdle) return a.isIdle ? 1 : -1
return 0 // Keep original order within each group
})
}
const pills = [mainPill, ...teammatePills];
t6 = pills.map(_temp0);
$[6] = mainPill;
$[7] = tasksSelected;
$[8] = teammateEntries;
$[9] = t6;
} else {
t6 = $[9];
}
const allPills = t6;
let t7;
if ($[10] !== allPills) {
t7 = allPills.map(_temp1);
$[10] = allPills;
$[11] = t7;
} else {
t7 = $[11];
}
const pillWidths = t7;
if (allTeammates || !showSpinnerTree && isViewingTeammate) {
const selectedIdx = tasksSelected ? teammateFooterIndex : -1;
let t8;
if ($[12] !== teammateEntries || $[13] !== viewingAgentTaskId) {
t8 = viewingAgentTaskId ? teammateEntries.findIndex(t_3 => t_3.id === viewingAgentTaskId) + 1 : 0;
$[12] = teammateEntries;
$[13] = viewingAgentTaskId;
$[14] = t8;
} else {
t8 = $[14];
}
const viewedIdx = t8;
const availableWidth = Math.max(20, columns - 20 - 4);
const t9 = selectedIdx >= 0 ? selectedIdx : 0;
let t10;
if ($[15] !== availableWidth || $[16] !== pillWidths || $[17] !== t9) {
t10 = calculateHorizontalScrollWindow(pillWidths, availableWidth, 2, t9);
$[15] = availableWidth;
$[16] = pillWidths;
$[17] = t9;
$[18] = t10;
} else {
t10 = $[18];
}
const {
startIndex,
endIndex,
showLeftArrow,
showRightArrow
} = t10;
let t11;
if ($[19] !== allPills || $[20] !== endIndex || $[21] !== startIndex) {
t11 = allPills.slice(startIndex, endIndex);
$[19] = allPills;
$[20] = endIndex;
$[21] = startIndex;
$[22] = t11;
} else {
t11 = $[22];
}
const visiblePills = t11;
let t12;
if ($[23] !== showLeftArrow) {
t12 = showLeftArrow && <Text dimColor={true}>{figures.arrowLeft} </Text>;
$[23] = showLeftArrow;
$[24] = t12;
} else {
t12 = $[24];
}
let t13;
if ($[25] !== selectedIdx || $[26] !== setAppState || $[27] !== viewedIdx || $[28] !== visiblePills) {
t13 = visiblePills.map((pill_1, i_1) => {
const needsSeparator = i_1 > 0;
return <React.Fragment key={pill_1.name}>{needsSeparator && <Text> </Text>}<AgentPill name={pill_1.name} color={pill_1.color} isSelected={selectedIdx === pill_1.idx} isViewed={viewedIdx === pill_1.idx} isIdle={pill_1.isIdle} onClick={() => pill_1.taskId ? enterTeammateView(pill_1.taskId, setAppState) : exitTeammateView(setAppState)} /></React.Fragment>;
});
$[25] = selectedIdx;
$[26] = setAppState;
$[27] = viewedIdx;
$[28] = visiblePills;
$[29] = t13;
} else {
t13 = $[29];
}
let t14;
if ($[30] !== showRightArrow) {
t14 = showRightArrow && <Text dimColor={true}> {figures.arrowRight}</Text>;
$[30] = showRightArrow;
$[31] = t14;
} else {
t14 = $[31];
}
let t15;
if ($[32] === Symbol.for("react.memo_cache_sentinel")) {
t15 = <Text dimColor={true}>{" \xB7 "}<KeyboardShortcutHint shortcut={"shift + \u2193"} action="expand" /></Text>;
$[32] = t15;
} else {
t15 = $[32];
}
let t16;
if ($[33] !== t12 || $[34] !== t13 || $[35] !== t14) {
t16 = <>{t12}{t13}{t14}{t15}</>;
$[33] = t12;
$[34] = t13;
$[35] = t14;
$[36] = t16;
} else {
t16 = $[36];
}
return t16;
// main always first, then sorted teammates
const pills = [mainPill, ...teammatePills]
// Add idx after sorting
return pills.map((pill, i) => ({ ...pill, idx: i }))
}, [teammateEntries, isLeaderIdle, tasksSelected])
// Calculate pill widths (including separator space, except first)
const pillWidths = useMemo(
() =>
allPills.map((pill, i) => {
const pillText = `@${pill.name}`
// First pill has no leading space, others have 1 space separator
return stringWidth(pillText) + (i > 0 ? 1 : 0)
}),
[allPills],
)
if (allTeammates || (!showSpinnerTree && isViewingTeammate)) {
const selectedIdx = tasksSelected ? teammateFooterIndex : -1
// Which agent is currently foregrounded (bold)
const viewedIdx = viewingAgentTaskId
? teammateEntries.findIndex(t => t.id === viewingAgentTaskId) + 1
: 0 // 0 = main/leader
// Calculate available width for pills
// Reserve space for: arrows, hint, and minimal padding
// Pills are rendered on their own line when in team mode
const ARROW_WIDTH = 2 // arrow char + space
const HINT_WIDTH = 20 // shift+↓ to expand
const PADDING = 4 // minimal safety margin
const availableWidth = Math.max(20, columns - HINT_WIDTH - PADDING)
// Calculate visible window of pills
const { startIndex, endIndex, showLeftArrow, showRightArrow } =
calculateHorizontalScrollWindow(
pillWidths,
availableWidth,
ARROW_WIDTH,
selectedIdx >= 0 ? selectedIdx : 0,
)
const visiblePills = allPills.slice(startIndex, endIndex)
return (
<>
{showLeftArrow && <Text dimColor>{figures.arrowLeft} </Text>}
{visiblePills.map((pill, i) => {
// First visible pill has no leading separator
// (left arrow already provides spacing if present)
const needsSeparator = i > 0
return (
<React.Fragment key={pill.name}>
{needsSeparator && <Text> </Text>}
<AgentPill
name={pill.name}
color={pill.color}
isSelected={selectedIdx === pill.idx}
isViewed={viewedIdx === pill.idx}
isIdle={pill.isIdle}
onClick={() =>
pill.taskId
? enterTeammateView(pill.taskId, setAppState)
: exitTeammateView(setAppState)
}
/>
</React.Fragment>
)
})}
{showRightArrow && <Text dimColor> {figures.arrowRight}</Text>}
<Text dimColor>
{' · '}
<KeyboardShortcutHint shortcut="shift + ↓" action="expand" />
</Text>
</>
)
}
// In spinner-tree mode, don't show any footer status for teammates
// (they appear in the spinner tree above)
if (shouldHideTasksFooter(tasks ?? {}, showSpinnerTree)) {
return null;
return null
}
if (runningTasks.length === 0) {
return null;
return null
}
let t8;
if ($[37] !== runningTasks) {
t8 = getPillLabel(runningTasks);
$[37] = runningTasks;
$[38] = t8;
} else {
t8 = $[38];
}
let t9;
if ($[39] !== onOpenDialog || $[40] !== t8 || $[41] !== tasksSelected) {
t9 = <SummaryPill selected={tasksSelected} onClick={onOpenDialog}>{t8}</SummaryPill>;
$[39] = onOpenDialog;
$[40] = t8;
$[41] = tasksSelected;
$[42] = t9;
} else {
t9 = $[42];
}
let t10;
if ($[43] !== runningTasks) {
t10 = pillNeedsCta(runningTasks) && <Text dimColor={true}> · {figures.arrowDown} to view</Text>;
$[43] = runningTasks;
$[44] = t10;
} else {
t10 = $[44];
}
let t11;
if ($[45] !== t10 || $[46] !== t9) {
t11 = <>{t9}{t10}</>;
$[45] = t10;
$[46] = t9;
$[47] = t11;
} else {
t11 = $[47];
}
return t11;
}
function _temp1(pill_0, i_0) {
const pillText = `@${pill_0.name}`;
return stringWidth(pillText) + (i_0 > 0 ? 1 : 0);
}
function _temp0(pill, i) {
return {
...pill,
idx: i
};
}
function _temp9(a_0, b_0) {
if (a_0.isIdle !== b_0.isIdle) {
return a_0.isIdle ? 1 : -1;
}
return 0;
}
function _temp8(t_2) {
return {
name: t_2.identity.agentName,
color: getAgentThemeColor(t_2.identity.color),
isIdle: t_2.isIdle,
taskId: t_2.id
};
}
function _temp7(a, b) {
return a.identity.agentName.localeCompare(b.identity.agentName);
}
function _temp6(t_1) {
return t_1.type === "in_process_teammate";
}
function _temp5(t_0) {
return t_0.type === "in_process_teammate";
}
function _temp4(s_1) {
return s_1.expandedView;
}
function _temp3(t) {
return isBackgroundTask(t) && !(false && isPanelAgentTask(t));
}
function _temp2(s_0) {
return s_0.viewingAgentTaskId;
}
function _temp(s) {
return s.tasks;
return (
<>
<SummaryPill selected={tasksSelected} onClick={onOpenDialog}>
{getPillLabel(runningTasks)}
</SummaryPill>
{pillNeedsCta(runningTasks) && (
<Text dimColor> · {figures.arrowDown} to view</Text>
)}
</>
)
}
type AgentPillProps = {
name: string;
color?: keyof Theme;
isSelected: boolean;
isViewed: boolean;
isIdle: boolean;
onClick?: () => void;
};
function AgentPill(t0) {
const $ = _c(19);
const {
name,
color,
isSelected,
isViewed,
isIdle,
onClick
} = t0;
const [hover, setHover] = useState(false);
const highlighted = isSelected || hover;
let label;
name: string
color?: keyof Theme
isSelected: boolean
isViewed: boolean
isIdle: boolean
onClick?: () => void
}
function AgentPill({
name,
color,
isSelected,
isViewed,
isIdle,
onClick,
}: AgentPillProps): React.ReactNode {
const [hover, setHover] = useState(false)
// Hover mirrors the keyboard-selected look so the affordance is familiar.
const highlighted = isSelected || hover
let label: React.ReactNode
if (highlighted) {
let t1;
if ($[0] !== color || $[1] !== isViewed || $[2] !== name) {
t1 = color ? <Text backgroundColor={color} color="inverseText" bold={isViewed}>@{name}</Text> : <Text color="background" inverse={true} bold={isViewed}>@{name}</Text>;
$[0] = color;
$[1] = isViewed;
$[2] = name;
$[3] = t1;
} else {
t1 = $[3];
}
label = t1;
label = color ? (
<Text backgroundColor={color} color="inverseText" bold={isViewed}>
@{name}
</Text>
) : (
<Text color="background" inverse bold={isViewed}>
@{name}
</Text>
)
} else if (isIdle) {
label = (
<Text dimColor bold={isViewed}>
@{name}
</Text>
)
} else if (isViewed) {
label = (
<Text color={color} bold>
@{name}
</Text>
)
} else {
if (isIdle) {
let t1;
if ($[4] !== isViewed || $[5] !== name) {
t1 = <Text dimColor={true} bold={isViewed}>@{name}</Text>;
$[4] = isViewed;
$[5] = name;
$[6] = t1;
} else {
t1 = $[6];
}
label = t1;
} else {
if (isViewed) {
let t1;
if ($[7] !== color || $[8] !== name) {
t1 = <Text color={color} bold={true}>@{name}</Text>;
$[7] = color;
$[8] = name;
$[9] = t1;
} else {
t1 = $[9];
}
label = t1;
} else {
const t1 = !color;
let t2;
if ($[10] !== color || $[11] !== name || $[12] !== t1) {
t2 = <Text color={color} dimColor={t1}>@{name}</Text>;
$[10] = color;
$[11] = name;
$[12] = t1;
$[13] = t2;
} else {
t2 = $[13];
}
label = t2;
}
}
label = (
<Text color={color} dimColor={!color}>
@{name}
</Text>
)
}
if (!onClick) {
return label;
}
let t1;
let t2;
if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => setHover(true);
t2 = () => setHover(false);
$[14] = t1;
$[15] = t2;
} else {
t1 = $[14];
t2 = $[15];
}
let t3;
if ($[16] !== label || $[17] !== onClick) {
t3 = <Box onClick={onClick} onMouseEnter={t1} onMouseLeave={t2}>{label}</Box>;
$[16] = label;
$[17] = onClick;
$[18] = t3;
} else {
t3 = $[18];
}
return t3;
if (!onClick) return label
return (
<Box
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{label}
</Box>
)
}
function SummaryPill(t0) {
const $ = _c(8);
const {
selected,
onClick,
children
} = t0;
const [hover, setHover] = useState(false);
const t1 = selected || hover;
let t2;
if ($[0] !== children || $[1] !== t1) {
t2 = <Text color="background" inverse={t1}>{children}</Text>;
$[0] = children;
$[1] = t1;
$[2] = t2;
} else {
t2 = $[2];
}
const label = t2;
if (!onClick) {
return label;
}
let t3;
let t4;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => setHover(true);
t4 = () => setHover(false);
$[3] = t3;
$[4] = t4;
} else {
t3 = $[3];
t4 = $[4];
}
let t5;
if ($[5] !== label || $[6] !== onClick) {
t5 = <Box onClick={onClick} onMouseEnter={t3} onMouseLeave={t4}>{label}</Box>;
$[5] = label;
$[6] = onClick;
$[7] = t5;
} else {
t5 = $[7];
}
return t5;
function SummaryPill({
selected,
onClick,
children,
}: {
selected: boolean
onClick?: () => void
children: React.ReactNode
}): React.ReactNode {
const [hover, setHover] = useState(false)
const label = (
<Text color="background" inverse={selected || hover}>
{children}
</Text>
)
if (!onClick) return label
return (
<Box
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{label}
</Box>
)
}
function getAgentThemeColor(colorName: string | undefined): keyof Theme | undefined {
if (!colorName) return undefined;
function getAgentThemeColor(
colorName: string | undefined,
): keyof Theme | undefined {
if (!colorName) return undefined
if (AGENT_COLORS.includes(colorName as AgentColorName)) {
return AGENT_COLOR_TO_THEME_COLOR[colorName as AgentColorName];
return AGENT_COLOR_TO_THEME_COLOR[colorName as AgentColorName]
}
return undefined;
return undefined
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,250 +1,136 @@
import { c as _c } from "react/compiler-runtime";
import React from 'react';
import type { DeepImmutable } from 'src/types/utils.js';
import { useElapsedTime } from '../../hooks/useElapsedTime.js';
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
import { Box, Text } from '../../ink.js';
import { useKeybindings } from '../../keybindings/useKeybinding.js';
import type { DreamTaskState } from '../../tasks/DreamTask/DreamTask.js';
import { plural } from '../../utils/stringUtils.js';
import { Byline } from '../design-system/Byline.js';
import { Dialog } from '../design-system/Dialog.js';
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
import React from 'react'
import type { DeepImmutable } from 'src/types/utils.js'
import { useElapsedTime } from '../../hooks/useElapsedTime.js'
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
import { Box, Text } from '../../ink.js'
import { useKeybindings } from '../../keybindings/useKeybinding.js'
import type { DreamTaskState } from '../../tasks/DreamTask/DreamTask.js'
import { plural } from '../../utils/stringUtils.js'
import { Byline } from '../design-system/Byline.js'
import { Dialog } from '../design-system/Dialog.js'
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
type Props = {
task: DeepImmutable<DreamTaskState>;
onDone: () => void;
onBack?: () => void;
onKill?: () => void;
};
task: DeepImmutable<DreamTaskState>
onDone: () => void
onBack?: () => void
onKill?: () => void
}
// How many recent turns to render. Earlier turns collapse to a count.
const VISIBLE_TURNS = 6;
export function DreamDetailDialog(t0) {
const $ = _c(70);
const {
task,
onDone,
onBack,
onKill
} = t0;
const elapsedTime = useElapsedTime(task.startTime, task.status === "running", 1000, 0);
let t1;
if ($[0] !== onDone) {
t1 = {
"confirm:yes": onDone
};
$[0] = onDone;
$[1] = t1;
} else {
t1 = $[1];
const VISIBLE_TURNS = 6
export function DreamDetailDialog({
task,
onDone,
onBack,
onKill,
}: Props): React.ReactNode {
const elapsedTime = useElapsedTime(
task.startTime,
task.status === 'running',
1000,
0,
)
// Dialog handles confirm:no (Esc) → onCancel. Wire confirm:yes (Enter/y) too.
useKeybindings({ 'confirm:yes': onDone }, { context: 'Confirmation' })
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === ' ') {
e.preventDefault()
onDone()
} else if (e.key === 'left' && onBack) {
e.preventDefault()
onBack()
} else if (e.key === 'x' && task.status === 'running' && onKill) {
e.preventDefault()
onKill()
}
}
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = {
context: "Confirmation"
};
$[2] = t2;
} else {
t2 = $[2];
}
useKeybindings(t1, t2);
let t3;
if ($[3] !== onBack || $[4] !== onDone || $[5] !== onKill || $[6] !== task.status) {
t3 = e => {
if (e.key === " ") {
e.preventDefault();
onDone();
} else {
if (e.key === "left" && onBack) {
e.preventDefault();
onBack();
} else {
if (e.key === "x" && task.status === "running" && onKill) {
e.preventDefault();
onKill();
}
// Turns with text to show. Tool-only turns (text='') are dropped entirely —
// the per-turn toolUseCount already captures that work.
const visibleTurns = task.turns.filter(t => t.text !== '')
const shown = visibleTurns.slice(-VISIBLE_TURNS)
const hidden = visibleTurns.length - shown.length
return (
<Box
flexDirection="column"
tabIndex={0}
autoFocus
onKeyDown={handleKeyDown}
>
<Dialog
title="Memory consolidation"
subtitle={
<Text dimColor>
{elapsedTime} · reviewing {task.sessionsReviewing}{' '}
{plural(task.sessionsReviewing, 'session')}
{task.filesTouched.length > 0 && (
<>
{' '}
· {task.filesTouched.length}{' '}
{plural(task.filesTouched.length, 'file')} touched
</>
)}
</Text>
}
}
};
$[3] = onBack;
$[4] = onDone;
$[5] = onKill;
$[6] = task.status;
$[7] = t3;
} else {
t3 = $[7];
}
const handleKeyDown = t3;
let T0;
let T1;
let T2;
let t10;
let t11;
let t12;
let t13;
let t14;
let t15;
let t16;
let t4;
let t5;
let t6;
let t7;
let t8;
let t9;
if ($[8] !== elapsedTime || $[9] !== handleKeyDown || $[10] !== onBack || $[11] !== onDone || $[12] !== onKill || $[13] !== task.filesTouched.length || $[14] !== task.sessionsReviewing || $[15] !== task.status || $[16] !== task.turns) {
const visibleTurns = task.turns.filter(_temp);
const shown = visibleTurns.slice(-VISIBLE_TURNS);
const hidden = visibleTurns.length - shown.length;
T2 = Box;
t13 = "column";
t14 = 0;
t15 = true;
t16 = handleKeyDown;
T1 = Dialog;
t8 = "Memory consolidation";
const t17 = task.sessionsReviewing;
let t18;
if ($[33] !== task.sessionsReviewing) {
t18 = plural(task.sessionsReviewing, "session");
$[33] = task.sessionsReviewing;
$[34] = t18;
} else {
t18 = $[34];
}
let t19;
if ($[35] !== task.filesTouched.length) {
t19 = task.filesTouched.length > 0 && <>{" "}· {task.filesTouched.length}{" "}{plural(task.filesTouched.length, "file")} touched</>;
$[35] = task.filesTouched.length;
$[36] = t19;
} else {
t19 = $[36];
}
if ($[37] !== elapsedTime || $[38] !== t18 || $[39] !== t19 || $[40] !== task.sessionsReviewing) {
t9 = <Text dimColor={true}>{elapsedTime} · reviewing {t17}{" "}{t18}{t19}</Text>;
$[37] = elapsedTime;
$[38] = t18;
$[39] = t19;
$[40] = task.sessionsReviewing;
$[41] = t9;
} else {
t9 = $[41];
}
t10 = onDone;
t11 = "background";
if ($[42] !== onBack || $[43] !== onKill || $[44] !== task.status) {
t12 = exitState => exitState.pending ? <Text>Press {exitState.keyName} again to exit</Text> : <Byline>{onBack && <KeyboardShortcutHint shortcut={"\u2190"} action="go back" />}<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />{task.status === "running" && onKill && <KeyboardShortcutHint shortcut="x" action="stop" />}</Byline>;
$[42] = onBack;
$[43] = onKill;
$[44] = task.status;
$[45] = t12;
} else {
t12 = $[45];
}
T0 = Box;
t4 = "column";
t5 = 1;
let t20;
if ($[46] === Symbol.for("react.memo_cache_sentinel")) {
t20 = <Text bold={true}>Status:</Text>;
$[46] = t20;
} else {
t20 = $[46];
}
if ($[47] !== task.status) {
t6 = <Text>{t20}{" "}{task.status === "running" ? <Text color="background">running</Text> : task.status === "completed" ? <Text color="success">{task.status}</Text> : <Text color="error">{task.status}</Text>}</Text>;
$[47] = task.status;
$[48] = t6;
} else {
t6 = $[48];
}
t7 = shown.length === 0 ? <Text dimColor={true}>{task.status === "running" ? "Starting\u2026" : "(no text output)"}</Text> : <>{hidden > 0 && <Text dimColor={true}>({hidden} earlier {plural(hidden, "turn")})</Text>}{shown.map(_temp2)}</>;
$[8] = elapsedTime;
$[9] = handleKeyDown;
$[10] = onBack;
$[11] = onDone;
$[12] = onKill;
$[13] = task.filesTouched.length;
$[14] = task.sessionsReviewing;
$[15] = task.status;
$[16] = task.turns;
$[17] = T0;
$[18] = T1;
$[19] = T2;
$[20] = t10;
$[21] = t11;
$[22] = t12;
$[23] = t13;
$[24] = t14;
$[25] = t15;
$[26] = t16;
$[27] = t4;
$[28] = t5;
$[29] = t6;
$[30] = t7;
$[31] = t8;
$[32] = t9;
} else {
T0 = $[17];
T1 = $[18];
T2 = $[19];
t10 = $[20];
t11 = $[21];
t12 = $[22];
t13 = $[23];
t14 = $[24];
t15 = $[25];
t16 = $[26];
t4 = $[27];
t5 = $[28];
t6 = $[29];
t7 = $[30];
t8 = $[31];
t9 = $[32];
}
let t17;
if ($[49] !== T0 || $[50] !== t4 || $[51] !== t5 || $[52] !== t6 || $[53] !== t7) {
t17 = <T0 flexDirection={t4} gap={t5}>{t6}{t7}</T0>;
$[49] = T0;
$[50] = t4;
$[51] = t5;
$[52] = t6;
$[53] = t7;
$[54] = t17;
} else {
t17 = $[54];
}
let t18;
if ($[55] !== T1 || $[56] !== t10 || $[57] !== t11 || $[58] !== t12 || $[59] !== t17 || $[60] !== t8 || $[61] !== t9) {
t18 = <T1 title={t8} subtitle={t9} onCancel={t10} color={t11} inputGuide={t12}>{t17}</T1>;
$[55] = T1;
$[56] = t10;
$[57] = t11;
$[58] = t12;
$[59] = t17;
$[60] = t8;
$[61] = t9;
$[62] = t18;
} else {
t18 = $[62];
}
let t19;
if ($[63] !== T2 || $[64] !== t13 || $[65] !== t14 || $[66] !== t15 || $[67] !== t16 || $[68] !== t18) {
t19 = <T2 flexDirection={t13} tabIndex={t14} autoFocus={t15} onKeyDown={t16}>{t18}</T2>;
$[63] = T2;
$[64] = t13;
$[65] = t14;
$[66] = t15;
$[67] = t16;
$[68] = t18;
$[69] = t19;
} else {
t19 = $[69];
}
return t19;
}
function _temp2(turn, i) {
return <Box key={i} flexDirection="column"><Text wrap="wrap">{turn.text}</Text>{turn.toolUseCount > 0 && <Text dimColor={true}>{" "}({turn.toolUseCount}{" "}{plural(turn.toolUseCount, "tool")})</Text>}</Box>;
}
function _temp(t) {
return t.text !== "";
onCancel={onDone}
color="background"
inputGuide={exitState =>
exitState.pending ? (
<Text>Press {exitState.keyName} again to exit</Text>
) : (
<Byline>
{onBack && <KeyboardShortcutHint shortcut="←" action="go back" />}
<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />
{task.status === 'running' && onKill && (
<KeyboardShortcutHint shortcut="x" action="stop" />
)}
</Byline>
)
}
>
<Box flexDirection="column" gap={1}>
<Text>
<Text bold>Status:</Text>{' '}
{task.status === 'running' ? (
<Text color="background">running</Text>
) : task.status === 'completed' ? (
<Text color="success">{task.status}</Text>
) : (
<Text color="error">{task.status}</Text>
)}
</Text>
{shown.length === 0 ? (
<Text dimColor>
{task.status === 'running' ? 'Starting…' : '(no text output)'}
</Text>
) : (
<>
{hidden > 0 && (
<Text dimColor>
({hidden} earlier {plural(hidden, 'turn')})
</Text>
)}
{shown.map((turn, i) => (
<Box key={i} flexDirection="column">
<Text wrap="wrap">{turn.text}</Text>
{turn.toolUseCount > 0 && (
<Text dimColor>
{' '}({turn.toolUseCount}{' '}
{plural(turn.toolUseCount, 'tool')})
</Text>
)}
</Box>
))}
</>
)}
</Box>
</Dialog>
</Box>
)
}

View File

@@ -1,265 +1,193 @@
import { c as _c } from "react/compiler-runtime";
import React, { useMemo } from 'react';
import type { DeepImmutable } from 'src/types/utils.js';
import { useElapsedTime } from '../../hooks/useElapsedTime.js';
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
import { Box, Text, useTheme } from '../../ink.js';
import { useKeybindings } from '../../keybindings/useKeybinding.js';
import { getEmptyToolPermissionContext } from '../../Tool.js';
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js';
import { getTools } from '../../tools.js';
import { formatNumber, truncateToWidth } from '../../utils/format.js';
import { toInkColor } from '../../utils/ink.js';
import { Byline } from '../design-system/Byline.js';
import { Dialog } from '../design-system/Dialog.js';
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
import { renderToolActivity } from './renderToolActivity.js';
import { describeTeammateActivity } from './taskStatusUtils.js';
import React, { useMemo } from 'react'
import type { DeepImmutable } from 'src/types/utils.js'
import { useElapsedTime } from '../../hooks/useElapsedTime.js'
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
import { Box, Text, useTheme } from '../../ink.js'
import { useKeybindings } from '../../keybindings/useKeybinding.js'
import { getEmptyToolPermissionContext } from '../../Tool.js'
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js'
import { getTools } from '../../tools.js'
import { formatNumber, truncateToWidth } from '../../utils/format.js'
import { toInkColor } from '../../utils/ink.js'
import { Byline } from '../design-system/Byline.js'
import { Dialog } from '../design-system/Dialog.js'
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
import { renderToolActivity } from './renderToolActivity.js'
import { describeTeammateActivity } from './taskStatusUtils.js'
type Props = {
teammate: DeepImmutable<InProcessTeammateTaskState>;
onDone: () => void;
onKill?: () => void;
onBack?: () => void;
onForeground?: () => void;
};
export function InProcessTeammateDetailDialog(t0) {
const $ = _c(63);
const {
teammate,
onDone,
onKill,
onBack,
onForeground
} = t0;
const [theme] = useTheme();
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = getTools(getEmptyToolPermissionContext());
$[0] = t1;
} else {
t1 = $[0];
}
const tools = t1;
const elapsedTime = useElapsedTime(teammate.startTime, teammate.status === "running", 1000, teammate.totalPausedMs ?? 0);
let t2;
if ($[1] !== onDone) {
t2 = {
"confirm:yes": onDone
};
$[1] = onDone;
$[2] = t2;
} else {
t2 = $[2];
}
let t3;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = {
context: "Confirmation"
};
$[3] = t3;
} else {
t3 = $[3];
}
useKeybindings(t2, t3);
let t4;
if ($[4] !== onBack || $[5] !== onDone || $[6] !== onForeground || $[7] !== onKill || $[8] !== teammate.status) {
t4 = e => {
if (e.key === " ") {
e.preventDefault();
onDone();
} else {
if (e.key === "left" && onBack) {
e.preventDefault();
onBack();
} else {
if (e.key === "x" && teammate.status === "running" && onKill) {
e.preventDefault();
onKill();
} else {
if (e.key === "f" && teammate.status === "running" && onForeground) {
e.preventDefault();
onForeground();
}
}
}
}
};
$[4] = onBack;
$[5] = onDone;
$[6] = onForeground;
$[7] = onKill;
$[8] = teammate.status;
$[9] = t4;
} else {
t4 = $[9];
}
const handleKeyDown = t4;
let t5;
if ($[10] !== teammate) {
t5 = describeTeammateActivity(teammate);
$[10] = teammate;
$[11] = t5;
} else {
t5 = $[11];
}
const activity = t5;
const tokenCount = teammate.result?.totalTokens ?? teammate.progress?.tokenCount;
const toolUseCount = teammate.result?.totalToolUseCount ?? teammate.progress?.toolUseCount;
let t6;
if ($[12] !== teammate.prompt) {
t6 = truncateToWidth(teammate.prompt, 300);
$[12] = teammate.prompt;
$[13] = t6;
} else {
t6 = $[13];
}
const displayPrompt = t6;
let t7;
if ($[14] !== teammate.identity.color) {
t7 = toInkColor(teammate.identity.color);
$[14] = teammate.identity.color;
$[15] = t7;
} else {
t7 = $[15];
}
let t8;
if ($[16] !== t7 || $[17] !== teammate.identity.agentName) {
t8 = <Text color={t7}>@{teammate.identity.agentName}</Text>;
$[16] = t7;
$[17] = teammate.identity.agentName;
$[18] = t8;
} else {
t8 = $[18];
}
let t9;
if ($[19] !== activity) {
t9 = activity && <Text dimColor={true}> ({activity})</Text>;
$[19] = activity;
$[20] = t9;
} else {
t9 = $[20];
}
let t10;
if ($[21] !== t8 || $[22] !== t9) {
t10 = <Text>{t8}{t9}</Text>;
$[21] = t8;
$[22] = t9;
$[23] = t10;
} else {
t10 = $[23];
}
const title = t10;
let t11;
if ($[24] !== teammate.status) {
t11 = teammate.status !== "running" && <Text color={teammate.status === "completed" ? "success" : teammate.status === "killed" ? "warning" : "error"}>{teammate.status === "completed" ? "Completed" : teammate.status === "failed" ? "Failed" : "Stopped"}{" \xB7 "}</Text>;
$[24] = teammate.status;
$[25] = t11;
} else {
t11 = $[25];
}
let t12;
if ($[26] !== tokenCount) {
t12 = tokenCount !== undefined && tokenCount > 0 && <> · {formatNumber(tokenCount)} tokens</>;
$[26] = tokenCount;
$[27] = t12;
} else {
t12 = $[27];
}
let t13;
if ($[28] !== toolUseCount) {
t13 = toolUseCount !== undefined && toolUseCount > 0 && <>{" "}· {toolUseCount} {toolUseCount === 1 ? "tool" : "tools"}</>;
$[28] = toolUseCount;
$[29] = t13;
} else {
t13 = $[29];
}
let t14;
if ($[30] !== elapsedTime || $[31] !== t12 || $[32] !== t13) {
t14 = <Text dimColor={true}>{elapsedTime}{t12}{t13}</Text>;
$[30] = elapsedTime;
$[31] = t12;
$[32] = t13;
$[33] = t14;
} else {
t14 = $[33];
}
let t15;
if ($[34] !== t11 || $[35] !== t14) {
t15 = <Text>{t11}{t14}</Text>;
$[34] = t11;
$[35] = t14;
$[36] = t15;
} else {
t15 = $[36];
}
const subtitle = t15;
let t16;
if ($[37] !== onBack || $[38] !== onForeground || $[39] !== onKill || $[40] !== teammate.status) {
t16 = exitState => exitState.pending ? <Text>Press {exitState.keyName} again to exit</Text> : <Byline>{onBack && <KeyboardShortcutHint shortcut={"\u2190"} action="go back" />}<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />{teammate.status === "running" && onKill && <KeyboardShortcutHint shortcut="x" action="stop" />}{teammate.status === "running" && onForeground && <KeyboardShortcutHint shortcut="f" action="foreground" />}</Byline>;
$[37] = onBack;
$[38] = onForeground;
$[39] = onKill;
$[40] = teammate.status;
$[41] = t16;
} else {
t16 = $[41];
}
let t17;
if ($[42] !== teammate.progress || $[43] !== teammate.status || $[44] !== theme) {
t17 = teammate.status === "running" && teammate.progress?.recentActivities && teammate.progress.recentActivities.length > 0 && <Box flexDirection="column"><Text bold={true} dimColor={true}>Progress</Text>{teammate.progress.recentActivities.map((activity_0, i) => <Text key={i} dimColor={i < teammate.progress.recentActivities.length - 1} wrap="truncate-end">{i === teammate.progress.recentActivities.length - 1 ? "\u203A " : " "}{renderToolActivity(activity_0, tools, theme)}</Text>)}</Box>;
$[42] = teammate.progress;
$[43] = teammate.status;
$[44] = theme;
$[45] = t17;
} else {
t17 = $[45];
}
let t18;
if ($[46] === Symbol.for("react.memo_cache_sentinel")) {
t18 = <Text bold={true} dimColor={true}>Prompt</Text>;
$[46] = t18;
} else {
t18 = $[46];
}
let t19;
if ($[47] !== displayPrompt) {
t19 = <Box flexDirection="column" marginTop={1}>{t18}<Text wrap="wrap">{displayPrompt}</Text></Box>;
$[47] = displayPrompt;
$[48] = t19;
} else {
t19 = $[48];
}
let t20;
if ($[49] !== teammate.error || $[50] !== teammate.status) {
t20 = teammate.status === "failed" && teammate.error && <Box flexDirection="column" marginTop={1}><Text bold={true} color="error">Error</Text><Text color="error" wrap="wrap">{teammate.error}</Text></Box>;
$[49] = teammate.error;
$[50] = teammate.status;
$[51] = t20;
} else {
t20 = $[51];
}
let t21;
if ($[52] !== onDone || $[53] !== subtitle || $[54] !== t16 || $[55] !== t17 || $[56] !== t19 || $[57] !== t20 || $[58] !== title) {
t21 = <Dialog title={title} subtitle={subtitle} onCancel={onDone} color="background" inputGuide={t16}>{t17}{t19}{t20}</Dialog>;
$[52] = onDone;
$[53] = subtitle;
$[54] = t16;
$[55] = t17;
$[56] = t19;
$[57] = t20;
$[58] = title;
$[59] = t21;
} else {
t21 = $[59];
}
let t22;
if ($[60] !== handleKeyDown || $[61] !== t21) {
t22 = <Box flexDirection="column" tabIndex={0} autoFocus={true} onKeyDown={handleKeyDown}>{t21}</Box>;
$[60] = handleKeyDown;
$[61] = t21;
$[62] = t22;
} else {
t22 = $[62];
}
return t22;
teammate: DeepImmutable<InProcessTeammateTaskState>
onDone: () => void
onKill?: () => void
onBack?: () => void
onForeground?: () => void
}
export function InProcessTeammateDetailDialog({
teammate,
onDone,
onKill,
onBack,
onForeground,
}: Props): React.ReactNode {
const [theme] = useTheme()
const tools = useMemo(() => getTools(getEmptyToolPermissionContext()), [])
const elapsedTime = useElapsedTime(
teammate.startTime,
teammate.status === 'running',
1000,
teammate.totalPausedMs ?? 0,
)
// Restore confirm:yes (Enter/y) dismissal — Dialog handles confirm:no (Esc)
useKeybindings(
{
'confirm:yes': onDone,
},
{ context: 'Confirmation' },
)
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === ' ') {
e.preventDefault()
onDone()
} else if (e.key === 'left' && onBack) {
e.preventDefault()
onBack()
} else if (e.key === 'x' && teammate.status === 'running' && onKill) {
e.preventDefault()
onKill()
} else if (e.key === 'f' && teammate.status === 'running' && onForeground) {
e.preventDefault()
onForeground()
}
}
const activity = describeTeammateActivity(teammate)
const tokenCount =
teammate.result?.totalTokens ?? teammate.progress?.tokenCount
const toolUseCount =
teammate.result?.totalToolUseCount ?? teammate.progress?.toolUseCount
const displayPrompt = truncateToWidth(teammate.prompt, 300)
const title = (
<Text>
<Text color={toInkColor(teammate.identity.color)}>
@{teammate.identity.agentName}
</Text>
{activity && <Text dimColor> ({activity})</Text>}
</Text>
)
const subtitle = (
<Text>
{teammate.status !== 'running' && (
<Text
color={
teammate.status === 'completed'
? 'success'
: teammate.status === 'killed'
? 'warning'
: 'error'
}
>
{teammate.status === 'completed'
? 'Completed'
: teammate.status === 'failed'
? 'Failed'
: 'Stopped'}
{' · '}
</Text>
)}
<Text dimColor>
{elapsedTime}
{tokenCount !== undefined && tokenCount > 0 && (
<> · {formatNumber(tokenCount)} tokens</>
)}
{toolUseCount !== undefined && toolUseCount > 0 && (
<>
{' '}
· {toolUseCount} {toolUseCount === 1 ? 'tool' : 'tools'}
</>
)}
</Text>
</Text>
)
return (
<Box
flexDirection="column"
tabIndex={0}
autoFocus
onKeyDown={handleKeyDown}
>
<Dialog
title={title}
subtitle={subtitle}
onCancel={onDone}
color="background"
inputGuide={exitState =>
exitState.pending ? (
<Text>Press {exitState.keyName} again to exit</Text>
) : (
<Byline>
{onBack && <KeyboardShortcutHint shortcut="←" action="go back" />}
<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />
{teammate.status === 'running' && onKill && (
<KeyboardShortcutHint shortcut="x" action="stop" />
)}
{teammate.status === 'running' && onForeground && (
<KeyboardShortcutHint shortcut="f" action="foreground" />
)}
</Byline>
)
}
>
{/* Recent activities for running teammates */}
{teammate.status === 'running' &&
teammate.progress?.recentActivities &&
teammate.progress.recentActivities.length > 0 && (
<Box flexDirection="column">
<Text bold dimColor>
Progress
</Text>
{teammate.progress.recentActivities.map((activity, i) => (
<Text
key={i}
dimColor={i < teammate.progress!.recentActivities!.length - 1}
wrap="truncate-end"
>
{i === teammate.progress!.recentActivities!.length - 1
? ' '
: ' '}
{renderToolActivity(activity, tools, theme)}
</Text>
))}
</Box>
)}
{/* Prompt section */}
<Box flexDirection="column" marginTop={1}>
<Text bold dimColor>
Prompt
</Text>
<Text wrap="wrap">{displayPrompt}</Text>
</Box>
{/* Error details if failed */}
{teammate.status === 'failed' && teammate.error && (
<Box flexDirection="column" marginTop={1}>
<Text bold color="error">
Error
</Text>
<Text color="error" wrap="wrap">
{teammate.error}
</Text>
</Box>
)}
</Dialog>
</Box>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,17 @@
import { c as _c } from "react/compiler-runtime";
import React, { useRef } from 'react';
import type { RemoteAgentTaskState } from 'src/tasks/RemoteAgentTask/RemoteAgentTask.js';
import type { DeepImmutable } from 'src/types/utils.js';
import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js';
import { useSettings } from '../../hooks/useSettings.js';
import { Text, useAnimationFrame } from '../../ink.js';
import { count } from '../../utils/array.js';
import { getRainbowColor } from '../../utils/thinking.js';
const TICK_MS = 80;
type ReviewStage = NonNullable<NonNullable<RemoteAgentTaskState['reviewProgress']>['stage']>;
import React, { useRef } from 'react'
import type { RemoteAgentTaskState } from 'src/tasks/RemoteAgentTask/RemoteAgentTask.js'
import type { DeepImmutable } from 'src/types/utils.js'
import { DIAMOND_FILLED, DIAMOND_OPEN } from '../../constants/figures.js'
import { useSettings } from '../../hooks/useSettings.js'
import { Text, useAnimationFrame } from '../../ink.js'
import { count } from '../../utils/array.js'
import { getRainbowColor } from '../../utils/thinking.js'
const TICK_MS = 80
type ReviewStage = NonNullable<
NonNullable<RemoteAgentTaskState['reviewProgress']>['stage']
>
/**
* Stage-appropriate counts line for a running review. Shared between the
@@ -19,52 +22,48 @@ type ReviewStage = NonNullable<NonNullable<RemoteAgentTaskState['reviewProgress'
* Canonical behavior: word labels (not ✓/✗), hide refuted when 0, "deduping"
* for the synthesizing stage (matches STAGE_LABELS in the detail dialog).
*/
export function formatReviewStageCounts(stage: ReviewStage | undefined, found: number, verified: number, refuted: number): string {
export function formatReviewStageCounts(
stage: ReviewStage | undefined,
found: number,
verified: number,
refuted: number,
): string {
// Pre-stage orchestrator images don't write the stage field.
if (!stage) return `${found} found · ${verified} verified`;
if (!stage) return `${found} found · ${verified} verified`
if (stage === 'synthesizing') {
const parts = [`${verified} verified`];
if (refuted > 0) parts.push(`${refuted} refuted`);
parts.push('deduping');
return parts.join(' · ');
const parts = [`${verified} verified`]
if (refuted > 0) parts.push(`${refuted} refuted`)
parts.push('deduping')
return parts.join(' · ')
}
if (stage === 'verifying') {
const parts = [`${found} found`, `${verified} verified`];
if (refuted > 0) parts.push(`${refuted} refuted`);
return parts.join(' · ');
const parts = [`${found} found`, `${verified} verified`]
if (refuted > 0) parts.push(`${refuted} refuted`)
return parts.join(' · ')
}
// stage === 'finding'
return found > 0 ? `${found} found` : 'finding';
return found > 0 ? `${found} found` : 'finding'
}
// Per-character rainbow gradient, same treatment as the ultraplan keyword.
// The phase offset lets the gradient cycle — so the colors sweep along the
// text on each animation frame instead of being static.
function RainbowText(t0) {
const $ = _c(5);
const {
text,
phase: t1
} = t0;
const phase = t1 === undefined ? 0 : t1;
let t2;
if ($[0] !== text) {
t2 = [...text];
$[0] = text;
$[1] = t2;
} else {
t2 = $[1];
}
let t3;
if ($[2] !== phase || $[3] !== t2) {
t3 = <>{t2.map((ch, i) => <Text key={i} color={getRainbowColor(i + phase)}>{ch}</Text>)}</>;
$[2] = phase;
$[3] = t2;
$[4] = t3;
} else {
t3 = $[4];
}
return t3;
function RainbowText({
text,
phase = 0,
}: {
text: string
phase?: number
}): React.ReactNode {
return (
<>
{[...text].map((ch, i) => (
<Text key={i} color={getRainbowColor(i + phase)}>
{ch}
</Text>
))}
</>
)
}
// Smooth-tick a count toward target, +1 per frame. Same pattern as the
@@ -74,169 +73,129 @@ function RainbowText(t0) {
// the clock is frozen), bypass the tick and jump straight to target —
// otherwise a frozen `time` would leave the ref stuck at its init value.
function useSmoothCount(target: number, time: number, snap: boolean): number {
const displayed = useRef(target);
const lastTick = useRef(time);
const displayed = useRef(target)
const lastTick = useRef(time)
if (snap || target < displayed.current) {
displayed.current = target;
displayed.current = target
} else if (target > displayed.current && time !== lastTick.current) {
displayed.current += 1;
lastTick.current = time;
displayed.current += 1
lastTick.current = time
}
return displayed.current;
return displayed.current
}
function ReviewRainbowLine(t0) {
const $ = _c(15);
const {
session
} = t0;
const settings = useSettings();
const reducedMotion = settings.prefersReducedMotion ?? false;
const p = session.reviewProgress;
const running = session.status === "running";
const [, time] = useAnimationFrame(running && !reducedMotion ? TICK_MS : null);
const targetFound = p?.bugsFound ?? 0;
const targetVerified = p?.bugsVerified ?? 0;
const targetRefuted = p?.bugsRefuted ?? 0;
const snap = reducedMotion || !running;
const found = useSmoothCount(targetFound, time, snap);
const verified = useSmoothCount(targetVerified, time, snap);
const refuted = useSmoothCount(targetRefuted, time, snap);
const phase = Math.floor(time / (TICK_MS * 3)) % 7;
if (session.status === "completed") {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <><Text color="background">{DIAMOND_FILLED} </Text><RainbowText text="ultrareview" phase={0} /><Text dimColor={true}> ready · shift+ to view</Text></>;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
function ReviewRainbowLine({
session,
}: {
session: DeepImmutable<RemoteAgentTaskState>
}): React.ReactNode {
const settings = useSettings()
const reducedMotion = settings.prefersReducedMotion ?? false
const p = session.reviewProgress
const running = session.status === 'running'
// Animation clock runs only while running — completed/failed are static.
// Disabled entirely when the user prefers reduced motion.
//
// The ref is intentionally discarded: this component is rendered inside
// <Text> wrappers (BackgroundTasksDialog, RemoteSessionDetailDialog), and
// Ink can't nest <Box> inside <Text>. Dropping the ref means
// useTerminalViewport's isVisible stays true, so the clock ticks even when
// scrolled off-screen — acceptable for a single 30-char line.
const [, time] = useAnimationFrame(running && !reducedMotion ? TICK_MS : null)
const targetFound = p?.bugsFound ?? 0
const targetVerified = p?.bugsVerified ?? 0
const targetRefuted = p?.bugsRefuted ?? 0
// snap when the clock isn't advancing (reduced motion, or not running) —
// useAnimationFrame(null) freezes `time` at its mount value, which would
// leave the tick-gate permanently false.
const snap = reducedMotion || !running
const found = useSmoothCount(targetFound, time, snap)
const verified = useSmoothCount(targetVerified, time, snap)
const refuted = useSmoothCount(targetRefuted, time, snap)
// Phase advances every 3 ticks so the gradient sweep is visible but
// not frantic. Modulo keeps it in the 7-color cycle.
const phase = Math.floor(time / (TICK_MS * 3)) % 7
// ◇ open diamond while running (teal, matches cloud-session accent), ◆
// filled when terminal. Rainbow is scoped to the word `ultrareview` only —
// per design feedback, "there is a limit to the glittering rainbow".
// Counts stay dimColor.
if (session.status === 'completed') {
return (
<>
<Text color="background">{DIAMOND_FILLED} </Text>
<RainbowText text="ultrareview" phase={0} />
<Text dimColor> ready · shift+ to view</Text>
</>
)
}
if (session.status === "failed") {
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <><Text color="background">{DIAMOND_FILLED} </Text><RainbowText text="ultrareview" phase={0} /><Text color="error" dimColor={true}>{" \xB7 "}error</Text></>;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
if (session.status === 'failed') {
return (
<>
<Text color="background">{DIAMOND_FILLED} </Text>
<RainbowText text="ultrareview" phase={0} />
<Text color="error" dimColor>
{' · '}
error
</Text>
</>
)
}
let t1;
if ($[2] !== found || $[3] !== p || $[4] !== refuted || $[5] !== verified) {
t1 = !p ? "setting up" : formatReviewStageCounts(p.stage, found, verified, refuted);
$[2] = found;
$[3] = p;
$[4] = refuted;
$[5] = verified;
$[6] = t1;
} else {
t1 = $[6];
}
const tail = t1;
let t2;
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <Text color="background">{DIAMOND_OPEN} </Text>;
$[7] = t2;
} else {
t2 = $[7];
}
const t3 = running ? phase : 0;
let t4;
if ($[8] !== t3) {
t4 = <RainbowText text="ultrareview" phase={t3} />;
$[8] = t3;
$[9] = t4;
} else {
t4 = $[9];
}
let t5;
if ($[10] !== tail) {
t5 = <Text dimColor={true}> · {tail}</Text>;
$[10] = tail;
$[11] = t5;
} else {
t5 = $[11];
}
let t6;
if ($[12] !== t4 || $[13] !== t5) {
t6 = <>{t2}{t4}{t5}</>;
$[12] = t4;
$[13] = t5;
$[14] = t6;
} else {
t6 = $[14];
}
return t6;
// The !p branch ("setting up") covers the window before the orchestrator
// writes its first progress snapshot — container boot + repo clone can
// take 1-3 min, during which "0 found" looked hung.
const tail = !p
? 'setting up'
: formatReviewStageCounts(p.stage, found, verified, refuted)
return (
<>
<Text color="background">{DIAMOND_OPEN} </Text>
<RainbowText text="ultrareview" phase={running ? phase : 0} />
<Text dimColor> · {tail}</Text>
</>
)
}
export function RemoteSessionProgress(t0) {
const $ = _c(11);
const {
session
} = t0;
export function RemoteSessionProgress({
session,
}: {
session: DeepImmutable<RemoteAgentTaskState>
}): React.ReactNode {
// Lite-review: rainbow gradient over the full line, ultraplan-style.
// BackgroundTask.tsx delegates the whole <Text> wrapper here so the
// gradient spans the title, not just the trailing status.
if (session.isRemoteReview) {
let t1;
if ($[0] !== session) {
t1 = <ReviewRainbowLine session={session} />;
$[0] = session;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
return <ReviewRainbowLine session={session} />
}
if (session.status === "completed") {
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Text bold={true} color="success" dimColor={true}>done</Text>;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
if (session.status === 'completed') {
return (
<Text bold color="success" dimColor>
done
</Text>
)
}
if (session.status === "failed") {
let t1;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Text bold={true} color="error" dimColor={true}>error</Text>;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
if (session.status === 'failed') {
return (
<Text bold color="error" dimColor>
error
</Text>
)
}
if (!session.todoList.length) {
let t1;
if ($[4] !== session.status) {
t1 = <Text dimColor={true}>{session.status}</Text>;
$[4] = session.status;
$[5] = t1;
} else {
t1 = $[5];
}
return t1;
return <Text dimColor>{session.status}</Text>
}
let t1;
if ($[6] !== session.todoList) {
t1 = count(session.todoList, _temp);
$[6] = session.todoList;
$[7] = t1;
} else {
t1 = $[7];
}
const completed = t1;
const total = session.todoList.length;
let t2;
if ($[8] !== completed || $[9] !== total) {
t2 = <Text dimColor={true}>{completed}/{total}</Text>;
$[8] = completed;
$[9] = total;
$[10] = t2;
} else {
t2 = $[10];
}
return t2;
}
function _temp(_) {
return _.status === "completed";
const completed = count(session.todoList, _ => _.status === 'completed')
const total = session.todoList.length
return (
<Text dimColor>
{completed}/{total}
</Text>
)
}

View File

@@ -1,403 +1,247 @@
import { c as _c } from "react/compiler-runtime";
import React, { Suspense, use, useDeferredValue, useEffect, useState } from 'react';
import type { DeepImmutable } from 'src/types/utils.js';
import type { CommandResultDisplay } from '../../commands.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
import { Box, Text } from '../../ink.js';
import { useKeybindings } from '../../keybindings/useKeybinding.js';
import type { LocalShellTaskState } from '../../tasks/LocalShellTask/guards.js';
import { formatDuration, formatFileSize, truncateToWidth } from '../../utils/format.js';
import { tailFile } from '../../utils/fsOperations.js';
import { getTaskOutputPath } from '../../utils/task/diskOutput.js';
import { Byline } from '../design-system/Byline.js';
import { Dialog } from '../design-system/Dialog.js';
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
import React, {
Suspense,
use,
useDeferredValue,
useEffect,
useState,
} from 'react'
import type { DeepImmutable } from 'src/types/utils.js'
import type { CommandResultDisplay } from '../../commands.js'
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
import { Box, Text } from '../../ink.js'
import { useKeybindings } from '../../keybindings/useKeybinding.js'
import type { LocalShellTaskState } from '../../tasks/LocalShellTask/guards.js'
import {
formatDuration,
formatFileSize,
truncateToWidth,
} from '../../utils/format.js'
import { tailFile } from '../../utils/fsOperations.js'
import { getTaskOutputPath } from '../../utils/task/diskOutput.js'
import { Byline } from '../design-system/Byline.js'
import { Dialog } from '../design-system/Dialog.js'
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
type Props = {
shell: DeepImmutable<LocalShellTaskState>;
onDone: (result?: string, options?: {
display?: CommandResultDisplay;
}) => void;
onKillShell?: () => void;
onBack?: () => void;
};
const SHELL_DETAIL_TAIL_BYTES = 8192;
shell: DeepImmutable<LocalShellTaskState>
onDone: (
result?: string,
options?: { display?: CommandResultDisplay },
) => void
onKillShell?: () => void
onBack?: () => void
}
const SHELL_DETAIL_TAIL_BYTES = 8192
type TaskOutputResult = {
content: string;
bytesTotal: number;
};
content: string
bytesTotal: number
}
/**
* Read the tail of the task output file. Only reads the last few KB,
* not the entire file.
*/
async function getTaskOutput(shell: DeepImmutable<LocalShellTaskState>): Promise<TaskOutputResult> {
const path = getTaskOutputPath(shell.id);
async function getTaskOutput(
shell: DeepImmutable<LocalShellTaskState>,
): Promise<TaskOutputResult> {
const path = getTaskOutputPath(shell.id)
try {
const result = await tailFile(path, SHELL_DETAIL_TAIL_BYTES);
return {
content: result.content,
bytesTotal: result.bytesTotal
};
const result = await tailFile(path, SHELL_DETAIL_TAIL_BYTES)
return { content: result.content, bytesTotal: result.bytesTotal }
} catch {
return {
content: '',
bytesTotal: 0
};
return { content: '', bytesTotal: 0 }
}
}
export function ShellDetailDialog(t0) {
const $ = _c(57);
const {
shell,
onDone,
onKillShell,
onBack
} = t0;
const {
columns
} = useTerminalSize();
let t1;
if ($[0] !== shell) {
t1 = () => getTaskOutput(shell);
$[0] = shell;
$[1] = t1;
} else {
t1 = $[1];
export function ShellDetailDialog({
shell,
onDone,
onKillShell,
onBack,
}: Props): React.ReactNode {
const { columns } = useTerminalSize()
// Promise created in initializer (not during render). For running shells,
// the effect timer replaces it periodically to pick up new output.
// useDeferredValue keeps showing the previous output while the new promise
// resolves, preventing the Suspense fallback from flickering.
const [outputPromise, setOutputPromise] = useState<Promise<TaskOutputResult>>(
() => getTaskOutput(shell),
)
const deferredOutputPromise = useDeferredValue(outputPromise)
useEffect(() => {
if (shell.status !== 'running') {
return
}
const timer = setInterval(
(setOutputPromise, shell) => setOutputPromise(getTaskOutput(shell)),
1000,
setOutputPromise,
shell,
)
return () => clearInterval(timer)
}, [shell.id, shell.status])
// Handle standard close action
const handleClose = () =>
onDone('Shell details dismissed', { display: 'system' })
// Handle additional close actions beyond Dialog's built-in Esc handler
useKeybindings(
{
'confirm:yes': handleClose,
},
{ context: 'Confirmation' },
)
// Handle dialog-specific keys
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === ' ') {
e.preventDefault()
onDone('Shell details dismissed', { display: 'system' })
} else if (e.key === 'left' && onBack) {
e.preventDefault()
onBack()
} else if (e.key === 'x' && shell.status === 'running' && onKillShell) {
e.preventDefault()
onKillShell()
}
}
const [outputPromise, setOutputPromise] = useState(t1);
const deferredOutputPromise = useDeferredValue(outputPromise);
let t2;
if ($[2] !== shell) {
t2 = () => {
if (shell.status !== "running") {
return;
}
const timer = setInterval(_temp, 1000, setOutputPromise, shell);
return () => clearInterval(timer);
};
$[2] = shell;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== shell.id || $[5] !== shell.status) {
t3 = [shell.id, shell.status];
$[4] = shell.id;
$[5] = shell.status;
$[6] = t3;
} else {
t3 = $[6];
}
useEffect(t2, t3);
let t4;
if ($[7] !== onDone) {
t4 = () => onDone("Shell details dismissed", {
display: "system"
});
$[7] = onDone;
$[8] = t4;
} else {
t4 = $[8];
}
const handleClose = t4;
let t5;
if ($[9] !== handleClose) {
t5 = {
"confirm:yes": handleClose
};
$[9] = handleClose;
$[10] = t5;
} else {
t5 = $[10];
}
let t6;
if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
t6 = {
context: "Confirmation"
};
$[11] = t6;
} else {
t6 = $[11];
}
useKeybindings(t5, t6);
let t7;
if ($[12] !== onBack || $[13] !== onDone || $[14] !== onKillShell || $[15] !== shell.status) {
t7 = e => {
if (e.key === " ") {
e.preventDefault();
onDone("Shell details dismissed", {
display: "system"
});
} else {
if (e.key === "left" && onBack) {
e.preventDefault();
onBack();
} else {
if (e.key === "x" && shell.status === "running" && onKillShell) {
e.preventDefault();
onKillShell();
}
// Truncate command if too long (for display purposes)
const isMonitor = shell.kind === 'monitor'
const displayCommand = truncateToWidth(shell.command, 280)
return (
<Box
flexDirection="column"
tabIndex={0}
autoFocus
onKeyDown={handleKeyDown}
>
<Dialog
title={isMonitor ? 'Monitor details' : 'Shell details'}
onCancel={handleClose}
color="background"
inputGuide={exitState =>
exitState.pending ? (
<Text>Press {exitState.keyName} again to exit</Text>
) : (
<Byline>
{onBack && <KeyboardShortcutHint shortcut="←" action="go back" />}
<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />
{shell.status === 'running' && onKillShell && (
<KeyboardShortcutHint shortcut="x" action="stop" />
)}
</Byline>
)
}
}
};
$[12] = onBack;
$[13] = onDone;
$[14] = onKillShell;
$[15] = shell.status;
$[16] = t7;
} else {
t7 = $[16];
}
const handleKeyDown = t7;
const isMonitor = shell.kind === "monitor";
let t8;
if ($[17] !== shell.command) {
t8 = truncateToWidth(shell.command, 280);
$[17] = shell.command;
$[18] = t8;
} else {
t8 = $[18];
}
const displayCommand = t8;
const t9 = isMonitor ? "Monitor details" : "Shell details";
let t10;
if ($[19] !== onBack || $[20] !== onKillShell || $[21] !== shell.status) {
t10 = exitState => exitState.pending ? <Text>Press {exitState.keyName} again to exit</Text> : <Byline>{onBack && <KeyboardShortcutHint shortcut={"\u2190"} action="go back" />}<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />{shell.status === "running" && onKillShell && <KeyboardShortcutHint shortcut="x" action="stop" />}</Byline>;
$[19] = onBack;
$[20] = onKillShell;
$[21] = shell.status;
$[22] = t10;
} else {
t10 = $[22];
}
let t11;
if ($[23] === Symbol.for("react.memo_cache_sentinel")) {
t11 = <Text bold={true}>Status:</Text>;
$[23] = t11;
} else {
t11 = $[23];
}
let t12;
if ($[24] !== shell.result || $[25] !== shell.status) {
t12 = <Text>{t11}{" "}{shell.status === "running" ? <Text color="background">{shell.status}{shell.result?.code !== undefined && ` (exit code: ${shell.result.code})`}</Text> : shell.status === "completed" ? <Text color="success">{shell.status}{shell.result?.code !== undefined && ` (exit code: ${shell.result.code})`}</Text> : <Text color="error">{shell.status}{shell.result?.code !== undefined && ` (exit code: ${shell.result.code})`}</Text>}</Text>;
$[24] = shell.result;
$[25] = shell.status;
$[26] = t12;
} else {
t12 = $[26];
}
let t13;
if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
t13 = <Text bold={true}>Runtime:</Text>;
$[27] = t13;
} else {
t13 = $[27];
}
let t14;
if ($[28] !== shell.endTime) {
t14 = shell.endTime ?? Date.now();
$[28] = shell.endTime;
$[29] = t14;
} else {
t14 = $[29];
}
const t15 = t14 - shell.startTime;
let t16;
if ($[30] !== t15) {
t16 = formatDuration(t15);
$[30] = t15;
$[31] = t16;
} else {
t16 = $[31];
}
let t17;
if ($[32] !== t16) {
t17 = <Text>{t13}{" "}{t16}</Text>;
$[32] = t16;
$[33] = t17;
} else {
t17 = $[33];
}
const t18 = isMonitor ? "Script:" : "Command:";
let t19;
if ($[34] !== t18) {
t19 = <Text bold={true}>{t18}</Text>;
$[34] = t18;
$[35] = t19;
} else {
t19 = $[35];
}
let t20;
if ($[36] !== displayCommand || $[37] !== t19) {
t20 = <Text wrap="wrap">{t19}{" "}{displayCommand}</Text>;
$[36] = displayCommand;
$[37] = t19;
$[38] = t20;
} else {
t20 = $[38];
}
let t21;
if ($[39] !== t12 || $[40] !== t17 || $[41] !== t20) {
t21 = <Box flexDirection="column">{t12}{t17}{t20}</Box>;
$[39] = t12;
$[40] = t17;
$[41] = t20;
$[42] = t21;
} else {
t21 = $[42];
}
let t22;
if ($[43] === Symbol.for("react.memo_cache_sentinel")) {
t22 = <Text bold={true}>Output:</Text>;
$[43] = t22;
} else {
t22 = $[43];
}
let t23;
if ($[44] === Symbol.for("react.memo_cache_sentinel")) {
t23 = <Text dimColor={true}>Loading output</Text>;
$[44] = t23;
} else {
t23 = $[44];
}
let t24;
if ($[45] !== columns || $[46] !== deferredOutputPromise) {
t24 = <Box flexDirection="column">{t22}<Suspense fallback={t23}><ShellOutputContent outputPromise={deferredOutputPromise} columns={columns} /></Suspense></Box>;
$[45] = columns;
$[46] = deferredOutputPromise;
$[47] = t24;
} else {
t24 = $[47];
}
let t25;
if ($[48] !== handleClose || $[49] !== t10 || $[50] !== t21 || $[51] !== t24 || $[52] !== t9) {
t25 = <Dialog title={t9} onCancel={handleClose} color="background" inputGuide={t10}>{t21}{t24}</Dialog>;
$[48] = handleClose;
$[49] = t10;
$[50] = t21;
$[51] = t24;
$[52] = t9;
$[53] = t25;
} else {
t25 = $[53];
}
let t26;
if ($[54] !== handleKeyDown || $[55] !== t25) {
t26 = <Box flexDirection="column" tabIndex={0} autoFocus={true} onKeyDown={handleKeyDown}>{t25}</Box>;
$[54] = handleKeyDown;
$[55] = t25;
$[56] = t26;
} else {
t26 = $[56];
}
return t26;
}
function _temp(setOutputPromise_0, shell_0) {
return setOutputPromise_0(getTaskOutput(shell_0));
>
<Box flexDirection="column">
<Text>
<Text bold>Status:</Text>{' '}
{shell.status === 'running' ? (
<Text color="background">
{shell.status}
{shell.result?.code !== undefined &&
` (exit code: ${shell.result.code})`}
</Text>
) : shell.status === 'completed' ? (
<Text color="success">
{shell.status}
{shell.result?.code !== undefined &&
` (exit code: ${shell.result.code})`}
</Text>
) : (
<Text color="error">
{shell.status}
{shell.result?.code !== undefined &&
` (exit code: ${shell.result.code})`}
</Text>
)}
</Text>
<Text>
<Text bold>Runtime:</Text>{' '}
{formatDuration((shell.endTime ?? Date.now()) - shell.startTime)}
</Text>
<Text wrap="wrap">
<Text bold>{isMonitor ? 'Script:' : 'Command:'}</Text>{' '}
{displayCommand}
</Text>
</Box>
<Box flexDirection="column">
<Text bold>Output:</Text>
<Suspense fallback={<Text dimColor>Loading output</Text>}>
<ShellOutputContent
outputPromise={deferredOutputPromise}
columns={columns}
/>
</Suspense>
</Box>
</Dialog>
</Box>
)
}
type ShellOutputContentProps = {
outputPromise: Promise<TaskOutputResult>;
columns: number;
};
function ShellOutputContent(t0) {
const $ = _c(19);
const {
outputPromise,
columns
} = t0;
const {
content,
bytesTotal
} = use(outputPromise) as any;
outputPromise: Promise<TaskOutputResult>
columns: number
}
function ShellOutputContent({
outputPromise,
columns,
}: ShellOutputContentProps): React.ReactNode {
const { content, bytesTotal } = use(outputPromise)
if (!content) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Text dimColor={true}>No output available</Text>;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
return <Text dimColor>No output available</Text>
}
let isIncomplete;
let rendered;
if ($[1] !== bytesTotal || $[2] !== content) {
const starts = [];
let pos = content.length;
for (let i = 0; i < 10 && pos > 0; i++) {
const prev = content.lastIndexOf("\n", pos - 1);
starts.push(prev + 1);
pos = prev;
}
starts.reverse();
isIncomplete = bytesTotal > content.length;
rendered = [];
for (let i_0 = 0; i_0 < starts.length; i_0++) {
const start = starts[i_0];
const end = i_0 < starts.length - 1 ? starts[i_0 + 1] - 1 : content.length;
const line = content.slice(start, end);
if (line) {
rendered.push(line);
}
}
$[1] = bytesTotal;
$[2] = content;
$[3] = isIncomplete;
$[4] = rendered;
} else {
isIncomplete = $[3];
rendered = $[4];
// Find last 10 line boundaries via lastIndexOf
const starts: number[] = []
let pos = content.length
for (let i = 0; i < 10 && pos > 0; i++) {
const prev = content.lastIndexOf('\n', pos - 1)
starts.push(prev + 1)
pos = prev
}
const t1 = columns - 6;
let t2;
if ($[5] !== rendered) {
t2 = rendered.map(_temp2);
$[5] = rendered;
$[6] = t2;
} else {
t2 = $[6];
starts.reverse()
const isIncomplete = bytesTotal > content.length
// Build lines, skip empty trailing/leading segments
const rendered: string[] = []
for (let i = 0; i < starts.length; i++) {
const start = starts[i]!
const end = i < starts.length - 1 ? starts[i + 1]! - 1 : content.length
const line = content.slice(start, end)
if (line) rendered.push(line)
}
let t3;
if ($[7] !== t1 || $[8] !== t2) {
t3 = <Box borderStyle="round" paddingX={1} flexDirection="column" height={12} maxWidth={t1}>{t2}</Box>;
$[7] = t1;
$[8] = t2;
$[9] = t3;
} else {
t3 = $[9];
}
const t4 = `Showing ${rendered.length} lines`;
let t5;
if ($[10] !== bytesTotal || $[11] !== isIncomplete) {
t5 = isIncomplete ? ` of ${formatFileSize(bytesTotal)}` : "";
$[10] = bytesTotal;
$[11] = isIncomplete;
$[12] = t5;
} else {
t5 = $[12];
}
let t6;
if ($[13] !== t4 || $[14] !== t5) {
t6 = <Text dimColor={true} italic={true}>{t4}{t5}</Text>;
$[13] = t4;
$[14] = t5;
$[15] = t6;
} else {
t6 = $[15];
}
let t7;
if ($[16] !== t3 || $[17] !== t6) {
t7 = <>{t3}{t6}</>;
$[16] = t3;
$[17] = t6;
$[18] = t7;
} else {
t7 = $[18];
}
return t7;
}
function _temp2(line_0, i_1) {
return <Text key={i_1} wrap="truncate-end">{line_0}</Text>;
return (
<>
<Box
borderStyle="round"
paddingX={1}
flexDirection="column"
height={12}
maxWidth={columns - 6}
>
{rendered.map((line, i) => (
<Text key={i} wrap="truncate-end">
{line}
</Text>
))}
</Box>
<Text dimColor italic>
{`Showing ${rendered.length} lines`}
{isIncomplete ? ` of ${formatFileSize(bytesTotal)}` : ''}
</Text>
</>
)
}

View File

@@ -1,86 +1,52 @@
import { c as _c } from "react/compiler-runtime";
import type { ReactNode } from 'react';
import React from 'react';
import { Text } from 'src/ink.js';
import type { TaskStatus } from 'src/Task.js';
import type { LocalShellTaskState } from 'src/tasks/LocalShellTask/guards.js';
import type { DeepImmutable } from 'src/types/utils.js';
import type { ReactNode } from 'react'
import React from 'react'
import { Text } from 'src/ink.js'
import type { TaskStatus } from 'src/Task.js'
import type { LocalShellTaskState } from 'src/tasks/LocalShellTask/guards.js'
import type { DeepImmutable } from 'src/types/utils.js'
type TaskStatusTextProps = {
status: TaskStatus;
label?: string;
suffix?: string;
};
export function TaskStatusText(t0) {
const $ = _c(4);
const {
status,
label,
suffix
} = t0;
const displayLabel = label ?? status;
const color = status === "completed" ? "success" : status === "failed" ? "error" : status === "killed" ? "warning" : undefined;
let t1;
if ($[0] !== color || $[1] !== displayLabel || $[2] !== suffix) {
t1 = <Text color={color} dimColor={true}>({displayLabel}{suffix})</Text>;
$[0] = color;
$[1] = displayLabel;
$[2] = suffix;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
status: TaskStatus
label?: string
suffix?: string
}
export function ShellProgress(t0) {
const $ = _c(4);
const {
shell
} = t0;
export function TaskStatusText({
status,
label,
suffix,
}: TaskStatusTextProps): ReactNode {
const displayLabel = label ?? status
const color =
status === 'completed'
? 'success'
: status === 'failed'
? 'error'
: status === 'killed'
? 'warning'
: undefined
return (
<Text color={color} dimColor>
({displayLabel}
{suffix})
</Text>
)
}
export function ShellProgress({
shell,
}: {
shell: DeepImmutable<LocalShellTaskState>
}): ReactNode {
switch (shell.status) {
case "completed":
{
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <TaskStatusText status="completed" label="done" />;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
case "failed":
{
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <TaskStatusText status="failed" label="error" />;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
case "killed":
{
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <TaskStatusText status="killed" label="stopped" />;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
case "running":
case "pending":
{
let t1;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <TaskStatusText status="running" />;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
case 'completed':
return <TaskStatusText status="completed" label="done" />
case 'failed':
return <TaskStatusText status="failed" label="error" />
case 'killed':
return <TaskStatusText status="killed" label="stopped" />
case 'running':
case 'pending':
return <TaskStatusText status="running" />
}
}

View File

@@ -1,32 +1,39 @@
import React from 'react';
import { Text } from '../../ink.js';
import type { Tools } from '../../Tool.js';
import { findToolByName } from '../../Tool.js';
import type { ToolActivity } from '../../tasks/LocalAgentTask/LocalAgentTask.js';
import type { ThemeName } from '../../utils/theme.js';
export function renderToolActivity(activity: ToolActivity, tools: Tools, theme: ThemeName): React.ReactNode {
const tool = findToolByName(tools, activity.toolName);
import React from 'react'
import { Text } from '../../ink.js'
import type { Tools } from '../../Tool.js'
import { findToolByName } from '../../Tool.js'
import type { ToolActivity } from '../../tasks/LocalAgentTask/LocalAgentTask.js'
import type { ThemeName } from '../../utils/theme.js'
export function renderToolActivity(
activity: ToolActivity,
tools: Tools,
theme: ThemeName,
): React.ReactNode {
const tool = findToolByName(tools, activity.toolName)
if (!tool) {
return activity.toolName;
return activity.toolName
}
try {
const parsed = tool.inputSchema.safeParse(activity.input);
const parsedInput = parsed.success ? parsed.data : {};
const userFacingName = tool.userFacingName(parsedInput);
const parsed = tool.inputSchema.safeParse(activity.input)
const parsedInput = parsed.success ? parsed.data : {}
const userFacingName = tool.userFacingName(parsedInput)
if (!userFacingName) {
return activity.toolName;
return activity.toolName
}
const toolArgs = tool.renderToolUseMessage(parsedInput, {
theme,
verbose: false
});
verbose: false,
})
if (toolArgs) {
return <Text>
return (
<Text>
{userFacingName}({toolArgs})
</Text>;
</Text>
)
}
return userFacingName;
return userFacingName
} catch {
return activity.toolName;
return activity.toolName
}
}

View File

@@ -2,71 +2,73 @@
* Shared utilities for displaying task status across different task types.
*/
import figures from 'figures';
import type { TaskStatus } from 'src/Task.js';
import type { InProcessTeammateTaskState } from 'src/tasks/InProcessTeammateTask/types.js';
import { isPanelAgentTask } from 'src/tasks/LocalAgentTask/LocalAgentTask.js';
import { isBackgroundTask, type TaskState } from 'src/tasks/types.js';
import type { DeepImmutable } from 'src/types/utils.js';
import { summarizeRecentActivities } from 'src/utils/collapseReadSearch.js';
import figures from 'figures'
import type { TaskStatus } from 'src/Task.js'
import type { InProcessTeammateTaskState } from 'src/tasks/InProcessTeammateTask/types.js'
import { isPanelAgentTask } from 'src/tasks/LocalAgentTask/LocalAgentTask.js'
import { isBackgroundTask, type TaskState } from 'src/tasks/types.js'
import type { DeepImmutable } from 'src/types/utils.js'
import { summarizeRecentActivities } from 'src/utils/collapseReadSearch.js'
/**
* Returns true if the given task status represents a terminal (finished) state.
*/
export function isTerminalStatus(status: TaskStatus): boolean {
return status === 'completed' || status === 'failed' || status === 'killed';
return status === 'completed' || status === 'failed' || status === 'killed'
}
/**
* Returns the appropriate icon for a task based on status and state flags.
*/
export function getTaskStatusIcon(status: TaskStatus, options?: {
isIdle?: boolean;
awaitingApproval?: boolean;
hasError?: boolean;
shutdownRequested?: boolean;
}): string {
const {
isIdle,
awaitingApproval,
hasError,
shutdownRequested
} = options ?? {};
if (hasError) return figures.cross;
if (awaitingApproval) return figures.questionMarkPrefix;
if (shutdownRequested) return figures.warning;
export function getTaskStatusIcon(
status: TaskStatus,
options?: {
isIdle?: boolean
awaitingApproval?: boolean
hasError?: boolean
shutdownRequested?: boolean
},
): string {
const { isIdle, awaitingApproval, hasError, shutdownRequested } =
options ?? {}
if (hasError) return figures.cross
if (awaitingApproval) return figures.questionMarkPrefix
if (shutdownRequested) return figures.warning
if (status === 'running') {
if (isIdle) return figures.ellipsis;
return figures.play;
if (isIdle) return figures.ellipsis
return figures.play
}
if (status === 'completed') return figures.tick;
if (status === 'failed' || status === 'killed') return figures.cross;
return figures.bullet;
if (status === 'completed') return figures.tick
if (status === 'failed' || status === 'killed') return figures.cross
return figures.bullet
}
/**
* Returns the appropriate semantic color for a task based on status and state flags.
*/
export function getTaskStatusColor(status: TaskStatus, options?: {
isIdle?: boolean;
awaitingApproval?: boolean;
hasError?: boolean;
shutdownRequested?: boolean;
}): 'success' | 'error' | 'warning' | 'background' {
const {
isIdle,
awaitingApproval,
hasError,
shutdownRequested
} = options ?? {};
if (hasError) return 'error';
if (awaitingApproval) return 'warning';
if (shutdownRequested) return 'warning';
if (isIdle) return 'background';
if (status === 'completed') return 'success';
if (status === 'failed') return 'error';
if (status === 'killed') return 'warning';
return 'background';
export function getTaskStatusColor(
status: TaskStatus,
options?: {
isIdle?: boolean
awaitingApproval?: boolean
hasError?: boolean
shutdownRequested?: boolean
},
): 'success' | 'error' | 'warning' | 'background' {
const { isIdle, awaitingApproval, hasError, shutdownRequested } =
options ?? {}
if (hasError) return 'error'
if (awaitingApproval) return 'warning'
if (shutdownRequested) return 'warning'
if (isIdle) return 'background'
if (status === 'completed') return 'success'
if (status === 'failed') return 'error'
if (status === 'killed') return 'warning'
return 'background'
}
/**
@@ -74,11 +76,18 @@ export function getTaskStatusColor(status: TaskStatus, options?: {
* accounting for shutdown/approval/idle states and falling back through
* recent-activity summary → last activity description → 'working'.
*/
export function describeTeammateActivity(t: DeepImmutable<InProcessTeammateTaskState>): string {
if (t.shutdownRequested) return 'stopping';
if (t.awaitingPlanApproval) return 'awaiting approval';
if (t.isIdle) return 'idle';
return (t.progress?.recentActivities && summarizeRecentActivities(t.progress.recentActivities)) ?? t.progress?.lastActivity?.activityDescription ?? 'working';
export function describeTeammateActivity(
t: DeepImmutable<InProcessTeammateTaskState>,
): string {
if (t.shutdownRequested) return 'stopping'
if (t.awaitingPlanApproval) return 'awaiting approval'
if (t.isIdle) return 'idle'
return (
(t.progress?.recentActivities &&
summarizeRecentActivities(t.progress.recentActivities)) ??
t.progress?.lastActivity?.activityDescription ??
'working'
)
}
/**
@@ -90,17 +99,21 @@ export function describeTeammateActivity(t: DeepImmutable<InProcessTeammateTaskS
* plus exclusion of panel-managed agent tasks for ants (those are shown
* by CoordinatorTaskPanel).
*/
export function shouldHideTasksFooter(tasks: {
[taskId: string]: TaskState;
}, showSpinnerTree: boolean): boolean {
if (!showSpinnerTree) return false;
let hasVisibleTask = false;
export function shouldHideTasksFooter(
tasks: { [taskId: string]: TaskState },
showSpinnerTree: boolean,
): boolean {
if (!showSpinnerTree) return false
let hasVisibleTask = false
for (const t of Object.values(tasks) as TaskState[]) {
if (!isBackgroundTask(t) || (process.env.USER_TYPE) === 'ant' && isPanelAgentTask(t)) {
continue;
if (
!isBackgroundTask(t) ||
(process.env.USER_TYPE === 'ant' && isPanelAgentTask(t))
) {
continue
}
hasVisibleTask = true;
if (t.type !== 'in_process_teammate') return false;
hasVisibleTask = true
if (t.type !== 'in_process_teammate') return false
}
return hasVisibleTask;
return hasVisibleTask
}