codemini-cli 0.4.1 → 0.4.3

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.
@@ -45,6 +45,22 @@ const ROLE_STYLES = {
45
45
  badgeText: 'black',
46
46
  chrome: 'gray'
47
47
  },
48
+ general: {
49
+ accent: 'greenBright',
50
+ border: 'green',
51
+ text: 'greenBright',
52
+ badgeBg: 'green',
53
+ badgeText: 'black',
54
+ chrome: 'gray'
55
+ },
56
+ advisor: {
57
+ accent: 'blueBright',
58
+ border: 'blue',
59
+ text: 'blueBright',
60
+ badgeBg: 'blue',
61
+ badgeText: 'white',
62
+ chrome: 'gray'
63
+ },
48
64
  planner: {
49
65
  accent: 'magentaBright',
50
66
  border: 'magenta',
@@ -105,7 +121,7 @@ const ROLE_STYLES = {
105
121
 
106
122
  const TUI_COPY = {
107
123
  zh: {
108
- roleLabels: { you: '👤 你', coder: '💻 CODER', planner: '📋 PLANNER', reviewer: '🔍 REVIEWER', tester: '🧪 TESTER', summarizer: '📝 SUMMARIZER', system: '⚙️ 系统', error: '❌ 错误', pending: '⏳ 等待中' },
124
+ roleLabels: { you: '👤 你', general: 'GENERAL', advisor: '💡 ADVISOR', coder: '💻 CODER', planner: '📋 PLANNER', reviewer: '🔍 REVIEWER', tester: '🧪 TESTER', summarizer: '📝 SUMMARIZER', system: '⚙️ 系统', error: '❌ 错误', pending: '⏳ 等待中' },
109
125
  generic: {
110
126
  waitingForInput: '等待输入',
111
127
  ready: '就绪',
@@ -142,6 +158,7 @@ const TUI_COPY = {
142
158
  '🧩 用 /mode plan 切换到规划模式,让 AI 先出方案再动手。',
143
159
  '🆕 /new 可以新建一个干净的会话,重新开始工作。',
144
160
  '🧠 /memory 查看和管理 AI 的持久记忆,帮助它更好地理解你的偏好。',
161
+ '🌐 web_fetch 默认轻量读取网页;如需更好读取 JS 渲染页面,可运行 npm install -g playwright && playwright install chromium。',
145
162
  '💤 CodeMini 会自动"做梦"休息,整理错误信息并自我优化,越用越聪明~'
146
163
  ],
147
164
  toolSummaryExpanded: '工具摘要:已展开',
@@ -218,6 +235,8 @@ const TUI_COPY = {
218
235
  doingProjectIndex: '正在初始化项目索引',
219
236
  doneFileIndex: '已刷新文件索引',
220
237
  doingFileIndex: '正在刷新文件索引',
238
+ donePromptBudget: '已测量 Prompt 预算',
239
+ doingPromptBudget: '正在测量 Prompt 预算',
221
240
  toolFailed: (name) => `工具执行失败: ${name}`,
222
241
  waitingModelContinue: (detail) => `${detail},等待模型继续`,
223
242
  waitingModelAdjust: (detail) => `${detail},等待模型调整`
@@ -314,10 +333,22 @@ const TUI_COPY = {
314
333
  inputLocked: '计划审批进行中,请在审批框输入 /yes、/edit 或 /reject',
315
334
  answerLabel: '审批输入',
316
335
  answerPlaceholder: '/yes | /edit <反馈> | /reject'
336
+ },
337
+ reflectApproval: {
338
+ title: '审阅 Reflect 技能草稿?',
339
+ scopeLabel: '范围',
340
+ nameLabel: '名称',
341
+ targetLabel: '目标',
342
+ prompt: '输入 /yes 写入,输入 /edit <反馈> 修改,输入 /no 丢弃。',
343
+ invalidAnswer: '请输入 /yes、/edit <反馈> 或 /no。',
344
+ missingFeedback: '请在 /edit 后提供反馈内容。',
345
+ inputLocked: 'Reflect 审阅进行中,请在审阅框输入 /yes、/edit 或 /no',
346
+ answerLabel: '审阅输入',
347
+ answerPlaceholder: '/yes | /edit <反馈> | /no'
317
348
  }
318
349
  },
319
350
  en: {
320
- roleLabels: { you: 'YOU', coder: 'CODER', planner: 'PLANNER', reviewer: 'REVIEWER', tester: 'TESTER', summarizer: 'SUMMARIZER', system: 'SYSTEM', error: 'ERROR', pending: 'PENDING' },
351
+ roleLabels: { you: 'YOU', general: 'GENERAL', advisor: 'ADVISOR', coder: 'CODER', planner: 'PLANNER', reviewer: 'REVIEWER', tester: 'TESTER', summarizer: 'SUMMARIZER', system: 'SYSTEM', error: 'ERROR', pending: 'PENDING' },
321
352
  generic: {
322
353
  waitingForInput: 'waiting for input',
323
354
  ready: 'ready',
@@ -354,6 +385,7 @@ const TUI_COPY = {
354
385
  '🧩 Use /mode plan to switch to planning mode — AI proposes a plan before coding.',
355
386
  '🆕 /new starts a fresh session to begin a clean slate.',
356
387
  '🧠 /memory lets you view and manage the AI\'s persistent memory for better personalization.',
388
+ '🌐 web_fetch uses a lightweight reader by default. For better JS-rendered pages: npm install -g playwright && playwright install chromium.',
357
389
  '💤 CodeMini auto-"dreams" to rest, consolidate errors, and self-optimize — it gets smarter over time~'
358
390
  ],
359
391
  toolSummaryExpanded: 'Tool summary: expanded',
@@ -430,6 +462,8 @@ const TUI_COPY = {
430
462
  doingProjectIndex: 'Initializing project index',
431
463
  doneFileIndex: 'File index refreshed',
432
464
  doingFileIndex: 'Refreshing file index',
465
+ donePromptBudget: 'Prompt budget measured',
466
+ doingPromptBudget: 'Measuring prompt budget',
433
467
  toolFailed: (name) => `Tool failed: ${name}`,
434
468
  waitingModelContinue: (detail) => `${detail}, waiting for model to continue`,
435
469
  waitingModelAdjust: (detail) => `${detail}, waiting for model to adjust`
@@ -526,6 +560,18 @@ const TUI_COPY = {
526
560
  inputLocked: 'Plan approval is active; type /yes, /edit <feedback>, or /reject',
527
561
  answerLabel: 'Approval input',
528
562
  answerPlaceholder: '/yes | /edit <feedback> | /reject'
563
+ },
564
+ reflectApproval: {
565
+ title: 'Review this reflected skill draft?',
566
+ scopeLabel: 'Scope',
567
+ nameLabel: 'Name',
568
+ targetLabel: 'Target',
569
+ prompt: 'Type /yes to write, /edit <feedback> to revise, or /no to discard.',
570
+ invalidAnswer: 'Please enter /yes, /edit <feedback>, or /no.',
571
+ missingFeedback: 'Please provide feedback after /edit.',
572
+ inputLocked: 'Reflect review is active; type /yes, /edit <feedback>, or /no',
573
+ answerLabel: 'Review input',
574
+ answerPlaceholder: '/yes | /edit <feedback> | /no'
529
575
  }
530
576
  }
531
577
  };
@@ -539,13 +585,24 @@ function getCopy(language) {
539
585
  }
540
586
 
541
587
  function messageLabel(label, copy) {
542
- return copy.roleLabels[label] || String(label || '').toUpperCase();
588
+ return copy?.roleLabels?.[label] || String(label || '').toUpperCase();
543
589
  }
544
590
 
545
591
  function roleStyle(label) {
546
592
  return ROLE_STYLES[label] || ROLE_STYLES.system;
547
593
  }
548
594
 
595
+ const PLAN_AGENT_ROLES = new Set(['planner', 'advisor', 'coder', 'reviewer', 'tester', 'summarizer']);
596
+
597
+ function normalizePlanAgentRole(role) {
598
+ const roleKey = String(role || '').trim().toLowerCase();
599
+ return PLAN_AGENT_ROLES.has(roleKey) ? roleKey : 'coder';
600
+ }
601
+
602
+ export function formatPlanAgentLabel(role, copy) {
603
+ return messageLabel(normalizePlanAgentRole(role), copy);
604
+ }
605
+
549
606
  function StatusPill({ label, value, color = 'cyanBright', textColor = 'black' }) {
550
607
  return h(
551
608
  Box,
@@ -892,7 +949,7 @@ function textFromSessionContent(content) {
892
949
  return sanitizeRenderableText(String(content || ''));
893
950
  }
894
951
 
895
- function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
952
+ export function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
896
953
  const source = Array.isArray(sessionMessages) ? sessionMessages : [];
897
954
  const out = [];
898
955
 
@@ -901,14 +958,14 @@ function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
901
958
  if (message.role === 'tool') continue;
902
959
 
903
960
  const text = textFromSessionContent(message.content);
904
- if (!text.trim() && message.role !== 'assistant') continue;
961
+ if (!text.trim()) continue;
905
962
 
906
963
  if (message.role === 'user') {
907
964
  out.push({ id: nextId(), label: 'you', text, color: 'blueBright' });
908
965
  continue;
909
966
  }
910
967
  if (message.role === 'assistant') {
911
- out.push({ id: nextId(), label: 'coder', text, color: 'greenBright' });
968
+ out.push({ id: nextId(), label: 'general', text, color: 'greenBright' });
912
969
  continue;
913
970
  }
914
971
  if (message.role === 'system') {
@@ -1109,6 +1166,25 @@ export function parsePlanApprovalAnswer(value) {
1109
1166
  return { action: 'invalid', command: '' };
1110
1167
  }
1111
1168
 
1169
+ export function parseReflectApprovalAnswer(value) {
1170
+ const raw = String(value || '').trim();
1171
+ if (!raw) return { action: 'empty', command: '' };
1172
+ const normalized = raw.toLowerCase();
1173
+ if (normalized === '/yes' || normalized === 'yes') {
1174
+ return { action: 'approve', command: '/yes' };
1175
+ }
1176
+ if (normalized === '/no' || normalized === 'no') {
1177
+ return { action: 'reject', command: '/no' };
1178
+ }
1179
+ const editMatch = raw.match(/^\/?edit(?:\s+(.+))?$/i);
1180
+ if (editMatch) {
1181
+ const feedback = String(editMatch[1] || '').trim();
1182
+ if (!feedback) return { action: 'missing_feedback', command: '' };
1183
+ return { action: 'edit', feedback, command: `/edit ${feedback}` };
1184
+ }
1185
+ return { action: 'invalid', command: '' };
1186
+ }
1187
+
1112
1188
  export function parsePendingPlanApprovalMessage(text = '') {
1113
1189
  const raw = String(text || '');
1114
1190
  if (!/^Plan approval is still pending\./i.test(raw.trim())) return null;
@@ -1122,6 +1198,21 @@ export function parsePendingPlanApprovalMessage(text = '') {
1122
1198
  return out;
1123
1199
  }
1124
1200
 
1201
+ export function parsePendingReflectSkillMessage(text = '') {
1202
+ const raw = String(text || '');
1203
+ if (!/\bReflect skill draft pending\./i.test(raw)) return null;
1204
+ const lines = raw.split(/\r?\n/);
1205
+ const out = { scope: '', name: '', confidence: '', targetPath: '' };
1206
+ for (const line of lines) {
1207
+ const trimmed = line.trim();
1208
+ if (trimmed.startsWith('Scope: ')) out.scope = trimmed.slice('Scope: '.length).trim();
1209
+ else if (/^\[\d+\]\s+/.test(trimmed) && !out.name) out.name = trimmed.replace(/^\[\d+\]\s+/, '').trim();
1210
+ else if (trimmed.startsWith('Confidence: ')) out.confidence = trimmed.slice('Confidence: '.length).trim();
1211
+ else if (trimmed.startsWith('Target: ')) out.targetPath = trimmed.slice('Target: '.length).trim();
1212
+ }
1213
+ return out;
1214
+ }
1215
+
1125
1216
  export function formatDeleteApprovalLines(copy, request) {
1126
1217
  const details = normalizeDeleteApprovalRequest(request);
1127
1218
  if (!details) return [];
@@ -1141,6 +1232,17 @@ export function formatPlanApprovalLines(copy, request) {
1141
1232
  return [String(copy?.planApproval?.title || '').trim()].filter(Boolean);
1142
1233
  }
1143
1234
 
1235
+ export function formatReflectApprovalLines(copy, request) {
1236
+ if (!request) return [];
1237
+ const c = copy?.reflectApproval || {};
1238
+ const lines = [String(c.title || '').trim()];
1239
+ if (request.scope) lines.push(`${c.scopeLabel || 'Scope'}: ${request.scope}`);
1240
+ if (request.name) lines.push(`${c.nameLabel || 'Name'}: ${request.name}`);
1241
+ if (request.targetPath) lines.push(`${c.targetLabel || 'Target'}: ${request.targetPath}`);
1242
+ if (c.prompt) lines.push(c.prompt);
1243
+ return lines.filter(Boolean);
1244
+ }
1245
+
1144
1246
  function getActivityDisplayParts(activity) {
1145
1247
  if (isCodeGenerationActivityName(activity?.name)) {
1146
1248
  return {
@@ -1243,7 +1345,7 @@ export function isIndexSystemToolName(name) {
1243
1345
  export function shouldShowCompletionFooter(msg) {
1244
1346
  if (!msg || msg.loading || (msg.phase || '').trim()) return false;
1245
1347
  const label = (msg.label || '').toLowerCase();
1246
- return label === 'coder' || label === 'planner' || label === 'reviewer' || label === 'tester';
1348
+ return label === 'general' || label === 'advisor' || label === 'coder' || label === 'planner' || label === 'reviewer' || label === 'tester';
1247
1349
  }
1248
1350
 
1249
1351
  function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
@@ -1419,10 +1521,9 @@ function PlanStrip({ planState, copy }) {
1419
1521
  Box,
1420
1522
  { flexDirection: 'column' },
1421
1523
  ...planState.steps.map((step, idx) => {
1422
- const roleKey = step.role ? step.role.toLowerCase() : '';
1423
- const normalizedRole = ['planner', 'coder', 'reviewer', 'tester', 'summarizer'].includes(roleKey) ? roleKey : 'coder';
1524
+ const normalizedRole = normalizePlanAgentRole(step.role);
1424
1525
  const stepTheme = roleStyle(normalizedRole);
1425
- const roleTag = step.role ? step.role.toUpperCase() : '';
1526
+ const roleTag = formatPlanAgentLabel(normalizedRole, copy);
1426
1527
  const stepDone = step.status === 'done' || isDone;
1427
1528
  const stepFailed = step.status === 'failed';
1428
1529
  const marker = stepFailed ? '✗' : stepDone ? '✓' : '·';
@@ -1431,8 +1532,8 @@ function PlanStrip({ planState, copy }) {
1431
1532
  Box,
1432
1533
  { key: `plan-step-${idx}` },
1433
1534
  h(Text, { color: markerColor }, `${marker} `),
1434
- roleTag ? h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `) : null,
1435
- roleTag ? h(Text, { color: 'gray' }, ' ') : null,
1535
+ h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `),
1536
+ h(Text, { color: 'gray' }, ' '),
1436
1537
  h(Text, { color: stepDone && !stepFailed ? 'gray' : 'white' }, `${step.index}. ${step.title}`)
1437
1538
  );
1438
1539
  }
@@ -1519,6 +1620,30 @@ function renderTextLine(msg, line, idx, color) {
1519
1620
  );
1520
1621
  }
1521
1622
 
1623
+ function historyListLineColor(line, fallbackColor) {
1624
+ const raw = String(line || '');
1625
+ const trimmed = raw.trim();
1626
+ if (!trimmed) return fallbackColor;
1627
+ if (/^Current session\s+/i.test(trimmed) || /^Recent sessions$/i.test(trimmed) || /^\d+\.\s+\S+/.test(trimmed)) {
1628
+ return 'cyanBright';
1629
+ }
1630
+ if (/^Messages\s+\d+$/i.test(trimmed) || /^\d+\s+msgs?\s+\|\s+updated\b/i.test(trimmed) || /^Tip:/i.test(trimmed)) {
1631
+ return 'gray';
1632
+ }
1633
+ if (/^resume:\s+\/history resume\b/i.test(trimmed)) {
1634
+ return 'blueBright';
1635
+ }
1636
+ return 'white';
1637
+ }
1638
+
1639
+ function isHistoryListMessage(msg) {
1640
+ const text = String(msg?.text || '');
1641
+ return msg?.label === 'system' &&
1642
+ /^Current session\s+/m.test(text) &&
1643
+ /^Recent sessions$/m.test(text) &&
1644
+ /resume:\s+\/history resume\b/m.test(text);
1645
+ }
1646
+
1522
1647
  export function parseAutoPlanSummaryMessage(text) {
1523
1648
  const raw = String(text || '').trim();
1524
1649
  if (!/^Auto plan finished\b/i.test(raw)) return null;
@@ -1963,7 +2088,7 @@ function PlanSummaryBubble({ msg, copy }) {
1963
2088
  Box,
1964
2089
  { marginBottom: planSteps.length > 0 || summary.approval || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1965
2090
  h(Text, { color: 'cyanBright' }, labels.plan),
1966
- h(Text, { color: 'gray' }, summary.planSummary)
2091
+ h(Text, { color: 'white' }, summary.planSummary)
1967
2092
  )
1968
2093
  : null,
1969
2094
  planSteps.length > 0
@@ -1972,12 +2097,20 @@ function PlanSummaryBubble({ msg, copy }) {
1972
2097
  { marginBottom: summary.approval || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1973
2098
  h(Text, { color: 'cyanBright' }, labels.steps),
1974
2099
  ...planSteps.flatMap((step, idx) => {
1975
- const roleTag = String(step?.role || '').trim().toUpperCase() || 'CODER';
2100
+ const roleKey = normalizePlanAgentRole(step?.role);
2101
+ const stepTheme = roleStyle(roleKey);
2102
+ const roleTag = formatPlanAgentLabel(roleKey, copy);
1976
2103
  const titleText = String(step?.title || '-').trim() || '-';
1977
2104
  const taskText = String(step?.task || '').trim();
1978
- const titleRow = h(Text, { key: `plan-step-title-${idx}`, color: 'gray' }, `${idx + 1}. [${roleTag}] ${titleText}`);
2105
+ const titleRow = h(
2106
+ Text,
2107
+ { key: `plan-step-title-${idx}`, color: 'white' },
2108
+ `${idx + 1}. `,
2109
+ h(Text, { color: stepTheme.badgeText, backgroundColor: stepTheme.badgeBg }, ` ${roleTag} `),
2110
+ ` ${titleText}`
2111
+ );
1979
2112
  if (!taskText) return [titleRow];
1980
- const taskRow = h(Text, { key: `plan-step-task-${idx}`, color: 'gray' }, ` - task: ${taskText}`);
2113
+ const taskRow = h(Text, { key: `plan-step-task-${idx}`, color: 'white' }, ` - task: ${taskText}`);
1981
2114
  return [titleRow, taskRow];
1982
2115
  })
1983
2116
  )
@@ -1987,7 +2120,7 @@ function PlanSummaryBubble({ msg, copy }) {
1987
2120
  Box,
1988
2121
  { marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1989
2122
  h(Text, { color: 'yellowBright' }, labels.approval),
1990
- h(Text, { color: 'gray' }, summary.approval)
2123
+ h(Text, { color: 'white' }, summary.approval)
1991
2124
  )
1992
2125
  : null,
1993
2126
  metaItems.length > 0
@@ -1996,7 +2129,7 @@ function PlanSummaryBubble({ msg, copy }) {
1996
2129
  { marginBottom: summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0 },
1997
2130
  ...metaItems.flatMap((item, idx) => [
1998
2131
  idx > 0 ? h(Text, { key: `sep-${idx}`, color: 'gray' }, ' ') : null,
1999
- h(Text, { key: `meta-${idx}`, color: 'gray' }, item)
2132
+ h(Text, { key: `meta-${idx}`, color: 'white' }, item)
2000
2133
  ])
2001
2134
  )
2002
2135
  : null,
@@ -2005,7 +2138,7 @@ function PlanSummaryBubble({ msg, copy }) {
2005
2138
  Box,
2006
2139
  { marginBottom: summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
2007
2140
  h(Text, { color: 'yellowBright' }, labels.warnings),
2008
- h(Text, { color: 'gray' }, summary.warningSteps)
2141
+ h(Text, { color: 'white' }, summary.warningSteps)
2009
2142
  )
2010
2143
  : null,
2011
2144
  summary.failedSteps
@@ -2013,7 +2146,7 @@ function PlanSummaryBubble({ msg, copy }) {
2013
2146
  Box,
2014
2147
  { marginBottom: shortFile ? 1 : 0, flexDirection: 'column' },
2015
2148
  h(Text, { color: 'redBright' }, labels.failed),
2016
- h(Text, { color: 'gray' }, summary.failedSteps)
2149
+ h(Text, { color: 'white' }, summary.failedSteps)
2017
2150
  )
2018
2151
  : null,
2019
2152
  shortFile
@@ -2309,6 +2442,7 @@ export function collapseActivityChainRows(inputRows, showToolDetails, copy, maxV
2309
2442
 
2310
2443
  export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
2311
2444
  const rows = [];
2445
+ const isHistoryList = isHistoryListMessage(msg);
2312
2446
  const pushTextRows = (text) => {
2313
2447
  const lines = String(text || '').split('\n');
2314
2448
  let codeFence = false;
@@ -2329,8 +2463,8 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2329
2463
  continue;
2330
2464
  }
2331
2465
  if (isMarkdownTableHeader(line, lines[lineIndex + 1])) {
2332
- const tableLines = [line];
2333
- lineIndex += 1; // skip separator
2466
+ const tableLines = [line, lines[lineIndex + 1]];
2467
+ lineIndex += 1; // separator included above
2334
2468
  while (lineIndex + 1 < lines.length && splitMarkdownTableCells(lines[lineIndex + 1]).length > 1) {
2335
2469
  tableLines.push(lines[lineIndex + 1]);
2336
2470
  lineIndex += 1;
@@ -2339,7 +2473,8 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2339
2473
  continue;
2340
2474
  }
2341
2475
  let color = msg.color || roleStyle(msg.label).text || 'white';
2342
- if (line.startsWith('#')) color = 'cyanBright';
2476
+ if (isHistoryList) color = historyListLineColor(line, color);
2477
+ else if (line.startsWith('#')) color = 'cyanBright';
2343
2478
  else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) color = 'magentaBright';
2344
2479
  else if (trimmed.startsWith('>')) color = 'yellow';
2345
2480
  else if (/^[|└├│]/.test(trimmed)) color = 'gray';
@@ -2391,13 +2526,27 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2391
2526
  }
2392
2527
  };
2393
2528
 
2529
+ const visiblePendingToolCalls = (existingCalls = []) => {
2530
+ const pendingToolCalls = Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : [];
2531
+ return pendingToolCalls.filter((pending) => {
2532
+ if (!pending) return false;
2533
+ if (pending.id && existingCalls.some((tool) => tool?.id && tool.id === pending.id)) return false;
2534
+ const pendingBase = parseToolDisplayName(pending.name).base;
2535
+ return !existingCalls.some(
2536
+ (tool) => parseToolDisplayName(tool?.name).base === pendingBase && tool?.status === 'running'
2537
+ );
2538
+ });
2539
+ };
2540
+
2394
2541
  if (Array.isArray(msg?.segments) && msg.segments.length > 0) {
2395
- const totalTools = msg.segments.filter(
2542
+ const segmentTools = msg.segments.filter(
2396
2543
  (segment) =>
2397
2544
  segment.type === 'tool' ||
2398
2545
  segment.type === 'skill' ||
2399
2546
  (segment.type === 'system_tool' && (showToolDetails || !isIndexSystemToolName(segment.name)))
2400
- ).length;
2547
+ );
2548
+ const pendingToolCalls = visiblePendingToolCalls(segmentTools);
2549
+ const totalTools = segmentTools.length + pendingToolCalls.length;
2401
2550
  let toolIndex = 0;
2402
2551
  for (const segment of msg.segments) {
2403
2552
  if (segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool') {
@@ -2410,19 +2559,14 @@ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy)
2410
2559
  pushTextRows(segment.text || '');
2411
2560
  }
2412
2561
  }
2562
+ pendingToolCalls.forEach((tool) => {
2563
+ pushActivityRows(tool, toolIndex, totalTools);
2564
+ toolIndex += 1;
2565
+ });
2413
2566
  } else {
2414
2567
  pushTextRows(msg?.text || '');
2415
2568
  const toolCalls = Array.isArray(msg?.toolCalls) ? msg.toolCalls : [];
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
- ];
2569
+ const visibleCalls = [...toolCalls, ...visiblePendingToolCalls(toolCalls)];
2426
2570
  visibleCalls.forEach((tool, idx) => pushActivityRows(tool, idx, visibleCalls.length));
2427
2571
  }
2428
2572
 
@@ -2515,36 +2659,36 @@ export function renderMessageRow(msg, row, idx, loaderTick) {
2515
2659
  return h(
2516
2660
  Box,
2517
2661
  { key: `row-table-${msg.id}-${idx}`, marginLeft: 1 },
2518
- h(Text, { color: row.isHeader ? 'cyanBright' : 'gray', bold: Boolean(row.isHeader) }, row.text)
2662
+ h(Text, { color: 'white', bold: Boolean(row.isHeader) }, row.text)
2519
2663
  );
2520
2664
  }
2521
2665
  if (row.kind === 'table-separator') {
2522
2666
  return h(
2523
2667
  Box,
2524
2668
  { key: `row-table-sep-${msg.id}-${idx}`, marginLeft: 1 },
2525
- h(Text, { color: 'gray' }, row.text)
2669
+ h(Text, { color: 'white' }, row.text)
2526
2670
  );
2527
2671
  }
2528
2672
  if (row.kind === 'table-vertical') {
2529
2673
  return h(
2530
2674
  Box,
2531
2675
  { key: `row-table-v-${msg.id}-${idx}`, marginLeft: 1 },
2532
- h(Text, { color: 'cyanBright', bold: true }, `${row.label}:`),
2533
- h(Text, { color: 'gray' }, row.text ? ` ${row.text}` : '')
2676
+ h(Text, { color: 'white', bold: true }, `${row.label}:`),
2677
+ h(Text, { color: 'white' }, row.text ? ` ${row.text}` : '')
2534
2678
  );
2535
2679
  }
2536
2680
  if (row.kind === 'table-vertical-continuation') {
2537
2681
  return h(
2538
2682
  Box,
2539
2683
  { key: `row-table-vc-${msg.id}-${idx}`, marginLeft: 3 },
2540
- h(Text, { color: 'gray' }, row.text)
2684
+ h(Text, { color: 'white' }, row.text)
2541
2685
  );
2542
2686
  }
2543
2687
  if (row.kind === 'table-vertical-separator') {
2544
2688
  return h(
2545
2689
  Box,
2546
2690
  { key: `row-table-vs-${msg.id}-${idx}`, marginLeft: 1 },
2547
- h(Text, { color: 'gray' }, row.text)
2691
+ h(Text, { color: 'white' }, row.text)
2548
2692
  );
2549
2693
  }
2550
2694
  if (row.kind === 'activity-collapsed') {
@@ -3150,6 +3294,38 @@ function PlanApprovalPanel({ request, inputValue, errorText, copy, cursorVisible
3150
3294
  );
3151
3295
  }
3152
3296
 
3297
+ function ReflectApprovalPanel({ request, inputValue, errorText, copy, cursorVisible }) {
3298
+ if (!request) return null;
3299
+ const placeholder = String(copy.reflectApproval.answerPlaceholder || '').trim();
3300
+ const lines = formatReflectApprovalLines(copy, request);
3301
+ return h(
3302
+ Box,
3303
+ {
3304
+ marginTop: 1,
3305
+ flexDirection: 'column',
3306
+ borderStyle: 'round',
3307
+ borderColor: 'yellowBright',
3308
+ paddingX: 1,
3309
+ paddingY: 0
3310
+ },
3311
+ ...lines.map((line, index) =>
3312
+ h(Text, { key: `reflect-approval-line-${index}`, color: 'yellowBright' }, line)
3313
+ ),
3314
+ h(
3315
+ Box,
3316
+ { marginTop: 1 },
3317
+ h(Text, { color: 'yellowBright' }, `${copy.reflectApproval.answerLabel}: `),
3318
+ h(ApprovalCursorLine, {
3319
+ inputValue,
3320
+ placeholder: placeholder || ' ',
3321
+ cursorVisible,
3322
+ accent: 'yellowBright'
3323
+ })
3324
+ ),
3325
+ errorText ? h(Text, { color: 'yellowBright' }, errorText) : null
3326
+ );
3327
+ }
3328
+
3153
3329
  function SignatureBar({ version = '' }) {
3154
3330
  return h(
3155
3331
  Box,
@@ -3177,6 +3353,7 @@ function formatRuntimeSnapshot(snapshot) {
3177
3353
  if (!snapshot || typeof snapshot !== 'object') return '';
3178
3354
  return [
3179
3355
  `mode=${snapshot.mode || '-'}`,
3356
+ `role=${snapshot.agentRole || 'general'}`,
3180
3357
  `model=${snapshot.model || '-'}`,
3181
3358
  `max_ctx=${snapshot.maxContextTokens || '-'}`,
3182
3359
  `session=${snapshot.sessionId || '-'}`
@@ -3240,7 +3417,10 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3240
3417
  const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
3241
3418
  const [planApprovalInput, setPlanApprovalInput] = useState('');
3242
3419
  const [planApprovalError, setPlanApprovalError] = useState('');
3243
- const approvalLockActive = Boolean(pendingDeleteApproval || pendingRunApproval || pendingPlanApproval);
3420
+ const [pendingReflectApproval, setPendingReflectApproval] = useState(null);
3421
+ const [reflectApprovalInput, setReflectApprovalInput] = useState('');
3422
+ const [reflectApprovalError, setReflectApprovalError] = useState('');
3423
+ const approvalLockActive = Boolean(pendingDeleteApproval || pendingRunApproval || pendingPlanApproval || pendingReflectApproval);
3244
3424
  const activeAssistantIdRef = useRef(null);
3245
3425
  const activeAssistantAutoSkillNamesRef = useRef([]);
3246
3426
  const streamedAssistantHandledRef = useRef(false);
@@ -3403,7 +3583,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3403
3583
  const current = Number(last[1]);
3404
3584
  const total = Number(last[2]);
3405
3585
  const role = String(last[3] || '').trim().toLowerCase();
3406
- const normalizedRole = ['planner', 'coder', 'reviewer', 'tester', 'summarizer'].includes(role) ? role : 'coder';
3586
+ const normalizedRole = PLAN_AGENT_ROLES.has(role) ? role : 'coder';
3407
3587
  const title = String(last[4] || '').trim();
3408
3588
 
3409
3589
  // Detect step transition — finalize old assistant and create a new one
@@ -3611,7 +3791,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3611
3791
  ...prev,
3612
3792
  {
3613
3793
  id: nextId(),
3614
- label: 'coder',
3794
+ label: 'general',
3615
3795
  text: sanitizeRenderableText(displayText),
3616
3796
  color: 'greenBright',
3617
3797
  autoSkillNames: activeAssistantAutoSkillNamesRef.current
@@ -3656,6 +3836,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3656
3836
  setPlanState((prev) => ({ ...prev, pendingApproval: true }));
3657
3837
  setPlanApprovalInput('');
3658
3838
  setPlanApprovalError('');
3839
+ } else {
3840
+ const pendingReflectMeta = parsePendingReflectSkillMessage(result.text || '');
3841
+ if (pendingReflectMeta) {
3842
+ setPendingReflectApproval(pendingReflectMeta);
3843
+ setReflectApprovalInput('');
3844
+ setReflectApprovalError('');
3845
+ }
3659
3846
  }
3660
3847
  }
3661
3848
  setMessages((prev) => [
@@ -3744,8 +3931,8 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3744
3931
  const aid = nextId();
3745
3932
  activeAssistantIdRef.current = aid;
3746
3933
  const planRole = activePlanStepRoleRef.current;
3747
- const label = planRole || 'coder';
3748
- const style = ROLE_STYLES[label] || ROLE_STYLES.coder;
3934
+ const label = planRole || 'general';
3935
+ const style = ROLE_STYLES[label] || ROLE_STYLES.general;
3749
3936
  const planStepInfo = activePlanStepInfoRef.current;
3750
3937
  const planStepTitle = activePlanStepTitleRef.current;
3751
3938
  const planStepDisplay = planStepInfo ? `${planStepInfo.current}/${planStepInfo.total} · ${planStepTitle}` : undefined;
@@ -3798,6 +3985,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3798
3985
  setPendingPlanApproval(null);
3799
3986
  setPlanApprovalInput('');
3800
3987
  setPlanApprovalError('');
3988
+ setPendingReflectApproval(null);
3989
+ setReflectApprovalInput('');
3990
+ setReflectApprovalError('');
3801
3991
  setPlanState({
3802
3992
  current: 0,
3803
3993
  total: 0,
@@ -3930,7 +4120,7 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3930
4120
  const cleanedStandaloneText = stripPlanExecutionResult(String(displayText || event.text)).trim();
3931
4121
  setMessages((prev) => [
3932
4122
  ...prev,
3933
- { id: nextId(), label: 'coder', text: cleanedStandaloneText, color: 'greenBright' }
4123
+ { id: nextId(), label: 'general', text: cleanedStandaloneText, color: 'greenBright' }
3934
4124
  ]);
3935
4125
  }
3936
4126
  }
@@ -4485,6 +4675,46 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4485
4675
  return;
4486
4676
  }
4487
4677
 
4678
+ if (pendingReflectApproval) {
4679
+ if (key.return) {
4680
+ const parsed = parseReflectApprovalAnswer(reflectApprovalInput);
4681
+ if (parsed.action === 'approve' || parsed.action === 'reject' || parsed.action === 'edit') {
4682
+ setPendingReflectApproval(null);
4683
+ setReflectApprovalInput('');
4684
+ setReflectApprovalError('');
4685
+ runSubmission(parsed.command);
4686
+ } else if (parsed.action === 'missing_feedback') {
4687
+ setReflectApprovalError(copy.reflectApproval.missingFeedback);
4688
+ } else {
4689
+ setReflectApprovalError(copy.reflectApproval.invalidAnswer);
4690
+ }
4691
+ return;
4692
+ }
4693
+
4694
+ if (isBackspaceKey(value, key) || isDeleteKey(value, key)) {
4695
+ setReflectApprovalInput((prev) => prev.slice(0, -1));
4696
+ setReflectApprovalError('');
4697
+ return;
4698
+ }
4699
+
4700
+ if (isPrintableInput(value, key)) {
4701
+ setReflectApprovalInput((prev) => `${prev}${value}`);
4702
+ setReflectApprovalError('');
4703
+ return;
4704
+ }
4705
+
4706
+ if (key.ctrl && value === 'c') {
4707
+ if (busy && typeof runtime.abort === 'function') {
4708
+ runtime.abort();
4709
+ return;
4710
+ }
4711
+ exit();
4712
+ return;
4713
+ }
4714
+
4715
+ return;
4716
+ }
4717
+
4488
4718
  if (key.upArrow) {
4489
4719
  if (suggestionNav && commandSuggestions.length > 0) {
4490
4720
  setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'up'));
@@ -4754,6 +4984,17 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4754
4984
  }
4755
4985
  }, [runtimeState?.pendingPlanApproval, busy]);
4756
4986
 
4987
+ useEffect(() => {
4988
+ const pending = Boolean(runtimeState?.pendingReflectSkill);
4989
+ if (!pending) {
4990
+ setPendingReflectApproval(null);
4991
+ return;
4992
+ }
4993
+ if (!busy) {
4994
+ setPendingReflectApproval((prev) => prev || { scope: '', name: '', targetPath: '' });
4995
+ }
4996
+ }, [runtimeState?.pendingReflectSkill, busy]);
4997
+
4757
4998
  useEffect(() => {
4758
4999
  if (commandSuggestions.length === 0) {
4759
5000
  setSuggestionNav(false);
@@ -4800,7 +5041,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4800
5041
  ? { text: copy.runApproval.inputLocked }
4801
5042
  : pendingPlanApproval
4802
5043
  ? { text: copy.planApproval.inputLocked }
4803
- : null;
5044
+ : pendingReflectApproval
5045
+ ? { text: copy.reflectApproval.inputLocked }
5046
+ : null;
4804
5047
 
4805
5048
  return h(
4806
5049
  Box,
@@ -4850,6 +5093,13 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
4850
5093
  copy,
4851
5094
  cursorVisible
4852
5095
  }),
5096
+ h(ReflectApprovalPanel, {
5097
+ request: pendingReflectApproval,
5098
+ inputValue: reflectApprovalInput,
5099
+ errorText: reflectApprovalError,
5100
+ copy,
5101
+ cursorVisible
5102
+ }),
4853
5103
  debugKeys
4854
5104
  ? h(
4855
5105
  Box,