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.
@@ -132,7 +132,18 @@ const TUI_COPY = {
132
132
  pendingQueue: '等待队列',
133
133
  commandPaletteGroupedSelect: '命令面板 | 分组选择模式',
134
134
  commandPaletteGroupedSuggestions: '命令面板 | 分组候选',
135
- startupHint: '使用 /help、/commands、/compact、/exit、/stop、!<shell>。Tab 可自动补全 slash 命令。',
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
- startupHint: 'Use /help, /commands, /compact, /stop, /exit, !<shell>. Tab for slash autocomplete.',
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
- if (parsed.base === 'run') {
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: `Skill`,
1154
+ primary: '🧩 Skill',
1052
1155
  secondary: `(${activity?.name || 'unknown'})`
1053
1156
  };
1054
1157
  }
1055
1158
  if ((activity?.type || 'tool') === 'system_tool') {
1056
- if (parsed.base === 'project_index') {
1057
- return { primary: 'Project Index', secondary: '' };
1159
+ if (base === 'project_index') {
1160
+ return { primary: '🗂️ Project Index', secondary: '' };
1058
1161
  }
1059
- if (parsed.base === 'file_index') {
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[parsed.base] || parsed.base || 'Tool',
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
- return Boolean(msg && msg.label === 'coder' && !msg.loading && !(msg.phase || '').trim());
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' }, spinnerChar),
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 }, row.status === 'running' ? SPINNER_FRAMES[loaderTick % SPINNER_FRAMES.length] : '●'),
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, marginLeft: 1, key: `row-completion-${msg.id}` },
2588
- h(Text, { color: 'gray', dimColor: true }, copy.generic.taskCompleted)
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
- h(Text, { color: 'yellowBright' }, copy.planApproval.title),
2878
- goal ? h(Text, { color: 'white' }, `${copy.planApproval.goalLabel}: ${goal}`) : null,
2879
- summary ? h(Text, { color: 'white' }, `${copy.planApproval.summaryLabel}: ${summary}`) : null,
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 normalized = normalizeDeleteApprovalRequest(request);
3025
- if (!normalized) {
3026
- return Promise.resolve({ approved: false });
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
- setDeleteApprovalInput('');
3029
- setDeleteApprovalError('');
3030
- setPendingDeleteApproval(normalized);
3031
- return new Promise((resolve) => {
3032
- deleteApprovalResolverRef.current = resolve;
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 startupHint = copy.generic.startupHint;
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
- : pendingPlanApproval
4458
- ? { text: copy.planApproval.inputLocked }
4459
- : null;
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-project/file-index.json', 72);
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
  }