mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 06:15:51 +00:00
更新大量 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:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user