@wangzhizhi/remi 0.0.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/doctor.js +108 -0
- package/dist/git.js +41 -0
- package/dist/help.js +27 -0
- package/dist/i18n.js +422 -0
- package/dist/index.js +97 -0
- package/dist/initPrompt.js +17 -0
- package/dist/model.js +116 -0
- package/dist/modelSelection.js +34 -0
- package/dist/permissionDisplay.js +46 -0
- package/dist/permissions.js +206 -0
- package/dist/repl.js +346 -0
- package/dist/resume.js +3 -0
- package/dist/setup.js +62 -0
- package/dist/statusline.js +59 -0
- package/dist/style.js +48 -0
- package/dist/syntaxTheme.js +39 -0
- package/dist/tui/RemiApp.js +1756 -0
- package/dist/tui/commands.js +427 -0
- package/dist/tui/index.js +42 -0
- package/dist/tui/renderers/Header.js +28 -0
- package/dist/tui/renderers/MessageList.js +1176 -0
- package/dist/tui/renderers/PromptBox.js +118 -0
- package/dist/tui/renderers/StatusLine.js +124 -0
- package/dist/tui/renderers/WorkingIndicator.js +70 -0
- package/dist/tui/slashCommandHighlight.js +8 -0
- package/dist/tui/theme.js +13 -0
- package/dist/tui/types.js +1 -0
- package/dist/usage.js +66 -0
- package/dist/version.js +5 -0
- package/node_modules/@remi/compact/dist/index.js +389 -0
- package/node_modules/@remi/compact/package.json +8 -0
- package/node_modules/@remi/config/dist/index.js +426 -0
- package/node_modules/@remi/config/package.json +8 -0
- package/node_modules/@remi/core/dist/contextBuilder.js +344 -0
- package/node_modules/@remi/core/dist/directoryOverview.js +359 -0
- package/node_modules/@remi/core/dist/index.js +2843 -0
- package/node_modules/@remi/core/dist/projectInstructions.js +123 -0
- package/node_modules/@remi/core/dist/responseStyles.js +98 -0
- package/node_modules/@remi/core/package.json +8 -0
- package/node_modules/@remi/llm/dist/index.js +804 -0
- package/node_modules/@remi/llm/package.json +8 -0
- package/node_modules/@remi/memory/dist/index.js +312 -0
- package/node_modules/@remi/memory/package.json +8 -0
- package/node_modules/@remi/permissions/dist/index.js +90 -0
- package/node_modules/@remi/permissions/package.json +8 -0
- package/node_modules/@remi/sessions/dist/index.js +370 -0
- package/node_modules/@remi/sessions/package.json +8 -0
- package/node_modules/@remi/skills/dist/index.js +273 -0
- package/node_modules/@remi/skills/package.json +8 -0
- package/node_modules/@remi/terminal-markdown/dist/index.js +1412 -0
- package/node_modules/@remi/terminal-markdown/package.json +8 -0
- package/node_modules/@remi/tools/dist/index.js +3875 -0
- package/node_modules/@remi/tools/package.json +8 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { ShellInlineCommand, StreamingMarkdown, StructuredDiffView, TerminalInlineMarkdown, TerminalMarkdown, applyTerminalSyntaxTheme, displayWidth, } from '@remi/terminal-markdown';
|
|
5
|
+
import { remiDarkTheme } from '../theme.js';
|
|
6
|
+
import { ActivityGlyph, ActivityText, AnimatedDots, FlowingText, activityBaseColor, useActivityTick, WorkingIndicator } from './WorkingIndicator.js';
|
|
7
|
+
import { formatPermissionFileActionFromParts } from '../../permissionDisplay.js';
|
|
8
|
+
import { executableSlashCommandPrefix } from '../slashCommandHighlight.js';
|
|
9
|
+
import { t } from '../../i18n.js';
|
|
10
|
+
const baseMarkdownTheme = {
|
|
11
|
+
text: remiDarkTheme.text,
|
|
12
|
+
muted: remiDarkTheme.muted,
|
|
13
|
+
accent: remiDarkTheme.accent,
|
|
14
|
+
success: remiDarkTheme.success,
|
|
15
|
+
danger: remiDarkTheme.danger,
|
|
16
|
+
warning: remiDarkTheme.warning,
|
|
17
|
+
emphasis: '#f4b8e4',
|
|
18
|
+
strong: '#ffffff',
|
|
19
|
+
strongEmphasis: '#c7a4ff',
|
|
20
|
+
orderedListMarker: '#c7a4ff',
|
|
21
|
+
quoteText: remiDarkTheme.muted,
|
|
22
|
+
diffAddBackground: '#123d28',
|
|
23
|
+
diffRemoveBackground: '#4a1e1e',
|
|
24
|
+
codeBackground: '#1f2226',
|
|
25
|
+
codeKeyword: '#c792ea',
|
|
26
|
+
codeString: '#a6e3a1',
|
|
27
|
+
codeNumber: '#f9e2af',
|
|
28
|
+
codeComment: remiDarkTheme.muted,
|
|
29
|
+
codeType: '#89ddff',
|
|
30
|
+
codeTitle: '#82aaff',
|
|
31
|
+
codeFunction: '#7aa2f7',
|
|
32
|
+
codeVariable: '#f2cdcd',
|
|
33
|
+
codeProperty: '#94e2d5',
|
|
34
|
+
codeOperator: '#89ddff',
|
|
35
|
+
codeMeta: '#f38ba8',
|
|
36
|
+
codeSection: '#fab387',
|
|
37
|
+
codeRegexp: '#fab387',
|
|
38
|
+
codePunctuation: '#bac2de',
|
|
39
|
+
};
|
|
40
|
+
const MarkdownThemeContext = createContext(baseMarkdownTheme);
|
|
41
|
+
function useMarkdownTheme() {
|
|
42
|
+
return useContext(MarkdownThemeContext);
|
|
43
|
+
}
|
|
44
|
+
function systemColor(level) {
|
|
45
|
+
if (level === 'error') {
|
|
46
|
+
return remiDarkTheme.danger;
|
|
47
|
+
}
|
|
48
|
+
if (level === 'warn') {
|
|
49
|
+
return remiDarkTheme.warning;
|
|
50
|
+
}
|
|
51
|
+
return remiDarkTheme.muted;
|
|
52
|
+
}
|
|
53
|
+
function toolStateColor(state) {
|
|
54
|
+
if (state === 'done') {
|
|
55
|
+
return remiDarkTheme.success;
|
|
56
|
+
}
|
|
57
|
+
if (state === 'failed' || state === 'denied') {
|
|
58
|
+
return remiDarkTheme.danger;
|
|
59
|
+
}
|
|
60
|
+
return remiDarkTheme.warning;
|
|
61
|
+
}
|
|
62
|
+
function MessageShell({ label, color, children, marginTop = 1, }) {
|
|
63
|
+
return (_jsxs(Box, { flexDirection: "row", marginTop: marginTop, children: [_jsx(Box, { width: 7, children: _jsx(Text, { color: color, bold: true, children: label }) }), _jsx(Box, { flexGrow: 1, flexDirection: "column", children: children })] }));
|
|
64
|
+
}
|
|
65
|
+
function MessageRow({ message, isFirst, width, language, slashCommandHints = [], }) {
|
|
66
|
+
const marginTop = isFirst ? 0 : 1;
|
|
67
|
+
const markdownTheme = useMarkdownTheme();
|
|
68
|
+
if (message.kind === 'user') {
|
|
69
|
+
return _jsx(UserMessage, { text: message.text, marginTop: marginTop, slashCommandHints: slashCommandHints });
|
|
70
|
+
}
|
|
71
|
+
if (message.kind === 'assistant') {
|
|
72
|
+
return _jsx(AssistantMessage, { text: message.text, streaming: Boolean(message.streaming), marginTop: marginTop, width: width });
|
|
73
|
+
}
|
|
74
|
+
if (message.kind === 'thinking') {
|
|
75
|
+
return (_jsx(MessageShell, { label: "think", color: remiDarkTheme.muted, marginTop: marginTop, children: _jsx(Text, { color: remiDarkTheme.muted, children: message.collapsed ? '[collapsed]' : message.text }) }));
|
|
76
|
+
}
|
|
77
|
+
if (message.kind === 'skill-use') {
|
|
78
|
+
return _jsx(SkillUseMessage, { message: message, marginTop: marginTop, language: language });
|
|
79
|
+
}
|
|
80
|
+
if (message.kind === 'model-panel') {
|
|
81
|
+
return _jsx(ModelPanel, { message: message, marginTop: marginTop });
|
|
82
|
+
}
|
|
83
|
+
if (message.kind === 'style-panel') {
|
|
84
|
+
return _jsx(StylePanel, { message: message, marginTop: marginTop });
|
|
85
|
+
}
|
|
86
|
+
if (message.kind === 'syntax-theme-panel') {
|
|
87
|
+
return _jsx(SyntaxThemePanel, { message: message, marginTop: marginTop, width: width });
|
|
88
|
+
}
|
|
89
|
+
if (message.kind === 'language-panel') {
|
|
90
|
+
return _jsx(LanguagePanel, { message: message, marginTop: marginTop });
|
|
91
|
+
}
|
|
92
|
+
if (message.kind === 'permission-profile-panel') {
|
|
93
|
+
return _jsx(PermissionProfilePanel, { message: message, marginTop: marginTop });
|
|
94
|
+
}
|
|
95
|
+
if (message.kind === 'skills-panel') {
|
|
96
|
+
return _jsx(SkillsPanel, { message: message, marginTop: marginTop, width: width });
|
|
97
|
+
}
|
|
98
|
+
if (message.kind === 'permission-request') {
|
|
99
|
+
return _jsx(PermissionRequestCard, { message: message, marginTop: marginTop });
|
|
100
|
+
}
|
|
101
|
+
if (message.kind === 'statusline-panel') {
|
|
102
|
+
return _jsx(StatusLinePanel, { message: message, marginTop: marginTop });
|
|
103
|
+
}
|
|
104
|
+
if (message.kind === 'tool-call' || message.kind === 'tool-result') {
|
|
105
|
+
return _jsx(ToolMessage, { message: message, marginTop: marginTop, width: width, language: language });
|
|
106
|
+
}
|
|
107
|
+
if (message.kind === 'plan-update') {
|
|
108
|
+
return _jsx(PlanMessage, { items: message.items, marginTop: marginTop, width: width });
|
|
109
|
+
}
|
|
110
|
+
if (message.kind === 'compact-boundary') {
|
|
111
|
+
return (_jsx(Box, { marginTop: marginTop, children: _jsxs(Text, { color: remiDarkTheme.muted, children: ["\u2022 ", message.summary] }) }));
|
|
112
|
+
}
|
|
113
|
+
if (message.kind === 'session-switch') {
|
|
114
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsx(Text, { color: remiDarkTheme.text, children: message.previousUsage }), _jsxs(Text, { color: remiDarkTheme.text, children: [message.label, " ", _jsx(Text, { color: remiDarkTheme.accent, children: message.sessionId })] })] }));
|
|
115
|
+
}
|
|
116
|
+
if (message.kind === 'task-summary') {
|
|
117
|
+
return _jsx(TaskSummary, { durationMs: message.durationMs, marginTop: marginTop, width: width, ...(message.label ? { label: message.label } : {}) });
|
|
118
|
+
}
|
|
119
|
+
if (message.kind === 'interrupted') {
|
|
120
|
+
return (_jsx(Box, { marginTop: marginTop, children: _jsxs(Text, { color: remiDarkTheme.danger, children: ["\u30FB ", message.text] }) }));
|
|
121
|
+
}
|
|
122
|
+
if (message.level === 'info') {
|
|
123
|
+
return (_jsx(Box, { marginTop: marginTop, children: _jsx(TerminalInlineMarkdown, { text: message.text, theme: markdownTheme }) }));
|
|
124
|
+
}
|
|
125
|
+
return (_jsx(MessageShell, { label: message.level, color: systemColor(message.level), marginTop: marginTop, children: _jsx(TerminalInlineMarkdown, { text: message.text, theme: { ...markdownTheme, text: systemColor(message.level) } }) }));
|
|
126
|
+
}
|
|
127
|
+
function SkillUseMessage({ message, marginTop, language, }) {
|
|
128
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsxs(Box, { children: [_jsx(Text, { color: remiDarkTheme.muted, children: "\u2022 " }), _jsxs(Text, { color: remiDarkTheme.accent, children: [t(language, 'skill.using'), " "] }), _jsx(Text, { color: remiDarkTheme.text, children: message.displayName })] }), _jsxs(Text, { color: remiDarkTheme.muted, children: [' ', "(", message.name, ", ", message.source, ") \u2014 ", message.filePath] })] }));
|
|
129
|
+
}
|
|
130
|
+
function MessageGroup({ messages, startIndex, width, language, slashCommandHints = [], }) {
|
|
131
|
+
if (messages.length === 1) {
|
|
132
|
+
const message = messages[0];
|
|
133
|
+
return message ? _jsx(MessageRow, { message: message, isFirst: startIndex === 0, width: width, language: language, slashCommandHints: slashCommandHints }) : null;
|
|
134
|
+
}
|
|
135
|
+
const first = messages[0];
|
|
136
|
+
if (first?.kind === 'tool-result' && messages.every(message => message.kind === 'tool-result')) {
|
|
137
|
+
return _jsx(ToolResultGroup, { messages: messages, marginTop: startIndex === 0 ? 0 : 1, width: width, language: language });
|
|
138
|
+
}
|
|
139
|
+
return (_jsx(_Fragment, { children: messages.map((message, offset) => (_jsx(MessageRow, { message: message, isFirst: startIndex + offset === 0, width: width, language: language, slashCommandHints: slashCommandHints }, `${message.kind}-${startIndex + offset}`))) }));
|
|
140
|
+
}
|
|
141
|
+
function UserMessage({ text, marginTop, slashCommandHints, }) {
|
|
142
|
+
const slashCommand = executableSlashCommandPrefix(text, slashCommandHints);
|
|
143
|
+
return (_jsxs(Box, { marginTop: marginTop, paddingX: 1, paddingY: 1, backgroundColor: remiDarkTheme.composerBackground, children: [_jsx(Text, { color: remiDarkTheme.muted, children: '> ' }), slashCommand ? (_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: remiDarkTheme.accent, children: slashCommand.command }), _jsx(Text, { color: remiDarkTheme.text, children: slashCommand.rest })] })) : (_jsx(Text, { color: remiDarkTheme.text, wrap: "wrap", children: text }))] }));
|
|
144
|
+
}
|
|
145
|
+
function AssistantMessage({ text, streaming, marginTop, width }) {
|
|
146
|
+
const markdownTheme = useMarkdownTheme();
|
|
147
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: marginTop, children: streaming ? _jsx(StreamingMarkdown, { text: text, theme: markdownTheme, width: width, paddingX: 2 }) : _jsx(TerminalMarkdown, { text: text, theme: markdownTheme, width: width, paddingX: 2 }) }));
|
|
148
|
+
}
|
|
149
|
+
export function PlanMessage({ items, marginTop, width, }) {
|
|
150
|
+
const panelWidth = width === undefined ? undefined : Math.max(20, width - 4);
|
|
151
|
+
const itemWidth = Math.max(12, (panelWidth ?? width ?? 100) - 8);
|
|
152
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, marginX: 2, paddingX: 1, paddingY: 1, backgroundColor: baseMarkdownTheme.codeBackground, width: panelWidth, children: [_jsxs(Text, { children: [_jsx(Text, { color: remiDarkTheme.accent, children: "\u2022 " }), _jsx(Text, { color: remiDarkTheme.accent, children: "Plan" })] }), items.map((item, index) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: remiDarkTheme.muted, children: index === items.length - 1 ? '└ ' : '├ ' }), _jsx(PlanItemMarker, { status: item.status }), _jsx(Text, { color: planItemTextColor(item.status), bold: planItemTextBold(item.status), strikethrough: item.status === 'completed', wrap: "truncate-end", children: truncateTextToWidth(item.content, itemWidth) })] }, item.id ?? `${index}-${item.content}`)))] }));
|
|
153
|
+
}
|
|
154
|
+
export function planItemTextColor(status) {
|
|
155
|
+
return status === 'in_progress' ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
156
|
+
}
|
|
157
|
+
export function planItemTextBold(_status) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
function PlanItemMarker({ status }) {
|
|
161
|
+
if (status === 'completed') {
|
|
162
|
+
return _jsx(Text, { color: planItemMarkerColor(status), children: "\u2713 " });
|
|
163
|
+
}
|
|
164
|
+
if (status === 'in_progress') {
|
|
165
|
+
return (_jsxs(Text, { color: planItemMarkerColor(status), bold: planItemMarkerBold(status), children: ["\u25B8", ' '] }));
|
|
166
|
+
}
|
|
167
|
+
return _jsx(Text, { color: planItemMarkerColor(status), children: "\u25A1 " });
|
|
168
|
+
}
|
|
169
|
+
export function planItemMarkerColor(status) {
|
|
170
|
+
if (status === 'completed') {
|
|
171
|
+
return remiDarkTheme.success;
|
|
172
|
+
}
|
|
173
|
+
if (status === 'in_progress') {
|
|
174
|
+
return remiDarkTheme.accent;
|
|
175
|
+
}
|
|
176
|
+
return remiDarkTheme.muted;
|
|
177
|
+
}
|
|
178
|
+
export function planItemMarkerBold(_status) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
function ToolMessage({ message, marginTop, width, language, }) {
|
|
182
|
+
if (message.state === 'running') {
|
|
183
|
+
if (isShellTool(message.toolName)) {
|
|
184
|
+
return _jsx(ShellRunningToolMessage, { message: message, marginTop: marginTop, width: width, language: language });
|
|
185
|
+
}
|
|
186
|
+
return (_jsx(Box, { flexDirection: "row", marginTop: marginTop, children: _jsx(ToolRunningActionText, { toolName: message.toolName, summary: message.summary, language: language }) }));
|
|
187
|
+
}
|
|
188
|
+
if (message.state === 'done') {
|
|
189
|
+
if (isShellTool(message.toolName)) {
|
|
190
|
+
return _jsx(ShellToolMessage, { message: message, marginTop: marginTop, width: width, language: language });
|
|
191
|
+
}
|
|
192
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsxs(Text, { children: [_jsx(Text, { color: remiDarkTheme.accent, children: "\u2022 " }), _jsx(ToolResultActionText, { message: message, language: language })] }), _jsx(ToolResultDetail, { toolName: message.toolName, summary: message.summary, detail: message.kind === 'tool-result' ? message.detail : undefined, width: width })] }));
|
|
193
|
+
}
|
|
194
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsxs(Text, { children: [_jsx(Text, { color: toolStateColor(message.state), children: "\u2022 " }), _jsx(Text, { color: toolStateColor(message.state), bold: toolStateLabelBold(message.state), children: toolStateLabel(message.state, language) })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: remiDarkTheme.muted, children: "\u2514 " }), _jsx(ToolActionText, { toolName: message.toolName, summary: message.summary, color: remiDarkTheme.muted, language: language })] }), message.kind === 'tool-result' && message.detail ? (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: remiDarkTheme.muted, children: message.detail }) })) : null] }));
|
|
195
|
+
}
|
|
196
|
+
function ShellRunningToolMessage({ message, marginTop, width, language, }) {
|
|
197
|
+
const markdownTheme = useMarkdownTheme();
|
|
198
|
+
const command = commandForToolMessage(message);
|
|
199
|
+
const commandLines = compactWrappedLines(command, commandPreviewWidth(width), 2);
|
|
200
|
+
const firstLine = commandLines[0] ?? command;
|
|
201
|
+
const continuationLines = commandLines.slice(1);
|
|
202
|
+
const elapsed = formatRunningElapsed(message.kind === 'tool-call' ? message.startedAt : undefined);
|
|
203
|
+
const tick = useActivityTick(true, message.callId ?? `${message.toolName}-${message.summary}`);
|
|
204
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsxs(Text, { children: [_jsx(ActivityGlyph, { tick: tick }), _jsx(Text, { color: activityBaseColor, children: " " }), _jsx(FlowingText, { text: toolRunningOperationLabel('running', language), tick: tick }), _jsx(Text, { color: remiDarkTheme.muted, children: " " }), _jsx(ShellInlineCommand, { command: firstLine, theme: markdownTheme }), _jsx(AnimatedDots, { tick: tick }), _jsxs(Text, { color: remiDarkTheme.muted, children: [" (", elapsed, " \u00B7 ", t(language, 'working.interrupt'), ")"] })] }), continuationLines.map((line, index) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: remiDarkTheme.muted, children: "\u2502 " }), _jsx(ShellInlineCommand, { command: line, theme: markdownTheme })] }, `${index}-${line}`)))] }));
|
|
205
|
+
}
|
|
206
|
+
function ShellToolMessage({ message, marginTop, width, language, }) {
|
|
207
|
+
const markdownTheme = useMarkdownTheme();
|
|
208
|
+
const command = commandForToolMessage(message);
|
|
209
|
+
const exitCode = message.kind === 'tool-result' ? parseCommandExitCode(message.summary) : undefined;
|
|
210
|
+
const commandLines = compactWrappedLines(command, commandPreviewWidth(width), 2);
|
|
211
|
+
const firstLine = commandLines[0] ?? command;
|
|
212
|
+
const continuationLines = commandLines.slice(1);
|
|
213
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsxs(Text, { children: [_jsx(Text, { color: remiDarkTheme.accent, children: "\u2022 " }), _jsx(Text, { color: remiDarkTheme.accent, children: toolOperationLabel('run', language) }), _jsx(Text, { color: remiDarkTheme.muted, children: " " }), _jsx(ShellInlineCommand, { command: firstLine, theme: markdownTheme }), exitCode !== undefined && exitCode !== 0 ? _jsxs(Text, { color: remiDarkTheme.danger, children: [" (exit ", exitCode, ")"] }) : null] }), continuationLines.map((line, index) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: remiDarkTheme.muted, children: "\u2502 " }), _jsx(ShellInlineCommand, { command: line, theme: markdownTheme })] }, `${index}-${line}`))), _jsx(ShellOutputDetail, { detail: message.kind === 'tool-result' ? message.detail : undefined, width: width })] }));
|
|
214
|
+
}
|
|
215
|
+
function ToolResultGroup({ messages, marginTop, width, language, }) {
|
|
216
|
+
const state = messages[0]?.state ?? 'done';
|
|
217
|
+
const failed = state === 'failed' || state === 'denied';
|
|
218
|
+
const bulletColor = failed ? toolStateColor(state) : remiDarkTheme.accent;
|
|
219
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsxs(Text, { children: [_jsx(Text, { color: bulletColor, children: "\u2022 " }), _jsx(ToolGroupTitle, { messages: messages, language: language })] }), messages.map((message, index) => isShellTool(message.toolName) ? (_jsx(ShellToolGroupItem, { message: message, failed: failed, isLast: index === messages.length - 1, width: width }, `${message.toolName}-${message.callId ?? index}`)) : (_jsxs(Box, { flexDirection: "column", marginTop: shouldSeparateToolGroupItem(messages, index) ? 1 : 0, children: [_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: remiDarkTheme.muted, children: index === messages.length - 1 ? '└ ' : '├ ' }), _jsx(ToolGroupItemText, { message: message, failed: failed, language: language })] }) }), _jsx(ToolResultDetail, { toolName: message.toolName, summary: message.summary, detail: message.detail, width: width })] }, `${message.toolName}-${message.callId ?? index}`)))] }));
|
|
220
|
+
}
|
|
221
|
+
function shouldSeparateToolGroupItem(messages, index) {
|
|
222
|
+
if (index === 0) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return Boolean(messages[index - 1]?.detail?.trim());
|
|
226
|
+
}
|
|
227
|
+
function ShellToolGroupItem({ message, failed, isLast, width, }) {
|
|
228
|
+
const markdownTheme = useMarkdownTheme();
|
|
229
|
+
const command = commandForToolMessage(message);
|
|
230
|
+
const commandLines = compactWrappedLines(command, commandPreviewWidth(width), 2);
|
|
231
|
+
const firstLine = commandLines[0] ?? command;
|
|
232
|
+
const continuationLines = commandLines.slice(1);
|
|
233
|
+
const outputLines = shellOutputLines(message.detail, width);
|
|
234
|
+
const branch = isLast ? '└ ' : '├ ';
|
|
235
|
+
const continuation = isLast ? ' ' : '│ ';
|
|
236
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: failed ? toolStateColor(message.state) : remiDarkTheme.muted, children: branch }), _jsx(ShellInlineCommand, { command: firstLine, theme: markdownTheme })] }) }), continuationLines.map((line, index) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: remiDarkTheme.muted, children: continuation }), _jsx(ShellInlineCommand, { command: line, theme: markdownTheme })] }) }, `command-${index}-${line}`))), outputLines.map((line, index) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: remiDarkTheme.muted, wrap: "truncate-end", children: [continuation, line.length > 0 ? line : ' '] }) }, `output-${index}-${line}`)))] }));
|
|
237
|
+
}
|
|
238
|
+
function ToolGroupTitle({ messages, language }) {
|
|
239
|
+
const first = messages[0];
|
|
240
|
+
if (!first) {
|
|
241
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: toolOperationLabel('done', language) });
|
|
242
|
+
}
|
|
243
|
+
if (first.state === 'failed' || first.state === 'denied') {
|
|
244
|
+
return _jsx(Text, { color: toolStateColor(first.state), children: toolStateLabel(first.state, language) });
|
|
245
|
+
}
|
|
246
|
+
if (messages.length === 1) {
|
|
247
|
+
return _jsx(ToolResultActionText, { message: first, language: language });
|
|
248
|
+
}
|
|
249
|
+
const operation = toolResultOperation(first);
|
|
250
|
+
if (operation === 'list') {
|
|
251
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('list', language), rest: toolCountRest(messages.length, 'directory', language) });
|
|
252
|
+
}
|
|
253
|
+
if (operation === 'read') {
|
|
254
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('read', language), rest: toolCountRest(messages.length, 'file', language) });
|
|
255
|
+
}
|
|
256
|
+
if (operation === 'search') {
|
|
257
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('search', language), rest: toolCountRest(messages.length, 'query', language) });
|
|
258
|
+
}
|
|
259
|
+
if (operation === 'find') {
|
|
260
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('find', language), rest: toolCountRest(messages.length, 'pattern', language) });
|
|
261
|
+
}
|
|
262
|
+
if (operation === 'create') {
|
|
263
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('create', language), rest: toolCountRest(messages.length, 'directory', language) });
|
|
264
|
+
}
|
|
265
|
+
if (operation === 'edit') {
|
|
266
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('edit', language), rest: toolCountRest(messages.length, 'file', language) });
|
|
267
|
+
}
|
|
268
|
+
if (operation === 'run') {
|
|
269
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('run', language), rest: toolCountRest(messages.length, 'command', language) });
|
|
270
|
+
}
|
|
271
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel('completed', language), rest: toolCountRest(messages.length, 'tool', language) });
|
|
272
|
+
}
|
|
273
|
+
function OperationTitle({ operation, rest, animated = false, tick = 0 }) {
|
|
274
|
+
return (_jsxs(_Fragment, { children: [animated ? _jsx(FlowingText, { text: operation, tick: tick }) : _jsx(Text, { color: remiDarkTheme.accent, children: operation }), _jsxs(Text, { color: remiDarkTheme.muted, children: [" ", rest] }), animated ? _jsx(AnimatedDots, { tick: tick }) : null] }));
|
|
275
|
+
}
|
|
276
|
+
function toolOperationLabel(operation, language) {
|
|
277
|
+
return t(language, `tool.action.${operation}`);
|
|
278
|
+
}
|
|
279
|
+
function toolRunningOperationLabel(operation, language) {
|
|
280
|
+
return t(language, `tool.running.${operation}`);
|
|
281
|
+
}
|
|
282
|
+
function toolCountRest(count, kind, language) {
|
|
283
|
+
const plurality = count === 1 ? 'one' : 'other';
|
|
284
|
+
return `${count} ${t(language, `tool.count.${kind}.${plurality}`)}`;
|
|
285
|
+
}
|
|
286
|
+
function toolOperationKeyFromCanonical(operation) {
|
|
287
|
+
if (operation === 'list') {
|
|
288
|
+
return 'list';
|
|
289
|
+
}
|
|
290
|
+
if (operation === 'read') {
|
|
291
|
+
return 'read';
|
|
292
|
+
}
|
|
293
|
+
if (operation === 'search') {
|
|
294
|
+
return 'search';
|
|
295
|
+
}
|
|
296
|
+
if (operation === 'find') {
|
|
297
|
+
return 'find';
|
|
298
|
+
}
|
|
299
|
+
if (operation === 'create') {
|
|
300
|
+
return 'create';
|
|
301
|
+
}
|
|
302
|
+
if (operation === 'exists') {
|
|
303
|
+
return 'exists';
|
|
304
|
+
}
|
|
305
|
+
if (operation === 'write') {
|
|
306
|
+
return 'write';
|
|
307
|
+
}
|
|
308
|
+
if (operation === 'edit') {
|
|
309
|
+
return 'edit';
|
|
310
|
+
}
|
|
311
|
+
if (operation === 'delete') {
|
|
312
|
+
return 'delete';
|
|
313
|
+
}
|
|
314
|
+
if (operation === 'run') {
|
|
315
|
+
return 'run';
|
|
316
|
+
}
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
function ToolGroupItemText({ message, failed, language }) {
|
|
320
|
+
const markdownTheme = useMarkdownTheme();
|
|
321
|
+
if (failed) {
|
|
322
|
+
return _jsx(ToolActionText, { toolName: message.toolName, summary: message.summary, color: remiDarkTheme.muted, language: language });
|
|
323
|
+
}
|
|
324
|
+
if (message.toolName === 'read_file' || message.toolName === 'list_files') {
|
|
325
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: toolPathFromInputSummary(message.inputSummary) ?? extractToolTarget(message.summary) });
|
|
326
|
+
}
|
|
327
|
+
const edited = parseEditedWriteSummary(message.summary);
|
|
328
|
+
if ((message.toolName === 'edit_file' || message.toolName === 'write_file') && edited) {
|
|
329
|
+
return _jsx(EditedPathText, { path: edited.path, addedLines: edited.addedLines, removedLines: edited.removedLines, color: remiDarkTheme.muted });
|
|
330
|
+
}
|
|
331
|
+
if (message.toolName === 'create_directory') {
|
|
332
|
+
const created = parseCreateDirectorySummary(message.summary);
|
|
333
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: created?.path ?? extractToolTarget(message.summary) });
|
|
334
|
+
}
|
|
335
|
+
if (message.toolName === 'delete_file' || message.toolName === 'delete_directory') {
|
|
336
|
+
const deleted = parseDeleteFileSummary(message.summary);
|
|
337
|
+
if (message.toolName === 'delete_file' && deleted?.addedLines !== undefined && deleted.removedLines !== undefined) {
|
|
338
|
+
return _jsx(EditedPathText, { path: deleted.path, addedLines: deleted.addedLines, removedLines: deleted.removedLines, color: remiDarkTheme.muted });
|
|
339
|
+
}
|
|
340
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: deleted?.path ?? extractToolTarget(message.summary) });
|
|
341
|
+
}
|
|
342
|
+
if (message.toolName === 'search_text' || message.toolName === 'glob') {
|
|
343
|
+
return _jsx(SearchResultText, { toolName: message.toolName, inputSummary: message.inputSummary, resultSummary: message.summary, includeOperation: true, language: language });
|
|
344
|
+
}
|
|
345
|
+
if (message.toolName === 'run_command' || message.toolName === 'execute_shell') {
|
|
346
|
+
return _jsx(ShellInlineCommand, { command: commandForToolMessage(message), theme: markdownTheme });
|
|
347
|
+
}
|
|
348
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: extractToolTarget(message.summary) });
|
|
349
|
+
}
|
|
350
|
+
function ToolResultActionText({ message, language, }) {
|
|
351
|
+
if (message.kind === 'tool-result' && (message.toolName === 'search_text' || message.toolName === 'glob')) {
|
|
352
|
+
return _jsx(SearchResultText, { toolName: message.toolName, inputSummary: message.inputSummary, resultSummary: message.summary, includeOperation: true, language: language });
|
|
353
|
+
}
|
|
354
|
+
if (message.kind === 'tool-result' && (message.toolName === 'read_file' || message.toolName === 'list_files')) {
|
|
355
|
+
const target = toolPathFromInputSummary(message.inputSummary);
|
|
356
|
+
if (target) {
|
|
357
|
+
return _jsx(ToolActionText, { toolName: message.toolName, summary: target, language: language });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (message.toolName === 'run_command' || message.toolName === 'execute_shell') {
|
|
361
|
+
return _jsx(ToolRunActionText, { message: message, language: language });
|
|
362
|
+
}
|
|
363
|
+
return _jsx(ToolActionText, { toolName: message.toolName, summary: message.summary, language: language });
|
|
364
|
+
}
|
|
365
|
+
function ToolRunActionText({ message, language, }) {
|
|
366
|
+
const exitCode = message.kind === 'tool-result' ? parseCommandExitCode(message.summary) : undefined;
|
|
367
|
+
const markdownTheme = useMarkdownTheme();
|
|
368
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { color: remiDarkTheme.accent, children: toolOperationLabel('run', language) }), _jsx(Text, { color: remiDarkTheme.muted, children: " " }), _jsx(ShellInlineCommand, { command: commandForToolMessage(message), theme: markdownTheme }), exitCode !== undefined && exitCode !== 0 ? _jsxs(Text, { color: remiDarkTheme.danger, children: [" (exit ", exitCode, ")"] }) : null] }));
|
|
369
|
+
}
|
|
370
|
+
function SearchResultText({ toolName, inputSummary, resultSummary, includeOperation, language, }) {
|
|
371
|
+
if (!inputSummary) {
|
|
372
|
+
return _jsx(ToolActionText, { toolName: toolName, summary: resultSummary, language: language });
|
|
373
|
+
}
|
|
374
|
+
const target = formatSearchTarget(inputSummary ?? extractToolTarget(resultSummary));
|
|
375
|
+
const result = formatSearchResultSummary(resultSummary, toolName, language);
|
|
376
|
+
const rest = result ? `${target} — ${result}` : target;
|
|
377
|
+
if (includeOperation) {
|
|
378
|
+
return _jsx(OperationTitle, { operation: toolOperationLabel(toolName === 'glob' ? 'find' : 'search', language), rest: rest });
|
|
379
|
+
}
|
|
380
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: rest });
|
|
381
|
+
}
|
|
382
|
+
function formatRunningElapsed(startedAt) {
|
|
383
|
+
if (startedAt === undefined) {
|
|
384
|
+
return '1s';
|
|
385
|
+
}
|
|
386
|
+
return formatTaskDuration(Date.now() - startedAt);
|
|
387
|
+
}
|
|
388
|
+
function formatSearchTarget(inputSummary) {
|
|
389
|
+
const matchingIndex = inputSummary.indexOf(' matching ');
|
|
390
|
+
const beforeMatching = matchingIndex >= 0 ? inputSummary.slice(0, matchingIndex) : inputSummary;
|
|
391
|
+
const matching = matchingIndex >= 0 ? inputSummary.slice(matchingIndex) : '';
|
|
392
|
+
const inIndex = beforeMatching.indexOf(' in ');
|
|
393
|
+
const query = inIndex >= 0 ? beforeMatching.slice(0, inIndex) : beforeMatching;
|
|
394
|
+
const scope = inIndex >= 0 ? beforeMatching.slice(inIndex) : '';
|
|
395
|
+
return `"${query}"${scope}${matching}`;
|
|
396
|
+
}
|
|
397
|
+
function formatSearchResultSummary(summary, toolName, language) {
|
|
398
|
+
const match = /^(\d+)\s+matches(?:\s+in\s+([^:]+))?/i.exec(summary.trim());
|
|
399
|
+
if (!match?.[1]) {
|
|
400
|
+
return summary;
|
|
401
|
+
}
|
|
402
|
+
const label = t(language, toolName === 'glob' ? 'tool.result.paths' : 'tool.result.matches');
|
|
403
|
+
if (language === 'zh-Hans') {
|
|
404
|
+
return match[2] ? `${match[1]} ${label},位于 ${match[2]}` : `${match[1]} ${label}`;
|
|
405
|
+
}
|
|
406
|
+
return match[2] ? `${match[1]} ${label} in ${match[2]}` : `${match[1]} ${label}`;
|
|
407
|
+
}
|
|
408
|
+
function ToolRunningActionText({ toolName, summary, language }) {
|
|
409
|
+
const label = toolRunningActionLabel(toolName, summary);
|
|
410
|
+
if (label === 'Updating plan') {
|
|
411
|
+
const tick = useActivityTick(true, `${toolName}-${summary}`);
|
|
412
|
+
return _jsx(ActivityText, { label: toolRunningOperationLabel('updatingPlan', language), tick: tick });
|
|
413
|
+
}
|
|
414
|
+
const parsed = splitToolAction(label, language);
|
|
415
|
+
const tick = useActivityTick(true, `${toolName}-${summary}`);
|
|
416
|
+
if (parsed) {
|
|
417
|
+
return _jsx(OperationTitle, { operation: parsed.operation, rest: parsed.rest, tick: tick, animated: true });
|
|
418
|
+
}
|
|
419
|
+
return _jsx(ActivityText, { label: label, tick: tick });
|
|
420
|
+
}
|
|
421
|
+
function ToolActionText({ toolName, summary, color = remiDarkTheme.muted, language, }) {
|
|
422
|
+
const action = toolActionLabel(toolName, summary);
|
|
423
|
+
if (action === 'Plan update') {
|
|
424
|
+
return _jsx(Text, { color: remiDarkTheme.accent, children: toolOperationLabel('planUpdate', language) });
|
|
425
|
+
}
|
|
426
|
+
const edited = /^(?:Edit|Edited)\s+(.+?)\s+\(\+(\d+)\s+-(\d+)\)$/.exec(action);
|
|
427
|
+
if (edited?.[1] && edited[2] !== undefined && edited[3] !== undefined) {
|
|
428
|
+
return _jsx(EditedPathText, { path: edited[1], addedLines: Number(edited[2]), removedLines: Number(edited[3]), color: color, prefix: toolOperationLabel('edit', language) });
|
|
429
|
+
}
|
|
430
|
+
const deleted = /^(?:Delete|Deleted)\s+(.+?)\s+\(\+(\d+)\s+-(\d+)\)$/.exec(action);
|
|
431
|
+
if (deleted?.[1] && deleted[2] !== undefined && deleted[3] !== undefined) {
|
|
432
|
+
return _jsx(EditedPathText, { path: deleted[1], addedLines: Number(deleted[2]), removedLines: Number(deleted[3]), color: color, prefix: toolOperationLabel('delete', language) });
|
|
433
|
+
}
|
|
434
|
+
const parsed = splitToolAction(action, language);
|
|
435
|
+
if (parsed) {
|
|
436
|
+
return _jsx(OperationTitle, { operation: parsed.operation, rest: parsed.rest });
|
|
437
|
+
}
|
|
438
|
+
return _jsx(Text, { color: color, children: action });
|
|
439
|
+
}
|
|
440
|
+
function EditedPathText({ path, addedLines, removedLines, color, prefix = '', }) {
|
|
441
|
+
const operation = prefix.trim();
|
|
442
|
+
return (_jsxs(_Fragment, { children: [operation ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: remiDarkTheme.accent, children: operation }), _jsx(Text, { color: color, children: " " })] })) : null, _jsxs(Text, { color: color, children: [path, " ("] }), _jsxs(Text, { color: remiDarkTheme.success, children: ["+", addedLines] }), _jsx(Text, { color: color, children: " " }), _jsxs(Text, { color: remiDarkTheme.danger, children: ["-", removedLines] }), _jsx(Text, { color: color, children: ")" })] }));
|
|
443
|
+
}
|
|
444
|
+
function splitToolAction(action, language) {
|
|
445
|
+
const match = /^(List|Read|Search|Find|Create|Exists|Write|Edit|Delete|Run|Edited|Deleted|Ran|Listing|Reading|Searching|Finding|Creating|Writing|Editing|Running|Deleting)\s+(.+)$/i.exec(action);
|
|
446
|
+
if (!match?.[1] || !match[2]) {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
operation: normalizeOperationLabel(match[1], language),
|
|
451
|
+
rest: match[2],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function normalizeOperationLabel(operation, language) {
|
|
455
|
+
const normalized = operation.toLowerCase();
|
|
456
|
+
if (normalized === 'ran') {
|
|
457
|
+
return toolOperationLabel('run', language);
|
|
458
|
+
}
|
|
459
|
+
if (normalized === 'edited') {
|
|
460
|
+
return toolOperationLabel('edit', language);
|
|
461
|
+
}
|
|
462
|
+
if (normalized === 'deleted') {
|
|
463
|
+
return toolOperationLabel('delete', language);
|
|
464
|
+
}
|
|
465
|
+
if (normalized === 'listing') {
|
|
466
|
+
return toolRunningOperationLabel('listing', language);
|
|
467
|
+
}
|
|
468
|
+
if (normalized === 'reading') {
|
|
469
|
+
return toolRunningOperationLabel('reading', language);
|
|
470
|
+
}
|
|
471
|
+
if (normalized === 'searching') {
|
|
472
|
+
return toolRunningOperationLabel('searching', language);
|
|
473
|
+
}
|
|
474
|
+
if (normalized === 'finding') {
|
|
475
|
+
return toolRunningOperationLabel('finding', language);
|
|
476
|
+
}
|
|
477
|
+
if (normalized === 'creating') {
|
|
478
|
+
return toolRunningOperationLabel('creating', language);
|
|
479
|
+
}
|
|
480
|
+
if (normalized === 'writing') {
|
|
481
|
+
return toolRunningOperationLabel('writing', language);
|
|
482
|
+
}
|
|
483
|
+
if (normalized === 'editing') {
|
|
484
|
+
return toolRunningOperationLabel('editing', language);
|
|
485
|
+
}
|
|
486
|
+
if (normalized === 'deleting') {
|
|
487
|
+
return toolRunningOperationLabel('deleting', language);
|
|
488
|
+
}
|
|
489
|
+
if (normalized === 'running') {
|
|
490
|
+
return toolRunningOperationLabel('running', language);
|
|
491
|
+
}
|
|
492
|
+
const operationKey = toolOperationKeyFromCanonical(normalized);
|
|
493
|
+
if (operationKey) {
|
|
494
|
+
return toolOperationLabel(operationKey, language);
|
|
495
|
+
}
|
|
496
|
+
return `${normalized.slice(0, 1).toUpperCase()}${normalized.slice(1)}`;
|
|
497
|
+
}
|
|
498
|
+
function ToolResultDetail({ toolName, summary, detail, width }) {
|
|
499
|
+
const markdownTheme = useMarkdownTheme();
|
|
500
|
+
if (isShellTool(toolName)) {
|
|
501
|
+
return _jsx(ShellOutputDetail, { detail: detail, width: width });
|
|
502
|
+
}
|
|
503
|
+
if (!detail || detail.trim().length === 0) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
const language = toolName === 'edit_file' || toolName === 'write_file' || toolName === 'delete_file'
|
|
507
|
+
? languageFromPath(parseEditedWriteSummary(summary)?.path ?? parseDeleteFileSummary(summary)?.path ?? extractToolTarget(summary))
|
|
508
|
+
: undefined;
|
|
509
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(StructuredDiffView, { diff: detail, theme: markdownTheme, width: width, ...(language ? { language } : {}) }) }));
|
|
510
|
+
}
|
|
511
|
+
function ShellOutputDetail({ detail, width }) {
|
|
512
|
+
const lines = shellOutputLines(detail, width);
|
|
513
|
+
return (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: lines.map((line, index) => (_jsxs(Text, { color: remiDarkTheme.muted, wrap: "truncate-end", children: [index === 0 ? '└ ' : ' ', line.length > 0 ? line : ' '] }, `${index}-${line}`))) }));
|
|
514
|
+
}
|
|
515
|
+
function shellOutputLines(detail, width) {
|
|
516
|
+
const text = detail && detail.trim().length > 0 ? detail.trim() : '(no output)';
|
|
517
|
+
return compactExistingLines(text, shellOutputWidth(width), 2);
|
|
518
|
+
}
|
|
519
|
+
function isShellTool(toolName) {
|
|
520
|
+
return toolName === 'run_command' || toolName === 'execute_shell';
|
|
521
|
+
}
|
|
522
|
+
function commandPreviewWidth(width) {
|
|
523
|
+
return width === undefined ? 96 : Math.max(24, width - 8);
|
|
524
|
+
}
|
|
525
|
+
function shellOutputWidth(width) {
|
|
526
|
+
return width === undefined ? 96 : Math.max(24, width - 4);
|
|
527
|
+
}
|
|
528
|
+
function compactWrappedLines(value, width, maxLines) {
|
|
529
|
+
const text = value.replace(/\s*\r?\n\s*/g, ' ').trim();
|
|
530
|
+
if (text.length === 0) {
|
|
531
|
+
return [''];
|
|
532
|
+
}
|
|
533
|
+
const lines = [];
|
|
534
|
+
let remaining = text;
|
|
535
|
+
while (remaining.length > 0 && lines.length < maxLines) {
|
|
536
|
+
if (lines.length === maxLines - 1) {
|
|
537
|
+
lines.push(truncateTextToWidth(remaining, width));
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
const { line, rest } = takeWrappedLine(remaining, width);
|
|
541
|
+
lines.push(line);
|
|
542
|
+
remaining = rest;
|
|
543
|
+
}
|
|
544
|
+
return lines;
|
|
545
|
+
}
|
|
546
|
+
function compactExistingLines(value, width, maxLines) {
|
|
547
|
+
const rawLines = value.split(/\r?\n/);
|
|
548
|
+
const visibleLines = rawLines.slice(0, maxLines).map(line => truncateTextToWidth(line, width));
|
|
549
|
+
if (visibleLines.length === 0) {
|
|
550
|
+
return [''];
|
|
551
|
+
}
|
|
552
|
+
if (rawLines.length > maxLines) {
|
|
553
|
+
const lastIndex = visibleLines.length - 1;
|
|
554
|
+
visibleLines[lastIndex] = appendEllipsis(visibleLines[lastIndex] ?? '', width);
|
|
555
|
+
}
|
|
556
|
+
return visibleLines;
|
|
557
|
+
}
|
|
558
|
+
function takeWrappedLine(value, width) {
|
|
559
|
+
if (displayWidth(value) <= width) {
|
|
560
|
+
return { line: value, rest: '' };
|
|
561
|
+
}
|
|
562
|
+
const chars = Array.from(value);
|
|
563
|
+
let usedWidth = 0;
|
|
564
|
+
let cutIndex = 0;
|
|
565
|
+
let lastSpaceIndex = -1;
|
|
566
|
+
for (let index = 0; index < chars.length; index += 1) {
|
|
567
|
+
const char = chars[index] ?? '';
|
|
568
|
+
const nextWidth = usedWidth + displayWidth(char);
|
|
569
|
+
if (nextWidth > width) {
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
usedWidth = nextWidth;
|
|
573
|
+
cutIndex = index + 1;
|
|
574
|
+
if (/\s/.test(char)) {
|
|
575
|
+
lastSpaceIndex = index;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const shellBreakIndex = preferredShellBreakIndex(chars.slice(0, cutIndex).join(''));
|
|
579
|
+
const breakIndex = shellBreakIndex ?? (lastSpaceIndex > 0 ? lastSpaceIndex : Math.max(1, cutIndex));
|
|
580
|
+
return {
|
|
581
|
+
line: chars.slice(0, breakIndex).join('').trimEnd(),
|
|
582
|
+
rest: chars.slice(breakIndex).join('').trimStart(),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function preferredShellBreakIndex(prefix) {
|
|
586
|
+
const candidates = [' && ', ' ; '];
|
|
587
|
+
let breakIndex = -1;
|
|
588
|
+
for (const candidate of candidates) {
|
|
589
|
+
const index = prefix.lastIndexOf(candidate);
|
|
590
|
+
if (index > 0) {
|
|
591
|
+
breakIndex = Math.max(breakIndex, index + candidate.length);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return breakIndex > 0 ? breakIndex : undefined;
|
|
595
|
+
}
|
|
596
|
+
function appendEllipsis(value, width) {
|
|
597
|
+
const suffix = ' ...';
|
|
598
|
+
if (displayWidth(value) + displayWidth(suffix) <= width) {
|
|
599
|
+
return `${value}${suffix}`;
|
|
600
|
+
}
|
|
601
|
+
return truncateTextToWidth(value, width);
|
|
602
|
+
}
|
|
603
|
+
function truncateTextToWidth(value, width) {
|
|
604
|
+
const ellipsis = '...';
|
|
605
|
+
if (displayWidth(value) <= width) {
|
|
606
|
+
return value;
|
|
607
|
+
}
|
|
608
|
+
const contentWidth = Math.max(1, width - displayWidth(ellipsis));
|
|
609
|
+
let usedWidth = 0;
|
|
610
|
+
let result = '';
|
|
611
|
+
for (const char of value) {
|
|
612
|
+
const charWidth = displayWidth(char);
|
|
613
|
+
if (usedWidth + charWidth > contentWidth) {
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
result += char;
|
|
617
|
+
usedWidth += charWidth;
|
|
618
|
+
}
|
|
619
|
+
return `${result.trimEnd()}${ellipsis}`;
|
|
620
|
+
}
|
|
621
|
+
function toolActionLabel(toolName, summary) {
|
|
622
|
+
if (toolName === 'list_files') {
|
|
623
|
+
return `List ${extractToolTarget(summary)}`;
|
|
624
|
+
}
|
|
625
|
+
if (toolName === 'read_file') {
|
|
626
|
+
return `Read ${extractToolTarget(summary)}`;
|
|
627
|
+
}
|
|
628
|
+
if (toolName === 'search_text') {
|
|
629
|
+
return `Search ${extractToolTarget(summary)}`;
|
|
630
|
+
}
|
|
631
|
+
if (toolName === 'glob') {
|
|
632
|
+
return `Find ${extractToolTarget(summary)}`;
|
|
633
|
+
}
|
|
634
|
+
if (toolName === 'todo_write') {
|
|
635
|
+
return 'Plan update';
|
|
636
|
+
}
|
|
637
|
+
if (toolName === 'create_directory') {
|
|
638
|
+
const created = parseCreateDirectorySummary(summary);
|
|
639
|
+
if (created) {
|
|
640
|
+
return `${created.created ? 'Create' : 'Exists'} ${created.path}`;
|
|
641
|
+
}
|
|
642
|
+
return `Create ${extractToolTarget(summary)}`;
|
|
643
|
+
}
|
|
644
|
+
if (toolName === 'edit_file' || toolName === 'write_file') {
|
|
645
|
+
const edited = parseEditedWriteSummary(summary);
|
|
646
|
+
if (edited) {
|
|
647
|
+
return `Edit ${edited.path} (+${edited.addedLines} -${edited.removedLines})`;
|
|
648
|
+
}
|
|
649
|
+
return `Write ${extractToolTarget(summary)}`;
|
|
650
|
+
}
|
|
651
|
+
if (toolName === 'delete_file' || toolName === 'delete_directory') {
|
|
652
|
+
const deleted = parseDeleteFileSummary(summary);
|
|
653
|
+
if (toolName === 'delete_file' && deleted?.addedLines !== undefined && deleted.removedLines !== undefined) {
|
|
654
|
+
return `Delete ${deleted.path} (+${deleted.addedLines} -${deleted.removedLines})`;
|
|
655
|
+
}
|
|
656
|
+
return `Delete ${deleted?.path ?? extractToolTarget(summary)}`;
|
|
657
|
+
}
|
|
658
|
+
if (toolName === 'run_command') {
|
|
659
|
+
return summary;
|
|
660
|
+
}
|
|
661
|
+
if (toolName === 'execute_shell') {
|
|
662
|
+
return `Run ${extractToolTarget(summary)}`;
|
|
663
|
+
}
|
|
664
|
+
return `${toolName} ${summary}`.trim();
|
|
665
|
+
}
|
|
666
|
+
function commandForToolMessage(message) {
|
|
667
|
+
if (message.kind === 'tool-result' && message.inputSummary) {
|
|
668
|
+
return commandFromInputSummary(message.inputSummary) ?? message.inputSummary;
|
|
669
|
+
}
|
|
670
|
+
return commandFromInputSummary(message.summary) ?? extractToolTarget(message.summary);
|
|
671
|
+
}
|
|
672
|
+
function commandFromInputSummary(inputSummary) {
|
|
673
|
+
try {
|
|
674
|
+
const parsed = JSON.parse(inputSummary);
|
|
675
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && typeof parsed['command'] === 'string') {
|
|
676
|
+
return parsed['command'];
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
// Plain command summaries are expected for tool-call events.
|
|
681
|
+
}
|
|
682
|
+
return inputSummary.trim().length > 0 ? inputSummary : undefined;
|
|
683
|
+
}
|
|
684
|
+
function toolPathFromInputSummary(inputSummary) {
|
|
685
|
+
if (!inputSummary) {
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const parsed = JSON.parse(inputSummary);
|
|
690
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && typeof parsed['path'] === 'string') {
|
|
691
|
+
return parsed['path'];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
// Current TUI tool-call summaries use plain path text for read/list calls.
|
|
696
|
+
}
|
|
697
|
+
const trimmed = inputSummary.trim();
|
|
698
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
699
|
+
}
|
|
700
|
+
function parseCommandExitCode(summary) {
|
|
701
|
+
const match = /\bexit\s+(-?\d+)/i.exec(summary);
|
|
702
|
+
return match?.[1] !== undefined ? Number(match[1]) : undefined;
|
|
703
|
+
}
|
|
704
|
+
function toolRunningActionLabel(toolName, summary) {
|
|
705
|
+
if (toolName === 'list_files') {
|
|
706
|
+
return `Listing ${extractToolTarget(summary)}`;
|
|
707
|
+
}
|
|
708
|
+
if (toolName === 'read_file') {
|
|
709
|
+
return `Reading ${extractToolTarget(summary)}`;
|
|
710
|
+
}
|
|
711
|
+
if (toolName === 'search_text') {
|
|
712
|
+
return `Searching ${extractToolTarget(summary)}`;
|
|
713
|
+
}
|
|
714
|
+
if (toolName === 'glob') {
|
|
715
|
+
return `Finding ${extractToolTarget(summary)}`;
|
|
716
|
+
}
|
|
717
|
+
if (toolName === 'todo_write') {
|
|
718
|
+
return 'Updating plan';
|
|
719
|
+
}
|
|
720
|
+
if (toolName === 'create_directory') {
|
|
721
|
+
return `Creating ${extractToolTarget(summary)}`;
|
|
722
|
+
}
|
|
723
|
+
if (toolName === 'edit_file') {
|
|
724
|
+
return `Editing ${extractToolTarget(summary)}`;
|
|
725
|
+
}
|
|
726
|
+
if (toolName === 'write_file') {
|
|
727
|
+
return `Writing ${extractToolTarget(summary)}`;
|
|
728
|
+
}
|
|
729
|
+
if (toolName === 'delete_file' || toolName === 'delete_directory') {
|
|
730
|
+
return `Deleting ${extractToolTarget(summary)}`;
|
|
731
|
+
}
|
|
732
|
+
if (toolName === 'run_command' || toolName === 'execute_shell') {
|
|
733
|
+
return `Running ${extractToolTarget(summary)}`;
|
|
734
|
+
}
|
|
735
|
+
return `Running ${summary}`.trim();
|
|
736
|
+
}
|
|
737
|
+
function parseEditedWriteSummary(summary) {
|
|
738
|
+
const match = /^edited\s+(.+?)\s+\(\+(\d+)\s+-(\d+)\)$/i.exec(summary.trim());
|
|
739
|
+
if (!match?.[1] || match[2] === undefined || match[3] === undefined) {
|
|
740
|
+
return undefined;
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
path: match[1],
|
|
744
|
+
addedLines: Number(match[2]),
|
|
745
|
+
removedLines: Number(match[3]),
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function parseCreateDirectorySummary(summary) {
|
|
749
|
+
const match = /^(created|exists)\s+(.+)$/i.exec(summary.trim());
|
|
750
|
+
if (!match?.[1] || !match[2]) {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
created: match[1].toLowerCase() === 'created',
|
|
755
|
+
path: match[2],
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function parseDeleteFileSummary(summary) {
|
|
759
|
+
const statsMatch = /^deleted\s+(.+?)\s+\(\+(\d+)\s+-(\d+)\)$/i.exec(summary.trim());
|
|
760
|
+
if (statsMatch?.[1] && statsMatch[2] !== undefined && statsMatch[3] !== undefined) {
|
|
761
|
+
return {
|
|
762
|
+
path: statsMatch[1],
|
|
763
|
+
addedLines: Number(statsMatch[2]),
|
|
764
|
+
removedLines: Number(statsMatch[3]),
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
const match = /^deleted\s+(.+)$/i.exec(summary.trim());
|
|
768
|
+
return match?.[1] ? { path: match[1] } : undefined;
|
|
769
|
+
}
|
|
770
|
+
function extractToolTarget(summary) {
|
|
771
|
+
const entriesMatch = summary.match(/^\d+\s+(?:entries|matches)\s+in\s+([^:]+)(?::|$)/);
|
|
772
|
+
if (entriesMatch?.[1]) {
|
|
773
|
+
return entriesMatch[1];
|
|
774
|
+
}
|
|
775
|
+
const readMatch = summary.match(/^read\s+\d+\s+bytes\s+from\s+(.+)$/);
|
|
776
|
+
if (readMatch?.[1]) {
|
|
777
|
+
return readMatch[1];
|
|
778
|
+
}
|
|
779
|
+
const writeMatch = summary.match(/^(?:created|updated|wrote|edited|exists|deleted)\s+(.+?)(?:\s+\(|$)/);
|
|
780
|
+
if (writeMatch?.[1]) {
|
|
781
|
+
return writeMatch[1];
|
|
782
|
+
}
|
|
783
|
+
return summary;
|
|
784
|
+
}
|
|
785
|
+
function languageFromPath(path) {
|
|
786
|
+
const extension = path.split(/[/.]/).pop()?.toLowerCase();
|
|
787
|
+
if (!extension || extension === path.toLowerCase()) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
const languages = {
|
|
791
|
+
c: 'c',
|
|
792
|
+
cc: 'cpp',
|
|
793
|
+
cpp: 'cpp',
|
|
794
|
+
css: 'css',
|
|
795
|
+
go: 'go',
|
|
796
|
+
h: 'c',
|
|
797
|
+
hpp: 'cpp',
|
|
798
|
+
html: 'xml',
|
|
799
|
+
java: 'java',
|
|
800
|
+
js: 'javascript',
|
|
801
|
+
json: 'json',
|
|
802
|
+
jsx: 'javascript',
|
|
803
|
+
md: 'markdown',
|
|
804
|
+
mjs: 'javascript',
|
|
805
|
+
py: 'python',
|
|
806
|
+
rs: 'rust',
|
|
807
|
+
sh: 'bash',
|
|
808
|
+
ts: 'typescript',
|
|
809
|
+
tsx: 'typescript',
|
|
810
|
+
xml: 'xml',
|
|
811
|
+
yaml: 'yaml',
|
|
812
|
+
yml: 'yaml',
|
|
813
|
+
};
|
|
814
|
+
return languages[extension];
|
|
815
|
+
}
|
|
816
|
+
function toolStateLabel(state, language) {
|
|
817
|
+
if (state === 'running') {
|
|
818
|
+
return t(language, 'tool.state.running');
|
|
819
|
+
}
|
|
820
|
+
if (state === 'done') {
|
|
821
|
+
return t(language, 'tool.state.done');
|
|
822
|
+
}
|
|
823
|
+
if (state === 'failed') {
|
|
824
|
+
return t(language, 'tool.state.failed');
|
|
825
|
+
}
|
|
826
|
+
if (state === 'denied') {
|
|
827
|
+
return t(language, 'tool.state.denied');
|
|
828
|
+
}
|
|
829
|
+
return t(language, 'tool.state.pending');
|
|
830
|
+
}
|
|
831
|
+
export function toolStateLabelBold(state) {
|
|
832
|
+
return state !== 'failed';
|
|
833
|
+
}
|
|
834
|
+
function toolResultOperation(message) {
|
|
835
|
+
if (message.state === 'failed' || message.state === 'denied') {
|
|
836
|
+
return message.state;
|
|
837
|
+
}
|
|
838
|
+
if (message.toolName === 'list_files') {
|
|
839
|
+
return 'list';
|
|
840
|
+
}
|
|
841
|
+
if (message.toolName === 'read_file') {
|
|
842
|
+
return 'read';
|
|
843
|
+
}
|
|
844
|
+
if (message.toolName === 'search_text') {
|
|
845
|
+
return 'search';
|
|
846
|
+
}
|
|
847
|
+
if (message.toolName === 'glob') {
|
|
848
|
+
return 'find';
|
|
849
|
+
}
|
|
850
|
+
if (message.toolName === 'create_directory') {
|
|
851
|
+
return 'create';
|
|
852
|
+
}
|
|
853
|
+
if (message.toolName === 'edit_file' || message.toolName === 'write_file') {
|
|
854
|
+
return 'edit';
|
|
855
|
+
}
|
|
856
|
+
if (message.toolName === 'delete_file' || message.toolName === 'delete_directory') {
|
|
857
|
+
return 'edit';
|
|
858
|
+
}
|
|
859
|
+
if (message.toolName === 'run_command') {
|
|
860
|
+
return 'run';
|
|
861
|
+
}
|
|
862
|
+
if (message.toolName === 'execute_shell') {
|
|
863
|
+
return 'run';
|
|
864
|
+
}
|
|
865
|
+
return message.toolName;
|
|
866
|
+
}
|
|
867
|
+
function TaskSummary({ durationMs, marginTop, width, label = 'Worked for' }) {
|
|
868
|
+
return (_jsx(Box, { marginTop: marginTop, ...(width !== undefined ? { width } : {}), children: _jsx(Text, { color: remiDarkTheme.muted, wrap: "truncate-end", children: fullWidthDivider(`${label} ${formatTaskDuration(durationMs)}`, width) }) }));
|
|
869
|
+
}
|
|
870
|
+
function fullWidthDivider(label, width) {
|
|
871
|
+
const targetWidth = Math.max(displayWidth(label) + 4, width ?? displayWidth(label) + 4);
|
|
872
|
+
const content = ` ${label} `;
|
|
873
|
+
const remaining = Math.max(0, targetWidth - displayWidth(content));
|
|
874
|
+
const leftWidth = Math.floor(remaining / 2);
|
|
875
|
+
const rightWidth = remaining - leftWidth;
|
|
876
|
+
return `${'─'.repeat(leftWidth)}${content}${'─'.repeat(rightWidth)}`;
|
|
877
|
+
}
|
|
878
|
+
export function formatTaskDuration(durationMs) {
|
|
879
|
+
const totalSeconds = Math.max(1, Math.round(durationMs / 1000));
|
|
880
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
881
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
882
|
+
const seconds = totalSeconds % 60;
|
|
883
|
+
if (hours > 0) {
|
|
884
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
885
|
+
}
|
|
886
|
+
if (minutes > 0) {
|
|
887
|
+
return `${minutes}m ${seconds}s`;
|
|
888
|
+
}
|
|
889
|
+
return `${seconds}s`;
|
|
890
|
+
}
|
|
891
|
+
function ModelPanel({ message, marginTop, }) {
|
|
892
|
+
const selectedIndex = normalizeSelectedIndex(message);
|
|
893
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), message.models.map((model, index) => {
|
|
894
|
+
const isActive = model.alias === message.activeAlias;
|
|
895
|
+
const isSelected = index === selectedIndex;
|
|
896
|
+
const rowColor = isSelected ? remiDarkTheme.accent : isActive ? remiDarkTheme.success : remiDarkTheme.text;
|
|
897
|
+
const prefix = `${isSelected ? '>' : ' '} ${index + 1}.`;
|
|
898
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 5, children: _jsx(Text, { color: rowColor, children: prefix }) }), _jsx(Box, { flexGrow: 1, children: _jsxs(Text, { color: rowColor, bold: isActive || isSelected, children: [model.displayName, isActive ? ` (${message.currentLabel})` : ''] }) })] }) }, model.alias));
|
|
899
|
+
})] }));
|
|
900
|
+
}
|
|
901
|
+
function normalizeSelectedIndex(message) {
|
|
902
|
+
if (message.models.length === 0) {
|
|
903
|
+
return -1;
|
|
904
|
+
}
|
|
905
|
+
const rawSelectedIndex = message.selectedIndex ??
|
|
906
|
+
message.models.findIndex(model => model.alias === message.activeAlias);
|
|
907
|
+
if (rawSelectedIndex < 0) {
|
|
908
|
+
return 0;
|
|
909
|
+
}
|
|
910
|
+
return Math.min(rawSelectedIndex, message.models.length - 1);
|
|
911
|
+
}
|
|
912
|
+
function StylePanel({ message, marginTop, }) {
|
|
913
|
+
const selectedIndex = normalizeStyleSelectedIndex(message);
|
|
914
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), message.styles.map((style, index) => {
|
|
915
|
+
const isActive = style.id === message.activeId;
|
|
916
|
+
const isSelected = index === selectedIndex;
|
|
917
|
+
const rowColor = isSelected ? remiDarkTheme.accent : isActive ? remiDarkTheme.success : remiDarkTheme.text;
|
|
918
|
+
const detailColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
919
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 4, children: _jsx(Text, { color: rowColor, children: isSelected ? '>' : `${index + 1}.` }) }), _jsx(Box, { width: 18, children: _jsxs(Text, { color: rowColor, bold: isActive || isSelected, children: [style.label, isActive ? ` (${message.currentLabel})` : ''] }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: detailColor, bold: isSelected, children: style.description }) })] }, style.id));
|
|
920
|
+
})] }));
|
|
921
|
+
}
|
|
922
|
+
function normalizeStyleSelectedIndex(message) {
|
|
923
|
+
if (message.styles.length === 0) {
|
|
924
|
+
return -1;
|
|
925
|
+
}
|
|
926
|
+
const rawSelectedIndex = message.selectedIndex ??
|
|
927
|
+
message.styles.findIndex(style => style.id === message.activeId);
|
|
928
|
+
if (rawSelectedIndex < 0) {
|
|
929
|
+
return 0;
|
|
930
|
+
}
|
|
931
|
+
return Math.min(rawSelectedIndex, message.styles.length - 1);
|
|
932
|
+
}
|
|
933
|
+
function SkillsPanel({ message, marginTop, width, }) {
|
|
934
|
+
const panelWidth = width === undefined ? undefined : Math.max(40, width);
|
|
935
|
+
const selectedIndex = normalizePanelSelectedIndex(message.selectedIndex, message.mode === 'actions' ? message.actions.length : message.skills.length);
|
|
936
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, paddingX: 1, paddingY: 1, backgroundColor: remiDarkTheme.composerBackground, width: panelWidth, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), message.mode === 'actions' ? (_jsx(SkillsActionRows, { actions: message.actions, selectedIndex: selectedIndex })) : (_jsx(SkillsListRows, { skills: message.skills, selectedIndex: selectedIndex, toggle: message.mode === 'toggle', width: width })), _jsx(Box, { height: 1 }), _jsx(Text, { color: remiDarkTheme.muted, children: message.helpText })] }));
|
|
937
|
+
}
|
|
938
|
+
function SkillsActionRows({ actions, selectedIndex, }) {
|
|
939
|
+
return (_jsx(_Fragment, { children: actions.map((action, index) => {
|
|
940
|
+
const isSelected = index === selectedIndex;
|
|
941
|
+
const color = isSelected ? remiDarkTheme.accent : remiDarkTheme.text;
|
|
942
|
+
const detailColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
943
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: color, children: `${index + 1}. ` }), _jsx(Text, { color: color, bold: isSelected, children: action.label }), _jsx(Text, { children: " " }), _jsx(Text, { color: detailColor, bold: isSelected, children: action.description })] }, action.id));
|
|
944
|
+
}) }));
|
|
945
|
+
}
|
|
946
|
+
function SkillsListRows({ skills, selectedIndex, toggle, width, }) {
|
|
947
|
+
if (skills.length === 0) {
|
|
948
|
+
return _jsx(Text, { color: remiDarkTheme.muted, children: "No skills found" });
|
|
949
|
+
}
|
|
950
|
+
return (_jsx(_Fragment, { children: skills.map((skill, index) => {
|
|
951
|
+
const isSelected = index === selectedIndex;
|
|
952
|
+
const color = isSelected ? remiDarkTheme.accent : skill.enabled ? remiDarkTheme.text : remiDarkTheme.muted;
|
|
953
|
+
const detailColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
954
|
+
const marker = toggle ? (skill.enabled ? '[x]' : '[ ]') : `${index + 1}.`;
|
|
955
|
+
const prefix = `${marker} ${skill.displayName} ${skill.source} `;
|
|
956
|
+
const detailWidth = Math.max(8, (width ?? 100) - displayWidth(prefix) - 4);
|
|
957
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: color, children: `${marker} ` }), _jsx(Text, { color: color, bold: isSelected, children: skill.displayName }), _jsx(Text, { children: " " }), _jsx(Text, { color: detailColor, children: skill.source }), _jsx(Text, { children: " " }), _jsx(Text, { color: detailColor, wrap: "truncate-end", children: truncateTextToWidth(skill.description, detailWidth) })] }, `${skill.source}-${skill.name}`));
|
|
958
|
+
}) }));
|
|
959
|
+
}
|
|
960
|
+
const syntaxThemePreviewDiff = [
|
|
961
|
+
'11 static int lowerBound(int[] nums, int target) {',
|
|
962
|
+
'12 int left = 0;',
|
|
963
|
+
'13 int right = nums.length;',
|
|
964
|
+
'14 while (left < right) {',
|
|
965
|
+
'15 int mid = left + (right - left) / 2;',
|
|
966
|
+
'16 - if (nums[mid] <= target) left = mid + 1;',
|
|
967
|
+
'16 + if (nums[mid] < target) left = mid + 1;',
|
|
968
|
+
'17 else right = mid;',
|
|
969
|
+
'18 }',
|
|
970
|
+
'19 return left;',
|
|
971
|
+
'20 }',
|
|
972
|
+
].join('\n');
|
|
973
|
+
function SyntaxThemePanel({ message, marginTop, width, }) {
|
|
974
|
+
const themes = visibleSyntaxThemePanelItems(message);
|
|
975
|
+
const selectedIndex = normalizePanelSelectedIndex(message.selectedIndex, themes.length);
|
|
976
|
+
const selectedTheme = themes[selectedIndex];
|
|
977
|
+
const previewTheme = applyTerminalSyntaxTheme(baseMarkdownTheme, selectedTheme?.id ?? message.activeId);
|
|
978
|
+
const listWidth = 36;
|
|
979
|
+
const previewWidth = Math.max(42, (width ?? 100) - listWidth - 4);
|
|
980
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, paddingX: 1, paddingY: 1, backgroundColor: remiDarkTheme.composerBackground, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", width: listWidth, flexShrink: 0, children: [_jsx(Text, { color: remiDarkTheme.muted, wrap: "truncate-end", children: themeSearchLabel(message) }), themes.length === 0 ? _jsx(Text, { color: remiDarkTheme.muted, children: "No matching themes" }) : null, themes.map((theme, index) => {
|
|
981
|
+
const isSelected = index === selectedIndex;
|
|
982
|
+
const isActive = theme.id === message.activeId;
|
|
983
|
+
const rowColor = isSelected ? remiDarkTheme.accent : isActive ? remiDarkTheme.success : remiDarkTheme.text;
|
|
984
|
+
return (_jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [isSelected ? '> ' : ' ', theme.id, isActive ? ` (${message.currentLabel})` : ''] }, theme.id));
|
|
985
|
+
})] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(StructuredDiffView, { diff: syntaxThemePreviewDiff, theme: previewTheme, language: "java", width: previewWidth }) })] }), _jsx(Text, { color: remiDarkTheme.muted, children: message.helpText })] }));
|
|
986
|
+
}
|
|
987
|
+
function themeSearchLabel(message) {
|
|
988
|
+
const query = message.query.trim();
|
|
989
|
+
return query.length > 0 ? `${message.searchLabel}: ${query}` : `${message.searchLabel}...`;
|
|
990
|
+
}
|
|
991
|
+
function visibleSyntaxThemePanelItems(message) {
|
|
992
|
+
const query = message.query.trim().toLowerCase();
|
|
993
|
+
if (!query) {
|
|
994
|
+
return message.themes;
|
|
995
|
+
}
|
|
996
|
+
return message.themes.filter(theme => `${theme.id} ${theme.label} ${theme.description}`.toLowerCase().includes(query));
|
|
997
|
+
}
|
|
998
|
+
function LanguagePanel({ message, marginTop, }) {
|
|
999
|
+
const selectedIndex = normalizeLanguageSelectedIndex(message);
|
|
1000
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), message.languages.map((language, index) => {
|
|
1001
|
+
const isActive = language.code === message.activeCode;
|
|
1002
|
+
const isSelected = index === selectedIndex;
|
|
1003
|
+
const rowColor = isSelected ? remiDarkTheme.accent : isActive ? remiDarkTheme.success : remiDarkTheme.text;
|
|
1004
|
+
const detailColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
1005
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 4, children: _jsx(Text, { color: rowColor, children: isSelected ? '>' : `${index + 1}.` }) }), _jsx(Box, { width: 24, children: _jsxs(Text, { color: rowColor, bold: isActive || isSelected, children: [language.label, isActive ? ` (${message.currentLabel})` : ''] }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: detailColor, bold: isSelected, children: language.description }) })] }, language.code));
|
|
1006
|
+
})] }));
|
|
1007
|
+
}
|
|
1008
|
+
function normalizeLanguageSelectedIndex(message) {
|
|
1009
|
+
if (message.languages.length === 0) {
|
|
1010
|
+
return -1;
|
|
1011
|
+
}
|
|
1012
|
+
const rawSelectedIndex = message.selectedIndex ??
|
|
1013
|
+
message.languages.findIndex(language => language.code === message.activeCode);
|
|
1014
|
+
if (rawSelectedIndex < 0) {
|
|
1015
|
+
return 0;
|
|
1016
|
+
}
|
|
1017
|
+
return Math.min(rawSelectedIndex, message.languages.length - 1);
|
|
1018
|
+
}
|
|
1019
|
+
function PermissionProfilePanel({ message, marginTop, }) {
|
|
1020
|
+
const selectedIndex = normalizePermissionProfileSelectedIndex(message);
|
|
1021
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), message.profiles.map((profile, index) => {
|
|
1022
|
+
const isActive = profile.id === message.activeProfile;
|
|
1023
|
+
const isSelected = index === selectedIndex;
|
|
1024
|
+
const rowColor = isSelected ? remiDarkTheme.accent : isActive ? remiDarkTheme.success : remiDarkTheme.text;
|
|
1025
|
+
const detailColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
1026
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 4, children: _jsx(Text, { color: rowColor, children: isSelected ? '>' : `${index + 1}.` }) }), _jsx(Box, { width: 20, children: _jsxs(Text, { color: rowColor, bold: isActive || isSelected, children: [profile.label, isActive ? ` (${message.currentLabel})` : ''] }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: detailColor, bold: isSelected, children: profile.description }) })] }, profile.id));
|
|
1027
|
+
})] }));
|
|
1028
|
+
}
|
|
1029
|
+
function normalizePermissionProfileSelectedIndex(message) {
|
|
1030
|
+
if (message.profiles.length === 0) {
|
|
1031
|
+
return -1;
|
|
1032
|
+
}
|
|
1033
|
+
const rawSelectedIndex = message.selectedIndex ??
|
|
1034
|
+
message.profiles.findIndex(profile => profile.id === message.activeProfile);
|
|
1035
|
+
if (rawSelectedIndex < 0) {
|
|
1036
|
+
return 0;
|
|
1037
|
+
}
|
|
1038
|
+
return Math.min(rawSelectedIndex, message.profiles.length - 1);
|
|
1039
|
+
}
|
|
1040
|
+
function PermissionRequestCard({ message, marginTop, }) {
|
|
1041
|
+
const selectedIndex = normalizePanelSelectedIndex(message.selectedIndex, message.options.length);
|
|
1042
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, paddingX: 1, paddingY: 1, backgroundColor: remiDarkTheme.composerBackground, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Box, { height: 1 }), _jsx(Text, { color: remiDarkTheme.muted, children: message.reason }), _jsx(PermissionRequestActionLine, { message: message }), message.capability ? _jsx(Text, { color: remiDarkTheme.muted, children: permissionCapabilityDisplay(message.capability) }) : null, _jsx(Box, { height: 1 }), message.options.map((option, index) => {
|
|
1043
|
+
const isSelected = index === selectedIndex;
|
|
1044
|
+
const rowColor = isSelected ? remiDarkTheme.accent : option.id === 'deny' ? remiDarkTheme.muted : remiDarkTheme.text;
|
|
1045
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 4, children: _jsx(Text, { color: rowColor, children: isSelected ? '>' : `${index + 1}.` }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: rowColor, bold: isSelected, children: option.label }) })] }, option.id));
|
|
1046
|
+
}), _jsx(Text, { color: remiDarkTheme.muted, children: message.helpText })] }));
|
|
1047
|
+
}
|
|
1048
|
+
function PermissionRequestActionLine({ message }) {
|
|
1049
|
+
const fileAction = message.action || message.actionDetail
|
|
1050
|
+
? { action: message.action, detail: message.actionDetail }
|
|
1051
|
+
: formatPermissionFileActionFromParts(message.toolName, message.summary, undefined, message.targetPath);
|
|
1052
|
+
if (fileAction?.action) {
|
|
1053
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: remiDarkTheme.accent, children: fileAction.action }), fileAction.detail ? _jsx(Text, { color: remiDarkTheme.muted, children: fileAction.detail }) : null] }));
|
|
1054
|
+
}
|
|
1055
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: remiDarkTheme.muted, children: "$ " }), _jsx(Text, { color: remiDarkTheme.accent, children: message.command ?? permissionRequestDisplay(message) })] }));
|
|
1056
|
+
}
|
|
1057
|
+
function permissionRequestDisplay(message) {
|
|
1058
|
+
const path = message.targetPath ?? parseJsonPath(message.summary);
|
|
1059
|
+
if (message.toolName === 'create_directory' && path) {
|
|
1060
|
+
return `mkdir -p ${path}`;
|
|
1061
|
+
}
|
|
1062
|
+
if (message.toolName === 'delete_file' && path) {
|
|
1063
|
+
return `rm ${path}`;
|
|
1064
|
+
}
|
|
1065
|
+
if (message.toolName === 'delete_directory' && path) {
|
|
1066
|
+
return `rmdir ${path}`;
|
|
1067
|
+
}
|
|
1068
|
+
if (message.toolName === 'write_file' && path) {
|
|
1069
|
+
return `write ${path}`;
|
|
1070
|
+
}
|
|
1071
|
+
if (message.toolName === 'edit_file' && path) {
|
|
1072
|
+
return `edit ${path}`;
|
|
1073
|
+
}
|
|
1074
|
+
return message.summary;
|
|
1075
|
+
}
|
|
1076
|
+
function permissionCapabilityDisplay(capability) {
|
|
1077
|
+
const parts = [`capability: ${capability.id}`, `risk: ${capability.risk}`];
|
|
1078
|
+
if (capability.platform) {
|
|
1079
|
+
parts.push(`platform: ${capability.platform}`);
|
|
1080
|
+
}
|
|
1081
|
+
if (capability.target) {
|
|
1082
|
+
parts.push(`target: ${capability.target}`);
|
|
1083
|
+
}
|
|
1084
|
+
if (capability.packageManager) {
|
|
1085
|
+
parts.push(`manager: ${capability.packageManager}`);
|
|
1086
|
+
}
|
|
1087
|
+
if (capability.requiresAdmin) {
|
|
1088
|
+
parts.push('admin required');
|
|
1089
|
+
}
|
|
1090
|
+
return parts.join(' · ');
|
|
1091
|
+
}
|
|
1092
|
+
function parseJsonPath(summary) {
|
|
1093
|
+
try {
|
|
1094
|
+
const parsed = JSON.parse(summary);
|
|
1095
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1096
|
+
const path = parsed['path'];
|
|
1097
|
+
return typeof path === 'string' && path.trim().length > 0 ? path : undefined;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
return undefined;
|
|
1102
|
+
}
|
|
1103
|
+
return undefined;
|
|
1104
|
+
}
|
|
1105
|
+
function StatusLinePanel({ message, marginTop, }) {
|
|
1106
|
+
const items = visibleStatusLinePanelItems(message);
|
|
1107
|
+
const selectedIndex = normalizePanelSelectedIndex(message.selectedIndex, items.length);
|
|
1108
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: marginTop, paddingX: 1, paddingY: 1, backgroundColor: remiDarkTheme.composerBackground, children: [_jsx(Text, { color: remiDarkTheme.text, bold: true, children: message.title }), _jsx(Text, { color: remiDarkTheme.muted, children: message.subtitle }), _jsx(Box, { height: 1 }), _jsx(Text, { color: remiDarkTheme.muted, children: message.searchLabel }), _jsx(Text, { color: remiDarkTheme.muted, children: `> ${message.query}` }), items.map((item, index) => {
|
|
1109
|
+
const isSelected = index === selectedIndex;
|
|
1110
|
+
const rowColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.text;
|
|
1111
|
+
const detailColor = isSelected ? remiDarkTheme.accent : remiDarkTheme.muted;
|
|
1112
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 4, children: _jsx(Text, { color: rowColor, children: item.enabled ? '[x]' : '[ ]' }) }), _jsx(Box, { width: 24, children: _jsx(Text, { color: rowColor, bold: isSelected, children: item.label }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: detailColor, bold: isSelected, children: item.description }) })] }, item.id));
|
|
1113
|
+
}), _jsx(Text, { color: remiDarkTheme.muted, children: message.helpText })] }));
|
|
1114
|
+
}
|
|
1115
|
+
function visibleStatusLinePanelItems(message) {
|
|
1116
|
+
const query = message.query.trim().toLowerCase();
|
|
1117
|
+
if (!query) {
|
|
1118
|
+
return message.items;
|
|
1119
|
+
}
|
|
1120
|
+
return message.items.filter(item => `${item.label} ${item.description}`.toLowerCase().includes(query));
|
|
1121
|
+
}
|
|
1122
|
+
function normalizePanelSelectedIndex(selectedIndex, length) {
|
|
1123
|
+
if (length <= 0) {
|
|
1124
|
+
return -1;
|
|
1125
|
+
}
|
|
1126
|
+
const rawSelectedIndex = selectedIndex ?? 0;
|
|
1127
|
+
return Math.min(Math.max(rawSelectedIndex, 0), length - 1);
|
|
1128
|
+
}
|
|
1129
|
+
export function MessageList({ messages, width, emptyText = 'Ready. Type a task, or press / for commands.', language, progress, syntaxTheme, slashCommandHints, }) {
|
|
1130
|
+
const markdownTheme = useMemo(() => applyTerminalSyntaxTheme(baseMarkdownTheme, syntaxTheme), [syntaxTheme]);
|
|
1131
|
+
if (messages.length === 0 && !progress) {
|
|
1132
|
+
if (emptyText.length === 0) {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
return (_jsx(MarkdownThemeContext.Provider, { value: markdownTheme, children: _jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: remiDarkTheme.muted, children: emptyText }) }) }));
|
|
1136
|
+
}
|
|
1137
|
+
const groups = groupMessages(messages);
|
|
1138
|
+
return (_jsx(MarkdownThemeContext.Provider, { value: markdownTheme, children: _jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [groups.map(group => (_jsx(MessageGroup, { messages: group.messages, startIndex: group.startIndex, width: width, language: language, slashCommandHints: slashCommandHints }, group.startIndex))), progress ? (_jsx(WorkingIndicator, { startedAt: progress.startedAt, visible: true, label: progress.label, interruptText: progress.interruptText, marginTop: messages.length === 0 ? 0 : 1 })) : null] }) }));
|
|
1139
|
+
}
|
|
1140
|
+
function groupMessages(messages) {
|
|
1141
|
+
const groups = [];
|
|
1142
|
+
let index = 0;
|
|
1143
|
+
while (index < messages.length) {
|
|
1144
|
+
const message = messages[index];
|
|
1145
|
+
if (message?.kind === 'tool-result') {
|
|
1146
|
+
const toolGroup = [message];
|
|
1147
|
+
const groupKey = toolResultGroupKey(message);
|
|
1148
|
+
let nextIndex = index + 1;
|
|
1149
|
+
while (nextIndex < messages.length) {
|
|
1150
|
+
const next = messages[nextIndex];
|
|
1151
|
+
if (next?.kind !== 'tool-result') {
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
1154
|
+
if (toolResultGroupKey(next) !== groupKey) {
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
toolGroup.push(next);
|
|
1158
|
+
nextIndex += 1;
|
|
1159
|
+
}
|
|
1160
|
+
groups.push({ startIndex: index, messages: toolGroup });
|
|
1161
|
+
index = nextIndex;
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
if (message) {
|
|
1165
|
+
groups.push({ startIndex: index, messages: [message] });
|
|
1166
|
+
}
|
|
1167
|
+
index += 1;
|
|
1168
|
+
}
|
|
1169
|
+
return groups;
|
|
1170
|
+
}
|
|
1171
|
+
function toolResultGroupKey(message) {
|
|
1172
|
+
if (message.state === 'failed' || message.state === 'denied') {
|
|
1173
|
+
return message.state;
|
|
1174
|
+
}
|
|
1175
|
+
return `done:${toolResultOperation(message)}`;
|
|
1176
|
+
}
|