codemini-cli 0.1.18 → 0.2.0
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 +6 -6
- package/package.json +1 -1
- package/skills/superpowers-lite/SKILL.md +10 -9
- package/src/core/agent-loop.js +63 -31
- package/src/core/chat-runtime.js +89 -16
- package/src/core/command-policy.js +4 -4
- package/src/core/config-store.js +16 -29
- 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/tools.js +527 -214
- package/src/tui/chat-app.js +849 -143
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;
|
|
@@ -63,6 +64,8 @@ const TUI_COPY = {
|
|
|
63
64
|
ready: '就绪',
|
|
64
65
|
noMessagesYet: '还没有消息',
|
|
65
66
|
code: '代码',
|
|
67
|
+
codeActivity: '代码活动',
|
|
68
|
+
textNotes: '说明文本',
|
|
66
69
|
live: '运行中',
|
|
67
70
|
idle: '空闲',
|
|
68
71
|
plan: '计划',
|
|
@@ -81,7 +84,7 @@ const TUI_COPY = {
|
|
|
81
84
|
pendingQueue: '等待队列',
|
|
82
85
|
commandPaletteGroupedSelect: '命令面板 | 分组选择模式',
|
|
83
86
|
commandPaletteGroupedSuggestions: '命令面板 | 分组候选',
|
|
84
|
-
startupHint: '使用 /help、/commands、/exit、!<shell>。Tab 可自动补全 slash 命令。',
|
|
87
|
+
startupHint: '使用 /help、/commands、/compact、/exit、!<shell>。Tab 可自动补全 slash 命令。',
|
|
85
88
|
toolSummaryExpanded: '工具摘要:已展开',
|
|
86
89
|
toolSummaryCollapsed: '工具摘要:已收起',
|
|
87
90
|
toggleToolSummary: 'Ctrl+T 切换',
|
|
@@ -103,8 +106,12 @@ const TUI_COPY = {
|
|
|
103
106
|
blocked: '工具被拦截',
|
|
104
107
|
doneRead: '已读取文件',
|
|
105
108
|
doingRead: '正在读取文件',
|
|
109
|
+
doneEdit: '已编辑文件',
|
|
110
|
+
doingEdit: '正在编辑文件',
|
|
106
111
|
doneWrite: '已写入文件',
|
|
107
112
|
doingWrite: '正在写入文件',
|
|
113
|
+
donePatch: '已应用补丁',
|
|
114
|
+
doingPatch: '正在应用补丁',
|
|
108
115
|
doneList: '已查看目录',
|
|
109
116
|
doingList: '正在查看目录',
|
|
110
117
|
doneCommand: '已执行命令',
|
|
@@ -115,6 +122,22 @@ const TUI_COPY = {
|
|
|
115
122
|
doingUpdateTask: '正在更新任务',
|
|
116
123
|
doneGeneric: '已完成工具',
|
|
117
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: '正在生成代码',
|
|
118
141
|
doneSkill: '已完成技能',
|
|
119
142
|
doingSkill: '正在执行技能',
|
|
120
143
|
toolFailed: (name) => `工具执行失败: ${name}`,
|
|
@@ -137,6 +160,7 @@ const TUI_COPY = {
|
|
|
137
160
|
modelThinking: '模型正在思考',
|
|
138
161
|
requestDelivered: '请求已送达,等待首个 token',
|
|
139
162
|
generatingReply: '正在生成回复',
|
|
163
|
+
generatingCode: '正在生成代码中',
|
|
140
164
|
streamingReply: '回复正在流式输出',
|
|
141
165
|
replyCompleted: '回复已完成',
|
|
142
166
|
outputFinished: '本轮输出结束',
|
|
@@ -147,6 +171,7 @@ const TUI_COPY = {
|
|
|
147
171
|
skillRunning: '技能执行中',
|
|
148
172
|
skillCompleted: '技能已完成',
|
|
149
173
|
skillFailed: '技能执行失败',
|
|
174
|
+
autoSkillInjected: (names) => `自动启用技能: ${names.map((name) => `/${name}`).join(', ')}`,
|
|
150
175
|
compactingContext: '正在压缩上下文',
|
|
151
176
|
autoCompactTriggered: (mode, threshold) => `自动压缩已触发(${mode},阈值 ${threshold}%)`,
|
|
152
177
|
requestFailed: '请求失败',
|
|
@@ -165,6 +190,8 @@ const TUI_COPY = {
|
|
|
165
190
|
ready: 'ready',
|
|
166
191
|
noMessagesYet: 'No messages yet',
|
|
167
192
|
code: 'code',
|
|
193
|
+
codeActivity: 'CODE ACTIVITY',
|
|
194
|
+
textNotes: 'NOTES',
|
|
168
195
|
live: 'LIVE',
|
|
169
196
|
idle: 'IDLE',
|
|
170
197
|
plan: 'PLAN',
|
|
@@ -183,7 +210,7 @@ const TUI_COPY = {
|
|
|
183
210
|
pendingQueue: 'pending queue',
|
|
184
211
|
commandPaletteGroupedSelect: 'command palette | grouped select mode',
|
|
185
212
|
commandPaletteGroupedSuggestions: 'command palette | grouped suggestions',
|
|
186
|
-
startupHint: 'Use /help, /commands, /exit, !<shell>. Tab for slash autocomplete.',
|
|
213
|
+
startupHint: 'Use /help, /commands, /compact, /exit, !<shell>. Tab for slash autocomplete.',
|
|
187
214
|
toolSummaryExpanded: 'Tool summary: expanded',
|
|
188
215
|
toolSummaryCollapsed: 'Tool summary: collapsed',
|
|
189
216
|
toggleToolSummary: 'Ctrl+T to toggle',
|
|
@@ -205,8 +232,12 @@ const TUI_COPY = {
|
|
|
205
232
|
blocked: 'Tool blocked',
|
|
206
233
|
doneRead: 'Read file',
|
|
207
234
|
doingRead: 'Reading file',
|
|
235
|
+
doneEdit: 'Edited file',
|
|
236
|
+
doingEdit: 'Editing file',
|
|
208
237
|
doneWrite: 'Wrote file',
|
|
209
238
|
doingWrite: 'Writing file',
|
|
239
|
+
donePatch: 'Applied patch',
|
|
240
|
+
doingPatch: 'Applying patch',
|
|
210
241
|
doneList: 'Listed directory',
|
|
211
242
|
doingList: 'Listing directory',
|
|
212
243
|
doneCommand: 'Ran command',
|
|
@@ -217,6 +248,22 @@ const TUI_COPY = {
|
|
|
217
248
|
doingUpdateTask: 'Updating task',
|
|
218
249
|
doneGeneric: 'Completed tool',
|
|
219
250
|
doingGeneric: 'Running tool',
|
|
251
|
+
doneInstall: 'Dependencies installed',
|
|
252
|
+
doingInstall: 'Installing dependencies',
|
|
253
|
+
doneBuild: 'Build completed',
|
|
254
|
+
doingBuild: 'Building',
|
|
255
|
+
doneTest: 'Tests completed',
|
|
256
|
+
doingTest: 'Running tests',
|
|
257
|
+
doneFrontend: 'Frontend started',
|
|
258
|
+
doingFrontend: 'Starting frontend service',
|
|
259
|
+
doneBackend: 'Backend started',
|
|
260
|
+
doingBackend: 'Starting backend service',
|
|
261
|
+
doneDatabase: 'Database started',
|
|
262
|
+
doingDatabase: 'Starting database service',
|
|
263
|
+
doneDocker: 'Docker command completed',
|
|
264
|
+
doingDocker: 'Running Docker command',
|
|
265
|
+
doneCodeGeneration: 'Code generated',
|
|
266
|
+
doingCodeGeneration: 'Generating code',
|
|
220
267
|
doneSkill: 'Completed skill',
|
|
221
268
|
doingSkill: 'Running skill',
|
|
222
269
|
toolFailed: (name) => `Tool failed: ${name}`,
|
|
@@ -239,6 +286,7 @@ const TUI_COPY = {
|
|
|
239
286
|
modelThinking: 'model is thinking',
|
|
240
287
|
requestDelivered: 'request sent, waiting for first token',
|
|
241
288
|
generatingReply: 'generating reply',
|
|
289
|
+
generatingCode: 'generating code',
|
|
242
290
|
streamingReply: 'reply is streaming',
|
|
243
291
|
replyCompleted: 'reply completed',
|
|
244
292
|
outputFinished: 'turn output finished',
|
|
@@ -249,6 +297,7 @@ const TUI_COPY = {
|
|
|
249
297
|
skillRunning: 'skill running',
|
|
250
298
|
skillCompleted: 'skill completed',
|
|
251
299
|
skillFailed: 'skill failed',
|
|
300
|
+
autoSkillInjected: (names) => `auto-enabled skills: ${names.map((name) => `/${name}`).join(', ')}`,
|
|
252
301
|
compactingContext: 'compacting context',
|
|
253
302
|
autoCompactTriggered: (mode, threshold) => `auto-compact triggered (${mode}, threshold ${threshold}%)`,
|
|
254
303
|
requestFailed: 'request failed',
|
|
@@ -294,6 +343,14 @@ function trimText(value, maxLen = 88) {
|
|
|
294
343
|
return `${text.slice(0, maxLen - 3)}...`;
|
|
295
344
|
}
|
|
296
345
|
|
|
346
|
+
function safeJsonParse(raw) {
|
|
347
|
+
try {
|
|
348
|
+
return JSON.parse(String(raw || '{}'));
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
297
354
|
function parseToolDisplayName(name) {
|
|
298
355
|
const raw = String(name || '').trim();
|
|
299
356
|
const match = raw.match(/^([^(]+)\((.*)\)$/);
|
|
@@ -304,18 +361,85 @@ function parseToolDisplayName(name) {
|
|
|
304
361
|
};
|
|
305
362
|
}
|
|
306
363
|
|
|
364
|
+
function isCodeGenerationActivityName(name) {
|
|
365
|
+
return String(name || '').trim() === 'Code generation';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function formatDurationMs(ms) {
|
|
369
|
+
const safeMs = Math.max(0, Number(ms) || 0);
|
|
370
|
+
return `${(safeMs / 1000).toFixed(1)}s`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getIntentLabel(kind) {
|
|
374
|
+
switch (kind) {
|
|
375
|
+
case 'install':
|
|
376
|
+
return 'Install';
|
|
377
|
+
case 'build':
|
|
378
|
+
return 'Build';
|
|
379
|
+
case 'test':
|
|
380
|
+
return 'Test';
|
|
381
|
+
case 'frontend-service':
|
|
382
|
+
return 'Frontend';
|
|
383
|
+
case 'backend-service':
|
|
384
|
+
return 'Backend';
|
|
385
|
+
case 'database-service':
|
|
386
|
+
return 'Database';
|
|
387
|
+
case 'docker-service':
|
|
388
|
+
return 'Docker';
|
|
389
|
+
case 'service':
|
|
390
|
+
return 'Service';
|
|
391
|
+
default:
|
|
392
|
+
return 'Run';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function formatActivityDurationText(row, nowMs = Date.now()) {
|
|
397
|
+
if (!row) return '';
|
|
398
|
+
if (row.status === 'running' && Number.isFinite(Number(row.startedAt))) {
|
|
399
|
+
const startedAt = Number(row.startedAt);
|
|
400
|
+
const endedAt = Number(row.endedAt);
|
|
401
|
+
const elapsed = Number.isFinite(endedAt) && endedAt > startedAt ? endedAt - startedAt : Math.max(0, Number(nowMs) - startedAt);
|
|
402
|
+
return formatDurationMs(elapsed);
|
|
403
|
+
}
|
|
404
|
+
if (typeof row.durationText === 'string' && row.durationText.trim()) {
|
|
405
|
+
return row.durationText.trim();
|
|
406
|
+
}
|
|
407
|
+
if (Number.isFinite(Number(row.durationMs))) {
|
|
408
|
+
return formatDurationMs(Number(row.durationMs));
|
|
409
|
+
}
|
|
410
|
+
return '';
|
|
411
|
+
}
|
|
412
|
+
|
|
307
413
|
function getActivityDisplayParts(activity) {
|
|
414
|
+
if (isCodeGenerationActivityName(activity?.name)) {
|
|
415
|
+
return {
|
|
416
|
+
primary: 'Code',
|
|
417
|
+
secondary: ' (generation)'
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const parsed = parseToolDisplayName(activity?.name);
|
|
421
|
+
if (parsed.base === 'run' || parsed.base === 'start_service') {
|
|
422
|
+
const intent = classifyCommandIntent(parsed.target);
|
|
423
|
+
return {
|
|
424
|
+
primary: getIntentLabel(intent.kind),
|
|
425
|
+
secondary: parsed.target ? `(${parsed.target})` : ''
|
|
426
|
+
};
|
|
427
|
+
}
|
|
308
428
|
if ((activity?.type || 'tool') === 'skill') {
|
|
309
429
|
return {
|
|
310
430
|
primary: `Skill`,
|
|
311
431
|
secondary: `(${activity?.name || 'unknown'})`
|
|
312
432
|
};
|
|
313
433
|
}
|
|
314
|
-
const parsed = parseToolDisplayName(activity?.name);
|
|
315
434
|
const labels = {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
435
|
+
read: 'Read',
|
|
436
|
+
edit: 'Edit',
|
|
437
|
+
write: 'Write',
|
|
438
|
+
patch: 'Patch',
|
|
439
|
+
run: 'Run',
|
|
440
|
+
grep: 'Grep',
|
|
441
|
+
glob: 'Glob',
|
|
442
|
+
list: 'List',
|
|
319
443
|
start_service: 'Service',
|
|
320
444
|
list_services: 'Service',
|
|
321
445
|
get_service_status: 'Service',
|
|
@@ -332,35 +456,117 @@ function getActivityDisplayParts(activity) {
|
|
|
332
456
|
}
|
|
333
457
|
|
|
334
458
|
function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
|
|
459
|
+
const parsed = parseToolDisplayName(name);
|
|
460
|
+
if (parsed.base === 'run' || parsed.base === 'start_service') {
|
|
461
|
+
const intent = classifyCommandIntent(parsed.target);
|
|
462
|
+
const target = parsed.target || intent.kind || 'command';
|
|
463
|
+
if (intent.kind === 'install') {
|
|
464
|
+
return blocked
|
|
465
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
466
|
+
: done
|
|
467
|
+
? `${copy.toolActivity.doneInstall}: ${target}`
|
|
468
|
+
: `${copy.toolActivity.doingInstall}: ${target}`;
|
|
469
|
+
}
|
|
470
|
+
if (intent.kind === 'build') {
|
|
471
|
+
return blocked
|
|
472
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
473
|
+
: done
|
|
474
|
+
? `${copy.toolActivity.doneBuild}: ${target}`
|
|
475
|
+
: `${copy.toolActivity.doingBuild}: ${target}`;
|
|
476
|
+
}
|
|
477
|
+
if (intent.kind === 'test') {
|
|
478
|
+
return blocked
|
|
479
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
480
|
+
: done
|
|
481
|
+
? `${copy.toolActivity.doneTest}: ${target}`
|
|
482
|
+
: `${copy.toolActivity.doingTest}: ${target}`;
|
|
483
|
+
}
|
|
484
|
+
if (intent.kind === 'frontend-service') {
|
|
485
|
+
return blocked
|
|
486
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
487
|
+
: done
|
|
488
|
+
? `${copy.toolActivity.doneFrontend}: ${target}`
|
|
489
|
+
: `${copy.toolActivity.doingFrontend}: ${target}`;
|
|
490
|
+
}
|
|
491
|
+
if (intent.kind === 'backend-service') {
|
|
492
|
+
return blocked
|
|
493
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
494
|
+
: done
|
|
495
|
+
? `${copy.toolActivity.doneBackend}: ${target}`
|
|
496
|
+
: `${copy.toolActivity.doingBackend}: ${target}`;
|
|
497
|
+
}
|
|
498
|
+
if (intent.kind === 'database-service') {
|
|
499
|
+
return blocked
|
|
500
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
501
|
+
: done
|
|
502
|
+
? `${copy.toolActivity.doneDatabase}: ${target}`
|
|
503
|
+
: `${copy.toolActivity.doingDatabase}: ${target}`;
|
|
504
|
+
}
|
|
505
|
+
if (intent.kind === 'docker-service') {
|
|
506
|
+
return blocked
|
|
507
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
508
|
+
: done
|
|
509
|
+
? `${copy.toolActivity.doneDocker}: ${target}`
|
|
510
|
+
: `${copy.toolActivity.doingDocker}: ${target}`;
|
|
511
|
+
}
|
|
512
|
+
if (intent.kind === 'service') {
|
|
513
|
+
return blocked
|
|
514
|
+
? `${copy.toolActivity.blocked}: ${target}`
|
|
515
|
+
: done
|
|
516
|
+
? `${copy.toolActivity.doneGeneric}: ${target}`
|
|
517
|
+
: `${copy.toolActivity.doingGeneric}: ${target}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (isCodeGenerationActivityName(name)) {
|
|
521
|
+
return blocked
|
|
522
|
+
? `${copy.toolActivity.blocked}: code generation`
|
|
523
|
+
: done
|
|
524
|
+
? copy.toolActivity.doneCodeGeneration
|
|
525
|
+
: copy.toolActivity.doingCodeGeneration;
|
|
526
|
+
}
|
|
335
527
|
const { raw, base, target } = parseToolDisplayName(name);
|
|
336
528
|
const safeTarget = trimText(target, 72);
|
|
337
|
-
if (base === '
|
|
529
|
+
if (base === 'read') {
|
|
338
530
|
return blocked
|
|
339
|
-
? `${copy.toolActivity.blocked}:
|
|
531
|
+
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
340
532
|
: done
|
|
341
533
|
? `${copy.toolActivity.doneRead}: ${safeTarget || '.'}`
|
|
342
534
|
: `${copy.toolActivity.doingRead}: ${safeTarget || '.'}`;
|
|
343
535
|
}
|
|
344
|
-
if (base === '
|
|
536
|
+
if (base === 'edit') {
|
|
537
|
+
return blocked
|
|
538
|
+
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
539
|
+
: done
|
|
540
|
+
? `${copy.toolActivity.doneEdit}: ${safeTarget || '.'}`
|
|
541
|
+
: `${copy.toolActivity.doingEdit}: ${safeTarget || '.'}`;
|
|
542
|
+
}
|
|
543
|
+
if (base === 'write') {
|
|
345
544
|
return blocked
|
|
346
|
-
? `${copy.toolActivity.blocked}:
|
|
545
|
+
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
347
546
|
: done
|
|
348
547
|
? `${copy.toolActivity.doneWrite}: ${safeTarget || '.'}`
|
|
349
548
|
: `${copy.toolActivity.doingWrite}: ${safeTarget || '.'}`;
|
|
350
549
|
}
|
|
351
|
-
if (base === '
|
|
550
|
+
if (base === 'patch') {
|
|
551
|
+
return blocked
|
|
552
|
+
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
553
|
+
: done
|
|
554
|
+
? `${copy.toolActivity.donePatch}: ${safeTarget || '.'}`
|
|
555
|
+
: `${copy.toolActivity.doingPatch}: ${safeTarget || '.'}`;
|
|
556
|
+
}
|
|
557
|
+
if (base === 'list' || base === 'glob' || base === 'grep') {
|
|
352
558
|
return blocked
|
|
353
|
-
? `${copy.toolActivity.blocked}:
|
|
559
|
+
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
354
560
|
: done
|
|
355
561
|
? `${copy.toolActivity.doneList}: ${safeTarget || '.'}`
|
|
356
562
|
: `${copy.toolActivity.doingList}: ${safeTarget || '.'}`;
|
|
357
563
|
}
|
|
358
|
-
if (base === '
|
|
564
|
+
if (base === 'run') {
|
|
359
565
|
return blocked
|
|
360
|
-
? `${copy.toolActivity.blocked}: ${safeTarget ||
|
|
566
|
+
? `${copy.toolActivity.blocked}: ${safeTarget || base}`
|
|
361
567
|
: done
|
|
362
|
-
? `${copy.toolActivity.doneCommand}: ${safeTarget ||
|
|
363
|
-
: `${copy.toolActivity.doingCommand}: ${safeTarget ||
|
|
568
|
+
? `${copy.toolActivity.doneCommand}: ${safeTarget || base}`
|
|
569
|
+
: `${copy.toolActivity.doingCommand}: ${safeTarget || base}`;
|
|
364
570
|
}
|
|
365
571
|
if (base === 'start_service' || base === 'list_services' || base === 'get_service_status' || base === 'get_service_logs' || base === 'stop_service') {
|
|
366
572
|
return blocked
|
|
@@ -384,6 +590,21 @@ function describeSkillActivity(name, copy, { done = false, failed = false } = {}
|
|
|
384
590
|
return `${copy.toolActivity.doingSkill}: /${name}`;
|
|
385
591
|
}
|
|
386
592
|
|
|
593
|
+
function describeAutoSkillActivity(names, copy) {
|
|
594
|
+
const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
|
|
595
|
+
if (safeNames.length === 0) return '';
|
|
596
|
+
return copy.runtime.autoSkillInjected(safeNames);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function formatAutoSkillBadge(names, copy) {
|
|
600
|
+
const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
|
|
601
|
+
if (safeNames.length === 0) return '';
|
|
602
|
+
const [first, ...rest] = safeNames;
|
|
603
|
+
const suffix = rest.length > 0 ? ` +${rest.length}` : '';
|
|
604
|
+
const prefix = copy?.roleLabels?.system === 'SYSTEM' ? 'AUTO' : '自动';
|
|
605
|
+
return `${prefix} /${first}${suffix}`;
|
|
606
|
+
}
|
|
607
|
+
|
|
387
608
|
function normalizeRuntimeStatus(status, copy) {
|
|
388
609
|
if (status && typeof status === 'object') {
|
|
389
610
|
return {
|
|
@@ -451,6 +672,52 @@ function RuntimeStrip({ busy, runtimeStatus, loaderTick, copy }) {
|
|
|
451
672
|
);
|
|
452
673
|
}
|
|
453
674
|
|
|
675
|
+
function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false }) {
|
|
676
|
+
const maxContextTokens = Number(runtimeState?.maxContextTokens || 0);
|
|
677
|
+
const currentContextTokens = Number(runtimeState?.currentContextTokens || 0);
|
|
678
|
+
const pctRaw =
|
|
679
|
+
Number.isFinite(runtimeState?.contextUsagePct) && runtimeState.contextUsagePct >= 0
|
|
680
|
+
? runtimeState.contextUsagePct
|
|
681
|
+
: maxContextTokens > 0
|
|
682
|
+
? (currentContextTokens / maxContextTokens) * 100
|
|
683
|
+
: 0;
|
|
684
|
+
const pct = Math.min(100, Math.max(0, pctRaw));
|
|
685
|
+
const filled = Math.min(12, Math.max(0, Math.round((pct / 100) * 12)));
|
|
686
|
+
const activeColor = pct < 40 ? 'greenBright' : pct < 75 ? 'yellowBright' : 'redBright';
|
|
687
|
+
const statusColor = runtimeStatus?.color || activeColor;
|
|
688
|
+
const chunks = Array.from({ length: 12 }, (_, idx) => {
|
|
689
|
+
const zoneColor = idx < 5 ? 'greenBright' : idx < 9 ? 'yellowBright' : 'redBright';
|
|
690
|
+
const color = idx < filled ? zoneColor : 'gray';
|
|
691
|
+
return h(Text, { key: `context-meter-${idx}`, color }, '|');
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
if (compact) {
|
|
695
|
+
return h(
|
|
696
|
+
Box,
|
|
697
|
+
{ justifyContent: 'flex-end', alignItems: 'center' },
|
|
698
|
+
h(Text, { color: 'gray' }, '上下文 '),
|
|
699
|
+
h(Text, { color: statusColor }, `${Math.round(pct)}% `),
|
|
700
|
+
h(
|
|
701
|
+
Box,
|
|
702
|
+
{ flexDirection: 'row' },
|
|
703
|
+
...chunks
|
|
704
|
+
)
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return h(
|
|
709
|
+
Box,
|
|
710
|
+
{ justifyContent: 'flex-end', alignItems: 'center' },
|
|
711
|
+
h(Text, { color: 'gray' }, '上下文 '),
|
|
712
|
+
h(Text, { color: statusColor }, `${Math.round(pct)}% `),
|
|
713
|
+
h(
|
|
714
|
+
Box,
|
|
715
|
+
null,
|
|
716
|
+
...chunks
|
|
717
|
+
)
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
454
721
|
function PlanStrip({ planState, copy }) {
|
|
455
722
|
if (!planState || !planState.total) return null;
|
|
456
723
|
const progress = `${planState.current}/${planState.total}`;
|
|
@@ -611,6 +878,188 @@ export function parsePlanProgressLine(text) {
|
|
|
611
878
|
};
|
|
612
879
|
}
|
|
613
880
|
|
|
881
|
+
function getTailPreviewLines(text, maxLines = 3) {
|
|
882
|
+
const source = String(text || '');
|
|
883
|
+
if (!source.trim()) return [];
|
|
884
|
+
|
|
885
|
+
const lines = source.split('\n').map((line) => line.replace(/\r$/, ''));
|
|
886
|
+
let insideFence = false;
|
|
887
|
+
let fenceLines = [];
|
|
888
|
+
let latestClosedFenceLines = [];
|
|
889
|
+
|
|
890
|
+
for (const line of lines) {
|
|
891
|
+
const trimmed = line.trim();
|
|
892
|
+
if (trimmed.startsWith('```')) {
|
|
893
|
+
if (insideFence) {
|
|
894
|
+
latestClosedFenceLines = fenceLines.slice();
|
|
895
|
+
insideFence = false;
|
|
896
|
+
fenceLines = [];
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
insideFence = true;
|
|
900
|
+
fenceLines = [];
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (insideFence) {
|
|
904
|
+
fenceLines.push(line);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (insideFence) {
|
|
909
|
+
const codeLines = fenceLines.filter((line) => line.trim().length > 0);
|
|
910
|
+
if (codeLines.length > 0) {
|
|
911
|
+
return codeLines.slice(-Math.max(1, maxLines));
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const closedFenceLines = latestClosedFenceLines.filter((line) => line.trim().length > 0);
|
|
916
|
+
if (closedFenceLines.length > 0) {
|
|
917
|
+
return closedFenceLines.slice(-Math.max(1, maxLines));
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const tailLines = source
|
|
921
|
+
.split('\n')
|
|
922
|
+
.map((line) => line.replace(/\r$/, ''))
|
|
923
|
+
.filter((line) => line.trim().length > 0);
|
|
924
|
+
if (tailLines.length === 0) return [];
|
|
925
|
+
return tailLines.slice(-Math.max(1, maxLines));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function collectPreviewStrings(value, out = []) {
|
|
929
|
+
if (out.length >= 3 || value == null) return out;
|
|
930
|
+
if (typeof value === 'string') {
|
|
931
|
+
if (value.trim()) out.push(value);
|
|
932
|
+
return out;
|
|
933
|
+
}
|
|
934
|
+
if (Array.isArray(value)) {
|
|
935
|
+
for (const item of value) {
|
|
936
|
+
collectPreviewStrings(item, out);
|
|
937
|
+
if (out.length >= 3) break;
|
|
938
|
+
}
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
if (typeof value !== 'object') return out;
|
|
942
|
+
|
|
943
|
+
const priorityKeys = ['content', 'new_content', 'new_text', 'patch', 'text', 'code', 'body', 'script', 'source', 'value'];
|
|
944
|
+
if (value.edit && typeof value.edit === 'object') {
|
|
945
|
+
collectPreviewStrings(value.edit, out);
|
|
946
|
+
}
|
|
947
|
+
for (const key of priorityKeys) {
|
|
948
|
+
if (out.length >= 3) break;
|
|
949
|
+
collectPreviewStrings(value[key], out);
|
|
950
|
+
}
|
|
951
|
+
return out;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function extractPreviewTextFromRawArguments(raw) {
|
|
955
|
+
const source = String(raw || '');
|
|
956
|
+
if (!source.trim()) return '';
|
|
957
|
+
|
|
958
|
+
const contentMatch = source.match(/"(content|new_content|new_text|patch|code|body|script|source|value)"\s*:\s*"([\s\S]*)$/);
|
|
959
|
+
if (!contentMatch) return '';
|
|
960
|
+
|
|
961
|
+
return contentMatch[2]
|
|
962
|
+
.replace(/\\n/g, '\n')
|
|
963
|
+
.replace(/\\"/g, '"')
|
|
964
|
+
.replace(/\\\\/g, '\\')
|
|
965
|
+
.replace(/",?\s*$/g, '')
|
|
966
|
+
.trim();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function compactPreviewLine(line, maxChars = 56) {
|
|
970
|
+
const text = String(line || '').replace(/\t/g, ' ').trimEnd();
|
|
971
|
+
if (!text) return '';
|
|
972
|
+
if (text.length <= maxChars) return text;
|
|
973
|
+
return `${text.slice(0, Math.max(1, maxChars - 3))}...`;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function getLatestToolPreviewLines(msg, maxLines = 3) {
|
|
977
|
+
const toolCalls = [
|
|
978
|
+
...(Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : []),
|
|
979
|
+
...(Array.isArray(msg?.toolCalls) ? msg.toolCalls : [])
|
|
980
|
+
];
|
|
981
|
+
const codeTools = new Set(['edit', 'write', 'patch', 'generate_diff']);
|
|
982
|
+
for (let index = toolCalls.length - 1; index >= 0; index -= 1) {
|
|
983
|
+
const tool = toolCalls[index];
|
|
984
|
+
const parsed = parseToolDisplayName(tool?.name);
|
|
985
|
+
if (!codeTools.has(parsed.base)) continue;
|
|
986
|
+
const rawArgumentPreview =
|
|
987
|
+
typeof tool?.arguments === 'string' ? extractPreviewTextFromRawArguments(tool.arguments) : '';
|
|
988
|
+
const previewSource = rawArgumentPreview
|
|
989
|
+
? [rawArgumentPreview]
|
|
990
|
+
: collectPreviewStrings(tool?.arguments || tool?.content || tool?.summary || []);
|
|
991
|
+
if (previewSource.length === 0) continue;
|
|
992
|
+
const combined = previewSource.join('\n');
|
|
993
|
+
const previewLines = getTailPreviewLines(combined, maxLines);
|
|
994
|
+
if (previewLines.length > 0) return previewLines.map((line) => compactPreviewLine(line));
|
|
995
|
+
}
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export function getGeneratingCodePlaceholderRows(msg, copy, contentWidth = 72) {
|
|
1000
|
+
const liveStatus = String(msg?.liveStatus || '').trim();
|
|
1001
|
+
if (!msg?.loading || (msg?.phase !== 'generating' && msg?.phase !== 'tooling')) return [];
|
|
1002
|
+
if (liveStatus !== String(copy?.runtime?.generatingCode || '').trim()) return [];
|
|
1003
|
+
|
|
1004
|
+
const previewLines = getLatestToolPreviewLines(msg, 3);
|
|
1005
|
+
if (previewLines.length === 0) return [];
|
|
1006
|
+
|
|
1007
|
+
return previewLines.map((line, idx) => ({
|
|
1008
|
+
kind: 'code-placeholder',
|
|
1009
|
+
lineNo: idx + 1,
|
|
1010
|
+
text: line,
|
|
1011
|
+
color: 'gray'
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
export function getCodeGenerationActivityRows(msg) {
|
|
1016
|
+
const startedAt = Number(msg?.codeGenerationStartedAt);
|
|
1017
|
+
const endedAt = Number(msg?.codeGenerationEndedAt);
|
|
1018
|
+
if (!startedAt || !msg?.loading || endedAt > 0) return [];
|
|
1019
|
+
|
|
1020
|
+
const status = 'running';
|
|
1021
|
+
const durationMs = Math.max(0, Date.now() - startedAt);
|
|
1022
|
+
|
|
1023
|
+
return [
|
|
1024
|
+
{
|
|
1025
|
+
kind: 'activity',
|
|
1026
|
+
activityType: 'tool',
|
|
1027
|
+
name: 'Code generation',
|
|
1028
|
+
status,
|
|
1029
|
+
statusIcon: status === 'done' ? '✓' : '…',
|
|
1030
|
+
statusColor: status === 'done' ? 'greenBright' : 'yellow',
|
|
1031
|
+
durationMs,
|
|
1032
|
+
durationText: formatDurationMs(durationMs),
|
|
1033
|
+
isLatestTool: true,
|
|
1034
|
+
synthetic: true
|
|
1035
|
+
}
|
|
1036
|
+
];
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
export function ensureCodeGenerationTiming(msg, now = Date.now()) {
|
|
1040
|
+
if (!msg || msg.codeGenerationStartedAt) return msg;
|
|
1041
|
+
return {
|
|
1042
|
+
...msg,
|
|
1043
|
+
codeGenerationStartedAt: now,
|
|
1044
|
+
codeGenerationEndedAt: undefined
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export function shouldAppendAssistantResult(result, activeAssistantId, streamedAssistantHandled = false) {
|
|
1049
|
+
if (result?.type !== 'assistant') return true;
|
|
1050
|
+
if (streamedAssistantHandled) return false;
|
|
1051
|
+
return !activeAssistantId;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function finishCodeGeneration(msg, now = Date.now()) {
|
|
1055
|
+
if (!msg?.codeGenerationStartedAt || msg?.codeGenerationEndedAt) return msg;
|
|
1056
|
+
return {
|
|
1057
|
+
...msg,
|
|
1058
|
+
codeGenerationEndedAt: now,
|
|
1059
|
+
pendingToolCalls: []
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
614
1063
|
export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
|
|
615
1064
|
const source = Array.isArray(messages) ? messages : [];
|
|
616
1065
|
if (!planState || !planState.total) return source;
|
|
@@ -632,6 +1081,21 @@ export function injectPlanStateMessage(messages, planState, activeUserMessageId,
|
|
|
632
1081
|
return [...withNoPlanStrip, synthetic];
|
|
633
1082
|
}
|
|
634
1083
|
|
|
1084
|
+
export function injectRuntimeStateMessage(messages, runtimeState, runtimeStatus, busy, activeUserMessageId, activeAssistantId) {
|
|
1085
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
1086
|
+
if (!runtimeState) return source;
|
|
1087
|
+
const withoutRuntimeStrip = source.filter((message) => !message?.runtimeStrip);
|
|
1088
|
+
const userIdx = withoutRuntimeStrip.findIndex((message) => message.id === activeUserMessageId);
|
|
1089
|
+
if (userIdx !== -1) {
|
|
1090
|
+
return withoutRuntimeStrip;
|
|
1091
|
+
}
|
|
1092
|
+
const assistantIdx = withoutRuntimeStrip.findIndex((message) => message.id === activeAssistantId);
|
|
1093
|
+
if (assistantIdx !== -1) {
|
|
1094
|
+
return withoutRuntimeStrip;
|
|
1095
|
+
}
|
|
1096
|
+
return withoutRuntimeStrip;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
635
1099
|
function PlanSummaryBubble({ msg, copy }) {
|
|
636
1100
|
const theme = roleStyle(msg.label);
|
|
637
1101
|
const summary = msg.planSummary || parseAutoPlanSummaryMessage(msg.text);
|
|
@@ -817,6 +1281,56 @@ function isBlankTextRow(row) {
|
|
|
817
1281
|
return row?.kind === 'text' && String(row?.text || '').trim() === '';
|
|
818
1282
|
}
|
|
819
1283
|
|
|
1284
|
+
function isCodeActivityName(name) {
|
|
1285
|
+
const parsed = parseToolDisplayName(name);
|
|
1286
|
+
return new Set([
|
|
1287
|
+
'edit',
|
|
1288
|
+
'write',
|
|
1289
|
+
'write_file',
|
|
1290
|
+
'patch',
|
|
1291
|
+
'replace_text',
|
|
1292
|
+
'replace_block',
|
|
1293
|
+
'insert_before',
|
|
1294
|
+
'insert_after',
|
|
1295
|
+
'validate_edit',
|
|
1296
|
+
'generate_diff'
|
|
1297
|
+
]).has(parsed.base);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export function isCodeLikeRow(row) {
|
|
1301
|
+
if (!row) return false;
|
|
1302
|
+
if (row.kind === 'code' || row.kind === 'activity' || row.kind === 'activity-summary') return true;
|
|
1303
|
+
if (row.kind === 'status') return true;
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
export function splitMessageRows(rows) {
|
|
1308
|
+
const textRows = [];
|
|
1309
|
+
const codeRows = [];
|
|
1310
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
1311
|
+
if (isCodeLikeRow(row)) codeRows.push(row);
|
|
1312
|
+
else textRows.push(row);
|
|
1313
|
+
}
|
|
1314
|
+
return { textRows, codeRows };
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
export function insertRowsAfterLastCodeRow(rows, extraRows) {
|
|
1318
|
+
const source = Array.isArray(rows) ? rows : [];
|
|
1319
|
+
const inserts = Array.isArray(extraRows) ? extraRows.filter(Boolean) : [];
|
|
1320
|
+
if (inserts.length === 0) return source.slice();
|
|
1321
|
+
|
|
1322
|
+
let insertIndex = -1;
|
|
1323
|
+
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
1324
|
+
if (source[index]?.kind === 'code') {
|
|
1325
|
+
insertIndex = index + 1;
|
|
1326
|
+
break;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (insertIndex === -1) return [...source, ...inserts];
|
|
1331
|
+
return [...source.slice(0, insertIndex), ...inserts, ...source.slice(insertIndex)];
|
|
1332
|
+
}
|
|
1333
|
+
|
|
820
1334
|
export function normalizeActivitySpacingRows(inputRows) {
|
|
821
1335
|
const rows = Array.isArray(inputRows) ? inputRows : [];
|
|
822
1336
|
const normalized = [];
|
|
@@ -863,7 +1377,7 @@ export function normalizeActivitySpacingRows(inputRows) {
|
|
|
863
1377
|
|
|
864
1378
|
function isReadActivityName(name) {
|
|
865
1379
|
const parsed = parseToolDisplayName(name);
|
|
866
|
-
return parsed.base === '
|
|
1380
|
+
return parsed.base === 'read' || parsed.base === 'Read';
|
|
867
1381
|
}
|
|
868
1382
|
|
|
869
1383
|
function isIgnorableSegmentAfterRead(item, activityType, activityName) {
|
|
@@ -917,7 +1431,7 @@ export function mergeActivitySummary(previousSummary, nextSummary, activityName)
|
|
|
917
1431
|
return lines.join('\n');
|
|
918
1432
|
}
|
|
919
1433
|
|
|
920
|
-
function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
1434
|
+
function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
921
1435
|
const rows = [];
|
|
922
1436
|
const pushTextRows = (text) => {
|
|
923
1437
|
const lines = String(text || '').split('\n');
|
|
@@ -1000,18 +1514,130 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
|
1000
1514
|
toolCalls.forEach((tool, idx) => pushActivityRows(tool, idx, toolCalls.length));
|
|
1001
1515
|
}
|
|
1002
1516
|
|
|
1517
|
+
const codeGenerationRows = getCodeGenerationActivityRows(msg);
|
|
1518
|
+
const generatingCodeRows = getGeneratingCodePlaceholderRows(msg, copy, contentWidth);
|
|
1519
|
+
const syntheticRows = [...codeGenerationRows, ...generatingCodeRows];
|
|
1003
1520
|
if (msg?.loading && (msg?.liveStatus || msg?.phase)) {
|
|
1521
|
+
const statusRows = [];
|
|
1004
1522
|
pushWrappedRow(
|
|
1005
|
-
|
|
1523
|
+
statusRows,
|
|
1006
1524
|
{
|
|
1007
1525
|
kind: 'status',
|
|
1008
1526
|
text: trimText(msg.liveStatus || msg.phase, 144)
|
|
1009
1527
|
},
|
|
1010
1528
|
Math.max(8, contentWidth - 2)
|
|
1011
1529
|
);
|
|
1530
|
+
syntheticRows.push(...statusRows);
|
|
1012
1531
|
}
|
|
1013
1532
|
|
|
1014
|
-
return normalizeActivitySpacingRows(rows);
|
|
1533
|
+
return normalizeActivitySpacingRows(insertRowsAfterLastCodeRow(rows, syntheticRows));
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function renderMessageRow(msg, row, idx, loaderTick) {
|
|
1537
|
+
if (row.kind === 'activity') {
|
|
1538
|
+
const activity = { type: row.activityType, name: row.name, status: row.status };
|
|
1539
|
+
const display = getActivityDisplayParts(activity);
|
|
1540
|
+
const dotColor =
|
|
1541
|
+
row.status === 'error' || row.status === 'blocked'
|
|
1542
|
+
? 'redBright'
|
|
1543
|
+
: row.status === 'done'
|
|
1544
|
+
? 'greenBright'
|
|
1545
|
+
: 'yellowBright';
|
|
1546
|
+
const textColor =
|
|
1547
|
+
activity.type === 'skill'
|
|
1548
|
+
? row.status === 'error'
|
|
1549
|
+
? 'redBright'
|
|
1550
|
+
: 'cyanBright'
|
|
1551
|
+
: row.status === 'error' || row.status === 'blocked'
|
|
1552
|
+
? 'redBright'
|
|
1553
|
+
: 'cyanBright';
|
|
1554
|
+
const durationText = formatActivityDurationText(row);
|
|
1555
|
+
return h(
|
|
1556
|
+
Box,
|
|
1557
|
+
{ key: `row-tool-${msg.id}-${idx}` },
|
|
1558
|
+
h(Text, { color: 'gray' }, ' '),
|
|
1559
|
+
h(Text, { color: dotColor }, '●'),
|
|
1560
|
+
h(Text, { color: 'gray' }, ' '),
|
|
1561
|
+
h(Text, { color: textColor }, display.primary),
|
|
1562
|
+
h(Text, { color: 'gray' }, display.secondary),
|
|
1563
|
+
durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
if (row.kind === 'activity-summary') {
|
|
1567
|
+
return h(
|
|
1568
|
+
Box,
|
|
1569
|
+
{ key: `row-tool-summary-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1570
|
+
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
if (row.kind === 'plan-progress') {
|
|
1574
|
+
return h(
|
|
1575
|
+
Box,
|
|
1576
|
+
{ key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
|
|
1577
|
+
h(Text, { color: 'cyanBright' }, '[plan] '),
|
|
1578
|
+
h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
|
|
1579
|
+
h(Text, { color: 'gray' }, ' -> '),
|
|
1580
|
+
h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
|
|
1581
|
+
h(Text, { color: 'gray' }, ': '),
|
|
1582
|
+
h(Text, { color: 'white' }, row.title)
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
if (row.kind === 'status') {
|
|
1586
|
+
const dots = '.'.repeat((loaderTick % 3) + 1);
|
|
1587
|
+
const phase = msg.phase;
|
|
1588
|
+
const color =
|
|
1589
|
+
phase === 'sending'
|
|
1590
|
+
? 'yellowBright'
|
|
1591
|
+
: phase === 'queued'
|
|
1592
|
+
? 'cyanBright'
|
|
1593
|
+
: phase === 'tooling'
|
|
1594
|
+
? 'magentaBright'
|
|
1595
|
+
: phase === 'generating'
|
|
1596
|
+
? 'greenBright'
|
|
1597
|
+
: 'cyanBright';
|
|
1598
|
+
return h(
|
|
1599
|
+
Box,
|
|
1600
|
+
{ key: `row-status-${msg.id}-${idx}`, marginTop: 1 },
|
|
1601
|
+
h(Text, { color: 'gray' }, ' '),
|
|
1602
|
+
h(Text, { color }, `${row.text}${dots}`)
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
if (row.kind === 'quote') {
|
|
1606
|
+
return h(
|
|
1607
|
+
Box,
|
|
1608
|
+
{ key: `row-quote-${msg.id}-${idx}`, marginTop: 1, marginLeft: 1, paddingLeft: 1 },
|
|
1609
|
+
h(Text, { color: 'yellow' }, '▍ '),
|
|
1610
|
+
h(Text, { color: row.color }, ...renderInlineCode(row.text, row.color))
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
if (row.kind === 'tree') {
|
|
1614
|
+
return h(
|
|
1615
|
+
Box,
|
|
1616
|
+
{ key: `row-tree-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1617
|
+
h(Text, { color: row.color }, row.text)
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
if (row.kind === 'code') {
|
|
1621
|
+
return h(
|
|
1622
|
+
Box,
|
|
1623
|
+
{ key: `row-code-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1624
|
+
h(Text, { color: 'gray' }, row.text)
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
if (row.kind === 'code-placeholder') {
|
|
1628
|
+
return h(
|
|
1629
|
+
Box,
|
|
1630
|
+
{ key: `row-code-placeholder-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1631
|
+
h(Text, { color: 'gray', dimColor: true }, String(row.lineNo || idx + 1).padStart(2, ' ')),
|
|
1632
|
+
h(Text, { color: 'gray' }, ' │ '),
|
|
1633
|
+
h(Text, { color: 'gray', dimColor: true }, row.text)
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
return renderTextLine(msg, row.text, idx, row.color);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function renderMessageRowsInOrder(msg, rows, loaderTick, copy) {
|
|
1640
|
+
return rows.map((row, idx) => renderMessageRow(msg, row, idx, loaderTick));
|
|
1015
1641
|
}
|
|
1016
1642
|
|
|
1017
1643
|
|
|
@@ -1124,104 +1750,12 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
|
|
|
1124
1750
|
return h(PlanSummaryBubble, { msg, copy });
|
|
1125
1751
|
}
|
|
1126
1752
|
const theme = roleStyle(msg.label);
|
|
1127
|
-
const allRows = buildMessageRows(msg, showToolDetails, contentWidth);
|
|
1753
|
+
const allRows = buildMessageRows(msg, showToolDetails, contentWidth, copy);
|
|
1128
1754
|
const start = rowWindow ? Math.max(0, rowWindow.start || 0) : 0;
|
|
1129
1755
|
const end = rowWindow ? Math.max(start, rowWindow.end || allRows.length) : allRows.length;
|
|
1130
1756
|
const visibleRows = allRows.slice(start, end);
|
|
1131
|
-
const rendered =
|
|
1132
|
-
|
|
1133
|
-
const activity = { type: row.activityType, name: row.name, status: row.status };
|
|
1134
|
-
const display = getActivityDisplayParts(activity);
|
|
1135
|
-
const dotColor =
|
|
1136
|
-
activity.type === 'skill'
|
|
1137
|
-
? row.status === 'error'
|
|
1138
|
-
? 'redBright'
|
|
1139
|
-
: 'blueBright'
|
|
1140
|
-
: row.status === 'error' || row.status === 'blocked'
|
|
1141
|
-
? 'redBright'
|
|
1142
|
-
: 'greenBright';
|
|
1143
|
-
const textColor =
|
|
1144
|
-
activity.type === 'skill'
|
|
1145
|
-
? row.status === 'error'
|
|
1146
|
-
? 'redBright'
|
|
1147
|
-
: 'cyanBright'
|
|
1148
|
-
: row.status === 'error' || row.status === 'blocked'
|
|
1149
|
-
? 'redBright'
|
|
1150
|
-
: 'greenBright';
|
|
1151
|
-
return h(
|
|
1152
|
-
Box,
|
|
1153
|
-
{ key: `row-tool-${msg.id}-${idx}` },
|
|
1154
|
-
h(Text, { color: 'gray' }, ' '),
|
|
1155
|
-
h(Text, { color: dotColor }, '●'),
|
|
1156
|
-
h(Text, { color: 'gray' }, ' '),
|
|
1157
|
-
h(Text, { color: textColor }, display.primary),
|
|
1158
|
-
h(Text, { color: 'gray' }, display.secondary),
|
|
1159
|
-
row.durationText ? h(Text, { color: row.statusColor }, ` ${row.durationText}`) : null
|
|
1160
|
-
);
|
|
1161
|
-
}
|
|
1162
|
-
if (row.kind === 'activity-summary') {
|
|
1163
|
-
return h(
|
|
1164
|
-
Box,
|
|
1165
|
-
{ key: `row-tool-summary-${msg.id}-${idx}`, marginLeft: 2 },
|
|
1166
|
-
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
if (row.kind === 'plan-progress') {
|
|
1170
|
-
return h(
|
|
1171
|
-
Box,
|
|
1172
|
-
{ key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
|
|
1173
|
-
h(Text, { color: 'cyanBright' }, '[plan] '),
|
|
1174
|
-
h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
|
|
1175
|
-
h(Text, { color: 'gray' }, ' -> '),
|
|
1176
|
-
h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
|
|
1177
|
-
h(Text, { color: 'gray' }, ': '),
|
|
1178
|
-
h(Text, { color: 'white' }, row.title)
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
|
-
if (row.kind === 'status') {
|
|
1182
|
-
const dots = '.'.repeat((loaderTick % 3) + 1);
|
|
1183
|
-
const phase = msg.phase;
|
|
1184
|
-
const color =
|
|
1185
|
-
phase === 'sending'
|
|
1186
|
-
? 'yellowBright'
|
|
1187
|
-
: phase === 'queued'
|
|
1188
|
-
? 'cyanBright'
|
|
1189
|
-
: phase === 'tooling'
|
|
1190
|
-
? 'magentaBright'
|
|
1191
|
-
: phase === 'generating'
|
|
1192
|
-
? 'greenBright'
|
|
1193
|
-
: 'cyanBright';
|
|
1194
|
-
return h(
|
|
1195
|
-
Box,
|
|
1196
|
-
{ key: `row-status-${msg.id}-${idx}`, marginTop: 1 },
|
|
1197
|
-
h(Text, { color: 'gray' }, ' '),
|
|
1198
|
-
h(Text, { color }, `${row.text}${dots}`)
|
|
1199
|
-
);
|
|
1200
|
-
}
|
|
1201
|
-
if (row.kind === 'quote') {
|
|
1202
|
-
return h(
|
|
1203
|
-
Box,
|
|
1204
|
-
{ key: `row-quote-${msg.id}-${idx}`, marginTop: 1, marginLeft: 1, paddingLeft: 1 },
|
|
1205
|
-
h(Text, { color: 'yellow' }, '▍ '),
|
|
1206
|
-
h(Text, { color: row.color }, ...renderInlineCode(row.text, row.color))
|
|
1207
|
-
);
|
|
1208
|
-
}
|
|
1209
|
-
if (row.kind === 'tree') {
|
|
1210
|
-
return h(
|
|
1211
|
-
Box,
|
|
1212
|
-
{ key: `row-tree-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1213
|
-
h(Text, { color: row.color }, row.text)
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
if (row.kind === 'code') {
|
|
1217
|
-
return h(
|
|
1218
|
-
Box,
|
|
1219
|
-
{ key: `row-code-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1220
|
-
h(Text, { color: 'gray' }, row.text)
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
return renderTextLine(msg, row.text, idx, row.color);
|
|
1224
|
-
});
|
|
1757
|
+
const rendered = renderMessageRowsInOrder(msg, visibleRows, loaderTick, copy);
|
|
1758
|
+
const autoSkillBadge = formatAutoSkillBadge(msg.autoSkillNames, copy);
|
|
1225
1759
|
|
|
1226
1760
|
return h(
|
|
1227
1761
|
Box,
|
|
@@ -1245,7 +1779,9 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
|
|
|
1245
1779
|
null,
|
|
1246
1780
|
h(Text, { color: theme.badgeText, backgroundColor: theme.badgeBg }, ` ${messageLabel(msg.label, copy)} `)
|
|
1247
1781
|
),
|
|
1248
|
-
|
|
1782
|
+
autoSkillBadge
|
|
1783
|
+
? h(Text, { color: 'blueBright' }, autoSkillBadge)
|
|
1784
|
+
: h(Text, { color: theme.chrome }, ' ')
|
|
1249
1785
|
),
|
|
1250
1786
|
...rendered
|
|
1251
1787
|
)
|
|
@@ -1336,7 +1872,7 @@ function PendingPanel({ pendingQueue, copy }) {
|
|
|
1336
1872
|
return h(
|
|
1337
1873
|
Box,
|
|
1338
1874
|
{
|
|
1339
|
-
marginTop:
|
|
1875
|
+
marginTop: 0,
|
|
1340
1876
|
flexDirection: 'column',
|
|
1341
1877
|
borderStyle: 'round',
|
|
1342
1878
|
borderColor: 'cyan',
|
|
@@ -1398,7 +1934,7 @@ function InputBar({
|
|
|
1398
1934
|
return h(
|
|
1399
1935
|
Box,
|
|
1400
1936
|
{
|
|
1401
|
-
marginTop:
|
|
1937
|
+
marginTop: 0,
|
|
1402
1938
|
flexDirection: 'column',
|
|
1403
1939
|
borderStyle: 'round',
|
|
1404
1940
|
borderColor: 'cyan',
|
|
@@ -1416,11 +1952,10 @@ function InputBar({
|
|
|
1416
1952
|
),
|
|
1417
1953
|
h(
|
|
1418
1954
|
Box,
|
|
1419
|
-
|
|
1420
|
-
h(Text, { color: '
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
h(Text, { color: showToolDetails ? 'greenBright' : 'gray' }, ` ${copy.generic.tools} ${showToolDetails ? copy.generic.open : copy.generic.collapsed}`)
|
|
1955
|
+
{ flexDirection: 'column', alignItems: 'flex-end' },
|
|
1956
|
+
h(Text, { color: showToolDetails ? 'greenBright' : 'gray' }, ` ${copy.generic.tools} ${showToolDetails ? copy.generic.open : copy.generic.collapsed}`),
|
|
1957
|
+
pendingQueueLength > 0 ? h(Text, { color: 'cyanBright' }, ` ${copy.generic.queued} ${pendingQueueLength}`) : null,
|
|
1958
|
+
inputStage !== 'idle' || busy ? h(Text, { color: status.color }, ` ${status.tag}`) : null
|
|
1424
1959
|
)
|
|
1425
1960
|
),
|
|
1426
1961
|
h(
|
|
@@ -1503,6 +2038,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1503
2038
|
const [runtimeStatus, setRuntimeStatus] = useState(
|
|
1504
2039
|
makeIdleStatus(copy, runtime.getRuntimeState?.(), 'ready')
|
|
1505
2040
|
);
|
|
2041
|
+
const [runtimeState, setRuntimeState] = useState(runtime.getRuntimeState?.() || null);
|
|
1506
2042
|
const [inputStage, setInputStage] = useState('idle');
|
|
1507
2043
|
const [planState, setPlanState] = useState({
|
|
1508
2044
|
current: 0,
|
|
@@ -1516,15 +2052,22 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1516
2052
|
const [lastKeyDebug, setLastKeyDebug] = useState('');
|
|
1517
2053
|
const [showToolDetails, setShowToolDetails] = useState(false);
|
|
1518
2054
|
const activeAssistantIdRef = useRef(null);
|
|
2055
|
+
const activeAssistantAutoSkillNamesRef = useRef([]);
|
|
2056
|
+
const streamedAssistantHandledRef = useRef(false);
|
|
1519
2057
|
const activeUserMessageIdRef = useRef(null);
|
|
1520
2058
|
const cursorIndexRef = useRef(0);
|
|
1521
2059
|
const inFlightRef = useRef(false);
|
|
2060
|
+
const messagesRef = useRef([]);
|
|
1522
2061
|
const pendingQueueRef = useRef([]);
|
|
1523
2062
|
const deltaBufferRef = useRef('');
|
|
1524
2063
|
const deltaFlushTimerRef = useRef(null);
|
|
1525
2064
|
const escSeqRef = useRef('');
|
|
1526
2065
|
const planTextBufferRef = useRef('');
|
|
1527
2066
|
const { exit } = useApp();
|
|
2067
|
+
|
|
2068
|
+
useEffect(() => {
|
|
2069
|
+
messagesRef.current = messages;
|
|
2070
|
+
}, [messages]);
|
|
1528
2071
|
const startupHint = copy.generic.startupHint;
|
|
1529
2072
|
const isBackspaceKey = (value, key) =>
|
|
1530
2073
|
Boolean(key?.backspace) || value === '\u0008' || value === '\u007f' || (key?.ctrl && value === 'h');
|
|
@@ -1565,6 +2108,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1565
2108
|
if (!snapshot) return;
|
|
1566
2109
|
setDisplaySessionId(snapshot.sessionId || sessionId);
|
|
1567
2110
|
setDisplayModel(snapshot.model || model);
|
|
2111
|
+
setRuntimeState(snapshot);
|
|
1568
2112
|
setRuntimeStatus(makeIdleStatus(copy, snapshot, variant));
|
|
1569
2113
|
};
|
|
1570
2114
|
|
|
@@ -1620,7 +2164,12 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1620
2164
|
} else {
|
|
1621
2165
|
segments.push({ type: 'text', text: delta });
|
|
1622
2166
|
}
|
|
1623
|
-
|
|
2167
|
+
const nextText = `${m.text}${delta}`;
|
|
2168
|
+
return {
|
|
2169
|
+
...m,
|
|
2170
|
+
text: nextText,
|
|
2171
|
+
segments
|
|
2172
|
+
};
|
|
1624
2173
|
})
|
|
1625
2174
|
);
|
|
1626
2175
|
};
|
|
@@ -1644,6 +2193,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1644
2193
|
const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
|
|
1645
2194
|
const activityType = toolEvent.type || 'tool';
|
|
1646
2195
|
const idx = findActivityUpdateIndex(toolCalls, toolEvent);
|
|
2196
|
+
const startedAt = toolEvent.status === 'running' ? Date.now() : undefined;
|
|
1647
2197
|
|
|
1648
2198
|
if (idx === -1) {
|
|
1649
2199
|
toolCalls.push({
|
|
@@ -1651,6 +2201,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1651
2201
|
id: toolEvent.id || '',
|
|
1652
2202
|
name: toolEvent.name,
|
|
1653
2203
|
status: toolEvent.status,
|
|
2204
|
+
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
2205
|
+
...(startedAt ? { startedAt } : {}),
|
|
1654
2206
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1655
2207
|
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
1656
2208
|
});
|
|
@@ -1660,6 +2212,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1660
2212
|
type: activityType,
|
|
1661
2213
|
id: toolEvent.id || toolCalls[idx].id,
|
|
1662
2214
|
status: toolEvent.status,
|
|
2215
|
+
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
2216
|
+
...(startedAt ? { startedAt } : {}),
|
|
1663
2217
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1664
2218
|
...(toolEvent.summary
|
|
1665
2219
|
? { summary: mergeActivitySummary(toolCalls[idx].summary, toolEvent.summary, toolEvent.name) }
|
|
@@ -1673,6 +2227,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1673
2227
|
id: toolEvent.id || '',
|
|
1674
2228
|
name: toolEvent.name,
|
|
1675
2229
|
status: toolEvent.status,
|
|
2230
|
+
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
2231
|
+
...(startedAt ? { startedAt } : {}),
|
|
1676
2232
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1677
2233
|
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
1678
2234
|
};
|
|
@@ -1737,7 +2293,13 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1737
2293
|
if (!activeAssistantIdRef.current && result.text) {
|
|
1738
2294
|
setMessages((prev) => [
|
|
1739
2295
|
...prev,
|
|
1740
|
-
{
|
|
2296
|
+
{
|
|
2297
|
+
id: nextId(),
|
|
2298
|
+
label: 'coder',
|
|
2299
|
+
text: result.text,
|
|
2300
|
+
color: 'greenBright',
|
|
2301
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
2302
|
+
}
|
|
1741
2303
|
]);
|
|
1742
2304
|
}
|
|
1743
2305
|
return;
|
|
@@ -1761,8 +2323,38 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1761
2323
|
setMessages((prev) => prev.map((m) => (m.id === targetId ? { ...m, ...patch } : m)));
|
|
1762
2324
|
};
|
|
1763
2325
|
|
|
2326
|
+
const updatePendingToolCallOnActiveAssistant = (toolCall) => {
|
|
2327
|
+
const targetId = activeAssistantIdRef.current;
|
|
2328
|
+
if (!targetId || !toolCall) return;
|
|
2329
|
+
setMessages((prev) =>
|
|
2330
|
+
prev.map((m) => {
|
|
2331
|
+
if (m.id !== targetId) return m;
|
|
2332
|
+
const pendingToolCalls = Array.isArray(m.pendingToolCalls) ? [...m.pendingToolCalls] : [];
|
|
2333
|
+
const nextCall = {
|
|
2334
|
+
id: toolCall.id || '',
|
|
2335
|
+
name: toolCall.name || '',
|
|
2336
|
+
arguments: typeof toolCall.arguments === 'string' ? safeJsonParse(toolCall.arguments) ?? toolCall.arguments : toolCall.arguments,
|
|
2337
|
+
status: 'pending',
|
|
2338
|
+
type: 'tool'
|
|
2339
|
+
};
|
|
2340
|
+
const idx = pendingToolCalls.findIndex((entry) => entry.id && entry.id === nextCall.id);
|
|
2341
|
+
if (idx === -1) pendingToolCalls.push(nextCall);
|
|
2342
|
+
else pendingToolCalls[idx] = { ...pendingToolCalls[idx], ...nextCall };
|
|
2343
|
+
return { ...m, pendingToolCalls };
|
|
2344
|
+
})
|
|
2345
|
+
);
|
|
2346
|
+
};
|
|
2347
|
+
|
|
1764
2348
|
const finalizeActiveAssistant = () => {
|
|
1765
|
-
setActiveAssistantMeta({
|
|
2349
|
+
setActiveAssistantMeta({
|
|
2350
|
+
loading: false,
|
|
2351
|
+
phase: undefined,
|
|
2352
|
+
liveStatus: undefined,
|
|
2353
|
+
planStep: undefined,
|
|
2354
|
+
pendingToolCalls: [],
|
|
2355
|
+
codeGenerationEndedAt: undefined,
|
|
2356
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
2357
|
+
});
|
|
1766
2358
|
};
|
|
1767
2359
|
|
|
1768
2360
|
const ensureActiveAssistant = () => {
|
|
@@ -1780,7 +2372,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1780
2372
|
segments: [],
|
|
1781
2373
|
loading: true,
|
|
1782
2374
|
phase: 'thinking',
|
|
1783
|
-
liveStatus: copy.runtime.modelThinking
|
|
2375
|
+
liveStatus: copy.runtime.modelThinking,
|
|
2376
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
1784
2377
|
}
|
|
1785
2378
|
]);
|
|
1786
2379
|
return aid;
|
|
@@ -1795,11 +2388,14 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1795
2388
|
setPlanState({ current: 0, total: 0, role: '', title: '', failed: false, steps: [] });
|
|
1796
2389
|
planTextBufferRef.current = '';
|
|
1797
2390
|
activeAssistantIdRef.current = null;
|
|
2391
|
+
activeAssistantAutoSkillNamesRef.current = [];
|
|
2392
|
+
streamedAssistantHandledRef.current = false;
|
|
1798
2393
|
deltaBufferRef.current = '';
|
|
1799
2394
|
|
|
1800
2395
|
runtime
|
|
1801
2396
|
.submit(line, (event) => {
|
|
1802
2397
|
if (event?.type === 'assistant:start') {
|
|
2398
|
+
streamedAssistantHandledRef.current = true;
|
|
1803
2399
|
setRuntimeStatus(makeStatus(copy.runtime.modelThinking, copy.runtime.requestDelivered, 'cyanBright'));
|
|
1804
2400
|
setInputStage('thinking');
|
|
1805
2401
|
updateMessageMeta(activeUserMessageIdRef.current, {
|
|
@@ -1815,15 +2411,85 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1815
2411
|
updatePlanProgressFromText(event.text);
|
|
1816
2412
|
setRuntimeStatus(makeStatus(copy.runtime.generatingReply, copy.runtime.streamingReply, 'greenBright'));
|
|
1817
2413
|
setInputStage('streaming');
|
|
1818
|
-
setActiveAssistantMeta(
|
|
2414
|
+
setActiveAssistantMeta((() => {
|
|
2415
|
+
const targetId = activeAssistantIdRef.current;
|
|
2416
|
+
let liveStatus = copy.runtime.generatingReply;
|
|
2417
|
+
if (targetId) {
|
|
2418
|
+
const current = messagesRef.current?.find?.((m) => m.id === targetId);
|
|
2419
|
+
const pendingToolCalls = Array.isArray(current?.pendingToolCalls) ? current.pendingToolCalls : [];
|
|
2420
|
+
if (pendingToolCalls.length > 0) {
|
|
2421
|
+
liveStatus = copy.runtime.generatingCode;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return { loading: true, phase: 'generating', liveStatus };
|
|
2425
|
+
})());
|
|
1819
2426
|
queueAssistantDelta(event.text);
|
|
1820
2427
|
}
|
|
2428
|
+
if (event?.type === 'assistant:tool_call_delta') {
|
|
2429
|
+
ensureActiveAssistant();
|
|
2430
|
+
const parsed = parseToolDisplayName(event.toolCall?.name);
|
|
2431
|
+
const isCodeTool = new Set(['write', 'edit', 'patch', 'generate_diff']).has(parsed.base);
|
|
2432
|
+
if (isCodeTool) {
|
|
2433
|
+
setRuntimeStatus(makeStatus(copy.runtime.generatingCode, copy.runtime.streamingReply, 'greenBright'));
|
|
2434
|
+
setInputStage('streaming');
|
|
2435
|
+
const startedAt = Date.now();
|
|
2436
|
+
const targetId = activeAssistantIdRef.current;
|
|
2437
|
+
if (targetId) {
|
|
2438
|
+
setMessages((prev) =>
|
|
2439
|
+
prev.map((m) => {
|
|
2440
|
+
if (m.id !== targetId) return m;
|
|
2441
|
+
return ensureCodeGenerationTiming(
|
|
2442
|
+
{
|
|
2443
|
+
...m,
|
|
2444
|
+
loading: true,
|
|
2445
|
+
phase: 'generating',
|
|
2446
|
+
liveStatus: copy.runtime.generatingCode
|
|
2447
|
+
},
|
|
2448
|
+
startedAt
|
|
2449
|
+
);
|
|
2450
|
+
})
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
updatePendingToolCallOnActiveAssistant(event.toolCall);
|
|
2455
|
+
}
|
|
1821
2456
|
if (event?.type === 'assistant:response') {
|
|
1822
|
-
|
|
1823
|
-
|
|
2457
|
+
const hasPlannedTools = Array.isArray(event.toolCalls) && event.toolCalls.length > 0;
|
|
2458
|
+
if (hasPlannedTools) {
|
|
2459
|
+
setRuntimeStatus(makeStatus(copy.runtime.toolRunning, copy.runtime.waitingToolStart || copy.runtime.streamingReply, 'magentaBright'));
|
|
2460
|
+
setInputStage('thinking');
|
|
2461
|
+
} else {
|
|
2462
|
+
setRuntimeStatus(makeStatus(copy.runtime.replyCompleted, copy.runtime.outputFinished, 'greenBright'));
|
|
2463
|
+
setInputStage('idle');
|
|
2464
|
+
}
|
|
1824
2465
|
flushAssistantDelta();
|
|
1825
|
-
|
|
1826
|
-
|
|
2466
|
+
const targetId = activeAssistantIdRef.current;
|
|
2467
|
+
const hadActiveAssistant = Boolean(targetId);
|
|
2468
|
+
if (hadActiveAssistant) {
|
|
2469
|
+
streamedAssistantHandledRef.current = true;
|
|
2470
|
+
}
|
|
2471
|
+
if (targetId && !hasPlannedTools) {
|
|
2472
|
+
setMessages((prev) =>
|
|
2473
|
+
prev.map((m) => {
|
|
2474
|
+
if (m.id !== targetId) return m;
|
|
2475
|
+
return {
|
|
2476
|
+
...m,
|
|
2477
|
+
...(typeof event.text === 'string' && event.text.length > 0 ? { text: event.text } : {}),
|
|
2478
|
+
loading: false,
|
|
2479
|
+
phase: undefined,
|
|
2480
|
+
liveStatus: undefined,
|
|
2481
|
+
planStep: undefined,
|
|
2482
|
+
pendingToolCalls: [],
|
|
2483
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current,
|
|
2484
|
+
...(m.codeGenerationStartedAt && !m.codeGenerationEndedAt ? { codeGenerationEndedAt: Date.now() } : {})
|
|
2485
|
+
};
|
|
2486
|
+
})
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
if (!hasPlannedTools) {
|
|
2490
|
+
activeAssistantIdRef.current = null;
|
|
2491
|
+
}
|
|
2492
|
+
if (!hadActiveAssistant && !hasPlannedTools && event.text) {
|
|
1827
2493
|
setMessages((prev) => [
|
|
1828
2494
|
...prev,
|
|
1829
2495
|
{ id: nextId(), label: 'coder', text: event.text, color: 'greenBright' }
|
|
@@ -1835,12 +2501,28 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1835
2501
|
const detail = describeToolActivity(event.name, copy);
|
|
1836
2502
|
setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'magentaBright'));
|
|
1837
2503
|
setInputStage('tooling');
|
|
1838
|
-
|
|
2504
|
+
const targetId = activeAssistantIdRef.current;
|
|
2505
|
+
if (targetId) {
|
|
2506
|
+
const finishedAt = Date.now();
|
|
2507
|
+
setMessages((prev) =>
|
|
2508
|
+
prev.map((m) => {
|
|
2509
|
+
if (m.id !== targetId) return m;
|
|
2510
|
+
const nextMessage = isCodeActivityName(event.name) ? finishCodeGeneration(m, finishedAt) : m;
|
|
2511
|
+
return {
|
|
2512
|
+
...nextMessage,
|
|
2513
|
+
loading: true,
|
|
2514
|
+
phase: 'tooling',
|
|
2515
|
+
liveStatus: detail
|
|
2516
|
+
};
|
|
2517
|
+
})
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
1839
2520
|
updateActivityStatusOnActiveAssistant({
|
|
1840
2521
|
type: 'tool',
|
|
1841
2522
|
id: event.id,
|
|
1842
2523
|
name: event.name,
|
|
1843
|
-
status: 'running'
|
|
2524
|
+
status: 'running',
|
|
2525
|
+
arguments: event.arguments
|
|
1844
2526
|
});
|
|
1845
2527
|
}
|
|
1846
2528
|
if (event?.type === 'tool:end') {
|
|
@@ -1854,7 +2536,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1854
2536
|
name: event.name,
|
|
1855
2537
|
status: 'done',
|
|
1856
2538
|
durationMs: event.durationMs,
|
|
1857
|
-
summary: event.summary
|
|
2539
|
+
summary: event.summary,
|
|
2540
|
+
arguments: event.arguments
|
|
1858
2541
|
});
|
|
1859
2542
|
}
|
|
1860
2543
|
if (event?.type === 'tool:blocked') {
|
|
@@ -1873,7 +2556,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1873
2556
|
type: 'tool',
|
|
1874
2557
|
id: event.id,
|
|
1875
2558
|
name: event.name,
|
|
1876
|
-
status: 'blocked'
|
|
2559
|
+
status: 'blocked',
|
|
2560
|
+
arguments: event.arguments
|
|
1877
2561
|
});
|
|
1878
2562
|
}
|
|
1879
2563
|
if (event?.type === 'tool:error') {
|
|
@@ -1894,7 +2578,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1894
2578
|
name: event.name,
|
|
1895
2579
|
status: 'error',
|
|
1896
2580
|
durationMs: event.durationMs,
|
|
1897
|
-
summary: event.summary
|
|
2581
|
+
summary: event.summary,
|
|
2582
|
+
arguments: event.arguments
|
|
1898
2583
|
});
|
|
1899
2584
|
}
|
|
1900
2585
|
if (event?.type === 'skill:start') {
|
|
@@ -1932,6 +2617,21 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1932
2617
|
summary: event.summary
|
|
1933
2618
|
});
|
|
1934
2619
|
}
|
|
2620
|
+
if (event?.type === 'skill:auto') {
|
|
2621
|
+
const detail = describeAutoSkillActivity(event.names, copy);
|
|
2622
|
+
if (Array.isArray(event.names) && event.names.length > 0) {
|
|
2623
|
+
activeAssistantAutoSkillNamesRef.current = event.names.filter(Boolean);
|
|
2624
|
+
const targetId = activeAssistantIdRef.current;
|
|
2625
|
+
if (targetId) {
|
|
2626
|
+
setMessages((prev) =>
|
|
2627
|
+
prev.map((m) => (m.id === targetId ? { ...m, autoSkillNames: activeAssistantAutoSkillNamesRef.current } : m))
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
if (detail) {
|
|
2632
|
+
setRuntimeStatus(makeStatus(copy.runtime.skillRunning, detail, 'blueBright'));
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
1935
2635
|
if (event?.type === 'compact:auto') {
|
|
1936
2636
|
setRuntimeStatus(makeStatus(copy.runtime.compactingContext, `auto compact ${event.mode}`, 'yellowBright'));
|
|
1937
2637
|
setMessages((prev) => [
|
|
@@ -1971,6 +2671,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1971
2671
|
}
|
|
1972
2672
|
syncRuntimeVisualState(result.type === 'noop' ? 'ready' : 'after');
|
|
1973
2673
|
if (result.type === 'noop') return;
|
|
2674
|
+
if (!shouldAppendAssistantResult(result, activeAssistantIdRef.current, streamedAssistantHandledRef.current)) return;
|
|
1974
2675
|
appendResultMessage(result);
|
|
1975
2676
|
})
|
|
1976
2677
|
.catch((err) => {
|
|
@@ -1997,6 +2698,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1997
2698
|
flushAssistantDelta();
|
|
1998
2699
|
finalizeActiveAssistant();
|
|
1999
2700
|
activeAssistantIdRef.current = null;
|
|
2701
|
+
streamedAssistantHandledRef.current = false;
|
|
2000
2702
|
activeUserMessageIdRef.current = null;
|
|
2001
2703
|
if (deltaFlushTimerRef.current) {
|
|
2002
2704
|
clearTimeout(deltaFlushTimerRef.current);
|
|
@@ -2386,7 +3088,6 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2386
3088
|
Box,
|
|
2387
3089
|
{ flexDirection: 'column' },
|
|
2388
3090
|
h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
|
|
2389
|
-
h(RuntimeStrip, { busy, runtimeStatus, loaderTick, copy }),
|
|
2390
3091
|
h(MessageList, {
|
|
2391
3092
|
messages: visibleMessages,
|
|
2392
3093
|
loaderTick,
|
|
@@ -2396,12 +3097,17 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2396
3097
|
}),
|
|
2397
3098
|
h(
|
|
2398
3099
|
Box,
|
|
2399
|
-
{ marginTop:
|
|
3100
|
+
{ marginTop: 0, marginBottom: 0, justifyContent: 'space-between', width: '100%' },
|
|
2400
3101
|
h(
|
|
2401
|
-
|
|
2402
|
-
{
|
|
2403
|
-
|
|
2404
|
-
|
|
3102
|
+
Box,
|
|
3103
|
+
{ flexGrow: 1 },
|
|
3104
|
+
h(
|
|
3105
|
+
Text,
|
|
3106
|
+
{ color: 'gray' },
|
|
3107
|
+
`${showToolDetails ? copy.generic.toolSummaryExpanded : copy.generic.toolSummaryCollapsed} (${copy.generic.toggleToolSummary}) · ${copy.generic.scrollHint}`
|
|
3108
|
+
)
|
|
3109
|
+
),
|
|
3110
|
+
h(ContextProgressMeter, { runtimeState, runtimeStatus, compact: true })
|
|
2405
3111
|
),
|
|
2406
3112
|
h(SuggestionPanel, { commandSuggestions, suggestionNav, menuIndex, copy }),
|
|
2407
3113
|
h(PendingPanel, { pendingQueue, copy }),
|