codemini-cli 0.1.19 → 0.2.1

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.
@@ -1,6 +1,7 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { Box, Text, useApp, useInput } from 'ink';
3
3
  import { shouldCaptureEscapeSequence } from './input-escape.js';
4
+ import { classifyCommandIntent } from '../core/shell.js';
4
5
 
5
6
  const h = React.createElement;
6
7
  const SUGGESTION_PAGE_SIZE = 8;
@@ -121,8 +122,28 @@ const TUI_COPY = {
121
122
  doingUpdateTask: '正在更新任务',
122
123
  doneGeneric: '已完成工具',
123
124
  doingGeneric: '正在执行工具',
125
+ doneInstall: '已安装依赖',
126
+ doingInstall: '正在安装依赖',
127
+ doneBuild: '已完成构建',
128
+ doingBuild: '正在构建',
129
+ doneTest: '已完成测试',
130
+ doingTest: '正在运行测试',
131
+ doneFrontend: '已启动前端服务',
132
+ doingFrontend: '正在启动前端服务',
133
+ doneBackend: '已启动后端服务',
134
+ doingBackend: '正在启动后端服务',
135
+ doneDatabase: '已启动数据库服务',
136
+ doingDatabase: '正在启动数据库服务',
137
+ doneDocker: '已完成 Docker 命令',
138
+ doingDocker: '正在执行 Docker 命令',
139
+ doneCodeGeneration: '已生成代码',
140
+ doingCodeGeneration: '正在生成代码',
124
141
  doneSkill: '已完成技能',
125
142
  doingSkill: '正在执行技能',
143
+ doneProjectIndex: '已初始化项目索引',
144
+ doingProjectIndex: '正在初始化项目索引',
145
+ doneFileIndex: '已刷新文件索引',
146
+ doingFileIndex: '正在刷新文件索引',
126
147
  toolFailed: (name) => `工具执行失败: ${name}`,
127
148
  waitingModelContinue: (detail) => `${detail},等待模型继续`,
128
149
  waitingModelAdjust: (detail) => `${detail},等待模型调整`
@@ -154,6 +175,7 @@ const TUI_COPY = {
154
175
  skillRunning: '技能执行中',
155
176
  skillCompleted: '技能已完成',
156
177
  skillFailed: '技能执行失败',
178
+ autoSkillInjected: (names) => `自动启用技能: ${names.map((name) => `/${name}`).join(', ')}`,
157
179
  compactingContext: '正在压缩上下文',
158
180
  autoCompactTriggered: (mode, threshold) => `自动压缩已触发(${mode},阈值 ${threshold}%)`,
159
181
  requestFailed: '请求失败',
@@ -230,8 +252,28 @@ const TUI_COPY = {
230
252
  doingUpdateTask: 'Updating task',
231
253
  doneGeneric: 'Completed tool',
232
254
  doingGeneric: 'Running tool',
255
+ doneInstall: 'Dependencies installed',
256
+ doingInstall: 'Installing dependencies',
257
+ doneBuild: 'Build completed',
258
+ doingBuild: 'Building',
259
+ doneTest: 'Tests completed',
260
+ doingTest: 'Running tests',
261
+ doneFrontend: 'Frontend started',
262
+ doingFrontend: 'Starting frontend service',
263
+ doneBackend: 'Backend started',
264
+ doingBackend: 'Starting backend service',
265
+ doneDatabase: 'Database started',
266
+ doingDatabase: 'Starting database service',
267
+ doneDocker: 'Docker command completed',
268
+ doingDocker: 'Running Docker command',
269
+ doneCodeGeneration: 'Code generated',
270
+ doingCodeGeneration: 'Generating code',
233
271
  doneSkill: 'Completed skill',
234
272
  doingSkill: 'Running skill',
273
+ doneProjectIndex: 'Project index initialized',
274
+ doingProjectIndex: 'Initializing project index',
275
+ doneFileIndex: 'File index refreshed',
276
+ doingFileIndex: 'Refreshing file index',
235
277
  toolFailed: (name) => `Tool failed: ${name}`,
236
278
  waitingModelContinue: (detail) => `${detail}, waiting for model to continue`,
237
279
  waitingModelAdjust: (detail) => `${detail}, waiting for model to adjust`
@@ -263,6 +305,7 @@ const TUI_COPY = {
263
305
  skillRunning: 'skill running',
264
306
  skillCompleted: 'skill completed',
265
307
  skillFailed: 'skill failed',
308
+ autoSkillInjected: (names) => `auto-enabled skills: ${names.map((name) => `/${name}`).join(', ')}`,
266
309
  compactingContext: 'compacting context',
267
310
  autoCompactTriggered: (mode, threshold) => `auto-compact triggered (${mode}, threshold ${threshold}%)`,
268
311
  requestFailed: 'request failed',
@@ -308,6 +351,14 @@ function trimText(value, maxLen = 88) {
308
351
  return `${text.slice(0, maxLen - 3)}...`;
309
352
  }
310
353
 
354
+ function safeJsonParse(raw) {
355
+ try {
356
+ return JSON.parse(String(raw || '{}'));
357
+ } catch {
358
+ return null;
359
+ }
360
+ }
361
+
311
362
  function parseToolDisplayName(name) {
312
363
  const raw = String(name || '').trim();
313
364
  const match = raw.match(/^([^(]+)\((.*)\)$/);
@@ -318,14 +369,82 @@ function parseToolDisplayName(name) {
318
369
  };
319
370
  }
320
371
 
372
+ function isCodeGenerationActivityName(name) {
373
+ return String(name || '').trim() === 'Code generation';
374
+ }
375
+
376
+ function formatDurationMs(ms) {
377
+ const safeMs = Math.max(0, Number(ms) || 0);
378
+ return `${(safeMs / 1000).toFixed(1)}s`;
379
+ }
380
+
381
+ function getIntentLabel(kind) {
382
+ switch (kind) {
383
+ case 'install':
384
+ return 'Install';
385
+ case 'build':
386
+ return 'Build';
387
+ case 'test':
388
+ return 'Test';
389
+ case 'frontend-service':
390
+ return 'Frontend';
391
+ case 'backend-service':
392
+ return 'Backend';
393
+ case 'database-service':
394
+ return 'Database';
395
+ case 'docker-service':
396
+ return 'Docker';
397
+ case 'service':
398
+ return 'Service';
399
+ default:
400
+ return 'Run';
401
+ }
402
+ }
403
+
404
+ export function formatActivityDurationText(row, nowMs = Date.now()) {
405
+ if (!row) return '';
406
+ if (row.status === 'running' && Number.isFinite(Number(row.startedAt))) {
407
+ const startedAt = Number(row.startedAt);
408
+ const endedAt = Number(row.endedAt);
409
+ const elapsed = Number.isFinite(endedAt) && endedAt > startedAt ? endedAt - startedAt : Math.max(0, Number(nowMs) - startedAt);
410
+ return formatDurationMs(elapsed);
411
+ }
412
+ if (typeof row.durationText === 'string' && row.durationText.trim()) {
413
+ return row.durationText.trim();
414
+ }
415
+ if (Number.isFinite(Number(row.durationMs))) {
416
+ return formatDurationMs(Number(row.durationMs));
417
+ }
418
+ return '';
419
+ }
420
+
321
421
  function getActivityDisplayParts(activity) {
422
+ if (isCodeGenerationActivityName(activity?.name)) {
423
+ return {
424
+ primary: 'Code',
425
+ secondary: ' (generation)'
426
+ };
427
+ }
428
+ const parsed = parseToolDisplayName(activity?.name);
429
+ if (parsed.base === 'run' || parsed.base === 'start_service') {
430
+ const intent = classifyCommandIntent(parsed.target);
431
+ return {
432
+ primary: getIntentLabel(intent.kind),
433
+ secondary: parsed.target ? `(${parsed.target})` : ''
434
+ };
435
+ }
322
436
  if ((activity?.type || 'tool') === 'skill') {
323
437
  return {
324
438
  primary: `Skill`,
325
439
  secondary: `(${activity?.name || 'unknown'})`
326
440
  };
327
441
  }
328
- const parsed = parseToolDisplayName(activity?.name);
442
+ if ((activity?.type || 'tool') === 'system_tool') {
443
+ return {
444
+ primary: 'Index',
445
+ secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
446
+ };
447
+ }
329
448
  const labels = {
330
449
  read: 'Read',
331
450
  edit: 'Edit',
@@ -351,6 +470,89 @@ function getActivityDisplayParts(activity) {
351
470
  }
352
471
 
353
472
  function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
473
+ const parsed = parseToolDisplayName(name);
474
+ if (parsed.base === 'project_index') {
475
+ return blocked
476
+ ? `${copy.toolActivity.blocked}: project index`
477
+ : done
478
+ ? copy.toolActivity.doneProjectIndex
479
+ : copy.toolActivity.doingProjectIndex;
480
+ }
481
+ if (parsed.base === 'file_index') {
482
+ const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
483
+ return blocked
484
+ ? `${copy.toolActivity.blocked}: ${safeTarget}`
485
+ : done
486
+ ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}`
487
+ : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
488
+ }
489
+ if (parsed.base === 'run' || parsed.base === 'start_service') {
490
+ const intent = classifyCommandIntent(parsed.target);
491
+ const target = parsed.target || intent.kind || 'command';
492
+ if (intent.kind === 'install') {
493
+ return blocked
494
+ ? `${copy.toolActivity.blocked}: ${target}`
495
+ : done
496
+ ? `${copy.toolActivity.doneInstall}: ${target}`
497
+ : `${copy.toolActivity.doingInstall}: ${target}`;
498
+ }
499
+ if (intent.kind === 'build') {
500
+ return blocked
501
+ ? `${copy.toolActivity.blocked}: ${target}`
502
+ : done
503
+ ? `${copy.toolActivity.doneBuild}: ${target}`
504
+ : `${copy.toolActivity.doingBuild}: ${target}`;
505
+ }
506
+ if (intent.kind === 'test') {
507
+ return blocked
508
+ ? `${copy.toolActivity.blocked}: ${target}`
509
+ : done
510
+ ? `${copy.toolActivity.doneTest}: ${target}`
511
+ : `${copy.toolActivity.doingTest}: ${target}`;
512
+ }
513
+ if (intent.kind === 'frontend-service') {
514
+ return blocked
515
+ ? `${copy.toolActivity.blocked}: ${target}`
516
+ : done
517
+ ? `${copy.toolActivity.doneFrontend}: ${target}`
518
+ : `${copy.toolActivity.doingFrontend}: ${target}`;
519
+ }
520
+ if (intent.kind === 'backend-service') {
521
+ return blocked
522
+ ? `${copy.toolActivity.blocked}: ${target}`
523
+ : done
524
+ ? `${copy.toolActivity.doneBackend}: ${target}`
525
+ : `${copy.toolActivity.doingBackend}: ${target}`;
526
+ }
527
+ if (intent.kind === 'database-service') {
528
+ return blocked
529
+ ? `${copy.toolActivity.blocked}: ${target}`
530
+ : done
531
+ ? `${copy.toolActivity.doneDatabase}: ${target}`
532
+ : `${copy.toolActivity.doingDatabase}: ${target}`;
533
+ }
534
+ if (intent.kind === 'docker-service') {
535
+ return blocked
536
+ ? `${copy.toolActivity.blocked}: ${target}`
537
+ : done
538
+ ? `${copy.toolActivity.doneDocker}: ${target}`
539
+ : `${copy.toolActivity.doingDocker}: ${target}`;
540
+ }
541
+ if (intent.kind === 'service') {
542
+ return blocked
543
+ ? `${copy.toolActivity.blocked}: ${target}`
544
+ : done
545
+ ? `${copy.toolActivity.doneGeneric}: ${target}`
546
+ : `${copy.toolActivity.doingGeneric}: ${target}`;
547
+ }
548
+ }
549
+ if (isCodeGenerationActivityName(name)) {
550
+ return blocked
551
+ ? `${copy.toolActivity.blocked}: code generation`
552
+ : done
553
+ ? copy.toolActivity.doneCodeGeneration
554
+ : copy.toolActivity.doingCodeGeneration;
555
+ }
354
556
  const { raw, base, target } = parseToolDisplayName(name);
355
557
  const safeTarget = trimText(target, 72);
356
558
  if (base === 'read') {
@@ -417,6 +619,21 @@ function describeSkillActivity(name, copy, { done = false, failed = false } = {}
417
619
  return `${copy.toolActivity.doingSkill}: /${name}`;
418
620
  }
419
621
 
622
+ function describeAutoSkillActivity(names, copy) {
623
+ const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
624
+ if (safeNames.length === 0) return '';
625
+ return copy.runtime.autoSkillInjected(safeNames);
626
+ }
627
+
628
+ function formatAutoSkillBadge(names, copy) {
629
+ const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
630
+ if (safeNames.length === 0) return '';
631
+ const [first, ...rest] = safeNames;
632
+ const suffix = rest.length > 0 ? ` +${rest.length}` : '';
633
+ const prefix = copy?.roleLabels?.system === 'SYSTEM' ? 'AUTO' : '自动';
634
+ return `${prefix} /${first}${suffix}`;
635
+ }
636
+
420
637
  function normalizeRuntimeStatus(status, copy) {
421
638
  if (status && typeof status === 'object') {
422
639
  return {
@@ -577,8 +794,11 @@ function PlanStrip({ planState, copy }) {
577
794
  );
578
795
  }
579
796
 
580
- function Header({ sessionId, model, shellName }) {
797
+ function Header({ sessionId, model, shellName, safeMode = true }) {
581
798
  const shortSession = String(sessionId || '').slice(-12) || '-';
799
+ const modeValue = safeMode ? 'SAFE' : 'OPEN';
800
+ const modeColor = safeMode ? 'greenBright' : 'redBright';
801
+ const modeTextColor = safeMode ? 'black' : 'white';
582
802
  return h(
583
803
  Box,
584
804
  { width: '100%', justifyContent: 'center', marginTop: 1, marginBottom: 2 },
@@ -593,12 +813,6 @@ function Header({ sessionId, model, shellName }) {
593
813
  alignItems: 'center',
594
814
  minWidth: 88
595
815
  },
596
- h(
597
- Box,
598
- { width: '100%', justifyContent: 'space-between', marginBottom: 1 },
599
- h(Text, { color: 'cyan' }, 'CLI'),
600
- h(Text, { color: 'greenBright' }, 'SAFE')
601
- ),
602
816
  ...BANNER.map((line, idx) =>
603
817
  h(
604
818
  Box,
@@ -613,8 +827,9 @@ function Header({ sessionId, model, shellName }) {
613
827
  Box,
614
828
  { flexDirection: 'row', justifyContent: 'center' },
615
829
  h(StatusPill, { label: 'MODEL', value: model, color: 'cyanBright', textColor: 'black' }),
616
- h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'greenBright', textColor: 'black' }),
617
- h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' })
830
+ h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'yellowBright', textColor: 'black' }),
831
+ h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' }),
832
+ h(StatusPill, { label: 'MODE', value: modeValue, color: modeColor, textColor: modeTextColor })
618
833
  )
619
834
  )
620
835
  );
@@ -690,6 +905,188 @@ export function parsePlanProgressLine(text) {
690
905
  };
691
906
  }
692
907
 
908
+ function getTailPreviewLines(text, maxLines = 3) {
909
+ const source = String(text || '');
910
+ if (!source.trim()) return [];
911
+
912
+ const lines = source.split('\n').map((line) => line.replace(/\r$/, ''));
913
+ let insideFence = false;
914
+ let fenceLines = [];
915
+ let latestClosedFenceLines = [];
916
+
917
+ for (const line of lines) {
918
+ const trimmed = line.trim();
919
+ if (trimmed.startsWith('```')) {
920
+ if (insideFence) {
921
+ latestClosedFenceLines = fenceLines.slice();
922
+ insideFence = false;
923
+ fenceLines = [];
924
+ continue;
925
+ }
926
+ insideFence = true;
927
+ fenceLines = [];
928
+ continue;
929
+ }
930
+ if (insideFence) {
931
+ fenceLines.push(line);
932
+ }
933
+ }
934
+
935
+ if (insideFence) {
936
+ const codeLines = fenceLines.filter((line) => line.trim().length > 0);
937
+ if (codeLines.length > 0) {
938
+ return codeLines.slice(-Math.max(1, maxLines));
939
+ }
940
+ }
941
+
942
+ const closedFenceLines = latestClosedFenceLines.filter((line) => line.trim().length > 0);
943
+ if (closedFenceLines.length > 0) {
944
+ return closedFenceLines.slice(-Math.max(1, maxLines));
945
+ }
946
+
947
+ const tailLines = source
948
+ .split('\n')
949
+ .map((line) => line.replace(/\r$/, ''))
950
+ .filter((line) => line.trim().length > 0);
951
+ if (tailLines.length === 0) return [];
952
+ return tailLines.slice(-Math.max(1, maxLines));
953
+ }
954
+
955
+ function collectPreviewStrings(value, out = []) {
956
+ if (out.length >= 3 || value == null) return out;
957
+ if (typeof value === 'string') {
958
+ if (value.trim()) out.push(value);
959
+ return out;
960
+ }
961
+ if (Array.isArray(value)) {
962
+ for (const item of value) {
963
+ collectPreviewStrings(item, out);
964
+ if (out.length >= 3) break;
965
+ }
966
+ return out;
967
+ }
968
+ if (typeof value !== 'object') return out;
969
+
970
+ const priorityKeys = ['content', 'new_content', 'new_text', 'patch', 'text', 'code', 'body', 'script', 'source', 'value'];
971
+ if (value.edit && typeof value.edit === 'object') {
972
+ collectPreviewStrings(value.edit, out);
973
+ }
974
+ for (const key of priorityKeys) {
975
+ if (out.length >= 3) break;
976
+ collectPreviewStrings(value[key], out);
977
+ }
978
+ return out;
979
+ }
980
+
981
+ function extractPreviewTextFromRawArguments(raw) {
982
+ const source = String(raw || '');
983
+ if (!source.trim()) return '';
984
+
985
+ const contentMatch = source.match(/"(content|new_content|new_text|patch|code|body|script|source|value)"\s*:\s*"([\s\S]*)$/);
986
+ if (!contentMatch) return '';
987
+
988
+ return contentMatch[2]
989
+ .replace(/\\n/g, '\n')
990
+ .replace(/\\"/g, '"')
991
+ .replace(/\\\\/g, '\\')
992
+ .replace(/",?\s*$/g, '')
993
+ .trim();
994
+ }
995
+
996
+ function compactPreviewLine(line, maxChars = 56) {
997
+ const text = String(line || '').replace(/\t/g, ' ').trimEnd();
998
+ if (!text) return '';
999
+ if (text.length <= maxChars) return text;
1000
+ return `${text.slice(0, Math.max(1, maxChars - 3))}...`;
1001
+ }
1002
+
1003
+ function getLatestToolPreviewLines(msg, maxLines = 3) {
1004
+ const toolCalls = [
1005
+ ...(Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : []),
1006
+ ...(Array.isArray(msg?.toolCalls) ? msg.toolCalls : [])
1007
+ ];
1008
+ const codeTools = new Set(['edit', 'write', 'patch', 'generate_diff']);
1009
+ for (let index = toolCalls.length - 1; index >= 0; index -= 1) {
1010
+ const tool = toolCalls[index];
1011
+ const parsed = parseToolDisplayName(tool?.name);
1012
+ if (!codeTools.has(parsed.base)) continue;
1013
+ const rawArgumentPreview =
1014
+ typeof tool?.arguments === 'string' ? extractPreviewTextFromRawArguments(tool.arguments) : '';
1015
+ const previewSource = rawArgumentPreview
1016
+ ? [rawArgumentPreview]
1017
+ : collectPreviewStrings(tool?.arguments || tool?.content || tool?.summary || []);
1018
+ if (previewSource.length === 0) continue;
1019
+ const combined = previewSource.join('\n');
1020
+ const previewLines = getTailPreviewLines(combined, maxLines);
1021
+ if (previewLines.length > 0) return previewLines.map((line) => compactPreviewLine(line));
1022
+ }
1023
+ return [];
1024
+ }
1025
+
1026
+ export function getGeneratingCodePlaceholderRows(msg, copy, contentWidth = 72) {
1027
+ const liveStatus = String(msg?.liveStatus || '').trim();
1028
+ if (!msg?.loading || (msg?.phase !== 'generating' && msg?.phase !== 'tooling')) return [];
1029
+ if (liveStatus !== String(copy?.runtime?.generatingCode || '').trim()) return [];
1030
+
1031
+ const previewLines = getLatestToolPreviewLines(msg, 3);
1032
+ if (previewLines.length === 0) return [];
1033
+
1034
+ return previewLines.map((line, idx) => ({
1035
+ kind: 'code-placeholder',
1036
+ lineNo: idx + 1,
1037
+ text: line,
1038
+ color: 'gray'
1039
+ }));
1040
+ }
1041
+
1042
+ export function getCodeGenerationActivityRows(msg) {
1043
+ const startedAt = Number(msg?.codeGenerationStartedAt);
1044
+ const endedAt = Number(msg?.codeGenerationEndedAt);
1045
+ if (!startedAt || !msg?.loading || endedAt > 0) return [];
1046
+
1047
+ const status = 'running';
1048
+ const durationMs = Math.max(0, Date.now() - startedAt);
1049
+
1050
+ return [
1051
+ {
1052
+ kind: 'activity',
1053
+ activityType: 'tool',
1054
+ name: 'Code generation',
1055
+ status,
1056
+ statusIcon: status === 'done' ? '✓' : '…',
1057
+ statusColor: status === 'done' ? 'greenBright' : 'yellow',
1058
+ durationMs,
1059
+ durationText: formatDurationMs(durationMs),
1060
+ isLatestTool: true,
1061
+ synthetic: true
1062
+ }
1063
+ ];
1064
+ }
1065
+
1066
+ export function ensureCodeGenerationTiming(msg, now = Date.now()) {
1067
+ if (!msg || msg.codeGenerationStartedAt) return msg;
1068
+ return {
1069
+ ...msg,
1070
+ codeGenerationStartedAt: now,
1071
+ codeGenerationEndedAt: undefined
1072
+ };
1073
+ }
1074
+
1075
+ export function shouldAppendAssistantResult(result, activeAssistantId, streamedAssistantHandled = false) {
1076
+ if (result?.type !== 'assistant') return true;
1077
+ if (streamedAssistantHandled) return false;
1078
+ return !activeAssistantId;
1079
+ }
1080
+
1081
+ function finishCodeGeneration(msg, now = Date.now()) {
1082
+ if (!msg?.codeGenerationStartedAt || msg?.codeGenerationEndedAt) return msg;
1083
+ return {
1084
+ ...msg,
1085
+ codeGenerationEndedAt: now,
1086
+ pendingToolCalls: []
1087
+ };
1088
+ }
1089
+
693
1090
  export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
694
1091
  const source = Array.isArray(messages) ? messages : [];
695
1092
  if (!planState || !planState.total) return source;
@@ -915,6 +1312,7 @@ function isCodeActivityName(name) {
915
1312
  const parsed = parseToolDisplayName(name);
916
1313
  return new Set([
917
1314
  'edit',
1315
+ 'write',
918
1316
  'write_file',
919
1317
  'patch',
920
1318
  'replace_text',
@@ -943,6 +1341,23 @@ export function splitMessageRows(rows) {
943
1341
  return { textRows, codeRows };
944
1342
  }
945
1343
 
1344
+ export function insertRowsAfterLastCodeRow(rows, extraRows) {
1345
+ const source = Array.isArray(rows) ? rows : [];
1346
+ const inserts = Array.isArray(extraRows) ? extraRows.filter(Boolean) : [];
1347
+ if (inserts.length === 0) return source.slice();
1348
+
1349
+ let insertIndex = -1;
1350
+ for (let index = source.length - 1; index >= 0; index -= 1) {
1351
+ if (source[index]?.kind === 'code') {
1352
+ insertIndex = index + 1;
1353
+ break;
1354
+ }
1355
+ }
1356
+
1357
+ if (insertIndex === -1) return [...source, ...inserts];
1358
+ return [...source.slice(0, insertIndex), ...inserts, ...source.slice(insertIndex)];
1359
+ }
1360
+
946
1361
  export function normalizeActivitySpacingRows(inputRows) {
947
1362
  const rows = Array.isArray(inputRows) ? inputRows : [];
948
1363
  const normalized = [];
@@ -1043,7 +1458,7 @@ export function mergeActivitySummary(previousSummary, nextSummary, activityName)
1043
1458
  return lines.join('\n');
1044
1459
  }
1045
1460
 
1046
- function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
1461
+ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
1047
1462
  const rows = [];
1048
1463
  const pushTextRows = (text) => {
1049
1464
  const lines = String(text || '').split('\n');
@@ -1110,10 +1525,12 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
1110
1525
  };
1111
1526
 
1112
1527
  if (Array.isArray(msg?.segments) && msg.segments.length > 0) {
1113
- const totalTools = msg.segments.filter((segment) => segment.type === 'tool' || segment.type === 'skill').length;
1528
+ const totalTools = msg.segments.filter(
1529
+ (segment) => segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool'
1530
+ ).length;
1114
1531
  let toolIndex = 0;
1115
1532
  for (const segment of msg.segments) {
1116
- if (segment.type === 'tool' || segment.type === 'skill') {
1533
+ if (segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool') {
1117
1534
  pushActivityRows(segment, toolIndex, totalTools);
1118
1535
  toolIndex += 1;
1119
1536
  } else {
@@ -1126,18 +1543,23 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
1126
1543
  toolCalls.forEach((tool, idx) => pushActivityRows(tool, idx, toolCalls.length));
1127
1544
  }
1128
1545
 
1546
+ const codeGenerationRows = getCodeGenerationActivityRows(msg);
1547
+ const generatingCodeRows = getGeneratingCodePlaceholderRows(msg, copy, contentWidth);
1548
+ const syntheticRows = [...codeGenerationRows, ...generatingCodeRows];
1129
1549
  if (msg?.loading && (msg?.liveStatus || msg?.phase)) {
1550
+ const statusRows = [];
1130
1551
  pushWrappedRow(
1131
- rows,
1552
+ statusRows,
1132
1553
  {
1133
1554
  kind: 'status',
1134
1555
  text: trimText(msg.liveStatus || msg.phase, 144)
1135
1556
  },
1136
1557
  Math.max(8, contentWidth - 2)
1137
1558
  );
1559
+ syntheticRows.push(...statusRows);
1138
1560
  }
1139
1561
 
1140
- return normalizeActivitySpacingRows(rows);
1562
+ return normalizeActivitySpacingRows(insertRowsAfterLastCodeRow(rows, syntheticRows));
1141
1563
  }
1142
1564
 
1143
1565
  function renderMessageRow(msg, row, idx, loaderTick) {
@@ -1145,21 +1567,24 @@ function renderMessageRow(msg, row, idx, loaderTick) {
1145
1567
  const activity = { type: row.activityType, name: row.name, status: row.status };
1146
1568
  const display = getActivityDisplayParts(activity);
1147
1569
  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';
1570
+ row.status === 'error' || row.status === 'blocked'
1571
+ ? 'redBright'
1572
+ : row.status === 'done'
1573
+ ? 'greenBright'
1574
+ : 'yellowBright';
1155
1575
  const textColor =
1156
1576
  activity.type === 'skill'
1157
1577
  ? row.status === 'error'
1158
1578
  ? 'redBright'
1159
1579
  : 'cyanBright'
1580
+ : activity.type === 'system_tool'
1581
+ ? row.status === 'error' || row.status === 'blocked'
1582
+ ? 'redBright'
1583
+ : 'blueBright'
1160
1584
  : row.status === 'error' || row.status === 'blocked'
1161
1585
  ? 'redBright'
1162
- : 'greenBright';
1586
+ : 'cyanBright';
1587
+ const durationText = formatActivityDurationText(row);
1163
1588
  return h(
1164
1589
  Box,
1165
1590
  { key: `row-tool-${msg.id}-${idx}` },
@@ -1168,7 +1593,7 @@ function renderMessageRow(msg, row, idx, loaderTick) {
1168
1593
  h(Text, { color: 'gray' }, ' '),
1169
1594
  h(Text, { color: textColor }, display.primary),
1170
1595
  h(Text, { color: 'gray' }, display.secondary),
1171
- row.durationText ? h(Text, { color: row.statusColor }, ` ${row.durationText}`) : null
1596
+ durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null
1172
1597
  );
1173
1598
  }
1174
1599
  if (row.kind === 'activity-summary') {
@@ -1232,6 +1657,15 @@ function renderMessageRow(msg, row, idx, loaderTick) {
1232
1657
  h(Text, { color: 'gray' }, row.text)
1233
1658
  );
1234
1659
  }
1660
+ if (row.kind === 'code-placeholder') {
1661
+ return h(
1662
+ Box,
1663
+ { key: `row-code-placeholder-${msg.id}-${idx}`, marginLeft: 1 },
1664
+ h(Text, { color: 'gray', dimColor: true }, String(row.lineNo || idx + 1).padStart(2, ' ')),
1665
+ h(Text, { color: 'gray' }, ' │ '),
1666
+ h(Text, { color: 'gray', dimColor: true }, row.text)
1667
+ );
1668
+ }
1235
1669
  return renderTextLine(msg, row.text, idx, row.color);
1236
1670
  }
1237
1671
 
@@ -1349,11 +1783,12 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
1349
1783
  return h(PlanSummaryBubble, { msg, copy });
1350
1784
  }
1351
1785
  const theme = roleStyle(msg.label);
1352
- const allRows = buildMessageRows(msg, showToolDetails, contentWidth);
1786
+ const allRows = buildMessageRows(msg, showToolDetails, contentWidth, copy);
1353
1787
  const start = rowWindow ? Math.max(0, rowWindow.start || 0) : 0;
1354
1788
  const end = rowWindow ? Math.max(start, rowWindow.end || allRows.length) : allRows.length;
1355
1789
  const visibleRows = allRows.slice(start, end);
1356
1790
  const rendered = renderMessageRowsInOrder(msg, visibleRows, loaderTick, copy);
1791
+ const autoSkillBadge = formatAutoSkillBadge(msg.autoSkillNames, copy);
1357
1792
 
1358
1793
  return h(
1359
1794
  Box,
@@ -1377,7 +1812,9 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
1377
1812
  null,
1378
1813
  h(Text, { color: theme.badgeText, backgroundColor: theme.badgeBg }, ` ${messageLabel(msg.label, copy)} `)
1379
1814
  ),
1380
- h(Text, { color: theme.chrome }, ' ')
1815
+ autoSkillBadge
1816
+ ? h(Text, { color: 'blueBright' }, autoSkillBadge)
1817
+ : h(Text, { color: theme.chrome }, ' ')
1381
1818
  ),
1382
1819
  ...rendered
1383
1820
  )
@@ -1530,7 +1967,7 @@ function InputBar({
1530
1967
  return h(
1531
1968
  Box,
1532
1969
  {
1533
- marginTop: 1,
1970
+ marginTop: 0,
1534
1971
  flexDirection: 'column',
1535
1972
  borderStyle: 'round',
1536
1973
  borderColor: 'cyan',
@@ -1613,7 +2050,7 @@ function makeIdleStatus(copy, snapshot, variant = 'ready') {
1613
2050
  );
1614
2051
  }
1615
2052
 
1616
- export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '' }) {
2053
+ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
1617
2054
  const copy = getCopy(language);
1618
2055
  const stdoutCols = Number(process.stdout?.columns || 120);
1619
2056
  const [inputValue, setInputValue] = useState('');
@@ -1648,12 +2085,31 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1648
2085
  const [lastKeyDebug, setLastKeyDebug] = useState('');
1649
2086
  const [showToolDetails, setShowToolDetails] = useState(false);
1650
2087
  const activeAssistantIdRef = useRef(null);
2088
+ const activeAssistantAutoSkillNamesRef = useRef([]);
2089
+ const streamedAssistantHandledRef = useRef(false);
1651
2090
  const activeUserMessageIdRef = useRef(null);
1652
2091
  const cursorIndexRef = useRef(0);
1653
2092
  const inFlightRef = useRef(false);
1654
2093
  const messagesRef = useRef([]);
1655
2094
  const pendingQueueRef = useRef([]);
1656
2095
  const deltaBufferRef = useRef('');
2096
+
2097
+ useEffect(() => {
2098
+ const rawStartupActivities = runtime.consumeStartupEvents?.();
2099
+ const startupActivities = Array.isArray(rawStartupActivities) ? rawStartupActivities : [];
2100
+ if (startupActivities.length === 0) return;
2101
+ setMessages((prev) => [
2102
+ ...prev,
2103
+ {
2104
+ id: nextId(),
2105
+ label: 'system',
2106
+ text: '',
2107
+ color: 'yellowBright',
2108
+ toolCalls: startupActivities,
2109
+ segments: startupActivities
2110
+ }
2111
+ ]);
2112
+ }, [runtime]);
1657
2113
  const deltaFlushTimerRef = useRef(null);
1658
2114
  const escSeqRef = useRef('');
1659
2115
  const planTextBufferRef = useRef('');
@@ -1758,7 +2214,12 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1758
2214
  } else {
1759
2215
  segments.push({ type: 'text', text: delta });
1760
2216
  }
1761
- return { ...m, text: `${m.text}${delta}`, segments };
2217
+ const nextText = `${m.text}${delta}`;
2218
+ return {
2219
+ ...m,
2220
+ text: nextText,
2221
+ segments
2222
+ };
1762
2223
  })
1763
2224
  );
1764
2225
  };
@@ -1782,6 +2243,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1782
2243
  const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
1783
2244
  const activityType = toolEvent.type || 'tool';
1784
2245
  const idx = findActivityUpdateIndex(toolCalls, toolEvent);
2246
+ const startedAt = toolEvent.status === 'running' ? Date.now() : undefined;
1785
2247
 
1786
2248
  if (idx === -1) {
1787
2249
  toolCalls.push({
@@ -1789,6 +2251,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1789
2251
  id: toolEvent.id || '',
1790
2252
  name: toolEvent.name,
1791
2253
  status: toolEvent.status,
2254
+ ...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
2255
+ ...(startedAt ? { startedAt } : {}),
1792
2256
  ...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
1793
2257
  ...(toolEvent.summary ? { summary: toolEvent.summary } : {})
1794
2258
  });
@@ -1798,6 +2262,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1798
2262
  type: activityType,
1799
2263
  id: toolEvent.id || toolCalls[idx].id,
1800
2264
  status: toolEvent.status,
2265
+ ...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
2266
+ ...(startedAt ? { startedAt } : {}),
1801
2267
  ...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
1802
2268
  ...(toolEvent.summary
1803
2269
  ? { summary: mergeActivitySummary(toolCalls[idx].summary, toolEvent.summary, toolEvent.name) }
@@ -1811,6 +2277,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1811
2277
  id: toolEvent.id || '',
1812
2278
  name: toolEvent.name,
1813
2279
  status: toolEvent.status,
2280
+ ...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
2281
+ ...(startedAt ? { startedAt } : {}),
1814
2282
  ...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
1815
2283
  ...(toolEvent.summary ? { summary: toolEvent.summary } : {})
1816
2284
  };
@@ -1875,7 +2343,13 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1875
2343
  if (!activeAssistantIdRef.current && result.text) {
1876
2344
  setMessages((prev) => [
1877
2345
  ...prev,
1878
- { id: nextId(), label: 'coder', text: result.text, color: 'greenBright' }
2346
+ {
2347
+ id: nextId(),
2348
+ label: 'coder',
2349
+ text: result.text,
2350
+ color: 'greenBright',
2351
+ autoSkillNames: activeAssistantAutoSkillNamesRef.current
2352
+ }
1879
2353
  ]);
1880
2354
  }
1881
2355
  return;
@@ -1899,8 +2373,38 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1899
2373
  setMessages((prev) => prev.map((m) => (m.id === targetId ? { ...m, ...patch } : m)));
1900
2374
  };
1901
2375
 
2376
+ const updatePendingToolCallOnActiveAssistant = (toolCall) => {
2377
+ const targetId = activeAssistantIdRef.current;
2378
+ if (!targetId || !toolCall) return;
2379
+ setMessages((prev) =>
2380
+ prev.map((m) => {
2381
+ if (m.id !== targetId) return m;
2382
+ const pendingToolCalls = Array.isArray(m.pendingToolCalls) ? [...m.pendingToolCalls] : [];
2383
+ const nextCall = {
2384
+ id: toolCall.id || '',
2385
+ name: toolCall.name || '',
2386
+ arguments: typeof toolCall.arguments === 'string' ? safeJsonParse(toolCall.arguments) ?? toolCall.arguments : toolCall.arguments,
2387
+ status: 'pending',
2388
+ type: 'tool'
2389
+ };
2390
+ const idx = pendingToolCalls.findIndex((entry) => entry.id && entry.id === nextCall.id);
2391
+ if (idx === -1) pendingToolCalls.push(nextCall);
2392
+ else pendingToolCalls[idx] = { ...pendingToolCalls[idx], ...nextCall };
2393
+ return { ...m, pendingToolCalls };
2394
+ })
2395
+ );
2396
+ };
2397
+
1902
2398
  const finalizeActiveAssistant = () => {
1903
- setActiveAssistantMeta({ loading: false, phase: undefined, liveStatus: undefined, planStep: undefined });
2399
+ setActiveAssistantMeta({
2400
+ loading: false,
2401
+ phase: undefined,
2402
+ liveStatus: undefined,
2403
+ planStep: undefined,
2404
+ pendingToolCalls: [],
2405
+ codeGenerationEndedAt: undefined,
2406
+ autoSkillNames: activeAssistantAutoSkillNamesRef.current
2407
+ });
1904
2408
  };
1905
2409
 
1906
2410
  const ensureActiveAssistant = () => {
@@ -1918,7 +2422,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1918
2422
  segments: [],
1919
2423
  loading: true,
1920
2424
  phase: 'thinking',
1921
- liveStatus: copy.runtime.modelThinking
2425
+ liveStatus: copy.runtime.modelThinking,
2426
+ autoSkillNames: activeAssistantAutoSkillNamesRef.current
1922
2427
  }
1923
2428
  ]);
1924
2429
  return aid;
@@ -1933,11 +2438,14 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1933
2438
  setPlanState({ current: 0, total: 0, role: '', title: '', failed: false, steps: [] });
1934
2439
  planTextBufferRef.current = '';
1935
2440
  activeAssistantIdRef.current = null;
2441
+ activeAssistantAutoSkillNamesRef.current = [];
2442
+ streamedAssistantHandledRef.current = false;
1936
2443
  deltaBufferRef.current = '';
1937
2444
 
1938
2445
  runtime
1939
2446
  .submit(line, (event) => {
1940
2447
  if (event?.type === 'assistant:start') {
2448
+ streamedAssistantHandledRef.current = true;
1941
2449
  setRuntimeStatus(makeStatus(copy.runtime.modelThinking, copy.runtime.requestDelivered, 'cyanBright'));
1942
2450
  setInputStage('thinking');
1943
2451
  updateMessageMeta(activeUserMessageIdRef.current, {
@@ -1958,8 +2466,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1958
2466
  let liveStatus = copy.runtime.generatingReply;
1959
2467
  if (targetId) {
1960
2468
  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))) {
2469
+ const pendingToolCalls = Array.isArray(current?.pendingToolCalls) ? current.pendingToolCalls : [];
2470
+ if (pendingToolCalls.length > 0) {
1963
2471
  liveStatus = copy.runtime.generatingCode;
1964
2472
  }
1965
2473
  }
@@ -1967,12 +2475,71 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1967
2475
  })());
1968
2476
  queueAssistantDelta(event.text);
1969
2477
  }
2478
+ if (event?.type === 'assistant:tool_call_delta') {
2479
+ ensureActiveAssistant();
2480
+ const parsed = parseToolDisplayName(event.toolCall?.name);
2481
+ const isCodeTool = new Set(['write', 'edit', 'patch', 'generate_diff']).has(parsed.base);
2482
+ if (isCodeTool) {
2483
+ setRuntimeStatus(makeStatus(copy.runtime.generatingCode, copy.runtime.streamingReply, 'greenBright'));
2484
+ setInputStage('streaming');
2485
+ const startedAt = Date.now();
2486
+ const targetId = activeAssistantIdRef.current;
2487
+ if (targetId) {
2488
+ setMessages((prev) =>
2489
+ prev.map((m) => {
2490
+ if (m.id !== targetId) return m;
2491
+ return ensureCodeGenerationTiming(
2492
+ {
2493
+ ...m,
2494
+ loading: true,
2495
+ phase: 'generating',
2496
+ liveStatus: copy.runtime.generatingCode
2497
+ },
2498
+ startedAt
2499
+ );
2500
+ })
2501
+ );
2502
+ }
2503
+ }
2504
+ updatePendingToolCallOnActiveAssistant(event.toolCall);
2505
+ }
1970
2506
  if (event?.type === 'assistant:response') {
1971
- setRuntimeStatus(makeStatus(copy.runtime.replyCompleted, copy.runtime.outputFinished, 'greenBright'));
1972
- setInputStage('idle');
2507
+ const hasPlannedTools = Array.isArray(event.toolCalls) && event.toolCalls.length > 0;
2508
+ if (hasPlannedTools) {
2509
+ setRuntimeStatus(makeStatus(copy.runtime.toolRunning, copy.runtime.waitingToolStart || copy.runtime.streamingReply, 'magentaBright'));
2510
+ setInputStage('thinking');
2511
+ } else {
2512
+ setRuntimeStatus(makeStatus(copy.runtime.replyCompleted, copy.runtime.outputFinished, 'greenBright'));
2513
+ setInputStage('idle');
2514
+ }
1973
2515
  flushAssistantDelta();
1974
- finalizeActiveAssistant();
1975
- if (!activeAssistantIdRef.current && event.text) {
2516
+ const targetId = activeAssistantIdRef.current;
2517
+ const hadActiveAssistant = Boolean(targetId);
2518
+ if (hadActiveAssistant) {
2519
+ streamedAssistantHandledRef.current = true;
2520
+ }
2521
+ if (targetId && !hasPlannedTools) {
2522
+ setMessages((prev) =>
2523
+ prev.map((m) => {
2524
+ if (m.id !== targetId) return m;
2525
+ return {
2526
+ ...m,
2527
+ ...(typeof event.text === 'string' && event.text.length > 0 ? { text: event.text } : {}),
2528
+ loading: false,
2529
+ phase: undefined,
2530
+ liveStatus: undefined,
2531
+ planStep: undefined,
2532
+ pendingToolCalls: [],
2533
+ autoSkillNames: activeAssistantAutoSkillNamesRef.current,
2534
+ ...(m.codeGenerationStartedAt && !m.codeGenerationEndedAt ? { codeGenerationEndedAt: Date.now() } : {})
2535
+ };
2536
+ })
2537
+ );
2538
+ }
2539
+ if (!hasPlannedTools) {
2540
+ activeAssistantIdRef.current = null;
2541
+ }
2542
+ if (!hadActiveAssistant && !hasPlannedTools && event.text) {
1976
2543
  setMessages((prev) => [
1977
2544
  ...prev,
1978
2545
  { id: nextId(), label: 'coder', text: event.text, color: 'greenBright' }
@@ -1984,16 +2551,28 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1984
2551
  const detail = describeToolActivity(event.name, copy);
1985
2552
  setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'magentaBright'));
1986
2553
  setInputStage('tooling');
1987
- setActiveAssistantMeta({
1988
- loading: true,
1989
- phase: 'tooling',
1990
- liveStatus: isCodeActivityName(event.name) ? copy.runtime.generatingCode : detail
1991
- });
2554
+ const targetId = activeAssistantIdRef.current;
2555
+ if (targetId) {
2556
+ const finishedAt = Date.now();
2557
+ setMessages((prev) =>
2558
+ prev.map((m) => {
2559
+ if (m.id !== targetId) return m;
2560
+ const nextMessage = isCodeActivityName(event.name) ? finishCodeGeneration(m, finishedAt) : m;
2561
+ return {
2562
+ ...nextMessage,
2563
+ loading: true,
2564
+ phase: 'tooling',
2565
+ liveStatus: detail
2566
+ };
2567
+ })
2568
+ );
2569
+ }
1992
2570
  updateActivityStatusOnActiveAssistant({
1993
2571
  type: 'tool',
1994
2572
  id: event.id,
1995
2573
  name: event.name,
1996
- status: 'running'
2574
+ status: 'running',
2575
+ arguments: event.arguments
1997
2576
  });
1998
2577
  }
1999
2578
  if (event?.type === 'tool:end') {
@@ -2007,7 +2586,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2007
2586
  name: event.name,
2008
2587
  status: 'done',
2009
2588
  durationMs: event.durationMs,
2010
- summary: event.summary
2589
+ summary: event.summary,
2590
+ arguments: event.arguments
2011
2591
  });
2012
2592
  }
2013
2593
  if (event?.type === 'tool:blocked') {
@@ -2026,7 +2606,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2026
2606
  type: 'tool',
2027
2607
  id: event.id,
2028
2608
  name: event.name,
2029
- status: 'blocked'
2609
+ status: 'blocked',
2610
+ arguments: event.arguments
2030
2611
  });
2031
2612
  }
2032
2613
  if (event?.type === 'tool:error') {
@@ -2047,8 +2628,57 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2047
2628
  name: event.name,
2048
2629
  status: 'error',
2049
2630
  durationMs: event.durationMs,
2631
+ summary: event.summary,
2632
+ arguments: event.arguments
2633
+ });
2634
+ }
2635
+ if (event?.type === 'system_tool:start') {
2636
+ ensureActiveAssistant();
2637
+ const detail = describeToolActivity(event.name, copy);
2638
+ setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'blueBright'));
2639
+ setInputStage('tooling');
2640
+ updateActivityStatusOnActiveAssistant({
2641
+ type: 'system_tool',
2642
+ id: event.id,
2643
+ name: event.name,
2644
+ status: 'running',
2050
2645
  summary: event.summary
2051
2646
  });
