codemini-cli 0.1.17 → 0.1.19

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.
@@ -63,6 +63,8 @@ const TUI_COPY = {
63
63
  ready: '就绪',
64
64
  noMessagesYet: '还没有消息',
65
65
  code: '代码',
66
+ codeActivity: '代码活动',
67
+ textNotes: '说明文本',
66
68
  live: '运行中',
67
69
  idle: '空闲',
68
70
  plan: '计划',
@@ -81,7 +83,7 @@ const TUI_COPY = {
81
83
  pendingQueue: '等待队列',
82
84
  commandPaletteGroupedSelect: '命令面板 | 分组选择模式',
83
85
  commandPaletteGroupedSuggestions: '命令面板 | 分组候选',
84
- startupHint: '使用 /help、/commands、/exit、!<shell>。Tab 可自动补全 slash 命令。',
86
+ startupHint: '使用 /help、/commands、/compact、/exit、!<shell>。Tab 可自动补全 slash 命令。',
85
87
  toolSummaryExpanded: '工具摘要:已展开',
86
88
  toolSummaryCollapsed: '工具摘要:已收起',
87
89
  toggleToolSummary: 'Ctrl+T 切换',
@@ -103,8 +105,12 @@ const TUI_COPY = {
103
105
  blocked: '工具被拦截',
104
106
  doneRead: '已读取文件',
105
107
  doingRead: '正在读取文件',
108
+ doneEdit: '已编辑文件',
109
+ doingEdit: '正在编辑文件',
106
110
  doneWrite: '已写入文件',
107
111
  doingWrite: '正在写入文件',
112
+ donePatch: '已应用补丁',
113
+ doingPatch: '正在应用补丁',
108
114
  doneList: '已查看目录',
109
115
  doingList: '正在查看目录',
110
116
  doneCommand: '已执行命令',
@@ -137,6 +143,7 @@ const TUI_COPY = {
137
143
  modelThinking: '模型正在思考',
138
144
  requestDelivered: '请求已送达,等待首个 token',
139
145
  generatingReply: '正在生成回复',
146
+ generatingCode: '正在生成代码中',
140
147
  streamingReply: '回复正在流式输出',
141
148
  replyCompleted: '回复已完成',
142
149
  outputFinished: '本轮输出结束',
@@ -165,6 +172,8 @@ const TUI_COPY = {
165
172
  ready: 'ready',
166
173
  noMessagesYet: 'No messages yet',
167
174
  code: 'code',
175
+ codeActivity: 'CODE ACTIVITY',
176
+ textNotes: 'NOTES',
168
177
  live: 'LIVE',
169
178
  idle: 'IDLE',
170
179
  plan: 'PLAN',
@@ -183,7 +192,7 @@ const TUI_COPY = {
183
192
  pendingQueue: 'pending queue',
184
193
  commandPaletteGroupedSelect: 'command palette | grouped select mode',
185
194
  commandPaletteGroupedSuggestions: 'command palette | grouped suggestions',
186
- startupHint: 'Use /help, /commands, /exit, !<shell>. Tab for slash autocomplete.',
195
+ startupHint: 'Use /help, /commands, /compact, /exit, !<shell>. Tab for slash autocomplete.',
187
196
  toolSummaryExpanded: 'Tool summary: expanded',
188
197
  toolSummaryCollapsed: 'Tool summary: collapsed',
189
198
  toggleToolSummary: 'Ctrl+T to toggle',
@@ -205,8 +214,12 @@ const TUI_COPY = {
205
214
  blocked: 'Tool blocked',
206
215
  doneRead: 'Read file',
207
216
  doingRead: 'Reading file',
217
+ doneEdit: 'Edited file',
218
+ doingEdit: 'Editing file',
208
219
  doneWrite: 'Wrote file',
209
220
  doingWrite: 'Writing file',
221
+ donePatch: 'Applied patch',
222
+ doingPatch: 'Applying patch',
210
223
  doneList: 'Listed directory',
211
224
  doingList: 'Listing directory',
212
225
  doneCommand: 'Ran command',
@@ -239,6 +252,7 @@ const TUI_COPY = {
239
252
  modelThinking: 'model is thinking',
240
253
  requestDelivered: 'request sent, waiting for first token',
241
254
  generatingReply: 'generating reply',
255
+ generatingCode: 'generating code',
242
256
  streamingReply: 'reply is streaming',
243
257
  replyCompleted: 'reply completed',
244
258
  outputFinished: 'turn output finished',
@@ -313,9 +327,14 @@ function getActivityDisplayParts(activity) {
313
327
  }
314
328
  const parsed = parseToolDisplayName(activity?.name);
315
329
  const labels = {
316
- read_file: 'Read',
317
- write_file: 'Write',
318
- run_command: 'Command',
330
+ read: 'Read',
331
+ edit: 'Edit',
332
+ write: 'Write',
333
+ patch: 'Patch',
334
+ run: 'Run',
335
+ grep: 'Grep',
336
+ glob: 'Glob',
337
+ list: 'List',
319
338
  start_service: 'Service',
320
339
  list_services: 'Service',
321
340
  get_service_status: 'Service',
@@ -334,33 +353,47 @@ function getActivityDisplayParts(activity) {
334
353
  function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
335
354
  const { raw, base, target } = parseToolDisplayName(name);
336
355
  const safeTarget = trimText(target, 72);
337
- if (base === 'read_file') {
356
+ if (base === 'read') {
338
357
  return blocked
339
- ? `${copy.toolActivity.blocked}: read_file(${safeTarget || '.'})`
358
+ ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
340
359
  : done
341
360
  ? `${copy.toolActivity.doneRead}: ${safeTarget || '.'}`
342
361
  : `${copy.toolActivity.doingRead}: ${safeTarget || '.'}`;
343
362
  }
344
- if (base === 'write_file') {
363
+ if (base === 'edit') {
345
364
  return blocked
346
- ? `${copy.toolActivity.blocked}: write_file(${safeTarget || '.'})`
365
+ ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
366
+ : done
367
+ ? `${copy.toolActivity.doneEdit}: ${safeTarget || '.'}`
368
+ : `${copy.toolActivity.doingEdit}: ${safeTarget || '.'}`;
369
+ }
370
+ if (base === 'write') {
371
+ return blocked
372
+ ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
347
373
  : done
348
374
  ? `${copy.toolActivity.doneWrite}: ${safeTarget || '.'}`
349
375
  : `${copy.toolActivity.doingWrite}: ${safeTarget || '.'}`;
350
376
  }
351
- if (base === 'list_files') {
377
+ if (base === 'patch') {
378
+ return blocked
379
+ ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
380
+ : done
381
+ ? `${copy.toolActivity.donePatch}: ${safeTarget || '.'}`
382
+ : `${copy.toolActivity.doingPatch}: ${safeTarget || '.'}`;
383
+ }
384
+ if (base === 'list' || base === 'glob' || base === 'grep') {
352
385
  return blocked
353
- ? `${copy.toolActivity.blocked}: list_files(${safeTarget || '.'})`
386
+ ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
354
387
  : done
355
388
  ? `${copy.toolActivity.doneList}: ${safeTarget || '.'}`
356
389
  : `${copy.toolActivity.doingList}: ${safeTarget || '.'}`;
357
390
  }
358
- if (base === 'run_command') {
391
+ if (base === 'run') {
359
392
  return blocked
360
- ? `${copy.toolActivity.blocked}: ${safeTarget || 'run_command'}`
393
+ ? `${copy.toolActivity.blocked}: ${safeTarget || base}`
361
394
  : done
362
- ? `${copy.toolActivity.doneCommand}: ${safeTarget || 'run_command'}`
363
- : `${copy.toolActivity.doingCommand}: ${safeTarget || 'run_command'}`;
395
+ ? `${copy.toolActivity.doneCommand}: ${safeTarget || base}`
396
+ : `${copy.toolActivity.doingCommand}: ${safeTarget || base}`;
364
397
  }
365
398
  if (base === 'start_service' || base === 'list_services' || base === 'get_service_status' || base === 'get_service_logs' || base === 'stop_service') {
366
399
  return blocked
@@ -451,6 +484,52 @@ function RuntimeStrip({ busy, runtimeStatus, loaderTick, copy }) {
451
484
  );
452
485
  }
453
486
 
487
+ function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false }) {
488
+ const maxContextTokens = Number(runtimeState?.maxContextTokens || 0);
489
+ const currentContextTokens = Number(runtimeState?.currentContextTokens || 0);
490
+ const pctRaw =
491
+ Number.isFinite(runtimeState?.contextUsagePct) && runtimeState.contextUsagePct >= 0
492
+ ? runtimeState.contextUsagePct
493
+ : maxContextTokens > 0
494
+ ? (currentContextTokens / maxContextTokens) * 100
495
+ : 0;
496
+ const pct = Math.min(100, Math.max(0, pctRaw));
497
+ const filled = Math.min(12, Math.max(0, Math.round((pct / 100) * 12)));
498
+ const activeColor = pct < 40 ? 'greenBright' : pct < 75 ? 'yellowBright' : 'redBright';
499
+ const statusColor = runtimeStatus?.color || activeColor;
500
+ const chunks = Array.from({ length: 12 }, (_, idx) => {
501
+ const zoneColor = idx < 5 ? 'greenBright' : idx < 9 ? 'yellowBright' : 'redBright';
502
+ const color = idx < filled ? zoneColor : 'gray';
503
+ return h(Text, { key: `context-meter-${idx}`, color }, '|');
504
+ });
505
+
506
+ if (compact) {
507
+ return h(
508
+ Box,
509
+ { justifyContent: 'flex-end', alignItems: 'center' },
510
+ h(Text, { color: 'gray' }, '上下文 '),
511
+ h(Text, { color: statusColor }, `${Math.round(pct)}% `),
512
+ h(
513
+ Box,
514
+ { flexDirection: 'row' },
515
+ ...chunks
516
+ )
517
+ );
518
+ }
519
+
520
+ return h(
521
+ Box,
522
+ { justifyContent: 'flex-end', alignItems: 'center' },
523
+ h(Text, { color: 'gray' }, '上下文 '),
524
+ h(Text, { color: statusColor }, `${Math.round(pct)}% `),
525
+ h(
526
+ Box,
527
+ null,
528
+ ...chunks
529
+ )
530
+ );
531
+ }
532
+
454
533
  function PlanStrip({ planState, copy }) {
455
534
  if (!planState || !planState.total) return null;
456
535
  const progress = `${planState.current}/${planState.total}`;
@@ -632,6 +711,21 @@ export function injectPlanStateMessage(messages, planState, activeUserMessageId,
632
711
  return [...withNoPlanStrip, synthetic];
633
712
  }
634
713
 
714
+ export function injectRuntimeStateMessage(messages, runtimeState, runtimeStatus, busy, activeUserMessageId, activeAssistantId) {
715
+ const source = Array.isArray(messages) ? messages : [];
716
+ if (!runtimeState) return source;
717
+ const withoutRuntimeStrip = source.filter((message) => !message?.runtimeStrip);
718
+ const userIdx = withoutRuntimeStrip.findIndex((message) => message.id === activeUserMessageId);
719
+ if (userIdx !== -1) {
720
+ return withoutRuntimeStrip;
721
+ }
722
+ const assistantIdx = withoutRuntimeStrip.findIndex((message) => message.id === activeAssistantId);
723
+ if (assistantIdx !== -1) {
724
+ return withoutRuntimeStrip;
725
+ }
726
+ return withoutRuntimeStrip;
727
+ }
728
+
635
729
  function PlanSummaryBubble({ msg, copy }) {
636
730
  const theme = roleStyle(msg.label);
637
731
  const summary = msg.planSummary || parseAutoPlanSummaryMessage(msg.text);
@@ -817,6 +911,38 @@ function isBlankTextRow(row) {
817
911
  return row?.kind === 'text' && String(row?.text || '').trim() === '';
818
912
  }
819
913
 
914
+ function isCodeActivityName(name) {
915
+ const parsed = parseToolDisplayName(name);
916
+ return new Set([
917
+ 'edit',
918
+ 'write_file',
919
+ 'patch',
920
+ 'replace_text',
921
+ 'replace_block',
922
+ 'insert_before',
923
+ 'insert_after',
924
+ 'validate_edit',
925
+ 'generate_diff'
926
+ ]).has(parsed.base);
927
+ }
928
+
929
+ export function isCodeLikeRow(row) {
930
+ if (!row) return false;
931
+ if (row.kind === 'code' || row.kind === 'activity' || row.kind === 'activity-summary') return true;
932
+ if (row.kind === 'status') return true;
933
+ return false;
934
+ }
935
+
936
+ export function splitMessageRows(rows) {
937
+ const textRows = [];
938
+ const codeRows = [];
939
+ for (const row of Array.isArray(rows) ? rows : []) {
940
+ if (isCodeLikeRow(row)) codeRows.push(row);
941
+ else textRows.push(row);
942
+ }
943
+ return { textRows, codeRows };
944
+ }
945
+
820
946
  export function normalizeActivitySpacingRows(inputRows) {
821
947
  const rows = Array.isArray(inputRows) ? inputRows : [];
822
948
  const normalized = [];
@@ -863,7 +989,7 @@ export function normalizeActivitySpacingRows(inputRows) {
863
989
 
864
990
  function isReadActivityName(name) {
865
991
  const parsed = parseToolDisplayName(name);
866
- return parsed.base === 'read_file' || parsed.base === 'Read';
992
+ return parsed.base === 'read' || parsed.base === 'Read';
867
993
  }
868
994
 
869
995
  function isIgnorableSegmentAfterRead(item, activityType, activityName) {
@@ -1014,6 +1140,105 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
1014
1140
  return normalizeActivitySpacingRows(rows);
1015
1141
  }
1016
1142
 
1143
+ function renderMessageRow(msg, row, idx, loaderTick) {
1144
+ if (row.kind === 'activity') {
1145
+ const activity = { type: row.activityType, name: row.name, status: row.status };
1146
+ const display = getActivityDisplayParts(activity);
1147
+ const dotColor =
1148
+ activity.type === 'skill'
1149
+ ? row.status === 'error'
1150
+ ? 'redBright'
1151
+ : 'blueBright'
1152
+ : row.status === 'error' || row.status === 'blocked'
1153
+ ? 'redBright'
1154
+ : 'greenBright';
1155
+ const textColor =
1156
+ activity.type === 'skill'
1157
+ ? row.status === 'error'
1158
+ ? 'redBright'
1159
+ : 'cyanBright'
1160
+ : row.status === 'error' || row.status === 'blocked'
1161
+ ? 'redBright'
1162
+ : 'greenBright';
1163
+ return h(
1164
+ Box,
1165
+ { key: `row-tool-${msg.id}-${idx}` },
1166
+ h(Text, { color: 'gray' }, ' '),
1167
+ h(Text, { color: dotColor }, '●'),
1168
+ h(Text, { color: 'gray' }, ' '),
1169
+ h(Text, { color: textColor }, display.primary),
1170
+ h(Text, { color: 'gray' }, display.secondary),
1171
+ row.durationText ? h(Text, { color: row.statusColor }, ` ${row.durationText}`) : null
1172
+ );
1173
+ }
1174
+ if (row.kind === 'activity-summary') {
1175
+ return h(
1176
+ Box,
1177
+ { key: `row-tool-summary-${msg.id}-${idx}`, marginLeft: 1 },
1178
+ h(Text, { color: 'gray' }, `└ ${row.text}`)
1179
+ );
1180
+ }
1181
+ if (row.kind === 'plan-progress') {
1182
+ return h(
1183
+ Box,
1184
+ { key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
1185
+ h(Text, { color: 'cyanBright' }, '[plan] '),
1186
+ h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
1187
+ h(Text, { color: 'gray' }, ' -> '),
1188
+ h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
1189
+ h(Text, { color: 'gray' }, ': '),
1190
+ h(Text, { color: 'white' }, row.title)
1191
+ );
1192
+ }
1193
+ if (row.kind === 'status') {
1194
+ const dots = '.'.repeat((loaderTick % 3) + 1);
1195
+ const phase = msg.phase;
1196
+ const color =
1197
+ phase === 'sending'
1198
+ ? 'yellowBright'
1199
+ : phase === 'queued'
1200
+ ? 'cyanBright'
1201
+ : phase === 'tooling'
1202
+ ? 'magentaBright'
1203
+ : phase === 'generating'
1204
+ ? 'greenBright'
1205
+ : 'cyanBright';
1206
+ return h(
1207
+ Box,
1208
+ { key: `row-status-${msg.id}-${idx}`, marginTop: 1 },
1209
+ h(Text, { color: 'gray' }, ' '),
1210
+ h(Text, { color }, `${row.text}${dots}`)
1211
+ );
1212
+ }
1213
+ if (row.kind === 'quote') {
1214
+ return h(
1215
+ Box,
1216
+ { key: `row-quote-${msg.id}-${idx}`, marginTop: 1, marginLeft: 1, paddingLeft: 1 },
1217
+ h(Text, { color: 'yellow' }, '▍ '),
1218
+ h(Text, { color: row.color }, ...renderInlineCode(row.text, row.color))
1219
+ );
1220
+ }
1221
+ if (row.kind === 'tree') {
1222
+ return h(
1223
+ Box,
1224
+ { key: `row-tree-${msg.id}-${idx}`, marginLeft: 1 },
1225
+ h(Text, { color: row.color }, row.text)
1226
+ );
1227
+ }
1228
+ if (row.kind === 'code') {
1229
+ return h(
1230
+ Box,
1231
+ { key: `row-code-${msg.id}-${idx}`, marginLeft: 1 },
1232
+ h(Text, { color: 'gray' }, row.text)
1233
+ );
1234
+ }
1235
+ return renderTextLine(msg, row.text, idx, row.color);
1236
+ }
1237
+
1238
+ function renderMessageRowsInOrder(msg, rows, loaderTick, copy) {
1239
+ return rows.map((row, idx) => renderMessageRow(msg, row, idx, loaderTick));
1240
+ }
1241
+
1017
1242
 
1018
1243
  function groupCommandSuggestions(items) {
1019
1244
  const categoryMap = {
@@ -1128,100 +1353,7 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
1128
1353
  const start = rowWindow ? Math.max(0, rowWindow.start || 0) : 0;
1129
1354
  const end = rowWindow ? Math.max(start, rowWindow.end || allRows.length) : allRows.length;
1130
1355
  const visibleRows = allRows.slice(start, end);
1131
- const rendered = visibleRows.map((row, idx) => {
1132
- if (row.kind === 'activity') {
1133
- const activity = { type: row.activityType, name: row.name, status: row.status };
1134
- const display = getActivityDisplayParts(activity);
1135
- const dotColor =
1136
- activity.type === 'skill'
1137
- ? row.status === 'error'
1138
- ? 'redBright'
1139
- : 'blueBright'
1140
- : row.status === 'error' || row.status === 'blocked'
1141
- ? 'redBright'
1142
- : 'greenBright';
1143
- const textColor =
1144
- activity.type === 'skill'
1145
- ? row.status === 'error'
1146
- ? 'redBright'
1147
- : 'cyanBright'
1148
- : row.status === 'error' || row.status === 'blocked'
1149
- ? 'redBright'
1150
- : 'greenBright';
1151
- return h(
1152
- Box,
1153
- { key: `row-tool-${msg.id}-${idx}` },
1154
- h(Text, { color: 'gray' }, ' '),
1155
- h(Text, { color: dotColor }, '●'),
1156
- h(Text, { color: 'gray' }, ' '),
1157
- h(Text, { color: textColor }, display.primary),
1158
- h(Text, { color: 'gray' }, display.secondary),
1159
- row.durationText ? h(Text, { color: row.statusColor }, ` ${row.durationText}`) : null
1160
- );
1161
- }
1162
- if (row.kind === 'activity-summary') {
1163
- return h(
1164
- Box,
1165
- { key: `row-tool-summary-${msg.id}-${idx}`, marginLeft: 2 },
1166
- h(Text, { color: 'gray' }, `└ ${row.text}`)
1167
- );
1168
- }
1169
- if (row.kind === 'plan-progress') {
1170
- return h(
1171
- Box,
1172
- { key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
1173
- h(Text, { color: 'cyanBright' }, '[plan] '),
1174
- h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
1175
- h(Text, { color: 'gray' }, ' -> '),
1176
- h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
1177
- h(Text, { color: 'gray' }, ': '),
1178
- h(Text, { color: 'white' }, row.title)
1179
- );
1180
- }
1181
- if (row.kind === 'status') {
1182
- const dots = '.'.repeat((loaderTick % 3) + 1);
1183
- const phase = msg.phase;
1184
- const color =
1185
- phase === 'sending'
1186
- ? 'yellowBright'
1187
- : phase === 'queued'
1188
- ? 'cyanBright'
1189
- : phase === 'tooling'
1190
- ? 'magentaBright'
1191
- : phase === 'generating'
1192
- ? 'greenBright'
1193
- : 'cyanBright';
1194
- return h(
1195
- Box,
1196
- { key: `row-status-${msg.id}-${idx}`, marginTop: 1 },
1197
- h(Text, { color: 'gray' }, ' '),
1198
- h(Text, { color }, `${row.text}${dots}`)
1199
- );
1200
- }
1201
- if (row.kind === 'quote') {
1202
- return h(
1203
- Box,
1204
- { key: `row-quote-${msg.id}-${idx}`, marginTop: 1, marginLeft: 1, paddingLeft: 1 },
1205
- h(Text, { color: 'yellow' }, '▍ '),
1206
- h(Text, { color: row.color }, ...renderInlineCode(row.text, row.color))
1207
- );
1208
- }
1209
- if (row.kind === 'tree') {
1210
- return h(
1211
- Box,
1212
- { key: `row-tree-${msg.id}-${idx}`, marginLeft: 1 },
1213
- h(Text, { color: row.color }, row.text)
1214
- );
1215
- }
1216
- if (row.kind === 'code') {
1217
- return h(
1218
- Box,
1219
- { key: `row-code-${msg.id}-${idx}`, marginLeft: 1 },
1220
- h(Text, { color: 'gray' }, row.text)
1221
- );
1222
- }
1223
- return renderTextLine(msg, row.text, idx, row.color);
1224
- });
1356
+ const rendered = renderMessageRowsInOrder(msg, visibleRows, loaderTick, copy);
1225
1357
 
1226
1358
  return h(
1227
1359
  Box,
@@ -1336,7 +1468,7 @@ function PendingPanel({ pendingQueue, copy }) {
1336
1468
  return h(
1337
1469
  Box,
1338
1470
  {
1339
- marginTop: 1,
1471
+ marginTop: 0,
1340
1472
  flexDirection: 'column',
1341
1473
  borderStyle: 'round',
1342
1474
  borderColor: 'cyan',
@@ -1416,11 +1548,10 @@ function InputBar({
1416
1548
  ),
1417
1549
  h(
1418
1550
  Box,
1419
- null,
1420
- h(Text, { color: 'black', backgroundColor: 'greenBright' }, ` ${copy.generic.safeMode} `),
1421
- inputStage !== 'idle' || busy ? h(Text, { color: status.color }, ` ${status.tag}`) : null,
1422
- pendingQueueLength > 0 ? h(Text, { color: 'cyanBright' }, ` ${copy.generic.queued} ${pendingQueueLength}`) : null,
1423
- h(Text, { color: showToolDetails ? 'greenBright' : 'gray' }, ` ${copy.generic.tools} ${showToolDetails ? copy.generic.open : copy.generic.collapsed}`)
1551
+ { flexDirection: 'column', alignItems: 'flex-end' },
1552
+ h(Text, { color: showToolDetails ? 'greenBright' : 'gray' }, ` ${copy.generic.tools} ${showToolDetails ? copy.generic.open : copy.generic.collapsed}`),
1553
+ pendingQueueLength > 0 ? h(Text, { color: 'cyanBright' }, ` ${copy.generic.queued} ${pendingQueueLength}`) : null,
1554
+ inputStage !== 'idle' || busy ? h(Text, { color: status.color }, ` ${status.tag}`) : null
1424
1555
  )
1425
1556
  ),
1426
1557
  h(
@@ -1503,6 +1634,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1503
1634
  const [runtimeStatus, setRuntimeStatus] = useState(
1504
1635
  makeIdleStatus(copy, runtime.getRuntimeState?.(), 'ready')
1505
1636
  );
1637
+ const [runtimeState, setRuntimeState] = useState(runtime.getRuntimeState?.() || null);
1506
1638
  const [inputStage, setInputStage] = useState('idle');
1507
1639
  const [planState, setPlanState] = useState({
1508
1640
  current: 0,
@@ -1519,12 +1651,17 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1519
1651
  const activeUserMessageIdRef = useRef(null);
1520
1652
  const cursorIndexRef = useRef(0);
1521
1653
  const inFlightRef = useRef(false);
1654
+ const messagesRef = useRef([]);
1522
1655
  const pendingQueueRef = useRef([]);
1523
1656
  const deltaBufferRef = useRef('');
1524
1657
  const deltaFlushTimerRef = useRef(null);
1525
1658
  const escSeqRef = useRef('');
1526
1659
  const planTextBufferRef = useRef('');
1527
1660
  const { exit } = useApp();
1661
+
1662
+ useEffect(() => {
1663
+ messagesRef.current = messages;
1664
+ }, [messages]);
1528
1665
  const startupHint = copy.generic.startupHint;
1529
1666
  const isBackspaceKey = (value, key) =>
1530
1667
  Boolean(key?.backspace) || value === '\u0008' || value === '\u007f' || (key?.ctrl && value === 'h');
@@ -1565,6 +1702,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1565
1702
  if (!snapshot) return;
1566
1703
  setDisplaySessionId(snapshot.sessionId || sessionId);
1567
1704
  setDisplayModel(snapshot.model || model);
1705
+ setRuntimeState(snapshot);
1568
1706
  setRuntimeStatus(makeIdleStatus(copy, snapshot, variant));
1569
1707
  };
1570
1708
 
@@ -1815,7 +1953,18 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1815
1953
  updatePlanProgressFromText(event.text);
1816
1954
  setRuntimeStatus(makeStatus(copy.runtime.generatingReply, copy.runtime.streamingReply, 'greenBright'));
1817
1955
  setInputStage('streaming');
1818
- setActiveAssistantMeta({ loading: true, phase: 'generating', liveStatus: copy.runtime.generatingReply });
1956
+ setActiveAssistantMeta((() => {
1957
+ const targetId = activeAssistantIdRef.current;
1958
+ let liveStatus = copy.runtime.generatingReply;
1959
+ if (targetId) {
1960
+ const current = messagesRef.current?.find?.((m) => m.id === targetId);
1961
+ const segments = Array.isArray(current?.segments) ? current.segments : [];
1962
+ if (segments.some((segment) => (segment.type === 'tool' || segment.type === 'skill') && isCodeActivityName(segment.name))) {
1963
+ liveStatus = copy.runtime.generatingCode;
1964
+ }
1965
+ }
1966
+ return { loading: true, phase: 'generating', liveStatus };
1967
+ })());
1819
1968
  queueAssistantDelta(event.text);
1820
1969
  }
1821
1970
  if (event?.type === 'assistant:response') {
@@ -1835,7 +1984,11 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1835
1984
  const detail = describeToolActivity(event.name, copy);
1836
1985
  setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'magentaBright'));
1837
1986
  setInputStage('tooling');
1838
- setActiveAssistantMeta({ loading: true, phase: 'tooling', liveStatus: detail });
1987
+ setActiveAssistantMeta({
1988
+ loading: true,
1989
+ phase: 'tooling',
1990
+ liveStatus: isCodeActivityName(event.name) ? copy.runtime.generatingCode : detail
1991
+ });
1839
1992
  updateActivityStatusOnActiveAssistant({
1840
1993
  type: 'tool',
1841
1994
  id: event.id,
@@ -2386,7 +2539,6 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2386
2539
  Box,
2387
2540
  { flexDirection: 'column' },
2388
2541
  h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
2389
- h(RuntimeStrip, { busy, runtimeStatus, loaderTick, copy }),
2390
2542
  h(MessageList, {
2391
2543
  messages: visibleMessages,
2392
2544
  loaderTick,
@@ -2396,12 +2548,17 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2396
2548
  }),
2397
2549
  h(
2398
2550
  Box,
2399
- { marginTop: 1 },
2551
+ { marginTop: 0, marginBottom: 0, justifyContent: 'space-between', width: '100%' },
2400
2552
  h(
2401
- Text,
2402
- { color: 'gray' },
2403
- `${showToolDetails ? copy.generic.toolSummaryExpanded : copy.generic.toolSummaryCollapsed} (${copy.generic.toggleToolSummary}) · ${copy.generic.scrollHint}`
2404
- )
2553
+ Box,
2554
+ { flexGrow: 1 },
2555
+ h(
2556
+ Text,
2557
+ { color: 'gray' },
2558
+ `${showToolDetails ? copy.generic.toolSummaryExpanded : copy.generic.toolSummaryCollapsed} (${copy.generic.toggleToolSummary}) · ${copy.generic.scrollHint}`
2559
+ )
2560
+ ),
2561
+ h(ContextProgressMeter, { runtimeState, runtimeStatus, compact: true })
2405
2562
  ),
2406
2563
  h(SuggestionPanel, { commandSuggestions, suggestionNav, menuIndex, copy }),
2407
2564
  h(PendingPanel, { pendingQueue, copy }),