codemini-cli 0.4.0 → 0.4.2

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.
@@ -142,6 +142,7 @@ const TUI_COPY = {
142
142
  '🧩 用 /mode plan 切换到规划模式,让 AI 先出方案再动手。',
143
143
  '🆕 /new 可以新建一个干净的会话,重新开始工作。',
144
144
  '🧠 /memory 查看和管理 AI 的持久记忆,帮助它更好地理解你的偏好。',
145
+ '🌐 web_fetch 默认轻量读取网页;如需更好读取 JS 渲染页面,可运行 npm install -g playwright && playwright install chromium。',
145
146
  '💤 CodeMini 会自动"做梦"休息,整理错误信息并自我优化,越用越聪明~'
146
147
  ],
147
148
  toolSummaryExpanded: '工具摘要:已展开',
@@ -180,6 +181,10 @@ const TUI_COPY = {
180
181
  doingGlob: '正在按模式查找文件',
181
182
  doneGrep: '已搜索关键词',
182
183
  doingGrep: '正在搜索关键词',
184
+ doneWebFetch: '已抓取网页',
185
+ doingWebFetch: '正在抓取网页',
186
+ doneWebSearch: '已搜索网页',
187
+ doingWebSearch: '正在搜索网页',
183
188
  doneCommand: '已执行命令',
184
189
  doingCommand: '正在执行命令',
185
190
  doneUpdateTodos: '已更新待办',
@@ -310,6 +315,18 @@ const TUI_COPY = {
310
315
  inputLocked: '计划审批进行中,请在审批框输入 /yes、/edit 或 /reject',
311
316
  answerLabel: '审批输入',
312
317
  answerPlaceholder: '/yes | /edit <反馈> | /reject'
318
+ },
319
+ reflectApproval: {
320
+ title: '审阅 Reflect 技能草稿?',
321
+ scopeLabel: '范围',
322
+ nameLabel: '名称',
323
+ targetLabel: '目标',
324
+ prompt: '输入 /yes 写入,输入 /edit <反馈> 修改,输入 /no 丢弃。',
325
+ invalidAnswer: '请输入 /yes、/edit <反馈> 或 /no。',
326
+ missingFeedback: '请在 /edit 后提供反馈内容。',
327
+ inputLocked: 'Reflect 审阅进行中,请在审阅框输入 /yes、/edit 或 /no',
328
+ answerLabel: '审阅输入',
329
+ answerPlaceholder: '/yes | /edit <反馈> | /no'
313
330
  }
314
331
  },
315
332
  en: {
@@ -350,6 +367,7 @@ const TUI_COPY = {
350
367
  '🧩 Use /mode plan to switch to planning mode — AI proposes a plan before coding.',
351
368
  '🆕 /new starts a fresh session to begin a clean slate.',
352
369
  '🧠 /memory lets you view and manage the AI\'s persistent memory for better personalization.',
370
+ '🌐 web_fetch uses a lightweight reader by default. For better JS-rendered pages: npm install -g playwright && playwright install chromium.',
353
371
  '💤 CodeMini auto-"dreams" to rest, consolidate errors, and self-optimize — it gets smarter over time~'
354
372
  ],
355
373
  toolSummaryExpanded: 'Tool summary: expanded',
@@ -388,6 +406,10 @@ const TUI_COPY = {
388
406
  doingGlob: 'Matching files by pattern',
389
407
  doneGrep: 'Searched keywords',
390
408
  doingGrep: 'Searching keywords',
409
+ doneWebFetch: 'Fetched page',
410
+ doingWebFetch: 'Fetching page',
411
+ doneWebSearch: 'Searched web',
412
+ doingWebSearch: 'Searching web',
391
413
  doneCommand: 'Ran command',
392
414
  doingCommand: 'Running command',
393
415
  doneUpdateTodos: 'Updated todos',
@@ -518,6 +540,18 @@ const TUI_COPY = {
518
540
  inputLocked: 'Plan approval is active; type /yes, /edit <feedback>, or /reject',
519
541
  answerLabel: 'Approval input',
520
542
  answerPlaceholder: '/yes | /edit <feedback> | /reject'
543
+ },
544
+ reflectApproval: {
545
+ title: 'Review this reflected skill draft?',
546
+ scopeLabel: 'Scope',
547
+ nameLabel: 'Name',
548
+ targetLabel: 'Target',
549
+ prompt: 'Type /yes to write, /edit <feedback> to revise, or /no to discard.',
550
+ invalidAnswer: 'Please enter /yes, /edit <feedback>, or /no.',
551
+ missingFeedback: 'Please provide feedback after /edit.',
552
+ inputLocked: 'Reflect review is active; type /yes, /edit <feedback>, or /no',
553
+ answerLabel: 'Review input',
554
+ answerPlaceholder: '/yes | /edit <feedback> | /no'
521
555
  }
522
556
  }
523
557
  };