2647
+ setActiveAssistantMeta({ loading: true, phase: 'tooling', liveStatus: detail });
2648
+ }
2649
+ if (event?.type === 'system_tool:end') {
2650
+ const detail = describeToolActivity(event.name, copy, { done: true });
2651
+ setRuntimeStatus(makeStatus(copy.runtime.toolCompleted, copy.toolActivity.waitingModelContinue(detail), 'blueBright'));
2652
+ setInputStage('thinking');
2653
+ updateActivityStatusOnActiveAssistant({
2654
+ type: 'system_tool',
2655
+ id: event.id,
2656
+ name: event.name,
2657
+ status: 'done',
2658
+ summary: event.summary
2659
+ });
2660
+ setActiveAssistantMeta({
2661
+ loading: true,
2662
+ phase: 'thinking',
2663
+ liveStatus: copy.toolActivity.waitingModelContinue(detail)
2664
+ });
2665
+ }
2666
+ if (event?.type === 'system_tool:error') {
2667
+ const detail = copy.toolActivity.toolFailed(event.name);
2668
+ setRuntimeStatus(makeStatus(copy.runtime.toolFailed, event.summary || detail, 'redBright'));
2669
+ setInputStage('thinking');
2670
+ updateActivityStatusOnActiveAssistant({
2671
+ type: 'system_tool',
2672
+ id: event.id,
2673
+ name: event.name,
2674
+ status: 'error',
2675
+ summary: event.summary
2676
+ });
2677
+ setActiveAssistantMeta({
2678
+ loading: true,
2679
+ phase: 'thinking',
2680
+ liveStatus: copy.toolActivity.waitingModelAdjust(detail)
2681
+ });
2052
2682
  }
