codemini-cli 0.3.4 → 0.3.6
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 +20 -18
- package/package.json +6 -6
- package/souls/anime.md +12 -9
- package/souls/caveman.md +6 -6
- package/souls/ceo.md +10 -9
- package/souls/default.md +1 -1
- package/souls/pirate.md +6 -6
- package/souls/playful.md +7 -7
- package/souls/professional.md +1 -1
- package/src/cli.js +3 -1
- package/src/commands/run.js +229 -16
- package/src/core/agent-loop.js +167 -49
- package/src/core/ast.js +40 -0
- package/src/core/chat-runtime.js +720 -126
- package/src/core/command-policy.js +56 -0
- package/src/core/config-store.js +0 -3
- package/src/core/crypto-utils.js +6 -2
- package/src/core/memory-store.js +3 -3
- package/src/core/project-index.js +4 -18
- package/src/core/provider/anthropic.js +15 -2
- package/src/core/provider/anthropic.sdk-backup.js +439 -0
- package/src/core/provider/openai-compatible.js +93 -11
- package/src/core/provider/openai-compatible.sdk-backup.js +412 -0
- package/src/core/session-store.js +90 -25
- package/src/core/shell-profile.js +26 -6
- package/src/core/string-utils.js +37 -0
- package/src/core/tools.js +216 -405
- package/src/tui/chat-app.js +490 -146
- package/src/tui/tool-activity/presenters/files.js +2 -2
- package/src/tui/tool-narration.js +0 -3
- package/src/tui/tool-narration/presenters/patch.js +0 -3
package/src/tui/chat-app.js
CHANGED
|
@@ -44,6 +44,38 @@ const ROLE_STYLES = {
|
|
|
44
44
|
badgeText: 'black',
|
|
45
45
|
chrome: 'gray'
|
|
46
46
|
},
|
|
47
|
+
planner: {
|
|
48
|
+
accent: 'magentaBright',
|
|
49
|
+
border: 'magenta',
|
|
50
|
+
text: 'magentaBright',
|
|
51
|
+
badgeBg: 'magenta',
|
|
52
|
+
badgeText: 'white',
|
|
53
|
+
chrome: 'gray'
|
|
54
|
+
},
|
|
55
|
+
reviewer: {
|
|
56
|
+
accent: 'yellowBright',
|
|
57
|
+
border: 'yellow',
|
|
58
|
+
text: 'yellowBright',
|
|
59
|
+
badgeBg: 'yellow',
|
|
60
|
+
badgeText: 'black',
|
|
61
|
+
chrome: 'gray'
|
|
62
|
+
},
|
|
63
|
+
tester: {
|
|
64
|
+
accent: 'blueBright',
|
|
65
|
+
border: 'blue',
|
|
66
|
+
text: 'blueBright',
|
|
67
|
+
badgeBg: 'blue',
|
|
68
|
+
badgeText: 'white',
|
|
69
|
+
chrome: 'gray'
|
|
70
|
+
},
|
|
71
|
+
summarizer: {
|
|
72
|
+
accent: 'cyanBright',
|
|
73
|
+
border: 'cyan',
|
|
74
|
+
text: 'cyanBright',
|
|
75
|
+
badgeBg: 'cyan',
|
|
76
|
+
badgeText: 'black',
|
|
77
|
+
chrome: 'gray'
|
|
78
|
+
},
|
|
47
79
|
system: {
|
|
48
80
|
accent: 'yellowBright',
|
|
49
81
|
border: 'yellow',
|
|
@@ -72,7 +104,7 @@ const ROLE_STYLES = {
|
|
|
72
104
|
|
|
73
105
|
const TUI_COPY = {
|
|
74
106
|
zh: {
|
|
75
|
-
roleLabels: { you: '你', coder: 'CODER', system: '系统', error: '错误', pending: '等待中' },
|
|
107
|
+
roleLabels: { you: '👤 你', coder: '💻 CODER', planner: '📋 PLANNER', reviewer: '🔍 REVIEWER', tester: '🧪 TESTER', summarizer: '📝 SUMMARIZER', system: '⚙️ 系统', error: '❌ 错误', pending: '⏳ 等待中' },
|
|
76
108
|
generic: {
|
|
77
109
|
waitingForInput: '等待输入',
|
|
78
110
|
ready: '就绪',
|
|
@@ -99,7 +131,7 @@ const TUI_COPY = {
|
|
|
99
131
|
pendingQueue: '等待队列',
|
|
100
132
|
commandPaletteGroupedSelect: '命令面板 | 分组选择模式',
|
|
101
133
|
commandPaletteGroupedSuggestions: '命令面板 | 分组候选',
|
|
102
|
-
startupHint: '使用 /help、/commands、/compact、/exit、!<shell>。Tab 可自动补全 slash 命令。',
|
|
134
|
+
startupHint: '使用 /help、/commands、/compact、/exit、/stop、!<shell>。Tab 可自动补全 slash 命令。',
|
|
103
135
|
toolSummaryExpanded: '工具摘要:已展开',
|
|
104
136
|
toolSummaryCollapsed: '工具摘要:已收起',
|
|
105
137
|
toolChainCollapsed: (count) => `已折叠更早的 ${count} 个工具调用`,
|
|
@@ -126,6 +158,8 @@ const TUI_COPY = {
|
|
|
126
158
|
doingEdit: '正在编辑文件',
|
|
127
159
|
doneWrite: '已写入文件',
|
|
128
160
|
doingWrite: '正在写入文件',
|
|
161
|
+
doneDelete: '已删除目标',
|
|
162
|
+
doingDelete: '正在等待删除确认',
|
|
129
163
|
donePatch: '已应用补丁',
|
|
130
164
|
doingPatch: '正在应用补丁',
|
|
131
165
|
doneList: '已列出目录',
|
|
@@ -204,16 +238,30 @@ const TUI_COPY = {
|
|
|
204
238
|
compactingContext: '正在压缩上下文',
|
|
205
239
|
autoCompactTriggered: (mode, threshold) => `自动压缩已触发(${mode},阈值 ${threshold}%)`,
|
|
206
240
|
requestFailed: '请求失败',
|
|
241
|
+
responseStopped: '回答已中止',
|
|
207
242
|
localCommandRunning: '正在执行本地命令',
|
|
208
243
|
queuedWaiting: '排队中,等待上一轮完成',
|
|
209
244
|
idleReady: '等待输入',
|
|
210
245
|
idleReadyDetail: '就绪',
|
|
211
246
|
idleAfterTurn: '空闲',
|
|
212
247
|
idleAfterTurnDetail: '等待下一轮输入'
|
|
248
|
+
},
|
|
249
|
+
deleteApproval: {
|
|
250
|
+
title: '确认删除?',
|
|
251
|
+
pathLabel: '路径',
|
|
252
|
+
nameLabel: '名称',
|
|
253
|
+
typeLabel: '类型',
|
|
254
|
+
fileType: '文件',
|
|
255
|
+
directoryType: '目录',
|
|
256
|
+
prompt: '输入 yes 确认删除,输入 no 取消。',
|
|
257
|
+
invalidAnswer: '请输入 yes 或 no。',
|
|
258
|
+
inputLocked: '删除确认进行中,请输入 yes 或 no',
|
|
259
|
+
answerLabel: '确认输入(yes/no)',
|
|
260
|
+
answerPlaceholder: 'yes 或 no'
|
|
213
261
|
}
|
|
214
262
|
},
|
|
215
263
|
en: {
|
|
216
|
-
roleLabels: { you: 'YOU', coder: 'CODER', system: 'SYSTEM', error: 'ERROR', pending: 'PENDING' },
|
|
264
|
+
roleLabels: { you: 'YOU', coder: 'CODER', planner: 'PLANNER', reviewer: 'REVIEWER', tester: 'TESTER', summarizer: 'SUMMARIZER', system: 'SYSTEM', error: 'ERROR', pending: 'PENDING' },
|
|
217
265
|
generic: {
|
|
218
266
|
waitingForInput: 'waiting for input',
|
|
219
267
|
ready: 'ready',
|
|
@@ -240,7 +288,7 @@ const TUI_COPY = {
|
|
|
240
288
|
pendingQueue: 'pending queue',
|
|
241
289
|
commandPaletteGroupedSelect: 'command palette | grouped select mode',
|
|
242
290
|
commandPaletteGroupedSuggestions: 'command palette | grouped suggestions',
|
|
243
|
-
startupHint: 'Use /help, /commands, /compact, /exit, !<shell>. Tab for slash autocomplete.',
|
|
291
|
+
startupHint: 'Use /help, /commands, /compact, /stop, /exit, !<shell>. Tab for slash autocomplete.',
|
|
244
292
|
toolSummaryExpanded: 'Tool summary: expanded',
|
|
245
293
|
toolSummaryCollapsed: 'Tool summary: collapsed',
|
|
246
294
|
toolChainCollapsed: (count) => `${count} earlier tool calls hidden`,
|
|
@@ -267,6 +315,8 @@ const TUI_COPY = {
|
|
|
267
315
|
doingEdit: 'Editing file',
|
|
268
316
|
doneWrite: 'Wrote file',
|
|
269
317
|
doingWrite: 'Writing file',
|
|
318
|
+
doneDelete: 'Deleted target',
|
|
319
|
+
doingDelete: 'Waiting for delete approval',
|
|
270
320
|
donePatch: 'Applied patch',
|
|
271
321
|
doingPatch: 'Applying patch',
|
|
272
322
|
doneList: 'Listed directory',
|
|
@@ -345,12 +395,26 @@ const TUI_COPY = {
|
|
|
345
395
|
compactingContext: 'compacting context',
|
|
346
396
|
autoCompactTriggered: (mode, threshold) => `auto-compact triggered (${mode}, threshold ${threshold}%)`,
|
|
347
397
|
requestFailed: 'request failed',
|
|
398
|
+
responseStopped: 'Response stopped',
|
|
348
399
|
localCommandRunning: 'running local command',
|
|
349
400
|
queuedWaiting: 'queued, waiting for current turn',
|
|
350
401
|
idleReady: 'waiting for input',
|
|
351
402
|
idleReadyDetail: 'ready',
|
|
352
403
|
idleAfterTurn: 'idle',
|
|
353
404
|
idleAfterTurnDetail: 'ready for next input'
|
|
405
|
+
},
|
|
406
|
+
deleteApproval: {
|
|
407
|
+
title: 'Confirm deletion?',
|
|
408
|
+
pathLabel: 'Path',
|
|
409
|
+
nameLabel: 'Name',
|
|
410
|
+
typeLabel: 'Type',
|
|
411
|
+
fileType: 'file',
|
|
412
|
+
directoryType: 'directory',
|
|
413
|
+
prompt: 'Type yes to delete, or no to cancel.',
|
|
414
|
+
invalidAnswer: 'Please enter yes or no.',
|
|
415
|
+
inputLocked: 'Delete approval is active; type yes or no',
|
|
416
|
+
answerLabel: 'Approval input (yes/no)',
|
|
417
|
+
answerPlaceholder: 'yes or no'
|
|
354
418
|
}
|
|
355
419
|
}
|
|
356
420
|
};
|
|
@@ -747,8 +811,8 @@ function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
|
|
|
747
811
|
function safeJsonParse(raw) {
|
|
748
812
|
try {
|
|
749
813
|
return JSON.parse(String(raw || '{}'));
|
|
750
|
-
} catch {
|
|
751
|
-
return
|
|
814
|
+
} catch (parseError) {
|
|
815
|
+
return { _raw: String(raw || ''), _invalid_json: true, _parseError: parseError.message };
|
|
752
816
|
}
|
|
753
817
|
}
|
|
754
818
|
|
|
@@ -867,6 +931,49 @@ export function getPendingUserMessageMeta(copy, { immediateLocal = false, inFlig
|
|
|
867
931
|
};
|
|
868
932
|
}
|
|
869
933
|
|
|
934
|
+
export function normalizeDeleteApprovalRequest(request) {
|
|
935
|
+
if (!request || String(request?.name || '').trim() !== 'delete') return null;
|
|
936
|
+
const details =
|
|
937
|
+
request?.approvalDetails && typeof request.approvalDetails === 'object' && !Array.isArray(request.approvalDetails)
|
|
938
|
+
? request.approvalDetails
|
|
939
|
+
: request?.arguments?.approval && typeof request.arguments.approval === 'object' && !Array.isArray(request.arguments.approval)
|
|
940
|
+
? request.arguments.approval
|
|
941
|
+
: {};
|
|
942
|
+
const fallbackPath = String(details.path || request?.arguments?.path || '').trim();
|
|
943
|
+
const pathValue = fallbackPath;
|
|
944
|
+
const nameValue = String(details.name || (pathValue ? pathValue.split(/[\\/]/).pop() : '') || '').trim();
|
|
945
|
+
const typeValue = String(details.type || '').trim() === 'directory' ? 'directory' : 'file';
|
|
946
|
+
if (!pathValue) return null;
|
|
947
|
+
return {
|
|
948
|
+
id: String(request?.id || '').trim(),
|
|
949
|
+
toolName: 'delete',
|
|
950
|
+
path: pathValue,
|
|
951
|
+
name: nameValue || pathValue,
|
|
952
|
+
type: typeValue
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export function parseDeleteApprovalAnswer(value) {
|
|
957
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
958
|
+
if (normalized === 'yes') return 'approve';
|
|
959
|
+
if (normalized === 'no') return 'deny';
|
|
960
|
+
return normalized ? 'invalid' : 'empty';
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
export function formatDeleteApprovalLines(copy, request) {
|
|
964
|
+
const details = normalizeDeleteApprovalRequest(request);
|
|
965
|
+
if (!details) return [];
|
|
966
|
+
const typeLabel = details.type === 'directory' ? copy.deleteApproval.directoryType : copy.deleteApproval.fileType;
|
|
967
|
+
const pathDisplay = details.path.includes('/') || details.path.includes('\\') ? details.path : `./${details.path}`;
|
|
968
|
+
return [
|
|
969
|
+
copy.deleteApproval.title,
|
|
970
|
+
`${copy.deleteApproval.pathLabel}: ${pathDisplay}`,
|
|
971
|
+
`${copy.deleteApproval.nameLabel}: ${details.name}`,
|
|
972
|
+
`${copy.deleteApproval.typeLabel}: ${typeLabel}`,
|
|
973
|
+
copy.deleteApproval.prompt
|
|
974
|
+
];
|
|
975
|
+
}
|
|
976
|
+
|
|
870
977
|
function getActivityDisplayParts(activity) {
|
|
871
978
|
if (isCodeGenerationActivityName(activity?.name)) {
|
|
872
979
|
return {
|
|
@@ -904,6 +1011,7 @@ function getActivityDisplayParts(activity) {
|
|
|
904
1011
|
read: 'Read',
|
|
905
1012
|
edit: 'Edit',
|
|
906
1013
|
write: 'Write',
|
|
1014
|
+
delete: 'Delete',
|
|
907
1015
|
patch: 'Patch',
|
|
908
1016
|
run: 'Run',
|
|
909
1017
|
grep: 'Search',
|
|
@@ -961,6 +1069,17 @@ function normalizeRuntimeStatus(status, copy) {
|
|
|
961
1069
|
};
|
|
962
1070
|
}
|
|
963
1071
|
|
|
1072
|
+
export function shouldRefreshRuntimeStateForEvent(event) {
|
|
1073
|
+
const type = String(event?.type || '');
|
|
1074
|
+
return (
|
|
1075
|
+
type === 'assistant:start' ||
|
|
1076
|
+
type === 'assistant:delta' ||
|
|
1077
|
+
type === 'assistant:response' ||
|
|
1078
|
+
type === 'tool:result' ||
|
|
1079
|
+
type === 'compact:auto'
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
964
1083
|
function stageDescriptor(inputStage, busy, runtimeStatus, copy) {
|
|
965
1084
|
const normalized = normalizeRuntimeStatus(runtimeStatus, copy);
|
|
966
1085
|
const tag =
|
|
@@ -1025,7 +1144,6 @@ function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false })
|
|
|
1025
1144
|
const pct = Math.min(100, Math.max(0, pctRaw));
|
|
1026
1145
|
const filled = Math.min(12, Math.max(0, Math.round((pct / 100) * 12)));
|
|
1027
1146
|
const activeColor = pct < 40 ? 'greenBright' : pct < 75 ? 'yellowBright' : 'redBright';
|
|
1028
|
-
const statusColor = runtimeStatus?.color || activeColor;
|
|
1029
1147
|
const chunks = Array.from({ length: 12 }, (_, idx) => {
|
|
1030
1148
|
const zoneColor = idx < 5 ? 'greenBright' : idx < 9 ? 'yellowBright' : 'redBright';
|
|
1031
1149
|
const color = idx < filled ? zoneColor : 'gray';
|
|
@@ -1037,7 +1155,7 @@ function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false })
|
|
|
1037
1155
|
Box,
|
|
1038
1156
|
{ justifyContent: 'flex-end', alignItems: 'center' },
|
|
1039
1157
|
h(Text, { color: 'gray' }, '上下文 '),
|
|
1040
|
-
h(Text, { color:
|
|
1158
|
+
h(Text, { color: activeColor }, `${Math.round(pct)}% `),
|
|
1041
1159
|
h(
|
|
1042
1160
|
Box,
|
|
1043
1161
|
{ flexDirection: 'row' },
|
|
@@ -1061,78 +1179,58 @@ function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false })
|
|
|
1061
1179
|
|
|
1062
1180
|
function PlanStrip({ planState, copy }) {
|
|
1063
1181
|
if (!planState || !planState.total) return null;
|
|
1064
|
-
const
|
|
1065
|
-
const
|
|
1066
|
-
const
|
|
1067
|
-
const
|
|
1068
|
-
const roleLabel =
|
|
1069
|
-
planState.resultStatus || stripComplete || planState.failed
|
|
1070
|
-
? copy?.roleLabels?.system === 'SYSTEM'
|
|
1071
|
-
? 'RESULT'
|
|
1072
|
-
: '结果'
|
|
1073
|
-
: String(planState.role || 'agent').toUpperCase();
|
|
1074
|
-
const titleLabel =
|
|
1075
|
-
planState.resultStatus || stripComplete || planState.failed
|
|
1076
|
-
? copy?.roleLabels?.system === 'SYSTEM'
|
|
1077
|
-
? 'Plan execution result'
|
|
1078
|
-
: '计划执行结果'
|
|
1079
|
-
: planState.title || 'running plan step';
|
|
1182
|
+
const isDone = planState.completed;
|
|
1183
|
+
const borderColor = isDone ? 'green' : planState.failed ? 'red' : 'cyan';
|
|
1184
|
+
const isEnglish = copy?.roleLabels?.system === 'SYSTEM';
|
|
1185
|
+
const completedLabel = isEnglish ? 'DONE' : '已完成';
|
|
1080
1186
|
return h(
|
|
1081
1187
|
Box,
|
|
1082
|
-
{
|
|
1083
|
-
|
|
1084
|
-
borderStyle: 'round',
|
|
1085
|
-
borderColor: planState.failed ? 'red' : 'cyan',
|
|
1086
|
-
paddingX: 1,
|
|
1087
|
-
paddingY: 0,
|
|
1088
|
-
flexDirection: 'column'
|
|
1089
|
-
},
|
|
1188
|
+
{ marginBottom: 1, flexDirection: 'row' },
|
|
1189
|
+
h(Box, { width: 2 }, h(Text, { color: borderColor }, '│')),
|
|
1090
1190
|
h(
|
|
1091
1191
|
Box,
|
|
1092
|
-
{
|
|
1192
|
+
{
|
|
1193
|
+
flexDirection: 'column',
|
|
1194
|
+
borderStyle: 'round',
|
|
1195
|
+
borderColor,
|
|
1196
|
+
paddingX: 1,
|
|
1197
|
+
paddingY: 0,
|
|
1198
|
+
width: '100%'
|
|
1199
|
+
},
|
|
1093
1200
|
h(
|
|
1094
1201
|
Box,
|
|
1095
|
-
|
|
1096
|
-
h(Text, { color: 'black', backgroundColor:
|
|
1097
|
-
|
|
1098
|
-
|
|
1202
|
+
{ justifyContent: 'space-between', marginBottom: planState.steps.length > 0 ? 1 : 0 },
|
|
1203
|
+
h(Text, { color: 'black', backgroundColor: isDone ? 'greenBright' : 'cyanBright' }, ' Plan Summary '),
|
|
1204
|
+
isDone
|
|
1205
|
+
? h(Text, { color: 'black', backgroundColor: 'greenBright' }, ` ${completedLabel} `)
|
|
1206
|
+
: null
|
|
1099
1207
|
),
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
h(Text, { color: 'gray' }, ' '),
|
|
1123
|
-
h(Text, { color: step.status === 'active' ? 'white' : step.status === 'failed' ? 'redBright' : 'gray' }, step.title)
|
|
1208
|
+
planState.steps.length > 0
|
|
1209
|
+
? h(
|
|
1210
|
+
Box,
|
|
1211
|
+
{ flexDirection: 'column' },
|
|
1212
|
+
...planState.steps.map((step, idx) => {
|
|
1213
|
+
const roleKey = step.role ? step.role.toLowerCase() : '';
|
|
1214
|
+
const normalizedRole = ['planner', 'coder', 'reviewer', 'tester', 'summarizer'].includes(roleKey) ? roleKey : 'coder';
|
|
1215
|
+
const stepTheme = roleStyle(normalizedRole);
|
|
1216
|
+
const roleTag = step.role ? step.role.toUpperCase() : '';
|
|
1217
|
+
const stepDone = step.status === 'done' || isDone;
|
|
1218
|
+
const stepFailed = step.status === 'failed';
|
|
1219
|
+
const marker = stepFailed ? '✗' : stepDone ? '✓' : '·';
|
|
1220
|
+
const markerColor = stepFailed ? 'redBright' : stepDone ? 'greenBright' : 'gray';
|
|
1221
|
+
return h(
|
|
1222
|
+
Box,
|
|
1223
|
+
{ key: `plan-step-${idx}` },
|
|
1224
|
+
h(Text, { color: markerColor }, `${marker} `),
|
|
1225
|
+
roleTag ? h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `) : null,
|
|
1226
|
+
roleTag ? h(Text, { color: 'gray' }, ' ') : null,
|
|
1227
|
+
h(Text, { color: stepDone && !stepFailed ? 'gray' : 'white' }, `${step.index}. ${step.title}`)
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1124
1230
|
)
|
|
1125
1231
|
)
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
planState.resultNext
|
|
1129
|
-
? h(
|
|
1130
|
-
Box,
|
|
1131
|
-
{ marginTop: 1 },
|
|
1132
|
-
h(Text, { color: 'black', backgroundColor: 'yellowBright' }, ' NEXT '),
|
|
1133
|
-
h(Text, { color: 'gray' }, ` ${trimText(planState.resultNext, 108)}`)
|
|
1134
|
-
)
|
|
1135
|
-
: null
|
|
1232
|
+
: null
|
|
1233
|
+
)
|
|
1136
1234
|
);
|
|
1137
1235
|
}
|
|
1138
1236
|
|
|
@@ -1231,21 +1329,43 @@ export function parseAutoPlanSummaryMessage(text) {
|
|
|
1231
1329
|
warnings: '',
|
|
1232
1330
|
failed: '',
|
|
1233
1331
|
warningSteps: '',
|
|
1234
|
-
failedSteps: ''
|
|
1332
|
+
failedSteps: '',
|
|
1333
|
+
planSteps: []
|
|
1235
1334
|
};
|
|
1236
1335
|
|
|
1336
|
+
let inPlanSteps = false;
|
|
1237
1337
|
for (const line of lines.slice(1)) {
|
|
1238
|
-
if (line
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1338
|
+
if (line === 'Plan Steps:') {
|
|
1339
|
+
inPlanSteps = true;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (inPlanSteps) {
|
|
1343
|
+
// Parse " 1. [role] title"
|
|
1344
|
+
const stepMatch = line.match(/^(\d+)\.\s*\[([^\]]+)\]\s+(.+)$/);
|
|
1345
|
+
if (stepMatch) {
|
|
1346
|
+
parsed.planSteps.push({
|
|
1347
|
+
index: Number(stepMatch[1]),
|
|
1348
|
+
role: String(stepMatch[2] || '').trim().toLowerCase(),
|
|
1349
|
+
title: String(stepMatch[3] || '').trim(),
|
|
1350
|
+
status: 'pending'
|
|
1351
|
+
});
|
|
1352
|
+
} else {
|
|
1353
|
+
inPlanSteps = false;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (!inPlanSteps) {
|
|
1357
|
+
if (line.startsWith('File: ')) parsed.filePath = line.slice('File: '.length).trim();
|
|
1358
|
+
else if (line.startsWith('Plan File: ')) parsed.filePath = line.slice('Plan File: '.length).trim();
|
|
1359
|
+
else if (line.startsWith('Plan Summary: ')) parsed.planSummary = line.slice('Plan Summary: '.length).trim();
|
|
1360
|
+
else if (line.startsWith('Final Summary: ')) parsed.finalSummary = line.slice('Final Summary: '.length).trim();
|
|
1361
|
+
else if (line.startsWith('Approval: ')) parsed.approval = line.slice('Approval: '.length).trim();
|
|
1362
|
+
else if (line.startsWith('Steps: ')) parsed.stepsTotal = line.slice('Steps: '.length).trim();
|
|
1363
|
+
else if (line.startsWith('Completed: ')) parsed.completed = line.slice('Completed: '.length).trim();
|
|
1364
|
+
else if (line.startsWith('Warnings: ')) parsed.warnings = line.slice('Warnings: '.length).trim();
|
|
1365
|
+
else if (line.startsWith('Failed: ')) parsed.failed = line.slice('Failed: '.length).trim();
|
|
1366
|
+
else if (line.startsWith('Warning steps: ')) parsed.warningSteps = line.slice('Warning steps: '.length).trim();
|
|
1367
|
+
else if (line.startsWith('Failed steps: ')) parsed.failedSteps = line.slice('Failed steps: '.length).trim();
|
|
1368
|
+
}
|
|
1249
1369
|
}
|
|
1250
1370
|
|
|
1251
1371
|
return parsed;
|
|
@@ -1394,7 +1514,7 @@ function extractPreviewLinesFromTool(tool, maxLines = 3) {
|
|
|
1394
1514
|
typeof tool?.arguments === 'string'
|
|
1395
1515
|
? (() => {
|
|
1396
1516
|
const parsedArguments = safeJsonParse(tool.arguments);
|
|
1397
|
-
if (parsedArguments) {
|
|
1517
|
+
if (parsedArguments && !parsedArguments._invalid_json) {
|
|
1398
1518
|
const parsedPreview = collectPreviewStrings(parsedArguments);
|
|
1399
1519
|
if (parsedPreview.length > 0) return parsedPreview;
|
|
1400
1520
|
}
|
|
@@ -1413,7 +1533,7 @@ function extractPreviewLinesFromTool(tool, maxLines = 3) {
|
|
|
1413
1533
|
}
|
|
1414
1534
|
|
|
1415
1535
|
function getLatestToolPreviewLines(msg, maxLines = 3) {
|
|
1416
|
-
const codeTools = new Set(['edit', 'write'
|
|
1536
|
+
const codeTools = new Set(['edit', 'write']);
|
|
1417
1537
|
const extractFromCalls = (calls) => {
|
|
1418
1538
|
for (let index = calls.length - 1; index >= 0; index -= 1) {
|
|
1419
1539
|
const tool = calls[index];
|
|
@@ -1451,7 +1571,7 @@ export function getGeneratingCodePlaceholderRows(msg, copy, contentWidth = 72) {
|
|
|
1451
1571
|
const previewWindow = getLatestToolPreviewLines(msg, 3);
|
|
1452
1572
|
if (previewWindow.lines.length === 0) return [];
|
|
1453
1573
|
const hasRunningCodeTool = (Array.isArray(msg?.toolCalls) ? msg.toolCalls : []).some(
|
|
1454
|
-
(tool) => tool?.status === 'running' && new Set(['edit', 'write'
|
|
1574
|
+
(tool) => tool?.status === 'running' && new Set(['edit', 'write']).has(parseToolDisplayName(tool?.name).base)
|
|
1455
1575
|
);
|
|
1456
1576
|
const isCodeGenerationStatus = liveStatus === String(copy?.runtime?.generatingCode || '').trim();
|
|
1457
1577
|
if (!isCodeGenerationStatus && !(msg?.phase === 'tooling' && hasRunningCodeTool)) return [];
|
|
@@ -1749,13 +1869,11 @@ function isCodeActivityName(name) {
|
|
|
1749
1869
|
'edit',
|
|
1750
1870
|
'write',
|
|
1751
1871
|
'write_file',
|
|
1752
|
-
'patch',
|
|
1753
1872
|
'replace_text',
|
|
1754
1873
|
'replace_block',
|
|
1755
1874
|
'insert_before',
|
|
1756
1875
|
'insert_after',
|
|
1757
|
-
'validate_edit'
|
|
1758
|
-
'generate_diff'
|
|
1876
|
+
'validate_edit'
|
|
1759
1877
|
]).has(parsed.base);
|
|
1760
1878
|
}
|
|
1761
1879
|
|
|
@@ -1829,6 +1947,10 @@ export function normalizeActivitySpacingRows(inputRows) {
|
|
|
1829
1947
|
}
|
|
1830
1948
|
}
|
|
1831
1949
|
|
|
1950
|
+
if (isTodoRow(row) && !isTodoRow(next) && next && !isBlankTextRow(next) && next.kind !== 'status') {
|
|
1951
|
+
normalized.push({ kind: 'todo-gap' });
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1832
1954
|
if (isBlankTextRow(row) && isBlankTextRow(prev)) {
|
|
1833
1955
|
normalized.pop();
|
|
1834
1956
|
}
|
|
@@ -1964,13 +2086,7 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
|
|
|
1964
2086
|
const trimmed = line.trim();
|
|
1965
2087
|
const planProgress = parsePlanProgressLine(trimmed);
|
|
1966
2088
|
if (planProgress) {
|
|
1967
|
-
|
|
1968
|
-
kind: 'plan-progress',
|
|
1969
|
-
current: planProgress.current,
|
|
1970
|
-
total: planProgress.total,
|
|
1971
|
-
role: planProgress.role,
|
|
1972
|
-
title: trimText(planProgress.title, Math.max(12, contentWidth - 18))
|
|
1973
|
-
});
|
|
2089
|
+
// Skip rendering plan progress lines inline — shown in header badge instead
|
|
1974
2090
|
continue;
|
|
1975
2091
|
}
|
|
1976
2092
|
if (trimmed.startsWith('```')) {
|
|
@@ -2146,6 +2262,9 @@ export function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
2146
2262
|
h(Text, { color, dimColor }, row.text || row.activeForm || ' ')
|
|
2147
2263
|
);
|
|
2148
2264
|
}
|
|
2265
|
+
if (row.kind === 'todo-gap') {
|
|
2266
|
+
return h(Box, { key: `row-todo-gap-${msg.id}-${idx}`, marginTop: 1 }, h(Text, { color: 'gray' }, ' '));
|
|
2267
|
+
}
|
|
2149
2268
|
if (row.kind === 'table') {
|
|
2150
2269
|
return h(
|
|
2151
2270
|
Box,
|
|
@@ -2190,16 +2309,8 @@ export function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
2190
2309
|
);
|
|
2191
2310
|
}
|
|
2192
2311
|
if (row.kind === 'plan-progress') {
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
{ key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
|
|
2196
|
-
h(Text, { color: 'cyanBright' }, '[plan] '),
|
|
2197
|
-
h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
|
|
2198
|
-
h(Text, { color: 'gray' }, ' -> '),
|
|
2199
|
-
h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
|
|
2200
|
-
h(Text, { color: 'gray' }, ': '),
|
|
2201
|
-
h(Text, { color: 'white' }, row.title)
|
|
2202
|
-
);
|
|
2312
|
+
// Already shown in header badge — skip inline rendering
|
|
2313
|
+
return null;
|
|
2203
2314
|
}
|
|
2204
2315
|
if (row.kind === 'status') {
|
|
2205
2316
|
const dots = '.'.repeat((loaderTick % 3) + 1);
|
|
@@ -2348,11 +2459,7 @@ export function moveSuggestionSelection(currentIndex, itemCount, direction, page
|
|
|
2348
2459
|
|
|
2349
2460
|
function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, contentWidth = 72, copy }) {
|
|
2350
2461
|
if (msg?.planStrip) {
|
|
2351
|
-
return h(
|
|
2352
|
-
Box,
|
|
2353
|
-
{ marginBottom: 1 },
|
|
2354
|
-
h(PlanStrip, { planState: msg.planState, copy })
|
|
2355
|
-
);
|
|
2462
|
+
return h(PlanStrip, { planState: msg.planState, copy });
|
|
2356
2463
|
}
|
|
2357
2464
|
if (msg?.planSummary || parseAutoPlanSummaryMessage(msg?.text)) {
|
|
2358
2465
|
return h(PlanSummaryBubble, { msg, copy });
|
|
@@ -2385,7 +2492,8 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
|
|
|
2385
2492
|
h(
|
|
2386
2493
|
Box,
|
|
2387
2494
|
null,
|
|
2388
|
-
h(Text, { color: theme.badgeText, backgroundColor: theme.badgeBg }, ` ${messageLabel(msg.label, copy)} `)
|
|
2495
|
+
h(Text, { color: theme.badgeText, backgroundColor: theme.badgeBg }, ` ${messageLabel(msg.label, copy)} `),
|
|
2496
|
+
msg.planStep ? h(Text, { color: 'gray', dimColor: true }, ` ${msg.planStep} `) : null
|
|
2389
2497
|
),
|
|
2390
2498
|
autoSkillBadge
|
|
2391
2499
|
? h(Text, { color: 'blueBright' }, autoSkillBadge)
|
|
@@ -2536,6 +2644,8 @@ function InputBar({
|
|
|
2536
2644
|
afterCursor,
|
|
2537
2645
|
cursorVisible,
|
|
2538
2646
|
busy,
|
|
2647
|
+
disabled = false,
|
|
2648
|
+
disabledText = '',
|
|
2539
2649
|
inputStage,
|
|
2540
2650
|
pendingQueueLength,
|
|
2541
2651
|
showToolDetails,
|
|
@@ -2577,20 +2687,60 @@ function InputBar({
|
|
|
2577
2687
|
Box,
|
|
2578
2688
|
null,
|
|
2579
2689
|
h(Text, { color: 'cyan' }, 'codemini> '),
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2690
|
+
disabled
|
|
2691
|
+
? h(Text, { color: 'gray' }, disabledText || '')
|
|
2692
|
+
: [
|
|
2693
|
+
h(Text, { key: 'before', color: 'white' }, beforeCursor),
|
|
2694
|
+
h(
|
|
2695
|
+
Text,
|
|
2696
|
+
{
|
|
2697
|
+
key: 'cursor',
|
|
2698
|
+
color: cursorVisible ? 'black' : 'white',
|
|
2699
|
+
backgroundColor: cursorVisible ? 'cyanBright' : undefined
|
|
2700
|
+
},
|
|
2701
|
+
underCursor || ' '
|
|
2702
|
+
),
|
|
2703
|
+
h(Text, { key: 'after', color: 'white' }, afterCursor)
|
|
2704
|
+
]
|
|
2590
2705
|
)
|
|
2591
2706
|
);
|
|
2592
2707
|
}
|
|
2593
2708
|
|
|
2709
|
+
function DeleteApprovalPanel({ request, inputValue, errorText, copy }) {
|
|
2710
|
+
if (!request) return null;
|
|
2711
|
+
const details =
|
|
2712
|
+
request?.toolName === 'delete'
|
|
2713
|
+
? request
|
|
2714
|
+
: normalizeDeleteApprovalRequest(request);
|
|
2715
|
+
if (!details) return null;
|
|
2716
|
+
const typeLabel = details.type === 'directory' ? copy.deleteApproval.directoryType : copy.deleteApproval.fileType;
|
|
2717
|
+
const pathDisplay = details.path.includes('/') || details.path.includes('\\') ? details.path : `./${details.path}`;
|
|
2718
|
+
const placeholder = String(copy.deleteApproval.answerPlaceholder || '').trim();
|
|
2719
|
+
return h(
|
|
2720
|
+
Box,
|
|
2721
|
+
{
|
|
2722
|
+
marginTop: 1,
|
|
2723
|
+
flexDirection: 'column',
|
|
2724
|
+
borderStyle: 'round',
|
|
2725
|
+
borderColor: 'redBright',
|
|
2726
|
+
paddingX: 1,
|
|
2727
|
+
paddingY: 0
|
|
2728
|
+
},
|
|
2729
|
+
h(Text, { color: 'redBright' }, copy.deleteApproval.title),
|
|
2730
|
+
h(Text, { color: 'white' }, `${copy.deleteApproval.nameLabel}: ${details.name}`),
|
|
2731
|
+
h(Text, { color: 'white' }, `${copy.deleteApproval.pathLabel}: ${pathDisplay}`),
|
|
2732
|
+
h(Text, { color: 'white' }, `${copy.deleteApproval.typeLabel}: ${typeLabel}`),
|
|
2733
|
+
h(Text, { color: 'gray' }, copy.deleteApproval.prompt),
|
|
2734
|
+
h(
|
|
2735
|
+
Box,
|
|
2736
|
+
{ marginTop: 1 },
|
|
2737
|
+
h(Text, { color: 'redBright' }, `${copy.deleteApproval.answerLabel}: `),
|
|
2738
|
+
h(Text, { color: inputValue ? 'white' : 'gray' }, inputValue || placeholder || ' ')
|
|
2739
|
+
),
|
|
2740
|
+
errorText ? h(Text, { color: 'yellowBright' }, errorText) : null
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2594
2744
|
function SignatureBar({ version = '' }) {
|
|
2595
2745
|
return h(
|
|
2596
2746
|
Box,
|
|
@@ -2672,6 +2822,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2672
2822
|
const [debugKeys, setDebugKeys] = useState(false);
|
|
2673
2823
|
const [lastKeyDebug, setLastKeyDebug] = useState('');
|
|
2674
2824
|
const [showToolDetails, setShowToolDetails] = useState(false);
|
|
2825
|
+
const [pendingDeleteApproval, setPendingDeleteApproval] = useState(null);
|
|
2826
|
+
const [deleteApprovalInput, setDeleteApprovalInput] = useState('');
|
|
2827
|
+
const [deleteApprovalError, setDeleteApprovalError] = useState('');
|
|
2675
2828
|
const activeAssistantIdRef = useRef(null);
|
|
2676
2829
|
const activeAssistantAutoSkillNamesRef = useRef([]);
|
|
2677
2830
|
const streamedAssistantHandledRef = useRef(false);
|
|
@@ -2681,6 +2834,11 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2681
2834
|
const messagesRef = useRef([]);
|
|
2682
2835
|
const pendingQueueRef = useRef([]);
|
|
2683
2836
|
const deltaBufferRef = useRef('');
|
|
2837
|
+
const activePlanStepNumberRef = useRef(0);
|
|
2838
|
+
const activePlanStepRoleRef = useRef(null);
|
|
2839
|
+
const activePlanStepInfoRef = useRef(null);
|
|
2840
|
+
const activePlanStepTitleRef = useRef('');
|
|
2841
|
+
const deleteApprovalResolverRef = useRef(null);
|
|
2684
2842
|
|
|
2685
2843
|
useEffect(() => {
|
|
2686
2844
|
const rawStartupActivities = runtime.consumeStartupEvents?.();
|
|
@@ -2703,6 +2861,26 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2703
2861
|
const planTextBufferRef = useRef('');
|
|
2704
2862
|
const { exit } = useApp();
|
|
2705
2863
|
|
|
2864
|
+
useEffect(() => {
|
|
2865
|
+
if (typeof runtime.setRequestToolApproval !== 'function') return () => {};
|
|
2866
|
+
runtime.setRequestToolApproval((request) => {
|
|
2867
|
+
const normalized = normalizeDeleteApprovalRequest(request);
|
|
2868
|
+
if (!normalized) {
|
|
2869
|
+
return Promise.resolve({ approved: false });
|
|
2870
|
+
}
|
|
2871
|
+
setDeleteApprovalInput('');
|
|
2872
|
+
setDeleteApprovalError('');
|
|
2873
|
+
setPendingDeleteApproval(normalized);
|
|
2874
|
+
return new Promise((resolve) => {
|
|
2875
|
+
deleteApprovalResolverRef.current = resolve;
|
|
2876
|
+
});
|
|
2877
|
+
});
|
|
2878
|
+
return () => {
|
|
2879
|
+
runtime.setRequestToolApproval(null);
|
|
2880
|
+
deleteApprovalResolverRef.current = null;
|
|
2881
|
+
};
|
|
2882
|
+
}, [runtime]);
|
|
2883
|
+
|
|
2706
2884
|
useEffect(() => {
|
|
2707
2885
|
messagesRef.current = messages;
|
|
2708
2886
|
}, [messages]);
|
|
@@ -2739,7 +2917,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2739
2917
|
: [];
|
|
2740
2918
|
const hasTransientPanels =
|
|
2741
2919
|
commandSuggestions.length > 0 || pendingQueue.length > 0 || debugKeys || Boolean(planState?.total);
|
|
2742
|
-
const messageContentWidth = Math.max(24, stdoutCols -
|
|
2920
|
+
const messageContentWidth = Math.max(24, stdoutCols - 8);
|
|
2743
2921
|
|
|
2744
2922
|
const syncRuntimeVisualState = (variant = 'ready') => {
|
|
2745
2923
|
const snapshot = runtime.getRuntimeState?.();
|
|
@@ -2747,10 +2925,35 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2747
2925
|
setDisplaySessionId(snapshot.sessionId || sessionId);
|
|
2748
2926
|
setDisplayModel(snapshot.model || model);
|
|
2749
2927
|
setDisplaySdkProvider(snapshot.sdkProvider || sdkProvider);
|
|
2750
|
-
setRuntimeState(
|
|
2928
|
+
setRuntimeState((prev) => {
|
|
2929
|
+
if (!prev || !planTextBufferRef.current) return snapshot;
|
|
2930
|
+
const prevTokens = Number(prev.currentContextTokens || 0);
|
|
2931
|
+
const newTokens = Number(snapshot.currentContextTokens || 0);
|
|
2932
|
+
if (newTokens < prevTokens) {
|
|
2933
|
+
return { ...snapshot, currentContextTokens: prevTokens, contextUsagePct: prev.contextUsagePct };
|
|
2934
|
+
}
|
|
2935
|
+
return snapshot;
|
|
2936
|
+
});
|
|
2751
2937
|
setRuntimeStatus(makeIdleStatus(copy, snapshot, variant));
|
|
2752
2938
|
};
|
|
2753
2939
|
|
|
2940
|
+
const refreshRuntimeSnapshot = () => {
|
|
2941
|
+
const snapshot = runtime.getRuntimeState?.();
|
|
2942
|
+
if (!snapshot) return;
|
|
2943
|
+
setDisplaySessionId(snapshot.sessionId || sessionId);
|
|
2944
|
+
setDisplayModel(snapshot.model || model);
|
|
2945
|
+
setDisplaySdkProvider(snapshot.sdkProvider || sdkProvider);
|
|
2946
|
+
setRuntimeState((prev) => {
|
|
2947
|
+
if (!prev || !planTextBufferRef.current) return snapshot;
|
|
2948
|
+
const prevTokens = Number(prev.currentContextTokens || 0);
|
|
2949
|
+
const newTokens = Number(snapshot.currentContextTokens || 0);
|
|
2950
|
+
if (newTokens < prevTokens) {
|
|
2951
|
+
return { ...snapshot, currentContextTokens: prevTokens, contextUsagePct: prev.contextUsagePct };
|
|
2952
|
+
}
|
|
2953
|
+
return snapshot;
|
|
2954
|
+
});
|
|
2955
|
+
};
|
|
2956
|
+
|
|
2754
2957
|
useEffect(() => {
|
|
2755
2958
|
syncRuntimeVisualState('ready');
|
|
2756
2959
|
}, []);
|
|
@@ -2767,15 +2970,34 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2767
2970
|
if (!last) return;
|
|
2768
2971
|
const current = Number(last[1]);
|
|
2769
2972
|
const total = Number(last[2]);
|
|
2770
|
-
const role = String(last[3] || '').trim();
|
|
2973
|
+
const role = String(last[3] || '').trim().toLowerCase();
|
|
2974
|
+
const normalizedRole = ['planner', 'coder', 'reviewer', 'tester', 'summarizer'].includes(role) ? role : 'coder';
|
|
2771
2975
|
const title = String(last[4] || '').trim();
|
|
2976
|
+
|
|
2977
|
+
// Detect step transition — finalize old assistant and create a new one
|
|
2978
|
+
if (activePlanStepNumberRef.current > 0 && current !== activePlanStepNumberRef.current) {
|
|
2979
|
+
flushAssistantDelta();
|
|
2980
|
+
const oldId = activeAssistantIdRef.current;
|
|
2981
|
+
if (oldId) {
|
|
2982
|
+
finalizeActiveAssistant();
|
|
2983
|
+
activeAssistantIdRef.current = null;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
activePlanStepNumberRef.current = current;
|
|
2987
|
+
|
|
2988
|
+
activePlanStepRoleRef.current = normalizedRole;
|
|
2989
|
+
activePlanStepInfoRef.current = { current, total };
|
|
2990
|
+
activePlanStepTitleRef.current = title;
|
|
2772
2991
|
setActiveAssistantMeta({
|
|
2773
|
-
|
|
2992
|
+
label: normalizedRole,
|
|
2993
|
+
planStepInfo: { current, total },
|
|
2994
|
+
planStep: `${current}/${total} · ${title}`
|
|
2774
2995
|
});
|
|
2775
2996
|
setPlanState((prev) => {
|
|
2776
|
-
|
|
2777
|
-
.map((step) => (step.
|
|
2997
|
+
let steps = (prev.steps || [])
|
|
2998
|
+
.map((step) => (step.status === 'active' ? { ...step, status: 'done' } : step))
|
|
2778
2999
|
.filter((step, idx, arr) => arr.findIndex((x) => x.index === step.index) === idx);
|
|
3000
|
+
|
|
2779
3001
|
const withoutCurrent = steps.filter((step) => step.index !== current);
|
|
2780
3002
|
return {
|
|
2781
3003
|
current,
|
|
@@ -2966,13 +3188,16 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2966
3188
|
}
|
|
2967
3189
|
const parsedPlanSummary = result.type === 'system' ? parseAutoPlanSummaryMessage(result.text || '') : null;
|
|
2968
3190
|
if (parsedPlanSummary?.approval === 'pending') {
|
|
3191
|
+
const preSteps = Array.isArray(parsedPlanSummary.planSteps) && parsedPlanSummary.planSteps.length > 0
|
|
3192
|
+
? parsedPlanSummary.planSteps
|
|
3193
|
+
: [];
|
|
2969
3194
|
setPlanState({
|
|
2970
3195
|
current: 0,
|
|
2971
|
-
total:
|
|
3196
|
+
total: preSteps.length,
|
|
2972
3197
|
role: '',
|
|
2973
3198
|
title: '',
|
|
2974
3199
|
failed: false,
|
|
2975
|
-
steps:
|
|
3200
|
+
steps: preSteps,
|
|
2976
3201
|
pendingApproval: true,
|
|
2977
3202
|
completed: false,
|
|
2978
3203
|
resultStatus: '',
|
|
@@ -3008,7 +3233,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3008
3233
|
const nextCall = {
|
|
3009
3234
|
id: toolCall.id || '',
|
|
3010
3235
|
name: toolCall.name || '',
|
|
3011
|
-
arguments: typeof toolCall.arguments === 'string'
|
|
3236
|
+
arguments: typeof toolCall.arguments === 'string'
|
|
3237
|
+
? (() => { const p = safeJsonParse(toolCall.arguments); return p._invalid_json ? toolCall.arguments : p; })()
|
|
3238
|
+
: toolCall.arguments,
|
|
3012
3239
|
status: 'pending',
|
|
3013
3240
|
type: 'tool'
|
|
3014
3241
|
};
|
|
@@ -3021,11 +3248,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3021
3248
|
};
|
|
3022
3249
|
|
|
3023
3250
|
const finalizeActiveAssistant = () => {
|
|
3251
|
+
activePlanStepRoleRef.current = null;
|
|
3252
|
+
activePlanStepInfoRef.current = null;
|
|
3253
|
+
activePlanStepTitleRef.current = '';
|
|
3024
3254
|
setActiveAssistantMeta({
|
|
3025
3255
|
loading: false,
|
|
3026
3256
|
phase: undefined,
|
|
3027
3257
|
liveStatus: undefined,
|
|
3028
|
-
planStep: undefined,
|
|
3029
3258
|
pendingToolCalls: [],
|
|
3030
3259
|
codeGenerationEndedAt: undefined,
|
|
3031
3260
|
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
@@ -3061,19 +3290,27 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3061
3290
|
if (activeAssistantIdRef.current) return activeAssistantIdRef.current;
|
|
3062
3291
|
const aid = nextId();
|
|
3063
3292
|
activeAssistantIdRef.current = aid;
|
|
3293
|
+
const planRole = activePlanStepRoleRef.current;
|
|
3294
|
+
const label = planRole || 'coder';
|
|
3295
|
+
const style = ROLE_STYLES[label] || ROLE_STYLES.coder;
|
|
3296
|
+
const planStepInfo = activePlanStepInfoRef.current;
|
|
3297
|
+
const planStepTitle = activePlanStepTitleRef.current;
|
|
3298
|
+
const planStepDisplay = planStepInfo ? `${planStepInfo.current}/${planStepInfo.total} · ${planStepTitle}` : undefined;
|
|
3064
3299
|
setMessages((prev) => [
|
|
3065
3300
|
...prev,
|
|
3066
3301
|
{
|
|
3067
3302
|
id: aid,
|
|
3068
|
-
label
|
|
3303
|
+
label,
|
|
3069
3304
|
text: '',
|
|
3070
|
-
color:
|
|
3305
|
+
color: style.text,
|
|
3071
3306
|
toolCalls: [],
|
|
3072
3307
|
segments: [],
|
|
3073
3308
|
loading: true,
|
|
3074
3309
|
phase: 'thinking',
|
|
3075
3310
|
liveStatus: copy.runtime.modelThinking,
|
|
3076
|
-
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
3311
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current,
|
|
3312
|
+
...(planStepInfo ? { planStepInfo } : {}),
|
|
3313
|
+
...(planStepDisplay ? { planStep: planStepDisplay } : {})
|
|
3077
3314
|
}
|
|
3078
3315
|
]);
|
|
3079
3316
|
return aid;
|
|
@@ -3119,6 +3356,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3119
3356
|
resultNext: ''
|
|
3120
3357
|
});
|
|
3121
3358
|
planTextBufferRef.current = '';
|
|
3359
|
+
activePlanStepNumberRef.current = 0;
|
|
3122
3360
|
activeAssistantIdRef.current = null;
|
|
3123
3361
|
activeAssistantAutoSkillNamesRef.current = [];
|
|
3124
3362
|
streamedAssistantHandledRef.current = false;
|
|
@@ -3126,6 +3364,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3126
3364
|
|
|
3127
3365
|
runtime
|
|
3128
3366
|
.submit(line, (event) => {
|
|
3367
|
+
if (shouldRefreshRuntimeStateForEvent(event)) {
|
|
3368
|
+
refreshRuntimeSnapshot();
|
|
3369
|
+
}
|
|
3129
3370
|
if (event?.type === 'assistant:start') {
|
|
3130
3371
|
streamedAssistantHandledRef.current = true;
|
|
3131
3372
|
setRuntimeStatus(makeStatus(copy.runtime.modelThinking, copy.runtime.requestDelivered, 'cyanBright'));
|
|
@@ -3160,7 +3401,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3160
3401
|
if (event?.type === 'assistant:tool_call_delta') {
|
|
3161
3402
|
ensureActiveAssistant();
|
|
3162
3403
|
const parsed = parseToolDisplayName(event.toolCall?.name);
|
|
3163
|
-
const isCodeTool = new Set(['write', 'edit'
|
|
3404
|
+
const isCodeTool = new Set(['write', 'edit']).has(parsed.base);
|
|
3164
3405
|
if (isCodeTool) {
|
|
3165
3406
|
setRuntimeStatus(makeStatus(copy.runtime.generatingCode, copy.runtime.streamingReply, 'greenBright'));
|
|
3166
3407
|
setInputStage('streaming');
|
|
@@ -3219,7 +3460,6 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3219
3460
|
loading: false,
|
|
3220
3461
|
phase: undefined,
|
|
3221
3462
|
liveStatus: undefined,
|
|
3222
|
-
planStep: undefined,
|
|
3223
3463
|
pendingToolCalls: [],
|
|
3224
3464
|
autoSkillNames: activeAssistantAutoSkillNamesRef.current,
|
|
3225
3465
|
...(m.codeGenerationStartedAt && !m.codeGenerationEndedAt ? { codeGenerationEndedAt: Date.now() } : {})
|
|
@@ -3227,7 +3467,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3227
3467
|
})
|
|
3228
3468
|
);
|
|
3229
3469
|
}
|
|
3230
|
-
if (!hasPlannedTools) {
|
|
3470
|
+
if (!hasPlannedTools && !activePlanStepInfoRef.current) {
|
|
3231
3471
|
activeAssistantIdRef.current = null;
|
|
3232
3472
|
}
|
|
3233
3473
|
if (!hadActiveAssistant && !hasPlannedTools && event.text) {
|
|
@@ -3380,6 +3620,22 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3380
3620
|
liveStatus: copy.toolActivity.waitingModelAdjust(detail)
|
|
3381
3621
|
});
|
|
3382
3622
|
}
|
|
3623
|
+
if (event?.type === 'plan:steps') {
|
|
3624
|
+
const planSteps = Array.isArray(event.steps) ? event.steps : [];
|
|
3625
|
+
if (planSteps.length > 0) {
|
|
3626
|
+
setPlanState((prev) => ({
|
|
3627
|
+
...prev,
|
|
3628
|
+
total: planSteps.length,
|
|
3629
|
+
steps: planSteps.map((s) => ({
|
|
3630
|
+
index: s.index,
|
|
3631
|
+
total: planSteps.length,
|
|
3632
|
+
role: s.role || '',
|
|
3633
|
+
title: s.title || '',
|
|
3634
|
+
status: 'pending'
|
|
3635
|
+
}))
|
|
3636
|
+
}));
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3383
3639
|
if (event?.type === 'skill:start') {
|
|
3384
3640
|
ensureActiveAssistant();
|
|
3385
3641
|
const detail = describeSkillActivity(event.name, copy);
|
|
@@ -3459,16 +3715,28 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3459
3715
|
return;
|
|
3460
3716
|
}
|
|
3461
3717
|
if (result.type !== 'noop') setInputStage('idle');
|
|
3462
|
-
if (planTextBufferRef.current
|
|
3463
|
-
setPlanState((prev) =>
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3718
|
+
if (planTextBufferRef.current) {
|
|
3719
|
+
setPlanState((prev) => {
|
|
3720
|
+
if (!prev.total) return prev;
|
|
3721
|
+
return {
|
|
3722
|
+
...prev,
|
|
3723
|
+
completed: !prev.failed,
|
|
3724
|
+
steps: (prev.steps || []).map((step) =>
|
|
3725
|
+
step.status === 'active' ? { ...step, status: prev.failed ? 'failed' : 'done' } : step
|
|
3726
|
+
)
|
|
3727
|
+
};
|
|
3728
|
+
});
|
|
3469
3729
|
}
|
|
3470
3730
|
syncRuntimeVisualState(result.type === 'noop' ? 'ready' : 'after');
|
|
3471
3731
|
if (result.type === 'noop') return;
|
|
3732
|
+
// 被用户中止时显示提示消息
|
|
3733
|
+
if (result.aborted) {
|
|
3734
|
+
setMessages((prev) => [
|
|
3735
|
+
...prev,
|
|
3736
|
+
{ id: nextId(), label: 'system', text: copy.runtime.responseStopped, color: 'yellowBright' }
|
|
3737
|
+
]);
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3472
3740
|
if (!shouldAppendAssistantResult(result, activeAssistantIdRef.current, streamedAssistantHandledRef.current)) return;
|
|
3473
3741
|
appendResultMessage(result);
|
|
3474
3742
|
})
|
|
@@ -3485,7 +3753,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3485
3753
|
...prev,
|
|
3486
3754
|
failed: prev.total > 0,
|
|
3487
3755
|
steps: (prev.steps || []).map((step) =>
|
|
3488
|
-
step.
|
|
3756
|
+
step.status === 'active' ? { ...step, status: 'failed' } : step
|
|
3489
3757
|
)
|
|
3490
3758
|
}));
|
|
3491
3759
|
setMessages((prev) => [
|
|
@@ -3497,6 +3765,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3497
3765
|
flushAssistantDelta();
|
|
3498
3766
|
finalizeActiveAssistant();
|
|
3499
3767
|
activeAssistantIdRef.current = null;
|
|
3768
|
+
activePlanStepNumberRef.current = 0;
|
|
3500
3769
|
streamedAssistantHandledRef.current = false;
|
|
3501
3770
|
activeUserMessageIdRef.current = null;
|
|
3502
3771
|
if (deltaFlushTimerRef.current) {
|
|
@@ -3552,6 +3821,19 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3552
3821
|
appendResultMessage(result);
|
|
3553
3822
|
})
|
|
3554
3823
|
.catch((err) => {
|
|
3824
|
+
// 用户主动中止,不显示为错误
|
|
3825
|
+
if (err?.name === 'AbortError') {
|
|
3826
|
+
updateMessageMeta(userMessageId, {
|
|
3827
|
+
loading: false,
|
|
3828
|
+
phase: undefined,
|
|
3829
|
+
liveStatus: undefined
|
|
3830
|
+
});
|
|
3831
|
+
setMessages((prev) => [
|
|
3832
|
+
...prev,
|
|
3833
|
+
{ id: nextId(), label: 'system', text: copy.runtime.responseStopped, color: 'yellowBright' }
|
|
3834
|
+
]);
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3555
3837
|
const message = sanitizeRenderableText(err?.message || String(err));
|
|
3556
3838
|
updateMessageMeta(userMessageId, {
|
|
3557
3839
|
loading: false,
|
|
@@ -3599,6 +3881,46 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3599
3881
|
escSeqRef.current = '';
|
|
3600
3882
|
}
|
|
3601
3883
|
|
|
3884
|
+
if (pendingDeleteApproval) {
|
|
3885
|
+
if (key.return) {
|
|
3886
|
+
const answer = parseDeleteApprovalAnswer(deleteApprovalInput);
|
|
3887
|
+
if (answer === 'approve' || answer === 'deny') {
|
|
3888
|
+
const resolver = deleteApprovalResolverRef.current;
|
|
3889
|
+
deleteApprovalResolverRef.current = null;
|
|
3890
|
+
setPendingDeleteApproval(null);
|
|
3891
|
+
setDeleteApprovalInput('');
|
|
3892
|
+
setDeleteApprovalError('');
|
|
3893
|
+
if (resolver) resolver({ approved: answer === 'approve' });
|
|
3894
|
+
} else {
|
|
3895
|
+
setDeleteApprovalError(copy.deleteApproval.invalidAnswer);
|
|
3896
|
+
}
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
if (isBackspaceKey(value, key) || isDeleteKey(value, key)) {
|
|
3901
|
+
setDeleteApprovalInput((prev) => prev.slice(0, -1));
|
|
3902
|
+
setDeleteApprovalError('');
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
if (isPrintableInput(value, key)) {
|
|
3907
|
+
setDeleteApprovalInput((prev) => `${prev}${value}`);
|
|
3908
|
+
setDeleteApprovalError('');
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
if (key.ctrl && value === 'c') {
|
|
3913
|
+
if (busy && typeof runtime.abort === 'function') {
|
|
3914
|
+
runtime.abort();
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
exit();
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3602
3924
|
if (key.upArrow) {
|
|
3603
3925
|
if (suggestionNav && commandSuggestions.length > 0) {
|
|
3604
3926
|
setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'up'));
|
|
@@ -3703,6 +4025,16 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3703
4025
|
setCursorIndex(0);
|
|
3704
4026
|
if (!line) return;
|
|
3705
4027
|
|
|
4028
|
+
// /stop 命令:中止当前正在进行的回答
|
|
4029
|
+
if (line === '/stop' && busy && typeof runtime.abort === 'function') {
|
|
4030
|
+
runtime.abort();
|
|
4031
|
+
setHistory((prev) => [...prev, line]);
|
|
4032
|
+
setHistoryIndex(null);
|
|
4033
|
+
setDraftBeforeHistory('');
|
|
4034
|
+
setHistoryMatches([]);
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
3706
4038
|
setHistory((prev) => [...prev, line]);
|
|
3707
4039
|
setHistoryIndex(null);
|
|
3708
4040
|
setDraftBeforeHistory('');
|
|
@@ -3772,6 +4104,10 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3772
4104
|
}
|
|
3773
4105
|
|
|
3774
4106
|
if (key.ctrl && value === 'c') {
|
|
4107
|
+
if (busy && typeof runtime.abort === 'function') {
|
|
4108
|
+
runtime.abort();
|
|
4109
|
+
return;
|
|
4110
|
+
}
|
|
3775
4111
|
exit();
|
|
3776
4112
|
return;
|
|
3777
4113
|
}
|
|
@@ -3911,6 +4247,12 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3911
4247
|
),
|
|
3912
4248
|
h(SuggestionPanel, { commandSuggestions, suggestionNav, menuIndex, copy }),
|
|
3913
4249
|
h(PendingPanel, { pendingQueue, copy }),
|
|
4250
|
+
h(DeleteApprovalPanel, {
|
|
4251
|
+
request: pendingDeleteApproval,
|
|
4252
|
+
inputValue: deleteApprovalInput,
|
|
4253
|
+
errorText: deleteApprovalError,
|
|
4254
|
+
copy
|
|
4255
|
+
}),
|
|
3914
4256
|
debugKeys
|
|
3915
4257
|
? h(
|
|
3916
4258
|
Box,
|
|
@@ -3924,6 +4266,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3924
4266
|
afterCursor,
|
|
3925
4267
|
cursorVisible,
|
|
3926
4268
|
busy,
|
|
4269
|
+
disabled: Boolean(pendingDeleteApproval),
|
|
4270
|
+
disabledText: pendingDeleteApproval ? copy.deleteApproval.inputLocked : '',
|
|
3927
4271
|
inputStage,
|
|
3928
4272
|
pendingQueueLength: pendingQueue.length,
|
|
3929
4273
|
showToolDetails,
|