@@ -531,13 +565,24 @@ function getCopy(language) {
531
565
  }
532
566
 
533
567
  function messageLabel(label, copy) {
534
- return copy.roleLabels[label] || String(label || '').toUpperCase();
568
+ return copy?.roleLabels?.[label] || String(label || '').toUpperCase();
535
569
  }
536
570
 
537
571
  function roleStyle(label) {
538
572
  return ROLE_STYLES[label] || ROLE_STYLES.system;
539
573
  }
540
574
 
575
+ const PLAN_AGENT_ROLES = new Set(['planner', 'coder', 'reviewer', 'tester', 'summarizer']);
576
+
577
+ function normalizePlanAgentRole(role) {
578
+ const roleKey = String(role || '').trim().toLowerCase();
579
+ return PLAN_AGENT_ROLES.has(roleKey) ? roleKey : 'coder';
580
+ }
581
+
582
+ export function formatPlanAgentLabel(role, copy) {
583
+ return messageLabel(normalizePlanAgentRole(role), copy);
584
+ }
585
+
541
586
  function StatusPill({ label, value, color = 'cyanBright', textColor = 'black' }) {
542
587
  return h(
543
588
  Box,
@@ -884,7 +929,7 @@ function textFromSessionContent(content) {
884
929
  return sanitizeRenderableText(String(content || ''));
885
930
  }
886
931
 
887
- function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
932
+ export function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
888
933
  const source = Array.isArray(sessionMessages) ? sessionMessages : [];
889
934
  const out = [];
890
935
 
@@ -893,7 +938,7 @@ function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
893
938
  if (message.role === 'tool') continue;
894
939
 
895
940
  const text = textFromSessionContent(message.content);
896
- if (!text.trim() && message.role !== 'assistant') continue;
941
+ if (!text.trim()) continue;
897
942
 
898
943
  if (message.role === 'user') {
899
944
  out.push({ id: nextId(), label: 'you', text, color: 'blueBright' });
@@ -1101,6 +1146,25 @@ export function parsePlanApprovalAnswer(value) {
1101
1146
  return { action: 'invalid', command: '' };
1102
1147
  }
1103
1148
 
1149
+ export function parseReflectApprovalAnswer(value) {
1150
+ const raw = String(value || '').trim();
1151
+ if (!raw) return { action: 'empty', command: '' };
1152
+ const normalized = raw.toLowerCase();
1153
+ if (normalized === '/yes' || normalized === 'yes') {
1154
+ return { action: 'approve', command: '/yes' };
1155
+ }
1156
+ if (normalized === '/no' || normalized === 'no') {
1157
+ return { action: 'reject', command: '/no' };
1158
+ }
1159
+ const editMatch = raw.match(/^\/?edit(?:\s+(.+))?$/i);
1160
+ if (editMatch) {
1161
+ const feedback = String(editMatch[1] || '').trim();
1162
+ if (!feedback) return { action: 'missing_feedback', command: '' };
1163
+ return { action: 'edit', feedback, command: `/edit ${feedback}` };
1164
+ }
1165
+ return { action: 'invalid', command: '' };
1166
+ }
1167
+
1104
1168
  export function parsePendingPlanApprovalMessage(text = '') {
1105
1169
  const raw = String(text || '');
1106
1170
  if (!/^Plan approval is still pending\./i.test(raw.trim())) return null;
@@ -1114,6 +1178,21 @@ export function parsePendingPlanApprovalMessage(text = '') {
1114
1178
  return out;
1115
1179
  }
1116
1180
 
1181
+ export function parsePendingReflectSkillMessage(text = '') {
1182
+ const raw = String(text || '');
1183
+ if (!/\bReflect skill draft pending\./i.test(raw)) return null;
1184
+ const lines = raw.split(/\r?\n/);
1185
+ const out = { scope: '', name: '', confidence: '', targetPath: '' };
1186
+ for (const line of lines) {
1187
+ const trimmed = line.trim();
1188
+ if (trimmed.startsWith('Scope: ')) out.scope = trimmed.slice('Scope: '.length).trim();
1189
+ else if (/^\[\d+\]\s+/.test(trimmed) && !out.name) out.name = trimmed.replace(/^\[\d+\]\s+/, '').trim();
1190
+ else if (trimmed.startsWith('Confidence: ')) out.confidence = trimmed.slice('Confidence: '.length).trim();
1191
+ else if (trimmed.startsWith('Target: ')) out.targetPath = trimmed.slice('Target: '.length).trim();
1192
+ }
1193
+ return out;
1194
+ }
1195
+
1117
1196
  export function formatDeleteApprovalLines(copy, request) {
1118
1197
  const details = normalizeDeleteApprovalRequest(request);
1119
1198
  if (!details) return [];
@@ -1133,6 +1212,17 @@ export function formatPlanApprovalLines(copy, request) {
1133
1212
  return [String(copy?.planApproval?.title || '').trim()].filter(Boolean);
1134
1213
  }
1135
1214
 
1215
+ export function formatReflectApprovalLines(copy, request) {
1216
+ if (!request) return [];
1217
+ const c = copy?.reflectApproval || {};
1218
+ const lines = [String(c.title || '').trim()];
1219
+ if (request.scope) lines.push(`${c.scopeLabel || 'Scope'}: ${request.scope}`);
1220
+ if (request.name) lines.push(`${c.nameLabel || 'Name'}: ${request.name}`);
1221
+ if (request.targetPath) lines.push(`${c.targetLabel || 'Target'}: ${request.targetPath}`);
1222
+ if (c.prompt) lines.push(c.prompt);
1223
+ return lines.filter(Boolean);
1224
+ }
1225
+
1136
1226
  function getActivityDisplayParts(activity) {
1137
1227
  if (isCodeGenerationActivityName(activity?.name)) {
1138
1228
  return {
@@ -1175,6 +1265,8 @@ function getActivityDisplayParts(activity) {
1175
1265
  patch: 'Patch',
1176
1266
  run: 'Run',
1177
1267
  grep: 'Search',
1268
+ web_fetch: 'Fetch',
1269
+ web_search: 'Web Search',
1178
1270
  glob: 'Glob',
1179
1271
  list: 'List',
1180
1272
  list_background_tasks: 'Tasks',
@@ -1193,6 +1285,8 @@ function getActivityDisplayParts(activity) {
1193
1285
  patch: '🩹',
1194
1286
  run: '⚙️',
1195
1287
  grep: '🔍',
1288
+ web_fetch: '🌐',
1289
+ web_search: '🌐',
1196
1290
  glob: '🧭',
1197
1291
  list: '📂',
1198
1292
  list_background_tasks: '🗃️',
@@ -1407,10 +1501,9 @@ function PlanStrip({ planState, copy }) {
1407
1501
  Box,
1408
1502
  { flexDirection: 'column' },
1409
1503
  ...planState.steps.map((step, idx) => {
1410
- const roleKey = step.role ? step.role.toLowerCase() : '';
1411
- const normalizedRole = ['planner', 'coder', 'reviewer', 'tester', 'summarizer'].includes(roleKey) ? roleKey : 'coder';
1504
+ const normalizedRole = normalizePlanAgentRole(step.role);
1412
1505
  const stepTheme = roleStyle(normalizedRole);
1413
- const roleTag = step.role ? step.role.toUpperCase() : '';
1506
+ const roleTag = formatPlanAgentLabel(normalizedRole, copy);
1414
1507
  const stepDone = step.status === 'done' || isDone;
1415
1508
  const stepFailed = step.status === 'failed';
1416
1509
  const marker = stepFailed ? '✗' : stepDone ? '✓' : '·';
@@ -1419,8 +1512,8 @@ function PlanStrip({ planState, copy }) {
1419
1512
  Box,
1420
1513
  { key: `plan-step-${idx}` },
1421
1514
  h(Text, { color: markerColor }, `${marker} `),
1422
- roleTag ? h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `) : null,
1423
- roleTag ? h(Text, { color: 'gray' }, ' ') : null,
1515
+ h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `),
1516
+ h(Text, { color: 'gray' }, ' '),
1424
1517
  h(Text, { color: stepDone && !stepFailed ? 'gray' : 'white' }, `${step.index}. ${step.title}`)
1425
1518
  );
1426
1519
  }
@@ -1507,6 +1600,30 @@ function renderTextLine(msg, line, idx, color) {
1507
1600
  );
1508
1601
  }
1509
1602
 
1603
+ function historyListLineColor(line, fallbackColor) {
1604
+ const raw = String(line || '');
1605
+ const trimmed = raw.trim();
1606
+ if (!trimmed) return fallbackColor;
1607
+ if (/^Current session\s+/i.test(trimmed) || /^Recent sessions$/i.test(trimmed) || /^\d+\.\s+\S+/.test(trimmed)) {
1608
+ return 'cyanBright';
1609
+ }
1610
+ if (/^Messages\s+\d+$/i.test(trimmed) || /^\d+\s+msgs?\s+\|\s+updated\b/i.test(trimmed) || /^Tip:/i.test(trimmed)) {
1611
+ return 'gray';
1612
+ }
1613
+ if (/^resume:\s+\/history resume\b/i.test(trimmed)) {
1614
+ return 'blueBright';
1615
+ }
1616
+ return 'white';
1617
+ }
1618
+
1619
+ function isHistoryListMessage(msg) {
1620
+ const text = String(msg?.text || '');
1621
+ return msg?.label === 'system' &&
1622
+ /^Current session\s+/m.test(text) &&
1623
+ /^Recent sessions$/m.test(text) &&
1624
+ /resume:\s+\/history resume\b/m.test(text);
1625
+ }
1626
+
1510
1627
  export function parseAutoPlanSummaryMessage(text) {
1511
1628
  const raw = String(text || '').trim();
1512
1629
  if (!/^Auto plan finished\b/i.test(raw)) return null;
@@ -1951,7 +2068,7 @@ function PlanSummaryBubble({ msg, copy }) {
1951
2068
  Box,
1952
2069
  { marginBottom: planSteps.length > 0 || summary.approval || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1953
2070
  h(Text, { color: 'cyanBright' }, labels.plan),
1954
- h(Text, { color: 'gray' }, summary.planSummary)
2071
+ h(Text, { color: 'white' }, summary.planSummary)
1955
2072
  )
1956
2073
  : null,
1957
2074
  planSteps.length > 0
@@ -1960,12 +2077,20 @@ function PlanSummaryBubble({ msg, copy }) {
1960
2077
  { marginBottom: summary.approval || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1961
2078
  h(Text, { color: 'cyanBright' }, labels.steps),
1962
2079
  ...planSteps.flatMap((step, idx) => {
1963
- const roleTag = String(step?.role || '').trim().toUpperCase() || 'CODER';
2080
+ const roleKey = normalizePlanAgentRole(step?.role);
2081
+ const stepTheme = roleStyle(roleKey);
2082
+ const roleTag = formatPlanAgentLabel(roleKey, copy);
1964
2083
  const titleText = String(step?.title || '-').trim() || '-';
1965
2084
  const taskText = String(step?.task || '').trim();
1966
- const titleRow = h(Text, { key: `plan-step-title-${idx}`, color: 'gray' }, `${idx + 1}. [${roleTag}] ${titleText}`);
2085
+ const titleRow = h(
2086
+ Text,
2087
+ { key: `plan-step-title-${idx}`, color: 'white' },
2088
+ `${idx + 1}. `,
2089
+ h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `),
2090
+ ` ${titleText}`
2091
+ );
1967
2092
  if (!taskText) return [titleRow];
1968
- const taskRow = h(Text, { key: `plan-step-task-${idx}`, color: 'gray' }, ` - task: ${taskText}`);
2093
+ const taskRow = h(Text, { key: `plan-step-task-${idx}`, color: 'white' }, ` - task: ${taskText}`);
1969
2094
  return [titleRow, taskRow];
1970
2095
  })
1971
2096
  )
@@ -1975,7 +2100,7 @@ function PlanSummaryBubble({ msg, copy }) {
1975
2100
  Box,
1976
2101
  { marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1977
2102
  h(Text, { color: 'yellowBright' }, labels.approval),
1978
- h(Text, { color: 'gray' }, summary.approval)
2103
+ h(Text, { color: 'white' }, summary.approval)
1979
2104
  )
1980
2105
  : null,
1981
2106
  metaItems.length > 0
@@ -1984,7 +2109,7 @@ function PlanSummaryBubble({ msg, copy }) {
1984
2109
  { marginBottom: summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0 },
1985
2110
  ...metaItems.flatMap((item, idx) => [
1986
2111
  idx > 0 ? h(Text, { key: `sep-${idx}`, color: 'gray' }, ' ') : null,
1987
- h(Text, { key: `meta-${idx}`, color: 'gray' }, item)
2112
+ h(Text, { key: `meta-${idx}`, color: 'white' }, item)
1988
2113
  ])
1989
2114
  )
1990
2115
  : null,
@@ -1993,7 +2118,7 @@ function PlanSummaryBubble({ msg, copy }) {
1993
2118
  Box,
1994
2119
  { marginBottom: summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1995
2120
  h(Text, { color: 'yellowBright' }, labels.warnings),
1996
- h(Text, { color: 'gray' }, summary.warningSteps)
2121
+ h(Text, { color: 'white' }, summary.warningSteps)
1997
2122
  )
1998
2123
  : null,
1999
2124
  summary.failedSteps
@@ -2001,7 +2126,7 @@ function PlanSummaryBubble({ msg, copy }) {
2001
2126
  Box,
2002
2127
  { marginBottom: shortFile ? 1 : 0, flexDirection: 'column' },
2003
2128
  h(Text, { color: 'redBright' }, labels.failed),
2004
- h(Text, { color: 'gray' }, summary.failedSteps)
2129
+ h(Text, { color: 'white' }, summary.failedSteps)
2005
2130
  )
2006
2131
  : null,
2007
2132
  shortFile
@@ -2297,6 +2422,7 @@ export function collapseActivityChainRows(inputRows, showToolDetails, copy, maxV
2297
2422
 
2298
2423
  export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
2299
2424
  const rows = [];
2425
+ const isHistoryList = isHistoryListMessage(msg);
2300
2426
  const pushTextRows = (text) => {
2301
2427
  const lines = String(text || '').split('\n');
2302
2428
  let codeFence = false;
@@ -2317,8 +2443,8 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2317
2443
  continue;
2318
2444
  }
2319
2445
  if (isMarkdownTableHeader(line, lines[lineIndex + 1])) {
2320
- const tableLines = [line];
2321
- lineIndex += 1; // skip separator
2446
+ const tableLines = [line, lines[lineIndex + 1]];
2447
+ lineIndex += 1; // separator included above
2322
2448
  while (lineIndex + 1 < lines.length && splitMarkdownTableCells(lines[lineIndex + 1]).length > 1) {
2323
2449
  tableLines.push(lines[lineIndex + 1]);
2324
2450
  lineIndex += 1;
@@ -2327,7 +2453,8 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2327
2453
  continue;
2328
2454
  }
2329
2455
  let color = msg.color || roleStyle(msg.label).text || 'white';
2330
- if (line.startsWith('#')) color = 'cyanBright';
2456
+ if (isHistoryList) color = historyListLineColor(line, color);
2457
+ else if (line.startsWith('#')) color = 'cyanBright';
2331
2458
  else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) color = 'magentaBright';
2332
2459
  else if (trimmed.startsWith('>')) color = 'yellow';
2333
2460
  else if (/^[|└├│]/.test(trimmed)) color = 'gray';
@@ -2401,7 +2528,17 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2401
2528
  } else {
2402
2529
  pushTextRows(msg?.text || '');
2403
2530
  const toolCalls = Array.isArray(msg?.toolCalls) ? msg.toolCalls : [];
2404
- toolCalls.forEach((tool, idx) => pushActivityRows(tool, idx, toolCalls.length));
2531
+ const pendingToolCalls = Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : [];
2532
+ const visibleCalls = [
2533
+ ...toolCalls,
2534
+ ...pendingToolCalls.filter((pending) => {
2535
+ if (!pending) return false;
2536
+ if (pending.id && toolCalls.some((tool) => tool?.id && tool.id === pending.id)) return false;
2537
+ const pendingBase = parseToolDisplayName(pending.name).base;
2538
+ return !toolCalls.some((tool) => parseToolDisplayName(tool?.name).base === pendingBase && tool?.status === 'running');
2539
+ })
2540
+ ];
2541
+ visibleCalls.forEach((tool, idx) => pushActivityRows(tool, idx, visibleCalls.length));
2405
2542
  }
2406
2543
 
2407
2544
  const codeGenerationRows = getCodeGenerationActivityRows(msg);
@@ -2493,36 +2630,36 @@ export function renderMessageRow(msg, row, idx, loaderTick) {
2493
2630
  return h(
2494
2631
  Box,
2495
2632
  { key: `row-table-${msg.id}-${idx}`, marginLeft: 1 },
2496
- h(Text, { color: row.isHeader ? 'cyanBright' : 'gray', bold: Boolean(row.isHeader) }, row.text)
2633
+ h(Text, { color: 'white', bold: Boolean(row.isHeader) }, row.text)
2497
2634
  );
2498
2635
  }
2499
2636
  if (row.kind === 'table-separator') {
2500
2637
  return h(
2501
2638
  Box,
2502
2639
  { key: `row-table-sep-${msg.id}-${idx}`, marginLeft: 1 },
2503
- h(Text, { color: 'gray' }, row.text)
2640
+ h(Text, { color: 'white' }, row.text)
2504
2641
  );
2505
2642
  }
2506
2643
  if (row.kind === 'table-vertical') {
2507
2644
  return h(
2508
2645
  Box,
2509
2646
  { key: `row-table-v-${msg.id}-${idx}`, marginLeft: 1 },
2510
- h(Text, { color: 'cyanBright', bold: true }, `${row.label}:`),
2511
- h(Text, { color: 'gray' }, row.text ? ` ${row.text}` : '')
2647
+ h(Text, { color: 'white', bold: true }, `${row.label}:`),
2648
+ h(Text, { color: 'white' }, row.text ? ` ${row.text}` : '')
2512
2649
  );
2513
2650
  }
2514
2651
  if (row.kind === 'table-vertical-continuation') {
2515
2652
  return h(
2516
2653
  Box,
2517
2654
  { key: `row-table-vc-${msg.id}-${idx}`, marginLeft: 3 },
2518
- h(Text, { color: 'gray' }, row.text)
2655
+ h(Text, { color: 'white' }, row.text)
2519
2656
  );
2520
2657
  }
2521
2658
  if (row.kind === 'table-vertical-separator') {
2522
2659
  return h(
2523
2660
  Box,
2524
2661
  { key: `row-table-vs-${msg.id}-${idx}`, marginLeft: 1 },
2525
- h(Text, { color: 'gray' }, row.text)
2662
+ h(Text, { color: 'white' }, row.text)
2526
2663
  );
2527
2664
  }
2528
2665
  if (row.kind === 'activity-collapsed') {
@@ -3128,6 +3265,38 @@ function PlanApprovalPanel({ request, inputValue, errorText, copy, cursorVisible
3128
3265
  );
3129
3266
  }
3130
3267
 
3268
+ function ReflectApprovalPanel({ request, inputValue, errorText, copy, cursorVisible }) {
3269
+ if (!request) return null;
3270
+ const placeholder = String(copy.reflectApproval.answerPlaceholder || '').trim();
3271
+ const lines = formatReflectApprovalLines(copy, request);
3272
+ return h(
3273
+ Box,
3274
+ {
3275
+ marginTop: 1,
3276
+ flexDirection: 'column',
3277
+ borderStyle: 'round',
3278
+ borderColor: 'yellowBright',
3279
+ paddingX: 1,
3280
+ paddingY: 0
3281
+ },
3282
+ ...lines.map((line, index) =>
3283
+ h(Text, { key: `reflect-approval-line-${index}`, color: 'yellowBright' }, line)
3284
+ ),
3285
+ h(
3286
+ Box,
3287
+ { marginTop: 1 },
3288
+ h(Text, { color: 'yellowBright' }, `${copy.reflectApproval.answerLabel}: `),
3289
+ h(ApprovalCursorLine, {
3290
+ inputValue,
3291
+ placeholder: placeholder || ' ',
3292
+ cursorVisible,
3293
+ accent: 'yellowBright'
3294
+ })
3295
+ ),
3296
+ errorText ? h(Text, { color: 'yellowBright' }, errorText) : null
3297
+ );
3298
+ }
3299
+
3131
3300
  function SignatureBar({ version = '' }) {
3132
3301
  return h(
3133
3302
  Box,
@@ -3218,7 +3387,10 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3218
3387
  const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
3219
3388
  const [planApprovalInput, setPlanApprovalInput] = useState('');
3220
3389
  const [planApprovalError, setPlanApprovalError] = useState('');
3221
- const approvalLockActive = Boolean(pendingDeleteApproval || pendingRunApproval || pendingPlanApproval);
3390
+ const [pendingReflectApproval, setPendingReflectApproval] = useState(null);
3391
+ const [reflectApprovalInput, setReflectApprovalInput] = useState('');
3392
+ const [reflectApprovalError, setReflectApprovalError] = useState('');
3393
+ const approvalLockActive = Boolean(pendingDeleteApproval || pendingRunApproval || pendingPlanApproval || pendingReflectApproval);
3222
3394
  const activeAssistantIdRef = useRef(null);
3223
3395
  const activeAssistantAutoSkillNamesRef = useRef([]);
3224
3396
  const streamedAssistantHandledRef = useRef(false);
@@ -3634,6 +3806,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3634
3806
  setPlanState((prev) => ({ ...prev, pendingApproval: true }));
3635
3807
  setPlanApprovalInput('');
3636
3808
  setPlanApprovalError('');
3809
+ } else {
3810
+ const pendingReflectMeta = parsePendingReflectSkillMessage(result.text || '');
3811
+ if (pendingReflectMeta) {
3812
+ setPendingReflectApproval(pendingReflectMeta);
3813
+ setReflectApprovalInput('');
3814
+ setReflectApprovalError('');
3815
+ }
3637
3816
  }
3638
3817
  }
3639
3818
  setMessages((prev) => [
@@ -3776,6 +3955,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3776
3955
  setPendingPlanApproval(null);
3777
3956
  setPlanApprovalInput('');
3778
3957
  setPlanApprovalError('');
3958
+ setPendingReflectApproval(null);
3959
+ setReflectApprovalInput('');
3960
+ setReflectApprovalError('');
3779
3961
  setPlanState({
3780
3962
  current: 0,
3781
3963
  total: 0,
@@ -4463,6 +4645,46 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4463
4645
  return;
4464
4646
  }
4465
4647
 
4648
+ if (pendingReflectApproval) {
4649
+ if (key.return) {
4650
+ const parsed = parseReflectApprovalAnswer(reflectApprovalInput);
4651
+ if (parsed.action === 'approve' || parsed.action === 'reject' || parsed.action === 'edit') {
4652
+ setPendingReflectApproval(null);
4653
+ setReflectApprovalInput('');
4654
+ setReflectApprovalError('');
4655
+ runSubmission(parsed.command);
4656
+ } else if (parsed.action === 'missing_feedback') {
4657
+ setReflectApprovalError(copy.reflectApproval.missingFeedback);
4658
+ } else {
4659
+ setReflectApprovalError(copy.reflectApproval.invalidAnswer);
4660
+ }
4661
+ return;
4662
+ }
4663
+
4664
+ if (isBackspaceKey(value, key) || isDeleteKey(value, key)) {
4665
+ setReflectApprovalInput((prev) => prev.slice(0, -1));
4666
+ setReflectApprovalError('');
4667
+ return;
4668
+ }
4669
+
4670
+ if (isPrintableInput(value, key)) {
4671
+ setReflectApprovalInput((prev) => `${prev}${value}`);
4672
+ setReflectApprovalError('');
4673
+ return;
4674
+ }
4675
+
4676
+ if (key.ctrl && value === 'c') {
4677
+ if (busy && typeof runtime.abort === 'function') {
4678
+ runtime.abort();
4679
+ return;
4680
+ }
4681
+ exit();
4682
+ return;
4683
+ }
4684
+
4685
+ return;
4686
+ }
4687
+
4466
4688
  if (key.upArrow) {
4467
4689
  if (suggestionNav && commandSuggestions.length > 0) {
4468
4690
  setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'up'));
@@ -4732,6 +4954,17 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4732
4954
  }
4733
4955
  }, [runtimeState?.pendingPlanApproval, busy]);
4734
4956
 
4957
+ useEffect(() => {
4958
+ const pending = Boolean(runtimeState?.pendingReflectSkill);
4959
+ if (!pending) {
4960
+ setPendingReflectApproval(null);
4961
+ return;
4962
+ }
4963
+ if (!busy) {
4964
+ setPendingReflectApproval((prev) => prev || { scope: '', name: '', targetPath: '' });
4965
+ }
4966
+ }, [runtimeState?.pendingReflectSkill, busy]);
4967
+
4735
4968
  useEffect(() => {
4736
4969
  if (commandSuggestions.length === 0) {
4737
4970
  setSuggestionNav(false);
@@ -4778,7 +5011,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4778
5011
  ? { text: copy.runApproval.inputLocked }
4779
5012
  : pendingPlanApproval
4780
5013
  ? { text: copy.planApproval.inputLocked }
4781
- : null;
5014
+ : pendingReflectApproval
5015
+ ? { text: copy.reflectApproval.inputLocked }
5016
+ : null;
4782
5017
 
4783
5018
  return h(
4784
5019
  Box,
@@ -4828,6 +5063,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4828
5063
  copy,
4829
5064
  cursorVisible
4830
5065
  }),
5066
+ h(ReflectApprovalPanel, {
5067
+ request: pendingReflectApproval,
5068
+ inputValue: reflectApprovalInput,
5069
+ errorText: reflectApprovalError,
5070
+ copy,
5071
+ cursorVisible
5072
+ }),
4831
5073
  debugKeys
4832
5074
  ? h(
4833
5075
  Box,
@@ -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
  }