codemini-cli 0.3.0 → 0.3.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.
@@ -102,7 +102,7 @@ const TUI_COPY = {
102
102
  startupHint: '使用 /help、/commands、/compact、/exit、!<shell>。Tab 可自动补全 slash 命令。',
103
103
  toolSummaryExpanded: '工具摘要:已展开',
104
104
  toolSummaryCollapsed: '工具摘要:已收起',
105
- toolChainCollapsed: (count) => `已折叠更早的 ${count} 个工具调用,按 Ctrl+T 展开全部`,
105
+ toolChainCollapsed: (count) => `已折叠更早的 ${count} 个工具调用`,
106
106
  toggleToolSummary: 'Ctrl+T 切换',
107
107
  scrollHint: '使用终端自己的滚动条或 scrollback',
108
108
  keyboardDebugEnabled: '键盘调试已开启',
@@ -128,8 +128,12 @@ const TUI_COPY = {
128
128
  doingWrite: '正在写入文件',
129
129
  donePatch: '已应用补丁',
130
130
  doingPatch: '正在应用补丁',
131
- doneList: '已查看目录',
132
- doingList: '正在查看目录',
131
+ doneList: '已列出目录',
132
+ doingList: '正在列出目录',
133
+ doneGlob: '已按模式查找文件',
134
+ doingGlob: '正在按模式查找文件',
135
+ doneGrep: '已搜索关键词',
136
+ doingGrep: '正在搜索关键词',
133
137
  doneCommand: '已执行命令',
134
138
  doingCommand: '正在执行命令',
135
139
  doneCreateTask: '已创建任务',
@@ -152,6 +156,14 @@ const TUI_COPY = {
152
156
  doingDatabase: '正在启动数据库服务',
153
157
  doneDocker: '已完成 Docker 命令',
154
158
  doingDocker: '正在执行 Docker 命令',
159
+ doneListServices: '已列出服务',
160
+ doingListServices: '正在列出服务',
161
+ doneServiceStatus: '已查看服务状态',
162
+ doingServiceStatus: '正在查看服务状态',
163
+ doneServiceLogs: '已查看服务日志',
164
+ doingServiceLogs: '正在查看服务日志',
165
+ doneStopService: '已停止服务',
166
+ doingStopService: '正在停止服务',
155
167
  doneCodeGeneration: '已生成代码',
156
168
  doingCodeGeneration: '正在生成代码',
157
169
  doneSkill: '已完成技能',
@@ -177,6 +189,7 @@ const TUI_COPY = {
177
189
  runtime: {
178
190
  sendingToGateway: '正在发送到网关',
179
191
  preparingRequest: '准备本轮请求',
192
+ submittedWaiting: '已提交,等待开始处理',
180
193
  modelThinking: '模型正在思考',
181
194
  requestDelivered: '请求已送达,等待首个 token',
182
195
  generatingReply: '正在生成回复',
@@ -234,7 +247,7 @@ const TUI_COPY = {
234
247
  startupHint: 'Use /help, /commands, /compact, /exit, !<shell>. Tab for slash autocomplete.',
235
248
  toolSummaryExpanded: 'Tool summary: expanded',
236
249
  toolSummaryCollapsed: 'Tool summary: collapsed',
237
- toolChainCollapsed: (count) => `${count} earlier tool calls hidden, press Ctrl+T to expand`,
250
+ toolChainCollapsed: (count) => `${count} earlier tool calls hidden`,
238
251
  toggleToolSummary: 'Ctrl+T to toggle',
239
252
  scrollHint: 'Scroll with your terminal scrollbar or scrollback',
240
253
  keyboardDebugEnabled: 'Keyboard debug enabled',
@@ -262,6 +275,10 @@ const TUI_COPY = {
262
275
  doingPatch: 'Applying patch',
263
276
  doneList: 'Listed directory',
264
277
  doingList: 'Listing directory',
278
+ doneGlob: 'Matched files by pattern',
279
+ doingGlob: 'Matching files by pattern',
280
+ doneGrep: 'Searched keywords',
281
+ doingGrep: 'Searching keywords',
265
282
  doneCommand: 'Ran command',
266
283
  doingCommand: 'Running command',
267
284
  doneCreateTask: 'Created task',
@@ -284,6 +301,14 @@ const TUI_COPY = {
284
301
  doingDatabase: 'Starting database service',
285
302
  doneDocker: 'Docker command completed',
286
303
  doingDocker: 'Running Docker command',
304
+ doneListServices: 'Listed services',
305
+ doingListServices: 'Listing services',
306
+ doneServiceStatus: 'Checked service status',
307
+ doingServiceStatus: 'Checking service status',
308
+ doneServiceLogs: 'Viewed service logs',
309
+ doingServiceLogs: 'Viewing service logs',
310
+ doneStopService: 'Stopped service',
311
+ doingStopService: 'Stopping service',
287
312
  doneCodeGeneration: 'Code generated',
288
313
  doingCodeGeneration: 'Generating code',
289
314
  doneSkill: 'Completed skill',
@@ -309,6 +334,7 @@ const TUI_COPY = {
309
334
  runtime: {
310
335
  sendingToGateway: 'sending to gateway',
311
336
  preparingRequest: 'preparing this turn',
337
+ submittedWaiting: 'submitted, waiting to start',
312
338
  modelThinking: 'model is thinking',
313
339
  requestDelivered: 'request sent, waiting for first token',
314
340
  generatingReply: 'generating reply',
@@ -671,6 +697,61 @@ function parseRichTextSegments(line, baseColor) {
671
697
  });
672
698
  }
673
699
 
700
+ export function sanitizeRenderableText(value) {
701
+ const input = String(value ?? '');
702
+ if (!input) return '';
703
+
704
+ return input
705
+ .replace(/\r\n/g, '\n')
706
+ .replace(/\r/g, '\n')
707
+ .replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '')
708
+ .replace(/[\u001b\u009b][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, '')
709
+ .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, '');
710
+ }
711
+
712
+ function textFromSessionContent(content) {
713
+ if (typeof content === 'string') return sanitizeRenderableText(content);
714
+ if (Array.isArray(content)) {
715
+ return sanitizeRenderableText(
716
+ content
717
+ .map((part) => {
718
+ if (typeof part === 'string') return part;
719
+ if (part?.type === 'text') return part.text || '';
720
+ return '';
721
+ })
722
+ .join('')
723
+ );
724
+ }
725
+ return sanitizeRenderableText(String(content || ''));
726
+ }
727
+
728
+ function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
729
+ const source = Array.isArray(sessionMessages) ? sessionMessages : [];
730
+ const out = [];
731
+
732
+ for (const message of source) {
733
+ if (!message || typeof message !== 'object') continue;
734
+ if (message.role === 'tool') continue;
735
+
736
+ const text = textFromSessionContent(message.content);
737
+ if (!text.trim() && message.role !== 'assistant') continue;
738
+
739
+ if (message.role === 'user') {
740
+ out.push({ id: nextId(), label: 'you', text, color: 'blueBright' });
741
+ continue;
742
+ }
743
+ if (message.role === 'assistant') {
744
+ out.push({ id: nextId(), label: 'coder', text, color: 'greenBright' });
745
+ continue;
746
+ }
747
+ if (message.role === 'system') {
748
+ out.push({ id: nextId(), label: 'system', text, color: 'yellowBright' });
749
+ }
750
+ }
751
+
752
+ return out;
753
+ }
754
+
674
755
  function safeJsonParse(raw) {
675
756
  try {
676
757
  return JSON.parse(String(raw || '{}'));
@@ -773,6 +854,27 @@ export function formatActivityDurationText(row, nowMs = Date.now()) {
773
854
  return '';
774
855
  }
775
856
 
857
+ export function getPendingUserMessageMeta(copy, { immediateLocal = false, inFlight = false } = {}) {
858
+ if (immediateLocal) {
859
+ return {
860
+ phase: 'sending',
861
+ liveStatus: copy.runtime.localCommandRunning
862
+ };
863
+ }
864
+
865
+ if (inFlight) {
866
+ return {
867
+ phase: 'queued',
868
+ liveStatus: copy.runtime.queuedWaiting
869
+ };
870
+ }
871
+
872
+ return {
873
+ phase: 'sending',
874
+ liveStatus: copy.runtime.submittedWaiting || copy.runtime.sendingToGateway
875
+ };
876
+ }
877
+
776
878
  function getActivityDisplayParts(activity) {
777
879
  if (isCodeGenerationActivityName(activity?.name)) {
778
880
  return {
@@ -795,6 +897,12 @@ function getActivityDisplayParts(activity) {
795
897
  };
796
898
  }
797
899
  if ((activity?.type || 'tool') === 'system_tool') {
900
+ if (parsed.base === 'project_index') {
901
+ return { primary: 'Project Index', secondary: '' };
902
+ }
903
+ if (parsed.base === 'file_index') {
904
+ return { primary: 'File Index', secondary: parsed.target ? `(${parsed.target})` : '' };
905
+ }
798
906
  return {
799
907
  primary: 'Index',
800
908
  secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
@@ -806,14 +914,14 @@ function getActivityDisplayParts(activity) {
806
914
  write: 'Write',
807
915
  patch: 'Patch',
808
916
  run: 'Run',
809
- grep: 'Grep',
917
+ grep: 'Search',
810
918
  glob: 'Glob',
811
919
  list: 'List',
812
920
  start_service: 'Service',
813
- list_services: 'Service',
814
- get_service_status: 'Service',
815
- get_service_logs: 'Service',
816
- stop_service: 'Service',
921
+ list_services: 'Services',
922
+ get_service_status: 'Status',
923
+ get_service_logs: 'Logs',
924
+ stop_service: 'Stop',
817
925
  list_files: 'Glob',
818
926
  create_task: 'Task',
819
927
  update_task: 'Task'
@@ -965,6 +1073,21 @@ function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false })
965
1073
  function PlanStrip({ planState, copy }) {
966
1074
  if (!planState || !planState.total) return null;
967
1075
  const progress = `${planState.current}/${planState.total}`;
1076
+ const stripComplete = Boolean(planState.completed) && !planState.failed;
1077
+ const statusLabel = planState.failed ? copy.generic.attention : stripComplete ? copy.generic.taskCompleted : copy.generic.active;
1078
+ const statusColor = planState.failed ? 'redBright' : stripComplete ? 'cyanBright' : 'greenBright';
1079
+ const roleLabel =
1080
+ planState.resultStatus || stripComplete || planState.failed
1081
+ ? copy?.roleLabels?.system === 'SYSTEM'
1082
+ ? 'RESULT'
1083
+ : '结果'
1084
+ : String(planState.role || 'agent').toUpperCase();
1085
+ const titleLabel =
1086
+ planState.resultStatus || stripComplete || planState.failed
1087
+ ? copy?.roleLabels?.system === 'SYSTEM'
1088
+ ? 'Plan execution result'
1089
+ : '计划执行结果'
1090
+ : planState.title || 'running plan step';
968
1091
  return h(
969
1092
  Box,
970
1093
  {
@@ -983,11 +1106,18 @@ function PlanStrip({ planState, copy }) {
983
1106
  null,
984
1107
  h(Text, { color: 'black', backgroundColor: planState.failed ? 'red' : 'cyanBright' }, ` ${copy.generic.plan} ${progress} `),
985
1108
  h(Text, { color: 'gray' }, ' '),
986
- h(Text, { color: 'magentaBright' }, String(planState.role || 'agent').toUpperCase())
1109
+ h(Text, { color: 'magentaBright' }, roleLabel)
987
1110
  ),
988
- h(Text, { color: planState.failed ? 'redBright' : 'greenBright' }, planState.failed ? copy.generic.attention : copy.generic.active)
1111
+ h(Text, { color: statusColor }, statusLabel)
989
1112
  ),
990
- h(Text, { color: 'white' }, planState.title || 'running plan step'),
1113
+ h(Text, { color: 'white' }, titleLabel),
1114
+ planState.resultVerified
1115
+ ? h(
1116
+ Box,
1117
+ { marginTop: 1 },
1118
+ h(Text, { color: 'gray' }, trimText(planState.resultVerified, 120))
1119
+ )
1120
+ : null,
991
1121
  planState.steps.length > 0
992
1122
  ? h(
993
1123
  Box,
@@ -1005,15 +1135,24 @@ function PlanStrip({ planState, copy }) {
1005
1135
  )
1006
1136
  )
1007
1137
  )
1138
+ : null,
1139
+ planState.resultNext
1140
+ ? h(
1141
+ Box,
1142
+ { marginTop: 1 },
1143
+ h(Text, { color: 'black', backgroundColor: 'yellowBright' }, ' NEXT '),
1144
+ h(Text, { color: 'gray' }, ` ${trimText(planState.resultNext, 108)}`)
1145
+ )
1008
1146
  : null
1009
1147
  );
1010
1148
  }
1011
1149
 
1012
- function Header({ sessionId, model, shellName, safeMode = true }) {
1150
+ function Header({ sessionId, model, sdkProvider, shellName, safeMode = true }) {
1013
1151
  const shortSession = String(sessionId || '').slice(-12) || '-';
1014
1152
  const modeValue = safeMode ? 'SAFE' : 'OPEN';
1015
1153
  const modeColor = safeMode ? 'greenBright' : 'redBright';
1016
1154
  const modeTextColor = safeMode ? 'black' : 'white';
1155
+ const sdkValue = String(sdkProvider || 'openai-compatible');
1017
1156
  return h(
1018
1157
  Box,
1019
1158
  { width: '100%', justifyContent: 'center', marginTop: 1, marginBottom: 2 },
@@ -1041,6 +1180,7 @@ function Header({ sessionId, model, shellName, safeMode = true }) {
1041
1180
  h(
1042
1181
  Box,
1043
1182
  { flexDirection: 'row', justifyContent: 'center' },
1183
+ h(StatusPill, { label: 'SDK', value: sdkValue, color: 'blueBright', textColor: 'white' }),
1044
1184
  h(StatusPill, { label: 'MODEL', value: model, color: 'cyanBright', textColor: 'black' }),
1045
1185
  h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'yellowBright', textColor: 'black' }),
1046
1186
  h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' }),
@@ -1096,6 +1236,7 @@ export function parseAutoPlanSummaryMessage(text) {
1096
1236
  filePath: '',
1097
1237
  planSummary: '',
1098
1238
  finalSummary: '',
1239
+ approval: '',
1099
1240
  stepsTotal: '',
1100
1241
  completed: '',
1101
1242
  warnings: '',
@@ -1106,8 +1247,10 @@ export function parseAutoPlanSummaryMessage(text) {
1106
1247
 
1107
1248
  for (const line of lines.slice(1)) {
1108
1249
  if (line.startsWith('File: ')) parsed.filePath = line.slice('File: '.length).trim();
1250
+ else if (line.startsWith('Plan File: ')) parsed.filePath = line.slice('Plan File: '.length).trim();
1109
1251
  else if (line.startsWith('Plan Summary: ')) parsed.planSummary = line.slice('Plan Summary: '.length).trim();
1110
1252
  else if (line.startsWith('Final Summary: ')) parsed.finalSummary = line.slice('Final Summary: '.length).trim();
1253
+ else if (line.startsWith('Approval: ')) parsed.approval = line.slice('Approval: '.length).trim();
1111
1254
  else if (line.startsWith('Steps: ')) parsed.stepsTotal = line.slice('Steps: '.length).trim();
1112
1255
  else if (line.startsWith('Completed: ')) parsed.completed = line.slice('Completed: '.length).trim();
1113
1256
  else if (line.startsWith('Warnings: ')) parsed.warnings = line.slice('Warnings: '.length).trim();
@@ -1131,6 +1274,31 @@ export function parsePlanProgressLine(text) {
1131
1274
  };
1132
1275
  }
1133
1276
 
1277
+ export function parsePlanExecutionResult(text) {
1278
+ const raw = String(text || '').trim();
1279
+ if (!raw) return null;
1280
+ const statusMatch = raw.match(/(?:^|\n)\s*Status:\s*(done|partial|blocked)\s*$/im);
1281
+ const verifiedMatch = raw.match(/(?:^|\n)\s*Verified:\s*(.+)\s*$/im);
1282
+ const nextMatch = raw.match(/(?:^|\n)\s*Next:\s*(.+)\s*$/im);
1283
+ if (!statusMatch && !verifiedMatch && !nextMatch) return null;
1284
+ return {
1285
+ status: String(statusMatch?.[1] || '').trim().toLowerCase(),
1286
+ verified: String(verifiedMatch?.[1] || '').trim(),
1287
+ next: String(nextMatch?.[1] || '').trim()
1288
+ };
1289
+ }
1290
+
1291
+ export function stripPlanExecutionResult(text) {
1292
+ const raw = String(text || '');
1293
+ if (!parsePlanExecutionResult(raw)) return raw;
1294
+ return raw
1295
+ .replace(/(?:^|\n)\s*Status:\s*(done|partial|blocked)\s*$/im, '')
1296
+ .replace(/(?:^|\n)\s*Verified:\s*.+\s*$/im, '')
1297
+ .replace(/(?:^|\n)\s*Next:\s*.+\s*$/im, '')
1298
+ .replace(/\n{3,}/g, '\n\n')
1299
+ .trim();
1300
+ }
1301
+
1134
1302
  function getTailPreviewWindow(text, maxLines = 3) {
1135
1303
  const source = String(text || '');
1136
1304
  if (!source.trim()) return { lines: [], startLine: 1 };
@@ -1357,7 +1525,7 @@ function finishCodeGeneration(msg, now = Date.now()) {
1357
1525
 
1358
1526
  export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
1359
1527
  const source = Array.isArray(messages) ? messages : [];
1360
- if (!planState || !planState.total) return source;
1528
+ if (!planState || !planState.total || planState.pendingApproval) return source;
1361
1529
  const synthetic = {
1362
1530
  id: `plan-state-${planState.current}-${planState.total}-${planState.role || 'agent'}`,
1363
1531
  label: 'system',
@@ -1403,6 +1571,7 @@ function PlanSummaryBubble({ msg, copy }) {
1403
1571
  ? {
1404
1572
  conclusion: 'Conclusion',
1405
1573
  plan: 'Plan',
1574
+ approval: 'Approval',
1406
1575
  warnings: 'Warnings',
1407
1576
  failed: 'Failed',
1408
1577
  file: 'File',
@@ -1414,6 +1583,7 @@ function PlanSummaryBubble({ msg, copy }) {
1414
1583
  : {
1415
1584
  conclusion: '结论',
1416
1585
  plan: '计划',
1586
+ approval: '审批',
1417
1587
  warnings: '警告',
1418
1588
  failed: '失败',
1419
1589
  file: '文件',
@@ -1467,11 +1637,19 @@ function PlanSummaryBubble({ msg, copy }) {
1467
1637
  summary.planSummary
1468
1638
  ? h(
1469
1639
  Box,
1470
- { marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1640
+ { marginBottom: summary.approval || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1471
1641
  h(Text, { color: 'cyanBright' }, labels.plan),
1472
1642
  h(Text, { color: 'gray' }, summary.planSummary)
1473
1643
  )
1474
1644
  : null,
1645
+ summary.approval
1646
+ ? h(
1647
+ Box,
1648
+ { marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
1649
+ h(Text, { color: 'yellowBright' }, labels.approval),
1650
+ h(Text, { color: 'gray' }, summary.approval)
1651
+ )
1652
+ : null,
1475
1653
  metaItems.length > 0
1476
1654
  ? h(
1477
1655
  Box,
@@ -2444,7 +2622,7 @@ function makeIdleStatus(copy, snapshot, variant = 'ready') {
2444
2622
  );
2445
2623
  }
2446
2624
 
2447
- export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
2625
+ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compatible', language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
2448
2626
  const copy = getCopy(language);
2449
2627
  const stdoutCols = Number(process.stdout?.columns || 120);
2450
2628
  const [inputValue, setInputValue] = useState('');
@@ -2460,6 +2638,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2460
2638
  const [cursorVisible, setCursorVisible] = useState(true);
2461
2639
  const [displaySessionId, setDisplaySessionId] = useState(sessionId);
2462
2640
  const [displayModel, setDisplayModel] = useState(model);
2641
+ const [displaySdkProvider, setDisplaySdkProvider] = useState(sdkProvider);
2463
2642
  const [pendingQueue, setPendingQueue] = useState([]);
2464
2643
  const [loaderTick, setLoaderTick] = useState(0);
2465
2644
  const [runtimeStatus, setRuntimeStatus] = useState(
@@ -2473,7 +2652,12 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2473
2652
  role: '',
2474
2653
  title: '',
2475
2654
  failed: false,
2476
- steps: []
2655
+ steps: [],
2656
+ pendingApproval: false,
2657
+ completed: false,
2658
+ resultStatus: '',
2659
+ resultVerified: '',
2660
+ resultNext: ''
2477
2661
  });
2478
2662
  const [debugKeys, setDebugKeys] = useState(false);
2479
2663
  const [lastKeyDebug, setLastKeyDebug] = useState('');
@@ -2552,6 +2736,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2552
2736
  if (!snapshot) return;
2553
2737
  setDisplaySessionId(snapshot.sessionId || sessionId);
2554
2738
  setDisplayModel(snapshot.model || model);
2739
+ setDisplaySdkProvider(snapshot.sdkProvider || sdkProvider);
2555
2740
  setRuntimeState(snapshot);
2556
2741
  setRuntimeStatus(makeIdleStatus(copy, snapshot, variant));
2557
2742
  };
@@ -2588,6 +2773,11 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2588
2773
  role,
2589
2774
  title,
2590
2775
  failed: false,
2776
+ pendingApproval: false,
2777
+ completed: false,
2778
+ resultStatus: '',
2779
+ resultVerified: '',
2780
+ resultNext: '',
2591
2781
  steps: [...withoutCurrent, { index: current, total, role, title, status: 'active' }].sort((a, b) => a.index - b.index)
2592
2782
  };
2593
2783
  });
@@ -2710,6 +2900,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2710
2900
 
2711
2901
  const appendResultMessage = (result) => {
2712
2902
  if (result.type === 'noop') return;
2903
+ if (Array.isArray(result.restoredMessages)) {
2904
+ setMessages(buildUiMessagesFromSessionHistory(result.restoredMessages, nextId));
2905
+ syncRuntimeVisualState('after');
2906
+ }
2713
2907
  if (
2714
2908
  result.type === 'system' &&
2715
2909
  typeof result.text === 'string' &&
@@ -2737,7 +2931,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2737
2931
  {
2738
2932
  id: nextId(),
2739
2933
  label: 'system',
2740
- text: copy.generic.keyboardDebugStatus(debugKeys),
2934
+ text: sanitizeRenderableText(copy.generic.keyboardDebugStatus(debugKeys)),
2741
2935
  color: 'yellowBright'
2742
2936
  }
2743
2937
  ]);
@@ -2745,13 +2939,14 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2745
2939
  }
2746
2940
  }
2747
2941
  if (result.type === 'assistant') {
2748
- if (!activeAssistantIdRef.current && result.text) {
2942
+ const { displayText } = applyPlanExecutionResult(result.text);
2943
+ if (!activeAssistantIdRef.current && displayText) {
2749
2944
  setMessages((prev) => [
2750
2945
  ...prev,
2751
2946
  {
2752
2947
  id: nextId(),
2753
2948
  label: 'coder',
2754
- text: result.text,
2949
+ text: sanitizeRenderableText(displayText),
2755
2950
  color: 'greenBright',
2756
2951
  autoSkillNames: activeAssistantAutoSkillNamesRef.current
2757
2952
  }
@@ -2760,12 +2955,27 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2760
2955
  return;
2761
2956
  }
2762
2957
  const parsedPlanSummary = result.type === 'system' ? parseAutoPlanSummaryMessage(result.text || '') : null;
2958
+ if (parsedPlanSummary?.approval === 'pending') {
2959
+ setPlanState({
2960
+ current: 0,
2961
+ total: 0,
2962
+ role: '',
2963
+ title: '',
2964
+ failed: false,
2965
+ steps: [],
2966
+ pendingApproval: true,
2967
+ completed: false,
2968
+ resultStatus: '',
2969
+ resultVerified: '',
2970
+ resultNext: ''
2971
+ });
2972
+ }
2763
2973
  setMessages((prev) => [
2764
2974
  ...prev,
2765
2975
  {
2766
2976
  id: nextId(),
2767
2977
  label: 'system',
2768
- text: result.text || '',
2978
+ text: sanitizeRenderableText(result.text || ''),
2769
2979
  color: 'yellowBright',
2770
2980
  ...(parsedPlanSummary ? { planSummary: parsedPlanSummary } : {})
2771
2981
  }
@@ -2812,6 +3022,31 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2812
3022
  });
2813
3023
  };
2814
3024
 
3025
+ const applyPlanExecutionResult = (rawText) => {
3026
+ const parsedExecution = parsePlanExecutionResult(rawText);
3027
+ if (!parsedExecution || !planTextBufferRef.current) return { parsedExecution: null, displayText: rawText };
3028
+ setPlanState((prev) => {
3029
+ if (!prev.total) return prev;
3030
+ return {
3031
+ ...prev,
3032
+ completed: parsedExecution.status === 'done',
3033
+ failed: parsedExecution.status === 'blocked',
3034
+ resultStatus: parsedExecution.status || prev.resultStatus,
3035
+ resultVerified: parsedExecution.verified || prev.resultVerified,
3036
+ resultNext: parsedExecution.next || prev.resultNext,
3037
+ steps: (prev.steps || []).map((step) =>
3038
+ step.index === prev.current && step.status === 'active'
3039
+ ? { ...step, status: parsedExecution.status === 'blocked' ? 'failed' : 'done' }
3040
+ : step
3041
+ )
3042
+ };
3043
+ });
3044
+ return {
3045
+ parsedExecution,
3046
+ displayText: stripPlanExecutionResult(rawText)
3047
+ };
3048
+ };
3049
+
2815
3050
  const ensureActiveAssistant = () => {
2816
3051
  if (activeAssistantIdRef.current) return activeAssistantIdRef.current;
2817
3052
  const aid = nextId();
@@ -2860,7 +3095,19 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2860
3095
  setBusy(true);
2861
3096
  setInputStage('sending');
2862
3097
  setRuntimeStatus(makeStatus(copy.runtime.sendingToGateway, copy.runtime.preparingRequest, 'yellowBright'));
2863
- setPlanState({ current: 0, total: 0, role: '', title: '', failed: false, steps: [] });
3098
+ setPlanState({
3099
+ current: 0,
3100
+ total: 0,
3101
+ role: '',
3102
+ title: '',
3103
+ failed: false,
3104
+ steps: [],
3105
+ pendingApproval: false,
3106
+ completed: false,
3107
+ resultStatus: '',
3108
+ resultVerified: '',
3109
+ resultNext: ''
3110
+ });
2864
3111
  planTextBufferRef.current = '';
2865
3112
  activeAssistantIdRef.current = null;
2866
3113
  activeAssistantAutoSkillNamesRef.current = [];
@@ -2943,16 +3190,19 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2943
3190
  if (hadActiveAssistant) {
2944
3191
  streamedAssistantHandledRef.current = true;
2945
3192
  }
3193
+ const { displayText } = applyPlanExecutionResult(event.text);
2946
3194
  if (targetId && !hasPlannedTools) {
2947
3195
  setMessages((prev) =>
2948
3196
  prev.map((m) => {
2949
3197
  if (m.id !== targetId) return m;
2950
- const responseText = typeof event.text === 'string' ? event.text.trim() : '';
2951
- const shouldSynthesizeCompletion = !responseText && m.syntheticPrelude;
3198
+ const responseText = typeof displayText === 'string' ? displayText.trim() : '';
3199
+ const cleanedExistingText = stripPlanExecutionResult(String(m.text || '')).trim();
3200
+ const finalText = responseText || cleanedExistingText;
3201
+ const shouldSynthesizeCompletion = !finalText && m.syntheticPrelude;
2952
3202
  return {
2953
3203
  ...m,
2954
- ...(responseText
2955
- ? { text: event.text, syntheticPrelude: false }
3204
+ ...(finalText
3205
+ ? { text: finalText, syntheticPrelude: false }
2956
3206
  : shouldSynthesizeCompletion
2957
3207
  ? { text: buildSyntheticCompletionText(m, copy), syntheticPrelude: false }
2958
3208
  : {}),
@@ -2971,9 +3221,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2971
3221
  activeAssistantIdRef.current = null;
2972
3222
  }
2973
3223
  if (!hadActiveAssistant && !hasPlannedTools && event.text) {
3224
+ const cleanedStandaloneText = stripPlanExecutionResult(String(displayText || event.text)).trim();
2974
3225
  setMessages((prev) => [
2975
3226
  ...prev,
2976
- { id: nextId(), label: 'coder', text: event.text, color: 'greenBright' }
3227
+ { id: nextId(), label: 'coder', text: cleanedStandaloneText, color: 'greenBright' }
2977
3228
  ]);
2978
3229
  }
2979
3230
  }
@@ -3212,7 +3463,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3212
3463
  appendResultMessage(result);
3213
3464
  })
3214
3465
  .catch((err) => {
3215
- setRuntimeStatus(makeStatus(copy.runtime.requestFailed, err.message, 'redBright'));
3466
+ const message = sanitizeRenderableText(err?.message || String(err));
3467
+ setRuntimeStatus(makeStatus(copy.runtime.requestFailed, message, 'redBright'));
3216
3468
  setInputStage('idle');
3217
3469
  updateMessageMeta(activeUserMessageIdRef.current, {
3218
3470
  loading: false,
@@ -3228,7 +3480,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3228
3480
  }));
3229
3481
  setMessages((prev) => [
3230
3482
  ...prev,
3231
- { id: nextId(), label: 'error', text: err.message, color: 'redBright' }
3483
+ { id: nextId(), label: 'error', text: message, color: 'redBright' }
3232
3484
  ]);
3233
3485
  })
3234
3486
  .finally(() => {
@@ -3257,7 +3509,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3257
3509
  updateMessageMeta(next.messageId, {
3258
3510
  loading: true,
3259
3511
  phase: 'sending',
3260
- liveStatus: copy.runtime.sendingToGateway
3512
+ liveStatus: copy.runtime.submittedWaiting || copy.runtime.sendingToGateway
3261
3513
  });
3262
3514
  runSubmission(next.line, next.messageId);
3263
3515
  }
@@ -3290,6 +3542,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3290
3542
  appendResultMessage(result);
3291
3543
  })
3292
3544
  .catch((err) => {
3545
+ const message = sanitizeRenderableText(err?.message || String(err));
3293
3546
  updateMessageMeta(userMessageId, {
3294
3547
  loading: false,
3295
3548
  phase: undefined,
@@ -3297,7 +3550,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3297
3550
  });
3298
3551
  setMessages((prev) => [
3299
3552
  ...prev,
3300
- { id: nextId(), label: 'error', text: err.message, color: 'redBright' }
3553
+ { id: nextId(), label: 'error', text: message, color: 'redBright' }
3301
3554
  ]);
3302
3555
  });
3303
3556
  };
@@ -3449,6 +3702,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3449
3702
  const immediateLocal =
3450
3703
  typeof runtime.isImmediateLocalInput === 'function' &&
3451
3704
  runtime.isImmediateLocalInput(line);
3705
+ const pendingUserMeta = getPendingUserMessageMeta(copy, {
3706
+ immediateLocal,
3707
+ inFlight: inFlightRef.current
3708
+ });
3452
3709
  setMessages((prev) => [
3453
3710
  ...prev,
3454
3711
  {
@@ -3457,12 +3714,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3457
3714
  text: line,
3458
3715
  color: 'white',
3459
3716
  loading: true,
3460
- phase: immediateLocal ? 'sending' : inFlightRef.current ? 'queued' : 'sending',
3461
- liveStatus: immediateLocal
3462
- ? copy.runtime.localCommandRunning
3463
- : inFlightRef.current
3464
- ? copy.runtime.queuedWaiting
3465
- : copy.runtime.sendingToGateway
3717
+ phase: pendingUserMeta.phase,
3718
+ liveStatus: pendingUserMeta.liveStatus
3466
3719
  }
3467
3720
  ]);
3468
3721
  if (immediateLocal) {
@@ -3624,7 +3877,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3624
3877
  return h(
3625
3878
  Box,
3626
3879
  { flexDirection: 'column' },
3627
- h(Header, { sessionId: displaySessionId, model: displayModel, shellName, safeMode }),
3880
+ h(Header, { sessionId: displaySessionId, model: displayModel, sdkProvider: displaySdkProvider, shellName, safeMode }),
3628
3881
  h(MessageList, {
3629
3882
  messages: visibleMessages,
3630
3883
  loaderTick,
@@ -21,7 +21,20 @@ export function describeCommandToolActivity(copy, parsed, { done = false, blocke
21
21
  if (parsed.base === 'run') return phaseText(copy, blocked, done, trimText(target, 72) || parsed.base, copy.toolActivity.doingCommand, copy.toolActivity.doneCommand);
22
22
  }
23
23
 
24
- if (parsed.base === 'start_service' || parsed.base === 'list_services' || parsed.base === 'get_service_status' || parsed.base === 'get_service_logs' || parsed.base === 'stop_service') {
24
+ if (parsed.base === 'list_services') {
25
+ return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingListServices, copy.toolActivity.doneListServices);
26
+ }
27
+ if (parsed.base === 'get_service_status') {
28
+ return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingServiceStatus, copy.toolActivity.doneServiceStatus);
29
+ }
30
+ if (parsed.base === 'get_service_logs') {
31
+ return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingServiceLogs, copy.toolActivity.doneServiceLogs);
32
+ }
33
+ if (parsed.base === 'stop_service') {
34
+ return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingStopService, copy.toolActivity.doneStopService);
35
+ }
36
+
37
+ if (parsed.base === 'start_service') {
25
38
  return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
26
39
  }
27
40