codemini-cli 0.3.9 → 0.4.1
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 +50 -6
- package/deployment.md +6 -6
- package/package.json +3 -1
- package/src/core/agent-loop.js +103 -115
- package/src/core/chat-runtime.js +134 -6
- package/src/core/command-evaluator.js +66 -0
- package/src/core/command-policy.js +16 -0
- package/src/core/command-risk.js +148 -0
- package/src/core/config-store.js +2 -0
- package/src/core/constants.js +0 -1
- package/src/core/context-compact.js +32 -8
- package/src/core/default-system-prompt.js +15 -8
- package/src/core/dream-consolidate.js +54 -14
- package/src/core/dream-evaluator.js +99 -0
- package/src/core/fff-adapter.js +1 -1
- package/src/core/memory-store.js +3 -2
- package/src/core/paths.js +1 -1
- package/src/core/project-index.js +2 -2
- package/src/core/provider/openai-compatible.js +40 -5
- package/src/core/shell-profile.js +13 -9
- package/src/core/tool-args.js +181 -0
- package/src/core/tool-output.js +184 -0
- package/src/core/tools.js +118 -315
- package/src/tui/chat-app.js +362 -45
- package/src/tui/tool-activity/presenters/misc.js +14 -0
- package/src/tui/tool-activity/presenters/system.js +1 -1
package/src/tui/chat-app.js
CHANGED
|
@@ -132,7 +132,18 @@ const TUI_COPY = {
|
|
|
132
132
|
pendingQueue: '等待队列',
|
|
133
133
|
commandPaletteGroupedSelect: '命令面板 | 分组选择模式',
|
|
134
134
|
commandPaletteGroupedSuggestions: '命令面板 | 分组候选',
|
|
135
|
-
|
|
135
|
+
startupHints: [
|
|
136
|
+
'🧭 使用 /help 可查看命令帮助。Tab 可自动补全 slash 命令。',
|
|
137
|
+
'📋 试试用 /plan 模式来规划复杂任务,让 AI 先给出方案再动手。',
|
|
138
|
+
'⏫ 使用 ↑↓ 键可以浏览历史输入,快速重复之前的操作。',
|
|
139
|
+
'🐚 输入 !<shell命令> 可以直接执行本地终端命令,如 !ls、!git status。',
|
|
140
|
+
'🔧 Ctrl+T 可以切换工具调用详情的展开/收起状态。',
|
|
141
|
+
'📊 试试 /status 查看当前会话模式、模型和 token 用量。',
|
|
142
|
+
'🧩 用 /mode plan 切换到规划模式,让 AI 先出方案再动手。',
|
|
143
|
+
'🆕 /new 可以新建一个干净的会话,重新开始工作。',
|
|
144
|
+
'🧠 /memory 查看和管理 AI 的持久记忆,帮助它更好地理解你的偏好。',
|
|
145
|
+
'💤 CodeMini 会自动"做梦"休息,整理错误信息并自我优化,越用越聪明~'
|
|
146
|
+
],
|
|
136
147
|
toolSummaryExpanded: '工具摘要:已展开',
|
|
137
148
|
toolSummaryCollapsed: '工具摘要:已收起',
|
|
138
149
|
toolChainCollapsed: (count) => `已折叠更早的 ${count} 个工具调用`,
|
|
@@ -169,6 +180,10 @@ const TUI_COPY = {
|
|
|
169
180
|
doingGlob: '正在按模式查找文件',
|
|
170
181
|
doneGrep: '已搜索关键词',
|
|
171
182
|
doingGrep: '正在搜索关键词',
|
|
183
|
+
doneWebFetch: '已抓取网页',
|
|
184
|
+
doingWebFetch: '正在抓取网页',
|
|
185
|
+
doneWebSearch: '已搜索网页',
|
|
186
|
+
doingWebSearch: '正在搜索网页',
|
|
172
187
|
doneCommand: '已执行命令',
|
|
173
188
|
doingCommand: '正在执行命令',
|
|
174
189
|
doneUpdateTodos: '已更新待办',
|
|
@@ -264,6 +279,30 @@ const TUI_COPY = {
|
|
|
264
279
|
answerLabel: '确认输入(yes/no)',
|
|
265
280
|
answerPlaceholder: 'yes 或 no'
|
|
266
281
|
},
|
|
282
|
+
runApproval: {
|
|
283
|
+
title: '确认执行命令?',
|
|
284
|
+
commandLabel: '命令',
|
|
285
|
+
riskLabel: '风险等级',
|
|
286
|
+
descriptionLabel: '说明',
|
|
287
|
+
sideEffectsLabel: '副作用',
|
|
288
|
+
lowRisk: '低',
|
|
289
|
+
mediumRisk: '中',
|
|
290
|
+
highRisk: '高',
|
|
291
|
+
prompt: '输入 yes 执行,输入 no 取消。',
|
|
292
|
+
invalidAnswer: '请输入 yes 或 no。',
|
|
293
|
+
inputLocked: '命令审批进行中,请输入 yes 或 no',
|
|
294
|
+
answerLabel: '审批输入(yes/no)',
|
|
295
|
+
answerPlaceholder: 'yes 或 no'
|
|
296
|
+
},
|
|
297
|
+
fileChangeSummary: {
|
|
298
|
+
title: '文件改动',
|
|
299
|
+
fileLabel: '文件',
|
|
300
|
+
statusLabel: '状态',
|
|
301
|
+
editStatus: '编辑',
|
|
302
|
+
createStatus: '新建',
|
|
303
|
+
deleteStatus: '删除',
|
|
304
|
+
changesLabel: '改动'
|
|
305
|
+
},
|
|
267
306
|
planApproval: {
|
|
268
307
|
title: '确认执行计划?',
|
|
269
308
|
goalLabel: '目标',
|
|
@@ -305,7 +344,18 @@ const TUI_COPY = {
|
|
|
305
344
|
pendingQueue: 'pending queue',
|
|
306
345
|
commandPaletteGroupedSelect: 'command palette | grouped select mode',
|
|
307
346
|
commandPaletteGroupedSuggestions: 'command palette | grouped suggestions',
|
|
308
|
-
|
|
347
|
+
startupHints: [
|
|
348
|
+
'🧭 Use /help to view command help. Tab for slash autocomplete.',
|
|
349
|
+
'📋 Try /plan mode for complex tasks — let the AI propose a plan before coding.',
|
|
350
|
+
'⏫ Use ↑↓ arrow keys to browse input history and repeat previous actions.',
|
|
351
|
+
'🐚 Type !<shell command> to run local terminal commands, e.g. !ls, !git status.',
|
|
352
|
+
'🔧 Ctrl+T toggles tool call detail expansion/collapse.',
|
|
353
|
+
'📊 Try /status to check current session mode, model, and token usage.',
|
|
354
|
+
'🧩 Use /mode plan to switch to planning mode — AI proposes a plan before coding.',
|
|
355
|
+
'🆕 /new starts a fresh session to begin a clean slate.',
|
|
356
|
+
'🧠 /memory lets you view and manage the AI\'s persistent memory for better personalization.',
|
|
357
|
+
'💤 CodeMini auto-"dreams" to rest, consolidate errors, and self-optimize — it gets smarter over time~'
|
|
358
|
+
],
|
|
309
359
|
toolSummaryExpanded: 'Tool summary: expanded',
|
|
310
360
|
toolSummaryCollapsed: 'Tool summary: collapsed',
|
|
311
361
|
toolChainCollapsed: (count) => `${count} earlier tool calls hidden`,
|
|
@@ -342,6 +392,10 @@ const TUI_COPY = {
|
|
|
342
392
|
doingGlob: 'Matching files by pattern',
|
|
343
393
|
doneGrep: 'Searched keywords',
|
|
344
394
|
doingGrep: 'Searching keywords',
|
|
395
|
+
doneWebFetch: 'Fetched page',
|
|
396
|
+
doingWebFetch: 'Fetching page',
|
|
397
|
+
doneWebSearch: 'Searched web',
|
|
398
|
+
doingWebSearch: 'Searching web',
|
|
345
399
|
doneCommand: 'Ran command',
|
|
346
400
|
doingCommand: 'Running command',
|
|
347
401
|
doneUpdateTodos: 'Updated todos',
|
|
@@ -437,6 +491,30 @@ const TUI_COPY = {
|
|
|
437
491
|
answerLabel: 'Approval input (yes/no)',
|
|
438
492
|
answerPlaceholder: 'yes or no'
|
|
439
493
|
},
|
|
494
|
+
runApproval: {
|
|
495
|
+
title: 'Confirm command execution?',
|
|
496
|
+
commandLabel: 'Command',
|
|
497
|
+
riskLabel: 'Risk Level',
|
|
498
|
+
descriptionLabel: 'Description',
|
|
499
|
+
sideEffectsLabel: 'Side Effects',
|
|
500
|
+
lowRisk: 'Low',
|
|
501
|
+
mediumRisk: 'Medium',
|
|
502
|
+
highRisk: 'High',
|
|
503
|
+
prompt: 'Type yes to execute, or no to cancel.',
|
|
504
|
+
invalidAnswer: 'Please enter yes or no.',
|
|
505
|
+
inputLocked: 'Command approval is active; type yes or no',
|
|
506
|
+
answerLabel: 'Approval input (yes/no)',
|
|
507
|
+
answerPlaceholder: 'yes or no'
|
|
508
|
+
},
|
|
509
|
+
fileChangeSummary: {
|
|
510
|
+
title: 'File Changes',
|
|
511
|
+
fileLabel: 'File',
|
|
512
|
+
statusLabel: 'Status',
|
|
513
|
+
editStatus: 'Edit',
|
|
514
|
+
createStatus: 'Create',
|
|
515
|
+
deleteStatus: 'Delete',
|
|
516
|
+
changesLabel: 'Changes'
|
|
517
|
+
},
|
|
440
518
|
planApproval: {
|
|
441
519
|
title: 'Approve this plan?',
|
|
442
520
|
goalLabel: 'Goal',
|
|
@@ -993,6 +1071,25 @@ export function parseDeleteApprovalAnswer(value) {
|
|
|
993
1071
|
return normalized ? 'invalid' : 'empty';
|
|
994
1072
|
}
|
|
995
1073
|
|
|
1074
|
+
export function normalizeRunApprovalRequest(request) {
|
|
1075
|
+
if (!request || String(request?.name || '').trim() !== 'run') return null;
|
|
1076
|
+
const details =
|
|
1077
|
+
request?.approvalDetails && typeof request.approvalDetails === 'object' && !Array.isArray(request.approvalDetails)
|
|
1078
|
+
? request.approvalDetails
|
|
1079
|
+
: {};
|
|
1080
|
+
const command = String(details.command || request?.arguments?.command || '').trim();
|
|
1081
|
+
if (!command) return null;
|
|
1082
|
+
return {
|
|
1083
|
+
id: String(request?.id || '').trim(),
|
|
1084
|
+
toolName: 'run',
|
|
1085
|
+
command,
|
|
1086
|
+
risk: details.risk || 'high',
|
|
1087
|
+
description: details.evaluation?.description || '',
|
|
1088
|
+
sideEffects: details.evaluation?.sideEffects || '',
|
|
1089
|
+
recommendation: details.evaluation?.recommendation || 'deny'
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
996
1093
|
export function parsePlanApprovalAnswer(value) {
|
|
997
1094
|
const raw = String(value || '').trim();
|
|
998
1095
|
if (!raw) return { action: 'empty', command: '' };
|
|
@@ -1039,36 +1136,42 @@ export function formatDeleteApprovalLines(copy, request) {
|
|
|
1039
1136
|
];
|
|
1040
1137
|
}
|
|
1041
1138
|
|
|
1139
|
+
export function formatPlanApprovalLines(copy, request) {
|
|
1140
|
+
if (!request) return [];
|
|
1141
|
+
return [String(copy?.planApproval?.title || '').trim()].filter(Boolean);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1042
1144
|
function getActivityDisplayParts(activity) {
|
|
1043
1145
|
if (isCodeGenerationActivityName(activity?.name)) {
|
|
1044
1146
|
return {
|
|
1045
|
-
primary: 'Code',
|
|
1147
|
+
primary: '🧠 Code',
|
|
1046
1148
|
secondary: ' (generation)'
|
|
1047
1149
|
};
|
|
1048
1150
|
}
|
|
1049
1151
|
const parsed = parseToolDisplayName(activity?.name);
|
|
1050
|
-
|
|
1152
|
+
const base = String(parsed.base || '').toLowerCase();
|
|
1153
|
+
if (base === 'run') {
|
|
1051
1154
|
const intent = classifyCommandIntent(parsed.target);
|
|
1052
1155
|
return {
|
|
1053
|
-
primary: getIntentLabel(intent.kind)
|
|
1156
|
+
primary: `${getIntentEmoji(intent.kind)} ${getIntentLabel(intent.kind)}`,
|
|
1054
1157
|
secondary: parsed.target ? `(${parsed.target})` : ''
|
|
1055
1158
|
};
|
|
1056
1159
|
}
|
|
1057
1160
|
if ((activity?.type || 'tool') === 'skill') {
|
|
1058
1161
|
return {
|
|
1059
|
-
primary:
|
|
1162
|
+
primary: '🧩 Skill',
|
|
1060
1163
|
secondary: `(${activity?.name || 'unknown'})`
|
|
1061
1164
|
};
|
|
1062
1165
|
}
|
|
1063
1166
|
if ((activity?.type || 'tool') === 'system_tool') {
|
|
1064
|
-
if (
|
|
1065
|
-
return { primary: 'Project Index', secondary: '' };
|
|
1167
|
+
if (base === 'project_index') {
|
|
1168
|
+
return { primary: '🗂️ Project Index', secondary: '' };
|
|
1066
1169
|
}
|
|
1067
|
-
if (
|
|
1068
|
-
return { primary: 'File Index', secondary: parsed.target ? `(${parsed.target})` : '' };
|
|
1170
|
+
if (base === 'file_index') {
|
|
1171
|
+
return { primary: '🗂️ File Index', secondary: parsed.target ? `(${parsed.target})` : '' };
|
|
1069
1172
|
}
|
|
1070
1173
|
return {
|
|
1071
|
-
primary: 'Index',
|
|
1174
|
+
primary: '🗂️ Index',
|
|
1072
1175
|
secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
|
|
1073
1176
|
};
|
|
1074
1177
|
}
|
|
@@ -1080,6 +1183,8 @@ function getActivityDisplayParts(activity) {
|
|
|
1080
1183
|
patch: 'Patch',
|
|
1081
1184
|
run: 'Run',
|
|
1082
1185
|
grep: 'Search',
|
|
1186
|
+
web_fetch: 'Fetch',
|
|
1187
|
+
web_search: 'Web Search',
|
|
1083
1188
|
glob: 'Glob',
|
|
1084
1189
|
list: 'List',
|
|
1085
1190
|
list_background_tasks: 'Tasks',
|
|
@@ -1090,19 +1195,55 @@ function getActivityDisplayParts(activity) {
|
|
|
1090
1195
|
read_plan: 'Read Plan',
|
|
1091
1196
|
update_plan: 'Update Plan'
|
|
1092
1197
|
};
|
|
1198
|
+
const emojis = {
|
|
1199
|
+
read: '📖',
|
|
1200
|
+
edit: '✏️',
|
|
1201
|
+
write: '📝',
|
|
1202
|
+
delete: '🗑️',
|
|
1203
|
+
patch: '🩹',
|
|
1204
|
+
run: '⚙️',
|
|
1205
|
+
grep: '🔍',
|
|
1206
|
+
web_fetch: '🌐',
|
|
1207
|
+
web_search: '🌐',
|
|
1208
|
+
glob: '🧭',
|
|
1209
|
+
list: '📂',
|
|
1210
|
+
list_background_tasks: '🗃️',
|
|
1211
|
+
get_background_task: '📌',
|
|
1212
|
+
stop_background_task: '⏹️',
|
|
1213
|
+
list_files: '🧭',
|
|
1214
|
+
update_todos: '✅',
|
|
1215
|
+
read_plan: '📋',
|
|
1216
|
+
update_plan: '🗓️'
|
|
1217
|
+
};
|
|
1093
1218
|
return {
|
|
1094
|
-
primary: labels[
|
|
1219
|
+
primary: `${emojis[base] || '🔧'} ${labels[base] || parsed.base || 'Tool'}`,
|
|
1095
1220
|
secondary: parsed.target ? `(${parsed.target})` : ''
|
|
1096
1221
|
};
|
|
1097
1222
|
}
|
|
1098
1223
|
|
|
1224
|
+
function getIntentEmoji(kind) {
|
|
1225
|
+
const map = {
|
|
1226
|
+
test: '🧪',
|
|
1227
|
+
install: '📦',
|
|
1228
|
+
build: '🏗️',
|
|
1229
|
+
frontend: '🖥️',
|
|
1230
|
+
backend: '🛰️',
|
|
1231
|
+
database: '🗄️',
|
|
1232
|
+
docker: '🐳',
|
|
1233
|
+
command: '⚙️'
|
|
1234
|
+
};
|
|
1235
|
+
return map[kind] || '⚙️';
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1099
1238
|
export function isIndexSystemToolName(name) {
|
|
1100
1239
|
const parsed = parseToolDisplayName(name);
|
|
1101
1240
|
return parsed.base === 'project_index' || parsed.base === 'file_index';
|
|
1102
1241
|
}
|
|
1103
1242
|
|
|
1104
1243
|
export function shouldShowCompletionFooter(msg) {
|
|
1105
|
-
|
|
1244
|
+
if (!msg || msg.loading || (msg.phase || '').trim()) return false;
|
|
1245
|
+
const label = (msg.label || '').toLowerCase();
|
|
1246
|
+
return label === 'coder' || label === 'planner' || label === 'reviewer' || label === 'tester';
|
|
1106
1247
|
}
|
|
1107
1248
|
|
|
1108
1249
|
function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
|
|
@@ -1183,7 +1324,6 @@ function stageDescriptor(inputStage, busy, runtimeStatus, copy) {
|
|
|
1183
1324
|
|
|
1184
1325
|
function RuntimeStrip({ busy, runtimeStatus, loaderTick, copy }) {
|
|
1185
1326
|
const status = normalizeRuntimeStatus(runtimeStatus, copy);
|
|
1186
|
-
const spinnerChar = SPINNER_FRAMES[loaderTick % SPINNER_FRAMES.length];
|
|
1187
1327
|
return h(
|
|
1188
1328
|
Box,
|
|
1189
1329
|
{
|
|
@@ -1195,7 +1335,7 @@ function RuntimeStrip({ busy, runtimeStatus, loaderTick, copy }) {
|
|
|
1195
1335
|
},
|
|
1196
1336
|
h(Text, { color: busy ? 'greenBright' : 'gray' }, busy ? copy.generic.live : copy.generic.idle),
|
|
1197
1337
|
h(Text, { color: 'gray' }, ' '),
|
|
1198
|
-
h(Text, { color: busy ? 'cyanBright' : 'gray' },
|
|
1338
|
+
h(Text, { color: busy ? 'cyanBright' : 'gray' }, busy ? '●' : '○'),
|
|
1199
1339
|
h(Text, { color: 'gray' }, ' '),
|
|
1200
1340
|
h(Text, { color: busy ? 'white' : 'gray' }, status.title || copy.generic.waitingForInput)
|
|
1201
1341
|
);
|
|
@@ -2273,7 +2413,17 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
|
|
|
2273
2413
|
} else {
|
|
2274
2414
|
pushTextRows(msg?.text || '');
|
|
2275
2415
|
const toolCalls = Array.isArray(msg?.toolCalls) ? msg.toolCalls : [];
|
|
2276
|
-
|
|
2416
|
+
const pendingToolCalls = Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : [];
|
|
2417
|
+
const visibleCalls = [
|
|
2418
|
+
...toolCalls,
|
|
2419
|
+
...pendingToolCalls.filter((pending) => {
|
|
2420
|
+
if (!pending) return false;
|
|
2421
|
+
if (pending.id && toolCalls.some((tool) => tool?.id && tool.id === pending.id)) return false;
|
|
2422
|
+
const pendingBase = parseToolDisplayName(pending.name).base;
|
|
2423
|
+
return !toolCalls.some((tool) => parseToolDisplayName(tool?.name).base === pendingBase && tool?.status === 'running');
|
|
2424
|
+
})
|
|
2425
|
+
];
|
|
2426
|
+
visibleCalls.forEach((tool, idx) => pushActivityRows(tool, idx, visibleCalls.length));
|
|
2277
2427
|
}
|
|
2278
2428
|
|
|
2279
2429
|
const codeGenerationRows = getCodeGenerationActivityRows(msg);
|
|
@@ -2320,15 +2470,20 @@ export function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
2320
2470
|
? 'redBright'
|
|
2321
2471
|
: 'cyanBright';
|
|
2322
2472
|
const durationText = formatActivityDurationText(row);
|
|
2473
|
+
const trailingLoader =
|
|
2474
|
+
row.status === 'running'
|
|
2475
|
+
? h(Text, { color: 'gray' }, ` ${SPINNER_FRAMES[loaderTick % SPINNER_FRAMES.length]}`)
|
|
2476
|
+
: null;
|
|
2323
2477
|
return h(
|
|
2324
2478
|
Box,
|
|
2325
2479
|
{ key: `row-tool-${msg.id}-${idx}` },
|
|
2326
2480
|
h(Text, { color: 'gray' }, ' '),
|
|
2327
|
-
h(Text, { color: dotColor },
|
|
2481
|
+
h(Text, { color: dotColor }, '●'),
|
|
2328
2482
|
h(Text, { color: 'gray' }, ' '),
|
|
2329
2483
|
h(Text, { color: textColor }, display.primary),
|
|
2330
2484
|
h(Text, { color: 'gray' }, display.secondary),
|
|
2331
|
-
durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null
|
|
2485
|
+
durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null,
|
|
2486
|
+
trailingLoader
|
|
2332
2487
|
);
|
|
2333
2488
|
}
|
|
2334
2489
|
if (row.kind === 'activity-summary') {
|
|
@@ -2594,8 +2749,11 @@ const MessageBubble = React.memo(function MessageBubble({ msg, loaderTick, showT
|
|
|
2594
2749
|
shouldShowCompletionFooter(msg)
|
|
2595
2750
|
? h(
|
|
2596
2751
|
Box,
|
|
2597
|
-
{ marginTop: 1,
|
|
2598
|
-
h(
|
|
2752
|
+
{ marginTop: 1, flexDirection: 'column', key: `row-completion-${msg.id}` },
|
|
2753
|
+
h(FileChangeSummary, { segments: msg.segments, copy }),
|
|
2754
|
+
h(Box, { marginLeft: 1, marginTop: 1 },
|
|
2755
|
+
h(Text, { color: 'gray', dimColor: true }, copy.generic.taskCompleted)
|
|
2756
|
+
)
|
|
2599
2757
|
)
|
|
2600
2758
|
: null
|
|
2601
2759
|
)
|
|
@@ -2868,12 +3026,102 @@ function DeleteApprovalPanel({ request, inputValue, errorText, copy, cursorVisib
|
|
|
2868
3026
|
);
|
|
2869
3027
|
}
|
|
2870
3028
|
|
|
3029
|
+
function RunApprovalPanel({ request, inputValue, errorText, copy, cursorVisible }) {
|
|
3030
|
+
if (!request) return null;
|
|
3031
|
+
const details = request?.toolName === 'run' ? request : normalizeRunApprovalRequest(request);
|
|
3032
|
+
if (!details) return null;
|
|
3033
|
+
const c = copy.runApproval || {};
|
|
3034
|
+
const riskColor = details.risk === 'low' ? 'green' : details.risk === 'medium' ? 'yellow' : 'redBright';
|
|
3035
|
+
const borderColor = details.risk === 'medium' ? 'yellow' : 'redBright';
|
|
3036
|
+
const riskLabel = details.risk === 'low' ? c.lowRisk : details.risk === 'medium' ? c.mediumRisk : c.highRisk;
|
|
3037
|
+
const placeholder = String(c.answerPlaceholder || '').trim();
|
|
3038
|
+
return h(
|
|
3039
|
+
Box,
|
|
3040
|
+
{
|
|
3041
|
+
marginTop: 1,
|
|
3042
|
+
flexDirection: 'column',
|
|
3043
|
+
borderStyle: 'round',
|
|
3044
|
+
borderColor,
|
|
3045
|
+
paddingX: 1,
|
|
3046
|
+
paddingY: 0
|
|
3047
|
+
},
|
|
3048
|
+
h(Text, { color: borderColor }, c.title),
|
|
3049
|
+
h(Text, { color: 'white' }, `${c.commandLabel}: ${details.command}`),
|
|
3050
|
+
h(Text, null, `${c.riskLabel}: `, h(Text, { color: riskColor, bold: true }, riskLabel || details.risk)),
|
|
3051
|
+
details.description ? h(Text, { color: 'gray' }, `${c.descriptionLabel}: ${details.description}`) : null,
|
|
3052
|
+
details.sideEffects ? h(Text, { color: 'gray' }, `${c.sideEffectsLabel}: ${details.sideEffects}`) : null,
|
|
3053
|
+
h(Text, { color: 'gray' }, c.prompt),
|
|
3054
|
+
h(
|
|
3055
|
+
Box,
|
|
3056
|
+
{ marginTop: 1 },
|
|
3057
|
+
h(Text, { color: borderColor }, `${c.answerLabel}: `),
|
|
3058
|
+
h(ApprovalCursorLine, {
|
|
3059
|
+
inputValue,
|
|
3060
|
+
placeholder: placeholder || ' ',
|
|
3061
|
+
cursorVisible,
|
|
3062
|
+
accent: borderColor
|
|
3063
|
+
})
|
|
3064
|
+
),
|
|
3065
|
+
errorText ? h(Text, { color: 'yellowBright' }, errorText) : null
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
function FileChangeSummary({ segments, copy }) {
|
|
3070
|
+
if (!Array.isArray(segments) || segments.length === 0) return null;
|
|
3071
|
+
const c = copy.fileChangeSummary || {};
|
|
3072
|
+
const changes = new Map();
|
|
3073
|
+
for (const seg of segments) {
|
|
3074
|
+
if (!seg.fileChange) continue;
|
|
3075
|
+
const p = seg.fileChange.path;
|
|
3076
|
+
if (!p) continue;
|
|
3077
|
+
const existing = changes.get(p);
|
|
3078
|
+
if (existing) {
|
|
3079
|
+
/* 同一文件多次编辑,合并行数,取最高 action */
|
|
3080
|
+
existing.linesAdded += seg.fileChange.linesAdded || 0;
|
|
3081
|
+
existing.linesRemoved += seg.fileChange.linesRemoved || 0;
|
|
3082
|
+
const ACTION_ORDER = { delete: 3, create: 2, edit: 1 };
|
|
3083
|
+
if ((ACTION_ORDER[seg.fileChange.action] || 0) > (ACTION_ORDER[existing.action] || 0)) {
|
|
3084
|
+
existing.action = seg.fileChange.action;
|
|
3085
|
+
}
|
|
3086
|
+
} else {
|
|
3087
|
+
changes.set(p, { path: p, action: seg.fileChange.action, linesAdded: seg.fileChange.linesAdded || 0, linesRemoved: seg.fileChange.linesRemoved || 0 });
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
if (changes.size === 0) return null;
|
|
3091
|
+
const entries = [...changes.values()];
|
|
3092
|
+
return h(
|
|
3093
|
+
Box,
|
|
3094
|
+
{ marginTop: 1, flexDirection: 'column', borderStyle: 'round', borderColor: 'gray', paddingX: 1 },
|
|
3095
|
+
h(Text, { color: 'cyan', bold: true }, c.title || 'File Changes'),
|
|
3096
|
+
...entries.map((entry) => {
|
|
3097
|
+
const statusMap = { edit: c.editStatus || 'Edit', create: c.createStatus || 'Create', delete: c.deleteStatus || 'Delete' };
|
|
3098
|
+
const statusColor = entry.action === 'create' ? 'green' : entry.action === 'delete' ? 'red' : 'yellow';
|
|
3099
|
+
const statusText = statusMap[entry.action] || entry.action;
|
|
3100
|
+
let changesText = '';
|
|
3101
|
+
if (entry.action !== 'delete') {
|
|
3102
|
+
const parts = [];
|
|
3103
|
+
if (entry.linesAdded > 0) parts.push(h(Text, { color: 'green' }, `+${entry.linesAdded}`));
|
|
3104
|
+
if (entry.linesRemoved > 0) parts.push(h(Text, { color: 'red' }, `-${entry.linesRemoved}`));
|
|
3105
|
+
if (parts.length > 0) {
|
|
3106
|
+
changesText = parts.reduce((acc, el, i) => i === 0 ? [el] : [...acc, ' ', el], []);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return h(
|
|
3110
|
+
Box,
|
|
3111
|
+
{ key: entry.path },
|
|
3112
|
+
h(Text, { color: 'white' }, ` ${entry.path}`),
|
|
3113
|
+
h(Text, { color: 'gray' }, ' '),
|
|
3114
|
+
h(Text, { color: statusColor }, statusText),
|
|
3115
|
+
changesText ? h(Text, null, ' ', changesText) : null
|
|
3116
|
+
);
|
|
3117
|
+
})
|
|
3118
|
+
);
|
|
3119
|
+
}
|
|
3120
|
+
|
|
2871
3121
|
function PlanApprovalPanel({ request, inputValue, errorText, copy, cursorVisible }) {
|
|
2872
3122
|
if (!request) return null;
|
|
2873
|
-
const goal = String(request.goal || '').trim();
|
|
2874
|
-
const summary = String(request.summary || '').trim();
|
|
2875
|
-
const filePath = String(request.filePath || '').trim();
|
|
2876
3123
|
const placeholder = String(copy.planApproval.answerPlaceholder || '').trim();
|
|
3124
|
+
const lines = formatPlanApprovalLines(copy, request);
|
|
2877
3125
|
return h(
|
|
2878
3126
|
Box,
|
|
2879
3127
|
{
|
|
@@ -2884,11 +3132,9 @@ function PlanApprovalPanel({ request, inputValue, errorText, copy, cursorVisible
|
|
|
2884
3132
|
paddingX: 1,
|
|
2885
3133
|
paddingY: 0
|
|
2886
3134
|
},
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
filePath ? h(Text, { color: 'gray' }, `${copy.planApproval.fileLabel}: ${filePath}`) : null,
|
|
2891
|
-
h(Text, { color: 'gray' }, copy.planApproval.prompt),
|
|
3135
|
+
...lines.map((line, index) =>
|
|
3136
|
+
h(Text, { key: `plan-approval-line-${index}`, color: 'yellowBright' }, line)
|
|
3137
|
+
),
|
|
2892
3138
|
h(
|
|
2893
3139
|
Box,
|
|
2894
3140
|
{ marginTop: 1 },
|
|
@@ -2988,10 +3234,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2988
3234
|
const [pendingDeleteApproval, setPendingDeleteApproval] = useState(null);
|
|
2989
3235
|
const [deleteApprovalInput, setDeleteApprovalInput] = useState('');
|
|
2990
3236
|
const [deleteApprovalError, setDeleteApprovalError] = useState('');
|
|
3237
|
+
const [pendingRunApproval, setPendingRunApproval] = useState(null);
|
|
3238
|
+
const [runApprovalInput, setRunApprovalInput] = useState('');
|
|
3239
|
+
const [runApprovalError, setRunApprovalError] = useState('');
|
|
2991
3240
|
const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
|
|
2992
3241
|
const [planApprovalInput, setPlanApprovalInput] = useState('');
|
|
2993
3242
|
const [planApprovalError, setPlanApprovalError] = useState('');
|
|
2994
|
-
const approvalLockActive = Boolean(pendingDeleteApproval || pendingPlanApproval);
|
|
3243
|
+
const approvalLockActive = Boolean(pendingDeleteApproval || pendingRunApproval || pendingPlanApproval);
|
|
2995
3244
|
const activeAssistantIdRef = useRef(null);
|
|
2996
3245
|
const activeAssistantAutoSkillNamesRef = useRef([]);
|
|
2997
3246
|
const streamedAssistantHandledRef = useRef(false);
|
|
@@ -3006,6 +3255,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3006
3255
|
const activePlanStepInfoRef = useRef(null);
|
|
3007
3256
|
const activePlanStepTitleRef = useRef('');
|
|
3008
3257
|
const deleteApprovalResolverRef = useRef(null);
|
|
3258
|
+
const runApprovalResolverRef = useRef(null);
|
|
3009
3259
|
|
|
3010
3260
|
useEffect(() => {
|
|
3011
3261
|
const rawStartupActivities = runtime.consumeStartupEvents?.();
|
|
@@ -3031,27 +3281,42 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3031
3281
|
useEffect(() => {
|
|
3032
3282
|
if (typeof runtime.setRequestToolApproval !== 'function') return () => {};
|
|
3033
3283
|
runtime.setRequestToolApproval((request) => {
|
|
3034
|
-
const
|
|
3035
|
-
if (
|
|
3036
|
-
|
|
3284
|
+
const deleteNorm = normalizeDeleteApprovalRequest(request);
|
|
3285
|
+
if (deleteNorm) {
|
|
3286
|
+
setDeleteApprovalInput('');
|
|
3287
|
+
setDeleteApprovalError('');
|
|
3288
|
+
setPendingDeleteApproval(deleteNorm);
|
|
3289
|
+
return new Promise((resolve) => {
|
|
3290
|
+
deleteApprovalResolverRef.current = resolve;
|
|
3291
|
+
});
|
|
3037
3292
|
}
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3293
|
+
const runNorm = normalizeRunApprovalRequest(request);
|
|
3294
|
+
if (runNorm) {
|
|
3295
|
+
setRunApprovalInput('');
|
|
3296
|
+
setRunApprovalError('');
|
|
3297
|
+
setPendingRunApproval(runNorm);
|
|
3298
|
+
return new Promise((resolve) => {
|
|
3299
|
+
runApprovalResolverRef.current = resolve;
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3302
|
+
return Promise.resolve({ approved: false });
|
|
3044
3303
|
});
|
|
3045
3304
|
return () => {
|
|
3046
3305
|
runtime.setRequestToolApproval(null);
|
|
3047
3306
|
deleteApprovalResolverRef.current = null;
|
|
3307
|
+
runApprovalResolverRef.current = null;
|
|
3048
3308
|
};
|
|
3049
3309
|
}, [runtime]);
|
|
3050
3310
|
|
|
3051
3311
|
useEffect(() => {
|
|
3052
3312
|
messagesRef.current = messages;
|
|
3053
3313
|
}, [messages]);
|
|
3054
|
-
const
|
|
3314
|
+
const startupHints = copy.generic.startupHints;
|
|
3315
|
+
const startupHint = useMemo(() => {
|
|
3316
|
+
const arr = Array.isArray(startupHints) ? startupHints : [];
|
|
3317
|
+
if (arr.length === 0) return '';
|
|
3318
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
3319
|
+
}, [startupHints]);
|
|
3055
3320
|
const isBackspaceKey = (value, key) =>
|
|
3056
3321
|
Boolean(key?.backspace) || value === '\u0008' || value === '\u007f' || (key?.ctrl && value === 'h');
|
|
3057
3322
|
const isDeleteKey = (value, key) =>
|
|
@@ -3239,7 +3504,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3239
3504
|
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
3240
3505
|
...(startedAt ? { startedAt } : {}),
|
|
3241
3506
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
3242
|
-
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
3507
|
+
...(toolEvent.summary ? { summary: toolEvent.summary } : {}),
|
|
3508
|
+
...(toolEvent.fileChange ? { fileChange: toolEvent.fileChange } : {})
|
|
3243
3509
|
});
|
|
3244
3510
|
} else {
|
|
3245
3511
|
toolCalls[idx] = {
|
|
@@ -3265,7 +3531,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3265
3531
|
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
3266
3532
|
...(startedAt ? { startedAt } : {}),
|
|
3267
3533
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
3268
|
-
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
3534
|
+
...(toolEvent.summary ? { summary: toolEvent.summary } : {}),
|
|
3535
|
+
...(toolEvent.fileChange ? { fileChange: toolEvent.fileChange } : {})
|
|
3269
3536
|
};
|
|
3270
3537
|
if (segmentIdx === -1) {
|
|
3271
3538
|
segments.push(patch);
|
|
@@ -3716,7 +3983,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3716
3983
|
status: 'done',
|
|
3717
3984
|
durationMs: event.durationMs,
|
|
3718
3985
|
summary: event.summary,
|
|
3719
|
-
arguments: event.arguments
|
|
3986
|
+
arguments: event.arguments,
|
|
3987
|
+
fileChange: event.fileChange || null
|
|
3720
3988
|
});
|
|
3721
3989
|
}
|
|
3722
3990
|
if (event?.type === 'tool:blocked') {
|
|
@@ -4137,6 +4405,46 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
4137
4405
|
return;
|
|
4138
4406
|
}
|
|
4139
4407
|
|
|
4408
|
+
if (pendingRunApproval) {
|
|
4409
|
+
if (key.return) {
|
|
4410
|
+
const answer = parseDeleteApprovalAnswer(runApprovalInput);
|
|
4411
|
+
if (answer === 'approve' || answer === 'deny') {
|
|
4412
|
+
const resolver = runApprovalResolverRef.current;
|
|
4413
|
+
runApprovalResolverRef.current = null;
|
|
4414
|
+
setPendingRunApproval(null);
|
|
4415
|
+
setRunApprovalInput('');
|
|
4416
|
+
setRunApprovalError('');
|
|
4417
|
+
if (resolver) resolver({ approved: answer === 'approve' });
|
|
4418
|
+
} else {
|
|
4419
|
+
setRunApprovalError(copy.runApproval.invalidAnswer);
|
|
4420
|
+
}
|
|
4421
|
+
return;
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
if (isBackspaceKey(value, key) || isDeleteKey(value, key)) {
|
|
4425
|
+
setRunApprovalInput((prev) => prev.slice(0, -1));
|
|
4426
|
+
setRunApprovalError('');
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
if (isPrintableInput(value, key)) {
|
|
4431
|
+
setRunApprovalInput((prev) => `${prev}${value}`);
|
|
4432
|
+
setRunApprovalError('');
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
if (key.ctrl && value === 'c') {
|
|
4437
|
+
if (busy && typeof runtime.abort === 'function') {
|
|
4438
|
+
runtime.abort();
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
exit();
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
return;
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4140
4448
|
if (pendingPlanApproval) {
|
|
4141
4449
|
if (key.return) {
|
|
4142
4450
|
const parsed = parsePlanApprovalAnswer(planApprovalInput);
|
|
@@ -4488,9 +4796,11 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
4488
4796
|
);
|
|
4489
4797
|
const activeApprovalLock = pendingDeleteApproval
|
|
4490
4798
|
? { text: copy.deleteApproval.inputLocked }
|
|
4491
|
-
:
|
|
4492
|
-
? { text: copy.
|
|
4493
|
-
:
|
|
4799
|
+
: pendingRunApproval
|
|
4800
|
+
? { text: copy.runApproval.inputLocked }
|
|
4801
|
+
: pendingPlanApproval
|
|
4802
|
+
? { text: copy.planApproval.inputLocked }
|
|
4803
|
+
: null;
|
|
4494
4804
|
|
|
4495
4805
|
return h(
|
|
4496
4806
|
Box,
|
|
@@ -4526,6 +4836,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
4526
4836
|
copy,
|
|
4527
4837
|
cursorVisible
|
|
4528
4838
|
}),
|
|
4839
|
+
h(RunApprovalPanel, {
|
|
4840
|
+
request: pendingRunApproval,
|
|
4841
|
+
inputValue: runApprovalInput,
|
|
4842
|
+
errorText: runApprovalError,
|
|
4843
|
+
copy,
|
|
4844
|
+
cursorVisible
|
|
4845
|
+
}),
|
|
4529
4846
|
h(PlanApprovalPanel, {
|
|
4530
4847
|
request: pendingPlanApproval,
|
|
4531
4848
|
inputValue: planApprovalInput,
|
|
@@ -12,5 +12,19 @@ export function describeMiscToolActivity(copy, parsed, rawName, { done = false,
|
|
|
12
12
|
if (parsed.base === 'update_todos') {
|
|
13
13
|
return blocked ? makeBlocked(copy, 'update_todos') : done ? copy.toolActivity.doneUpdateTodos : copy.toolActivity.doingUpdateTodos;
|
|
14
14
|
}
|
|
15
|
+
if (parsed.base === 'web_fetch') {
|
|
16
|
+
const target = parsed.target || parsed.raw;
|
|
17
|
+
const label = done
|
|
18
|
+
? (copy.toolActivity.doneWebFetch || copy.toolActivity.doneGeneric)
|
|
19
|
+
: (copy.toolActivity.doingWebFetch || copy.toolActivity.doingGeneric);
|
|
20
|
+
return blocked ? makeBlocked(copy, target) : `${label}: ${target}`;
|
|
21
|
+
}
|
|
22
|
+
if (parsed.base === 'web_search') {
|
|
23
|
+
const target = parsed.target || parsed.raw;
|
|
24
|
+
const label = done
|
|
25
|
+
? (copy.toolActivity.doneWebSearch || copy.toolActivity.doneGeneric)
|
|
26
|
+
: (copy.toolActivity.doingWebSearch || copy.toolActivity.doingGeneric);
|
|
27
|
+
return blocked ? makeBlocked(copy, target) : `${label}: ${target}`;
|
|
28
|
+
}
|
|
15
29
|
return blocked ? `${copy.toolActivity.blocked}: ${parsed.raw}` : done ? `${copy.toolActivity.doneGeneric}: ${parsed.raw}` : `${copy.toolActivity.doingGeneric}: ${parsed.raw}`;
|
|
16
30
|
}
|
|
@@ -6,7 +6,7 @@ export function describeSystemToolActivity(copy, parsed, { done = false, blocked
|
|
|
6
6
|
return done ? copy.toolActivity.doneProjectIndex : copy.toolActivity.doingProjectIndex;
|
|
7
7
|
}
|
|
8
8
|
if (parsed.base === 'file_index') {
|
|
9
|
-
const safeTarget = trimText(parsed.target || '.codemini
|
|
9
|
+
const safeTarget = trimText(parsed.target || '.codemini/file-index.json', 72);
|
|
10
10
|
if (blocked) return makeBlocked(copy, safeTarget);
|
|
11
11
|
return done ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}` : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
|
|
12
12
|
}
|