codemini-cli 0.3.8 → 0.4.0
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 +121 -1
- package/deployment.md +6 -6
- package/package.json +6 -1
- package/skills/brainstorm/SKILL.md +49 -29
- package/skills/superpowers-lite/SKILL.md +82 -90
- package/skills/writing-plans/SKILL.md +67 -0
- package/src/commands/chat.js +51 -47
- package/src/commands/doctor.js +27 -7
- package/src/commands/run.js +36 -28
- package/src/core/agent-loop.js +191 -10
- package/src/core/chat-runtime.js +170 -11
- 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 +7 -0
- package/src/core/constants.js +0 -1
- package/src/core/default-system-prompt.js +27 -0
- package/src/core/dream-audit.js +93 -0
- package/src/core/dream-consolidate.js +157 -0
- package/src/core/dream-evaluator.js +99 -0
- package/src/core/fff-adapter.js +386 -0
- package/src/core/memory-prompt.js +23 -0
- package/src/core/memory-store.js +228 -1
- package/src/core/paths.js +13 -1
- package/src/core/project-index.js +2 -2
- package/src/core/shell-profile.js +5 -1
- package/src/core/tool-output.js +184 -0
- package/src/core/tools.js +425 -110
- package/src/tui/chat-app.js +376 -47
- 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} 个工具调用`,
|
|
@@ -245,7 +256,11 @@ const TUI_COPY = {
|
|
|
245
256
|
idleReady: '等待输入',
|
|
246
257
|
idleReadyDetail: '就绪',
|
|
247
258
|
idleAfterTurn: '空闲',
|
|
248
|
-
idleAfterTurnDetail: '等待下一轮输入'
|
|
259
|
+
idleAfterTurnDetail: '等待下一轮输入',
|
|
260
|
+
dreamAutoTriggered: '瞌睡虫来了…自动整理记忆中,请稍等',
|
|
261
|
+
dreamRunning: '💤 打瞌睡中,请稍等…',
|
|
262
|
+
dreamCompleted: '✨ 做了一个好梦,记忆已整理',
|
|
263
|
+
dreamIdle: '💤'
|
|
249
264
|
},
|
|
250
265
|
deleteApproval: {
|
|
251
266
|
title: '确认删除?',
|
|
@@ -260,6 +275,30 @@ const TUI_COPY = {
|
|
|
260
275
|
answerLabel: '确认输入(yes/no)',
|
|
261
276
|
answerPlaceholder: 'yes 或 no'
|
|
262
277
|
},
|
|
278
|
+
runApproval: {
|
|
279
|
+
title: '确认执行命令?',
|
|
280
|
+
commandLabel: '命令',
|
|
281
|
+
riskLabel: '风险等级',
|
|
282
|
+
descriptionLabel: '说明',
|
|
283
|
+
sideEffectsLabel: '副作用',
|
|
284
|
+
lowRisk: '低',
|
|
285
|
+
mediumRisk: '中',
|
|
286
|
+
highRisk: '高',
|
|
287
|
+
prompt: '输入 yes 执行,输入 no 取消。',
|
|
288
|
+
invalidAnswer: '请输入 yes 或 no。',
|
|
289
|
+
inputLocked: '命令审批进行中,请输入 yes 或 no',
|
|
290
|
+
answerLabel: '审批输入(yes/no)',
|
|
291
|
+
answerPlaceholder: 'yes 或 no'
|
|
292
|
+
},
|
|
293
|
+
fileChangeSummary: {
|
|
294
|
+
title: '文件改动',
|
|
295
|
+
fileLabel: '文件',
|
|
296
|
+
statusLabel: '状态',
|
|
297
|
+
editStatus: '编辑',
|
|
298
|
+
createStatus: '新建',
|
|
299
|
+
deleteStatus: '删除',
|
|
300
|
+
changesLabel: '改动'
|
|
301
|
+
},
|
|
263
302
|
planApproval: {
|
|
264
303
|
title: '确认执行计划?',
|
|
265
304
|
goalLabel: '目标',
|
|
@@ -301,7 +340,18 @@ const TUI_COPY = {
|
|
|
301
340
|
pendingQueue: 'pending queue',
|
|
302
341
|
commandPaletteGroupedSelect: 'command palette | grouped select mode',
|
|
303
342
|
commandPaletteGroupedSuggestions: 'command palette | grouped suggestions',
|
|
304
|
-
|
|
343
|
+
startupHints: [
|
|
344
|
+
'🧭 Use /help to view command help. Tab for slash autocomplete.',
|
|
345
|
+
'📋 Try /plan mode for complex tasks — let the AI propose a plan before coding.',
|
|
346
|
+
'⏫ Use ↑↓ arrow keys to browse input history and repeat previous actions.',
|
|
347
|
+
'🐚 Type !<shell command> to run local terminal commands, e.g. !ls, !git status.',
|
|
348
|
+
'🔧 Ctrl+T toggles tool call detail expansion/collapse.',
|
|
349
|
+
'📊 Try /status to check current session mode, model, and token usage.',
|
|
350
|
+
'🧩 Use /mode plan to switch to planning mode — AI proposes a plan before coding.',
|
|
351
|
+
'🆕 /new starts a fresh session to begin a clean slate.',
|
|
352
|
+
'🧠 /memory lets you view and manage the AI\'s persistent memory for better personalization.',
|
|
353
|
+
'💤 CodeMini auto-"dreams" to rest, consolidate errors, and self-optimize — it gets smarter over time~'
|
|
354
|
+
],
|
|
305
355
|
toolSummaryExpanded: 'Tool summary: expanded',
|
|
306
356
|
toolSummaryCollapsed: 'Tool summary: collapsed',
|
|
307
357
|
toolChainCollapsed: (count) => `${count} earlier tool calls hidden`,
|
|
@@ -414,7 +464,11 @@ const TUI_COPY = {
|
|
|
414
464
|
idleReady: 'waiting for input',
|
|
415
465
|
idleReadyDetail: 'ready',
|
|
416
466
|
idleAfterTurn: 'idle',
|
|
417
|
-
idleAfterTurnDetail: 'ready for next input'
|
|
467
|
+
idleAfterTurnDetail: 'ready for next input',
|
|
468
|
+
dreamAutoTriggered: 'Getting sleepy... auto-consolidating memory, please wait',
|
|
469
|
+
dreamRunning: '💤 Dreaming... please wait',
|
|
470
|
+
dreamCompleted: '✨ Had a good dream, memory consolidated',
|
|
471
|
+
dreamIdle: '💤'
|
|
418
472
|
},
|
|
419
473
|
deleteApproval: {
|
|
420
474
|
title: 'Confirm deletion?',
|
|
@@ -429,6 +483,30 @@ const TUI_COPY = {
|
|
|
429
483
|
answerLabel: 'Approval input (yes/no)',
|
|
430
484
|
answerPlaceholder: 'yes or no'
|
|
431
485
|
},
|
|
486
|
+
runApproval: {
|
|
487
|
+
title: 'Confirm command execution?',
|
|
488
|
+
commandLabel: 'Command',
|
|
489
|
+
riskLabel: 'Risk Level',
|
|
490
|
+
descriptionLabel: 'Description',
|
|
491
|
+
sideEffectsLabel: 'Side Effects',
|
|
492
|
+
lowRisk: 'Low',
|
|
493
|
+
mediumRisk: 'Medium',
|
|
494
|
+
highRisk: 'High',
|
|
495
|
+
prompt: 'Type yes to execute, or no to cancel.',
|
|
496
|
+
invalidAnswer: 'Please enter yes or no.',
|
|
497
|
+
inputLocked: 'Command approval is active; type yes or no',
|
|
498
|
+
answerLabel: 'Approval input (yes/no)',
|
|
499
|
+
answerPlaceholder: 'yes or no'
|
|
500
|
+
},
|
|
501
|
+
fileChangeSummary: {
|
|
502
|
+
title: 'File Changes',
|
|
503
|
+
fileLabel: 'File',
|
|
504
|
+
statusLabel: 'Status',
|
|
505
|
+
editStatus: 'Edit',
|
|
506
|
+
createStatus: 'Create',
|
|
507
|
+
deleteStatus: 'Delete',
|
|
508
|
+
changesLabel: 'Changes'
|
|
509
|
+
},
|
|
432
510
|
planApproval: {
|
|
433
511
|
title: 'Approve this plan?',
|
|
434
512
|
goalLabel: 'Goal',
|
|
@@ -985,6 +1063,25 @@ export function parseDeleteApprovalAnswer(value) {
|
|
|
985
1063
|
return normalized ? 'invalid' : 'empty';
|
|
986
1064
|
}
|
|
987
1065
|
|
|
1066
|
+
export function normalizeRunApprovalRequest(request) {
|
|
1067
|
+
if (!request || String(request?.name || '').trim() !== 'run') return null;
|
|
1068
|
+
const details =
|
|
1069
|
+
request?.approvalDetails && typeof request.approvalDetails === 'object' && !Array.isArray(request.approvalDetails)
|
|
1070
|
+
? request.approvalDetails
|
|
1071
|
+
: {};
|
|
1072
|
+
const command = String(details.command || request?.arguments?.command || '').trim();
|
|
1073
|
+
if (!command) return null;
|
|
1074
|
+
return {
|
|
1075
|
+
id: String(request?.id || '').trim(),
|
|
1076
|
+
toolName: 'run',
|
|
1077
|
+
command,
|
|
1078
|
+
risk: details.risk || 'high',
|
|
1079
|
+
description: details.evaluation?.description || '',
|
|
1080
|
+
sideEffects: details.evaluation?.sideEffects || '',
|
|
1081
|
+
recommendation: details.evaluation?.recommendation || 'deny'
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
988
1085
|
export function parsePlanApprovalAnswer(value) {
|
|
989
1086
|
const raw = String(value || '').trim();
|
|
990
1087
|
if (!raw) return { action: 'empty', command: '' };
|
|
@@ -1031,36 +1128,42 @@ export function formatDeleteApprovalLines(copy, request) {
|
|
|
1031
1128
|
];
|
|
1032
1129
|
}
|
|
1033
1130
|
|
|
1131
|
+
export function formatPlanApprovalLines(copy, request) {
|
|
1132
|
+
if (!request) return [];
|
|
1133
|
+
return [String(copy?.planApproval?.title || '').trim()].filter(Boolean);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1034
1136
|
function getActivityDisplayParts(activity) {
|
|
1035
1137
|
if (isCodeGenerationActivityName(activity?.name)) {
|
|
1036
1138
|
return {
|
|
1037
|
-
primary: 'Code',
|
|
1139
|
+
primary: '🧠 Code',
|
|
1038
1140
|
secondary: ' (generation)'
|
|
1039
1141
|
};
|
|
1040
1142
|
}
|
|
1041
1143
|
const parsed = parseToolDisplayName(activity?.name);
|
|
1042
|
-
|
|
1144
|
+
const base = String(parsed.base || '').toLowerCase();
|
|
1145
|
+
if (base === 'run') {
|
|
1043
1146
|
const intent = classifyCommandIntent(parsed.target);
|
|
1044
1147
|
return {
|
|
1045
|
-
primary: getIntentLabel(intent.kind)
|
|
1148
|
+
primary: `${getIntentEmoji(intent.kind)} ${getIntentLabel(intent.kind)}`,
|
|
1046
1149
|
secondary: parsed.target ? `(${parsed.target})` : ''
|
|
1047
1150
|
};
|
|
1048
1151
|
}
|
|
1049
1152
|
if ((activity?.type || 'tool') === 'skill') {
|
|
1050
1153
|
return {
|
|
1051
|
-
primary:
|
|
1154
|
+
primary: '🧩 Skill',
|
|
1052
1155
|
secondary: `(${activity?.name || 'unknown'})`
|
|
1053
1156
|
};
|
|
1054
1157
|
}
|
|
1055
1158
|
if ((activity?.type || 'tool') === 'system_tool') {
|
|
1056
|
-
if (
|
|
1057
|
-
return { primary: 'Project Index', secondary: '' };
|
|
1159
|
+
if (base === 'project_index') {
|
|
1160
|
+
return { primary: '🗂️ Project Index', secondary: '' };
|
|
1058
1161
|
}
|
|
1059
|
-
if (
|
|
1060
|
-
return { primary: 'File Index', secondary: parsed.target ? `(${parsed.target})` : '' };
|
|
1162
|
+
if (base === 'file_index') {
|
|
1163
|
+
return { primary: '🗂️ File Index', secondary: parsed.target ? `(${parsed.target})` : '' };
|
|
1061
1164
|
}
|
|
1062
1165
|
return {
|
|
1063
|
-
primary: 'Index',
|
|
1166
|
+
primary: '🗂️ Index',
|
|
1064
1167
|
secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
|
|
1065
1168
|
};
|
|
1066
1169
|
}
|
|
@@ -1082,19 +1185,53 @@ function getActivityDisplayParts(activity) {
|
|
|
1082
1185
|
read_plan: 'Read Plan',
|
|
1083
1186
|
update_plan: 'Update Plan'
|
|
1084
1187
|
};
|
|
1188
|
+
const emojis = {
|
|
1189
|
+
read: '📖',
|
|
1190
|
+
edit: '✏️',
|
|
1191
|
+
write: '📝',
|
|
1192
|
+
delete: '🗑️',
|
|
1193
|
+
patch: '🩹',
|
|
1194
|
+
run: '⚙️',
|
|
1195
|
+
grep: '🔍',
|
|
1196
|
+
glob: '🧭',
|
|
1197
|
+
list: '📂',
|
|
1198
|
+
list_background_tasks: '🗃️',
|
|
1199
|
+
get_background_task: '📌',
|
|
1200
|
+
stop_background_task: '⏹️',
|
|
1201
|
+
list_files: '🧭',
|
|
1202
|
+
update_todos: '✅',
|
|
1203
|
+
read_plan: '📋',
|
|
1204
|
+
update_plan: '🗓️'
|
|
1205
|
+
};
|
|
1085
1206
|
return {
|
|
1086
|
-
primary: labels[
|
|
1207
|
+
primary: `${emojis[base] || '🔧'} ${labels[base] || parsed.base || 'Tool'}`,
|
|
1087
1208
|
secondary: parsed.target ? `(${parsed.target})` : ''
|
|
1088
1209
|
};
|
|
1089
1210
|
}
|
|
1090
1211
|
|
|
1212
|
+
function getIntentEmoji(kind) {
|
|
1213
|
+
const map = {
|
|
1214
|
+
test: '🧪',
|
|
1215
|
+
install: '📦',
|
|
1216
|
+
build: '🏗️',
|
|
1217
|
+
frontend: '🖥️',
|
|
1218
|
+
backend: '🛰️',
|
|
1219
|
+
database: '🗄️',
|
|
1220
|
+
docker: '🐳',
|
|
1221
|
+
command: '⚙️'
|
|
1222
|
+
};
|
|
1223
|
+
return map[kind] || '⚙️';
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1091
1226
|
export function isIndexSystemToolName(name) {
|
|
1092
1227
|
const parsed = parseToolDisplayName(name);
|
|
1093
1228
|
return parsed.base === 'project_index' || parsed.base === 'file_index';
|
|
1094
1229
|
}
|
|
1095
1230
|
|
|
1096
1231
|
export function shouldShowCompletionFooter(msg) {
|
|
1097
|
-
|
|
1232
|
+
if (!msg || msg.loading || (msg.phase || '').trim()) return false;
|
|
1233
|
+
const label = (msg.label || '').toLowerCase();
|
|
1234
|
+
return label === 'coder' || label === 'planner' || label === 'reviewer' || label === 'tester';
|
|
1098
1235
|
}
|
|
1099
1236
|
|
|
1100
1237
|
function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
|
|
@@ -1135,7 +1272,9 @@ export function shouldRefreshRuntimeStateForEvent(event) {
|
|
|
1135
1272
|
type === 'assistant:delta' ||
|
|
1136
1273
|
type === 'assistant:response' ||
|
|
1137
1274
|
type === 'tool:result' ||
|
|
1138
|
-
type === 'compact:auto'
|
|
1275
|
+
type === 'compact:auto' ||
|
|
1276
|
+
type === 'dream:auto' ||
|
|
1277
|
+
type === 'dream:complete'
|
|
1139
1278
|
);
|
|
1140
1279
|
}
|
|
1141
1280
|
|
|
@@ -1173,7 +1312,6 @@ function stageDescriptor(inputStage, busy, runtimeStatus, copy) {
|
|
|
1173
1312
|
|
|
1174
1313
|
function RuntimeStrip({ busy, runtimeStatus, loaderTick, copy }) {
|
|
1175
1314
|
const status = normalizeRuntimeStatus(runtimeStatus, copy);
|
|
1176
|
-
const spinnerChar = SPINNER_FRAMES[loaderTick % SPINNER_FRAMES.length];
|
|
1177
1315
|
return h(
|
|
1178
1316
|
Box,
|
|
1179
1317
|
{
|
|
@@ -1185,7 +1323,7 @@ function RuntimeStrip({ busy, runtimeStatus, loaderTick, copy }) {
|
|
|
1185
1323
|
},
|
|
1186
1324
|
h(Text, { color: busy ? 'greenBright' : 'gray' }, busy ? copy.generic.live : copy.generic.idle),
|
|
1187
1325
|
h(Text, { color: 'gray' }, ' '),
|
|
1188
|
-
h(Text, { color: busy ? 'cyanBright' : 'gray' },
|
|
1326
|
+
h(Text, { color: busy ? 'cyanBright' : 'gray' }, busy ? '●' : '○'),
|
|
1189
1327
|
h(Text, { color: 'gray' }, ' '),
|
|
1190
1328
|
h(Text, { color: busy ? 'white' : 'gray' }, status.title || copy.generic.waitingForInput)
|
|
1191
1329
|
);
|
|
@@ -2310,15 +2448,20 @@ export function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
2310
2448
|
? 'redBright'
|
|
2311
2449
|
: 'cyanBright';
|
|
2312
2450
|
const durationText = formatActivityDurationText(row);
|
|
2451
|
+
const trailingLoader =
|
|
2452
|
+
row.status === 'running'
|
|
2453
|
+
? h(Text, { color: 'gray' }, ` ${SPINNER_FRAMES[loaderTick % SPINNER_FRAMES.length]}`)
|
|
2454
|
+
: null;
|
|
2313
2455
|
return h(
|
|
2314
2456
|
Box,
|
|
2315
2457
|
{ key: `row-tool-${msg.id}-${idx}` },
|
|
2316
2458
|
h(Text, { color: 'gray' }, ' '),
|
|
2317
|
-
h(Text, { color: dotColor },
|
|
2459
|
+
h(Text, { color: dotColor }, '●'),
|
|
2318
2460
|
h(Text, { color: 'gray' }, ' '),
|
|
2319
2461
|
h(Text, { color: textColor }, display.primary),
|
|
2320
2462
|
h(Text, { color: 'gray' }, display.secondary),
|
|
2321
|
-
durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null
|
|
2463
|
+
durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null,
|
|
2464
|
+
trailingLoader
|
|
2322
2465
|
);
|
|
2323
2466
|
}
|
|
2324
2467
|
if (row.kind === 'activity-summary') {
|
|
@@ -2584,8 +2727,11 @@ const MessageBubble = React.memo(function MessageBubble({ msg, loaderTick, showT
|
|
|
2584
2727
|
shouldShowCompletionFooter(msg)
|
|
2585
2728
|
? h(
|
|
2586
2729
|
Box,
|
|
2587
|
-
{ marginTop: 1,
|
|
2588
|
-
h(
|
|
2730
|
+
{ marginTop: 1, flexDirection: 'column', key: `row-completion-${msg.id}` },
|
|
2731
|
+
h(FileChangeSummary, { segments: msg.segments, copy }),
|
|
2732
|
+
h(Box, { marginLeft: 1, marginTop: 1 },
|
|
2733
|
+
h(Text, { color: 'gray', dimColor: true }, copy.generic.taskCompleted)
|
|
2734
|
+
)
|
|
2589
2735
|
)
|
|
2590
2736
|
: null
|
|
2591
2737
|
)
|
|
@@ -2858,12 +3004,102 @@ function DeleteApprovalPanel({ request, inputValue, errorText, copy, cursorVisib
|
|
|
2858
3004
|
);
|
|
2859
3005
|
}
|
|
2860
3006
|
|
|
3007
|
+
function RunApprovalPanel({ request, inputValue, errorText, copy, cursorVisible }) {
|
|
3008
|
+
if (!request) return null;
|
|
3009
|
+
const details = request?.toolName === 'run' ? request : normalizeRunApprovalRequest(request);
|
|
3010
|
+
if (!details) return null;
|
|
3011
|
+
const c = copy.runApproval || {};
|
|
3012
|
+
const riskColor = details.risk === 'low' ? 'green' : details.risk === 'medium' ? 'yellow' : 'redBright';
|
|
3013
|
+
const borderColor = details.risk === 'medium' ? 'yellow' : 'redBright';
|
|
3014
|
+
const riskLabel = details.risk === 'low' ? c.lowRisk : details.risk === 'medium' ? c.mediumRisk : c.highRisk;
|
|
3015
|
+
const placeholder = String(c.answerPlaceholder || '').trim();
|
|
3016
|
+
return h(
|
|
3017
|
+
Box,
|
|
3018
|
+
{
|
|
3019
|
+
marginTop: 1,
|
|
3020
|
+
flexDirection: 'column',
|
|
3021
|
+
borderStyle: 'round',
|
|
3022
|
+
borderColor,
|
|
3023
|
+
paddingX: 1,
|
|
3024
|
+
paddingY: 0
|
|
3025
|
+
},
|
|
3026
|
+
h(Text, { color: borderColor }, c.title),
|
|
3027
|
+
h(Text, { color: 'white' }, `${c.commandLabel}: ${details.command}`),
|
|
3028
|
+
h(Text, null, `${c.riskLabel}: `, h(Text, { color: riskColor, bold: true }, riskLabel || details.risk)),
|
|
3029
|
+
details.description ? h(Text, { color: 'gray' }, `${c.descriptionLabel}: ${details.description}`) : null,
|
|
3030
|
+
details.sideEffects ? h(Text, { color: 'gray' }, `${c.sideEffectsLabel}: ${details.sideEffects}`) : null,
|
|
3031
|
+
h(Text, { color: 'gray' }, c.prompt),
|
|
3032
|
+
h(
|
|
3033
|
+
Box,
|
|
3034
|
+
{ marginTop: 1 },
|
|
3035
|
+
h(Text, { color: borderColor }, `${c.answerLabel}: `),
|
|
3036
|
+
h(ApprovalCursorLine, {
|
|
3037
|
+
inputValue,
|
|
3038
|
+
placeholder: placeholder || ' ',
|
|
3039
|
+
cursorVisible,
|
|
3040
|
+
accent: borderColor
|
|
3041
|
+
})
|
|
3042
|
+
),
|
|
3043
|
+
errorText ? h(Text, { color: 'yellowBright' }, errorText) : null
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function FileChangeSummary({ segments, copy }) {
|
|
3048
|
+
if (!Array.isArray(segments) || segments.length === 0) return null;
|
|
3049
|
+
const c = copy.fileChangeSummary || {};
|
|
3050
|
+
const changes = new Map();
|
|
3051
|
+
for (const seg of segments) {
|
|
3052
|
+
if (!seg.fileChange) continue;
|
|
3053
|
+
const p = seg.fileChange.path;
|
|
3054
|
+
if (!p) continue;
|
|
3055
|
+
const existing = changes.get(p);
|
|
3056
|
+
if (existing) {
|
|
3057
|
+
/* 同一文件多次编辑,合并行数,取最高 action */
|
|
3058
|
+
existing.linesAdded += seg.fileChange.linesAdded || 0;
|
|
3059
|
+
existing.linesRemoved += seg.fileChange.linesRemoved || 0;
|
|
3060
|
+
const ACTION_ORDER = { delete: 3, create: 2, edit: 1 };
|
|
3061
|
+
if ((ACTION_ORDER[seg.fileChange.action] || 0) > (ACTION_ORDER[existing.action] || 0)) {
|
|
3062
|
+
existing.action = seg.fileChange.action;
|
|
3063
|
+
}
|
|
3064
|
+
} else {
|
|
3065
|
+
changes.set(p, { path: p, action: seg.fileChange.action, linesAdded: seg.fileChange.linesAdded || 0, linesRemoved: seg.fileChange.linesRemoved || 0 });
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
if (changes.size === 0) return null;
|
|
3069
|
+
const entries = [...changes.values()];
|
|
3070
|
+
return h(
|
|
3071
|
+
Box,
|
|
3072
|
+
{ marginTop: 1, flexDirection: 'column', borderStyle: 'round', borderColor: 'gray', paddingX: 1 },
|
|
3073
|
+
h(Text, { color: 'cyan', bold: true }, c.title || 'File Changes'),
|
|
3074
|
+
...entries.map((entry) => {
|
|
3075
|
+
const statusMap = { edit: c.editStatus || 'Edit', create: c.createStatus || 'Create', delete: c.deleteStatus || 'Delete' };
|
|
3076
|
+
const statusColor = entry.action === 'create' ? 'green' : entry.action === 'delete' ? 'red' : 'yellow';
|
|
3077
|
+
const statusText = statusMap[entry.action] || entry.action;
|
|
3078
|
+
let changesText = '';
|
|
3079
|
+
if (entry.action !== 'delete') {
|
|
3080
|
+
const parts = [];
|
|
3081
|
+
if (entry.linesAdded > 0) parts.push(h(Text, { color: 'green' }, `+${entry.linesAdded}`));
|
|
3082
|
+
if (entry.linesRemoved > 0) parts.push(h(Text, { color: 'red' }, `-${entry.linesRemoved}`));
|
|
3083
|
+
if (parts.length > 0) {
|
|
3084
|
+
changesText = parts.reduce((acc, el, i) => i === 0 ? [el] : [...acc, ' ', el], []);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return h(
|
|
3088
|
+
Box,
|
|
3089
|
+
{ key: entry.path },
|
|
3090
|
+
h(Text, { color: 'white' }, ` ${entry.path}`),
|
|
3091
|
+
h(Text, { color: 'gray' }, ' '),
|
|
3092
|
+
h(Text, { color: statusColor }, statusText),
|
|
3093
|
+
changesText ? h(Text, null, ' ', changesText) : null
|
|
3094
|
+
);
|
|
3095
|
+
})
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
|
|
2861
3099
|
function PlanApprovalPanel({ request, inputValue, errorText, copy, cursorVisible }) {
|
|
2862
3100
|
if (!request) return null;
|
|
2863
|
-
const goal = String(request.goal || '').trim();
|
|
2864
|
-
const summary = String(request.summary || '').trim();
|
|
2865
|
-
const filePath = String(request.filePath || '').trim();
|
|
2866
3101
|
const placeholder = String(copy.planApproval.answerPlaceholder || '').trim();
|
|
3102
|
+
const lines = formatPlanApprovalLines(copy, request);
|
|
2867
3103
|
return h(
|
|
2868
3104
|
Box,
|
|
2869
3105
|
{
|
|
@@ -2874,11 +3110,9 @@ function PlanApprovalPanel({ request, inputValue, errorText, copy, cursorVisible
|
|
|
2874
3110
|
paddingX: 1,
|
|
2875
3111
|
paddingY: 0
|
|
2876
3112
|
},
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
filePath ? h(Text, { color: 'gray' }, `${copy.planApproval.fileLabel}: ${filePath}`) : null,
|
|
2881
|
-
h(Text, { color: 'gray' }, copy.planApproval.prompt),
|
|
3113
|
+
...lines.map((line, index) =>
|
|
3114
|
+
h(Text, { key: `plan-approval-line-${index}`, color: 'yellowBright' }, line)
|
|
3115
|
+
),
|
|
2882
3116
|
h(
|
|
2883
3117
|
Box,
|
|
2884
3118
|
{ marginTop: 1 },
|
|
@@ -2978,10 +3212,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2978
3212
|
const [pendingDeleteApproval, setPendingDeleteApproval] = useState(null);
|
|
2979
3213
|
const [deleteApprovalInput, setDeleteApprovalInput] = useState('');
|
|
2980
3214
|
const [deleteApprovalError, setDeleteApprovalError] = useState('');
|
|
3215
|
+
const [pendingRunApproval, setPendingRunApproval] = useState(null);
|
|
3216
|
+
const [runApprovalInput, setRunApprovalInput] = useState('');
|
|
3217
|
+
const [runApprovalError, setRunApprovalError] = useState('');
|
|
2981
3218
|
const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
|
|
2982
3219
|
const [planApprovalInput, setPlanApprovalInput] = useState('');
|
|
2983
3220
|
const [planApprovalError, setPlanApprovalError] = useState('');
|
|
2984
|
-
const approvalLockActive = Boolean(pendingDeleteApproval || pendingPlanApproval);
|
|
3221
|
+
const approvalLockActive = Boolean(pendingDeleteApproval || pendingRunApproval || pendingPlanApproval);
|
|
2985
3222
|
const activeAssistantIdRef = useRef(null);
|
|
2986
3223
|
const activeAssistantAutoSkillNamesRef = useRef([]);
|
|
2987
3224
|
const streamedAssistantHandledRef = useRef(false);
|
|
@@ -2996,6 +3233,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
2996
3233
|
const activePlanStepInfoRef = useRef(null);
|
|
2997
3234
|
const activePlanStepTitleRef = useRef('');
|
|
2998
3235
|
const deleteApprovalResolverRef = useRef(null);
|
|
3236
|
+
const runApprovalResolverRef = useRef(null);
|
|
2999
3237
|
|
|
3000
3238
|
useEffect(() => {
|
|
3001
3239
|
const rawStartupActivities = runtime.consumeStartupEvents?.();
|
|
@@ -3021,27 +3259,42 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3021
3259
|
useEffect(() => {
|
|
3022
3260
|
if (typeof runtime.setRequestToolApproval !== 'function') return () => {};
|
|
3023
3261
|
runtime.setRequestToolApproval((request) => {
|
|
3024
|
-
const
|
|
3025
|
-
if (
|
|
3026
|
-
|
|
3262
|
+
const deleteNorm = normalizeDeleteApprovalRequest(request);
|
|
3263
|
+
if (deleteNorm) {
|
|
3264
|
+
setDeleteApprovalInput('');
|
|
3265
|
+
setDeleteApprovalError('');
|
|
3266
|
+
setPendingDeleteApproval(deleteNorm);
|
|
3267
|
+
return new Promise((resolve) => {
|
|
3268
|
+
deleteApprovalResolverRef.current = resolve;
|
|
3269
|
+
});
|
|
3027
3270
|
}
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3271
|
+
const runNorm = normalizeRunApprovalRequest(request);
|
|
3272
|
+
if (runNorm) {
|
|
3273
|
+
setRunApprovalInput('');
|
|
3274
|
+
setRunApprovalError('');
|
|
3275
|
+
setPendingRunApproval(runNorm);
|
|
3276
|
+
return new Promise((resolve) => {
|
|
3277
|
+
runApprovalResolverRef.current = resolve;
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
return Promise.resolve({ approved: false });
|
|
3034
3281
|
});
|
|
3035
3282
|
return () => {
|
|
3036
3283
|
runtime.setRequestToolApproval(null);
|
|
3037
3284
|
deleteApprovalResolverRef.current = null;
|
|
3285
|
+
runApprovalResolverRef.current = null;
|
|
3038
3286
|
};
|
|
3039
3287
|
}, [runtime]);
|
|
3040
3288
|
|
|
3041
3289
|
useEffect(() => {
|
|
3042
3290
|
messagesRef.current = messages;
|
|
3043
3291
|
}, [messages]);
|
|
3044
|
-
const
|
|
3292
|
+
const startupHints = copy.generic.startupHints;
|
|
3293
|
+
const startupHint = useMemo(() => {
|
|
3294
|
+
const arr = Array.isArray(startupHints) ? startupHints : [];
|
|
3295
|
+
if (arr.length === 0) return '';
|
|
3296
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
3297
|
+
}, [startupHints]);
|
|
3045
3298
|
const isBackspaceKey = (value, key) =>
|
|
3046
3299
|
Boolean(key?.backspace) || value === '\u0008' || value === '\u007f' || (key?.ctrl && value === 'h');
|
|
3047
3300
|
const isDeleteKey = (value, key) =>
|
|
@@ -3229,7 +3482,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3229
3482
|
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
3230
3483
|
...(startedAt ? { startedAt } : {}),
|
|
3231
3484
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
3232
|
-
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
3485
|
+
...(toolEvent.summary ? { summary: toolEvent.summary } : {}),
|
|
3486
|
+
...(toolEvent.fileChange ? { fileChange: toolEvent.fileChange } : {})
|
|
3233
3487
|
});
|
|
3234
3488
|
} else {
|
|
3235
3489
|
toolCalls[idx] = {
|
|
@@ -3255,7 +3509,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3255
3509
|
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
3256
3510
|
...(startedAt ? { startedAt } : {}),
|
|
3257
3511
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
3258
|
-
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
3512
|
+
...(toolEvent.summary ? { summary: toolEvent.summary } : {}),
|
|
3513
|
+
...(toolEvent.fileChange ? { fileChange: toolEvent.fileChange } : {})
|
|
3259
3514
|
};
|
|
3260
3515
|
if (segmentIdx === -1) {
|
|
3261
3516
|
segments.push(patch);
|
|
@@ -3706,7 +3961,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3706
3961
|
status: 'done',
|
|
3707
3962
|
durationMs: event.durationMs,
|
|
3708
3963
|
summary: event.summary,
|
|
3709
|
-
arguments: event.arguments
|
|
3964
|
+
arguments: event.arguments,
|
|
3965
|
+
fileChange: event.fileChange || null
|
|
3710
3966
|
});
|
|
3711
3967
|
}
|
|
3712
3968
|
if (event?.type === 'tool:blocked') {
|
|
@@ -3880,6 +4136,30 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
3880
4136
|
}
|
|
3881
4137
|
]);
|
|
3882
4138
|
}
|
|
4139
|
+
if (event?.type === 'dream:auto') {
|
|
4140
|
+
setRuntimeStatus(makeStatus(copy.runtime.dreamRunning, copy.runtime.dreamAutoTriggered, 'magentaBright'));
|
|
4141
|
+
setMessages((prev) => [
|
|
4142
|
+
...prev,
|
|
4143
|
+
{
|
|
4144
|
+
id: nextId(),
|
|
4145
|
+
label: 'system',
|
|
4146
|
+
text: copy.runtime.dreamAutoTriggered,
|
|
4147
|
+
color: 'magentaBright'
|
|
4148
|
+
}
|
|
4149
|
+
]);
|
|
4150
|
+
}
|
|
4151
|
+
if (event?.type === 'dream:complete') {
|
|
4152
|
+
setRuntimeStatus(makeStatus(copy.runtime.dreamCompleted, '', 'greenBright'));
|
|
4153
|
+
setMessages((prev) => [
|
|
4154
|
+
...prev,
|
|
4155
|
+
{
|
|
4156
|
+
id: nextId(),
|
|
4157
|
+
label: 'system',
|
|
4158
|
+
text: copy.runtime.dreamCompleted,
|
|
4159
|
+
color: 'greenBright'
|
|
4160
|
+
}
|
|
4161
|
+
]);
|
|
4162
|
+
}
|
|
3883
4163
|
})
|
|
3884
4164
|
.then((result) => {
|
|
3885
4165
|
try {
|
|
@@ -4103,6 +4383,46 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
4103
4383
|
return;
|
|
4104
4384
|
}
|
|
4105
4385
|
|
|
4386
|
+
if (pendingRunApproval) {
|
|
4387
|
+
if (key.return) {
|
|
4388
|
+
const answer = parseDeleteApprovalAnswer(runApprovalInput);
|
|
4389
|
+
if (answer === 'approve' || answer === 'deny') {
|
|
4390
|
+
const resolver = runApprovalResolverRef.current;
|
|
4391
|
+
runApprovalResolverRef.current = null;
|
|
4392
|
+
setPendingRunApproval(null);
|
|
4393
|
+
setRunApprovalInput('');
|
|
4394
|
+
setRunApprovalError('');
|
|
4395
|
+
if (resolver) resolver({ approved: answer === 'approve' });
|
|
4396
|
+
} else {
|
|
4397
|
+
setRunApprovalError(copy.runApproval.invalidAnswer);
|
|
4398
|
+
}
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
if (isBackspaceKey(value, key) || isDeleteKey(value, key)) {
|
|
4403
|
+
setRunApprovalInput((prev) => prev.slice(0, -1));
|
|
4404
|
+
setRunApprovalError('');
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
if (isPrintableInput(value, key)) {
|
|
4409
|
+
setRunApprovalInput((prev) => `${prev}${value}`);
|
|
4410
|
+
setRunApprovalError('');
|
|
4411
|
+
return;
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
if (key.ctrl && value === 'c') {
|
|
4415
|
+
if (busy && typeof runtime.abort === 'function') {
|
|
4416
|
+
runtime.abort();
|
|
4417
|
+
return;
|
|
4418
|
+
}
|
|
4419
|
+
exit();
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
return;
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4106
4426
|
if (pendingPlanApproval) {
|
|
4107
4427
|
if (key.return) {
|
|
4108
4428
|
const parsed = parsePlanApprovalAnswer(planApprovalInput);
|
|
@@ -4454,9 +4774,11 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
4454
4774
|
);
|
|
4455
4775
|
const activeApprovalLock = pendingDeleteApproval
|
|
4456
4776
|
? { text: copy.deleteApproval.inputLocked }
|
|
4457
|
-
:
|
|
4458
|
-
? { text: copy.
|
|
4459
|
-
:
|
|
4777
|
+
: pendingRunApproval
|
|
4778
|
+
? { text: copy.runApproval.inputLocked }
|
|
4779
|
+
: pendingPlanApproval
|
|
4780
|
+
? { text: copy.planApproval.inputLocked }
|
|
4781
|
+
: null;
|
|
4460
4782
|
|
|
4461
4783
|
return h(
|
|
4462
4784
|
Box,
|
|
@@ -4492,6 +4814,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
|
|
|
4492
4814
|
copy,
|
|
4493
4815
|
cursorVisible
|
|
4494
4816
|
}),
|
|
4817
|
+
h(RunApprovalPanel, {
|
|
4818
|
+
request: pendingRunApproval,
|
|
4819
|
+
inputValue: runApprovalInput,
|
|
4820
|
+
errorText: runApprovalError,
|
|
4821
|
+
copy,
|
|
4822
|
+
cursorVisible
|
|
4823
|
+
}),
|
|
4495
4824
|
h(PlanApprovalPanel, {
|
|
4496
4825
|
request: pendingPlanApproval,
|
|
4497
4826
|
inputValue: planApprovalInput,
|
|
@@ -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
|
}
|