2053
2683
  if (event?.type === 'skill:start') {
2054
2684
  ensureActiveAssistant();
@@ -2085,6 +2715,21 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2085
2715
  summary: event.summary
2086
2716
  });
2087
2717
  }
2718
+ if (event?.type === 'skill:auto') {
2719
+ const detail = describeAutoSkillActivity(event.names, copy);
2720
+ if (Array.isArray(event.names) && event.names.length > 0) {
2721
+ activeAssistantAutoSkillNamesRef.current = event.names.filter(Boolean);
2722
+ const targetId = activeAssistantIdRef.current;
2723
+ if (targetId) {
2724
+ setMessages((prev) =>
2725
+ prev.map((m) => (m.id === targetId ? { ...m, autoSkillNames: activeAssistantAutoSkillNamesRef.current } : m))
2726
+ );
2727
+ }
2728
+ }
2729
+ if (detail) {
2730
+ setRuntimeStatus(makeStatus(copy.runtime.skillRunning, detail, 'blueBright'));
2731
+ }
2732
+ }
2088
2733
  if (event?.type === 'compact:auto') {
2089
2734
  setRuntimeStatus(makeStatus(copy.runtime.compactingContext, `auto compact ${event.mode}`, 'yellowBright'));
2090
2735
  setMessages((prev) => [
@@ -2124,6 +2769,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2124
2769
  }
2125
2770
  syncRuntimeVisualState(result.type === 'noop' ? 'ready' : 'after');
2126
2771
  if (result.type === 'noop') return;
2772
+ if (!shouldAppendAssistantResult(result, activeAssistantIdRef.current, streamedAssistantHandledRef.current)) return;
2127
2773
  appendResultMessage(result);
2128
2774
  })
2129
2775
  .catch((err) => {
@@ -2150,6 +2796,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2150
2796
  flushAssistantDelta();
2151
2797
  finalizeActiveAssistant();
2152
2798
  activeAssistantIdRef.current = null;
2799
+ streamedAssistantHandledRef.current = false;
2153
2800
  activeUserMessageIdRef.current = null;
2154
2801
  if (deltaFlushTimerRef.current) {
2155
2802
  clearTimeout(deltaFlushTimerRef.current);
@@ -2538,7 +3185,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2538
3185
  return h(
2539
3186
  Box,
2540
3187
  { flexDirection: 'column' },
2541
- h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
3188
+ h(Header, { sessionId: displaySessionId, model: displayModel, shellName, safeMode }),
2542
3189
  h(MessageList, {
2543
3190
  messages: visibleMessages,
2544
3191
  loaderTick,