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.
- package/README.md +44 -20
- package/package.json +4 -2
- package/src/cli.js +1 -1
- package/src/commands/chat.js +1 -0
- package/src/core/agent-loop.js +7 -2
- package/src/core/ast.js +310 -0
- package/src/core/chat-runtime.js +47 -15
- package/src/core/checkpoint-store.js +2 -1
- package/src/core/command-loader.js +3 -4
- package/src/core/config-store.js +6 -3
- package/src/core/default-system-prompt.js +1 -1
- package/src/core/paths.js +52 -58
- package/src/core/project-index.js +510 -0
- package/src/core/provider/openai-compatible.js +9 -0
- package/src/core/shell-profile.js +1 -1
- package/src/core/shell.js +122 -2
- package/src/core/task-store.js +3 -2
- package/src/core/tools.js +188 -9
- package/src/tui/chat-app.js +694 -47
package/src/tui/chat-app.js
CHANGED
|
@@ -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
|
-
|
|
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: '
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1149
|
-
?
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
-
: '
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
{
|
|
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({
|
|
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
|
|
1962
|
-
if (
|
|
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
|
-
|
|
1972
|
-
|
|
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
|
-
|
|
1975
|
-
|
|
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
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
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,
|