codemini-cli 0.1.19 → 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/package.json +1 -1
- package/src/core/agent-loop.js +7 -2
- package/src/core/chat-runtime.js +10 -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/tools.js +15 -3
- package/src/tui/chat-app.js +584 -35
package/package.json
CHANGED
package/src/core/agent-loop.js
CHANGED
|
@@ -262,7 +262,7 @@ export async function runAgentLoop({
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
if (!approved) {
|
|
265
|
-
if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id });
|
|
265
|
+
if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id, arguments: args });
|
|
266
266
|
const blockedMessage = {
|
|
267
267
|
role: 'tool',
|
|
268
268
|
tool_call_id: call.id,
|
|
@@ -274,6 +274,7 @@ export async function runAgentLoop({
|
|
|
274
274
|
type: 'tool:result',
|
|
275
275
|
name: displayName,
|
|
276
276
|
id: call.id,
|
|
277
|
+
arguments: args,
|
|
277
278
|
content: blockedMessage.content,
|
|
278
279
|
blocked: true
|
|
279
280
|
});
|
|
@@ -281,7 +282,7 @@ export async function runAgentLoop({
|
|
|
281
282
|
continue;
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id });
|
|
285
|
+
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id, arguments: args });
|
|
285
286
|
const handler = toolHandlers[toolName];
|
|
286
287
|
if (!handler) {
|
|
287
288
|
throw new Error(`Unknown tool: ${call.name}`);
|
|
@@ -297,6 +298,7 @@ export async function runAgentLoop({
|
|
|
297
298
|
type: 'tool:error',
|
|
298
299
|
name: displayName,
|
|
299
300
|
id: call.id,
|
|
301
|
+
arguments: args,
|
|
300
302
|
durationMs,
|
|
301
303
|
summary: trimInline(message, 120)
|
|
302
304
|
});
|
|
@@ -312,6 +314,7 @@ export async function runAgentLoop({
|
|
|
312
314
|
type: 'tool:result',
|
|
313
315
|
name: displayName,
|
|
314
316
|
id: call.id,
|
|
317
|
+
arguments: args,
|
|
315
318
|
content: toolMessage.content,
|
|
316
319
|
error: true
|
|
317
320
|
});
|
|
@@ -324,6 +327,7 @@ export async function runAgentLoop({
|
|
|
324
327
|
type: 'tool:end',
|
|
325
328
|
name: displayName,
|
|
326
329
|
id: call.id,
|
|
330
|
+
arguments: args,
|
|
327
331
|
durationMs,
|
|
328
332
|
summary: summarizeToolResult(toolResult)
|
|
329
333
|
});
|
|
@@ -339,6 +343,7 @@ export async function runAgentLoop({
|
|
|
339
343
|
type: 'tool:result',
|
|
340
344
|
name: displayName,
|
|
341
345
|
id: call.id,
|
|
346
|
+
arguments: args,
|
|
342
347
|
content: toolMessage.content
|
|
343
348
|
});
|
|
344
349
|
}
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -1377,6 +1377,9 @@ async function askModel({
|
|
|
1377
1377
|
maxRetries: config.gateway.max_retries ?? 2,
|
|
1378
1378
|
onTextDelta: (delta) => {
|
|
1379
1379
|
if (onAgentEvent) onAgentEvent({ type: 'assistant:delta', text: delta });
|
|
1380
|
+
},
|
|
1381
|
+
onToolCallDelta: (toolCall) => {
|
|
1382
|
+
if (onAgentEvent) onAgentEvent({ type: 'assistant:tool_call_delta', toolCall });
|
|
1380
1383
|
}
|
|
1381
1384
|
});
|
|
1382
1385
|
}
|
|
@@ -2672,6 +2675,13 @@ export async function createChatRuntime({
|
|
|
2672
2675
|
}
|
|
2673
2676
|
|
|
2674
2677
|
const expandedText = await expandFileMentions(parsedInput.text, process.cwd());
|
|
2678
|
+
const selectedAutoSkills = selectAutoSkillNames(expandedText).filter((name) => isSkillEnabled(config, name));
|
|
2679
|
+
if (selectedAutoSkills.length > 0 && onAgentEvent) {
|
|
2680
|
+
onAgentEvent({
|
|
2681
|
+
type: 'skill:auto',
|
|
2682
|
+
names: selectedAutoSkills
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2675
2685
|
const routedSystemPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
|
|
2676
2686
|
const result = await askModel({
|
|
2677
2687
|
text: expandedText,
|
|
@@ -205,6 +205,7 @@ export async function createChatCompletionStream({
|
|
|
205
205
|
temperature = 0.2,
|
|
206
206
|
tools,
|
|
207
207
|
onTextDelta,
|
|
208
|
+
onToolCallDelta,
|
|
208
209
|
timeoutMs = 90000,
|
|
209
210
|
maxRetries = 2
|
|
210
211
|
}) {
|
|
@@ -248,6 +249,14 @@ export async function createChatCompletionStream({
|
|
|
248
249
|
if (td.function?.name) current.name = `${current.name}${td.function.name}`;
|
|
249
250
|
if (td.function?.arguments) current.arguments = `${current.arguments}${td.function.arguments}`;
|
|
250
251
|
toolCallsByIndex.set(idx, current);
|
|
252
|
+
if (onToolCallDelta) {
|
|
253
|
+
onToolCallDelta({
|
|
254
|
+
index: idx,
|
|
255
|
+
id: current.id || `tc-${idx + 1}`,
|
|
256
|
+
name: current.name,
|
|
257
|
+
arguments: current.arguments || '{}'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
251
260
|
}
|
|
252
261
|
}
|
|
253
262
|
|
|
@@ -118,5 +118,5 @@ export function getEffectivePolicy(config) {
|
|
|
118
118
|
|
|
119
119
|
export function getShellSystemPrompt(value) {
|
|
120
120
|
const profile = getShellProfile(value);
|
|
121
|
-
return `You are CodeMini CLI working in a ${profile.label} shell environment. Prefer OpenCode-style primary tools first: use read to inspect files, grep to search file contents, glob to find files by pattern, list to inspect directories, edit to modify existing files, write to create or fully rewrite files when appropriate, patch to apply unified diffs, and run for one-shot shell commands. Treat edit as the default editing path for existing code. Internal low-level edit strategies such as target resolution, block replacement, exact text replacement, and anchored inserts are handled inside edit rather than exposed as separate tools. Use generate_diff when you need a structured preview of a proposed file change.
|
|
121
|
+
return `You are CodeMini CLI working in a ${profile.label} shell environment. Prefer OpenCode-style primary tools first: use read to inspect files, grep to search file contents, glob to find files by pattern, list to inspect directories, edit to modify existing files, write to create or fully rewrite files when appropriate, patch to apply unified diffs, and run for one-shot shell commands like install, build, test, or other finite tasks. Classify frontend, backend, database, and Docker work carefully: use run for finite commands, and use start_service, list_services, get_service_status, get_service_logs, and stop_service for long-running servers, watchers, and dev processes. Treat edit as the default editing path for existing code. Internal low-level edit strategies such as target resolution, block replacement, exact text replacement, and anchored inserts are handled inside edit rather than exposed as separate tools. Use generate_diff when you need a structured preview of a proposed file change. For existing code files, prefer grep/read/edit and only use write with full_file_rewrite=true when a whole-file rewrite is truly intended. Avoid unnecessary tool calls.`;
|
|
122
122
|
}
|
package/src/core/shell.js
CHANGED
|
@@ -26,9 +26,129 @@ const READY_OUTPUT_PATTERNS = [
|
|
|
26
26
|
const AUTO_STOP_GRACE_MS = 150;
|
|
27
27
|
const LONG_RUNNING_STARTUP_WINDOW_MS = 1500;
|
|
28
28
|
|
|
29
|
+
function normalizeCommand(command) {
|
|
30
|
+
return String(command || '').trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function matchesAny(value, patterns) {
|
|
34
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function classifyCommandIntent(command) {
|
|
38
|
+
const value = normalizeCommand(command);
|
|
39
|
+
|
|
40
|
+
if (!value) {
|
|
41
|
+
return { kind: 'generic', longRunning: false };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
/\b(?:npm|pnpm|yarn|bun)\s+install\b/i.test(value) ||
|
|
46
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:ci|i|add)\b/i.test(value) ||
|
|
47
|
+
/\buv\s+pip\s+install\b/i.test(value) ||
|
|
48
|
+
/\bpip\s+install\b/i.test(value) ||
|
|
49
|
+
/\bcargo\s+install\b/i.test(value) ||
|
|
50
|
+
/\bbundle\s+install\b/i.test(value) ||
|
|
51
|
+
/\bcomposer\s+install\b/i.test(value)
|
|
52
|
+
) {
|
|
53
|
+
return { kind: 'install', longRunning: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (/\b(?:build|compile|bundle|pack|transpile)\b/i.test(value)) {
|
|
57
|
+
return { kind: 'build', longRunning: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|lint|check|typecheck)\b/i.test(value) ||
|
|
62
|
+
/\b(?:jest|vitest|mocha|ava|pytest|go\s+test|cargo\s+test|dotnet\s+test)\b/i.test(value)
|
|
63
|
+
) {
|
|
64
|
+
return { kind: 'test', longRunning: false };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const frontendServicePatterns = [
|
|
68
|
+
/\bvite\b/i,
|
|
69
|
+
/\bnext\s+dev\b/i,
|
|
70
|
+
/\bnuxt\s+dev\b/i,
|
|
71
|
+
/\bastro\s+dev\b/i,
|
|
72
|
+
/\bremix\s+dev\b/i,
|
|
73
|
+
/\bsvelte-kit\s+dev\b/i,
|
|
74
|
+
/\bwebpack\s+serve\b/i,
|
|
75
|
+
/\bvue-cli-service\s+serve\b/i,
|
|
76
|
+
/\breact-scripts\s+start\b/i,
|
|
77
|
+
/\bstorybook\b/i,
|
|
78
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview)\b.*\b(?:client|frontend|front-end|web|ui)\b/i,
|
|
79
|
+
/\b(?:client|frontend|front-end|web|ui)\b.*\b(?:dev|start|serve|preview)\b/i
|
|
80
|
+
];
|
|
81
|
+
if (matchesAny(value, frontendServicePatterns)) {
|
|
82
|
+
return { kind: 'frontend-service', longRunning: true };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const backendServicePatterns = [
|
|
86
|
+
/\bpython\s+-m\s+http\.server\b/i,
|
|
87
|
+
/\buvicorn\b/i,
|
|
88
|
+
/\bgunicorn\b/i,
|
|
89
|
+
/\bflask\s+run\b/i,
|
|
90
|
+
/\bdjango\s+runserver\b/i,
|
|
91
|
+
/\brails\s+(?:s|server)\b/i,
|
|
92
|
+
/\bmvn(?:w)?\s+spring-boot:run\b/i,
|
|
93
|
+
/\bgradle(?:w)?\s+bootRun\b/i,
|
|
94
|
+
/\bgradle(?:w)?\s+run\b/i,
|
|
95
|
+
/\bjava\b.*\bserver\b/i,
|
|
96
|
+
/\bdotnet\s+run\b/i,
|
|
97
|
+
/\bgo\s+run\b.*\b(server|cmd\/server|main\.go)\b/i,
|
|
98
|
+
/\bnest\s+start\b/i,
|
|
99
|
+
/\bnodemon\b/i,
|
|
100
|
+
/\bts-node-dev\b/i,
|
|
101
|
+
/\bair\b/i,
|
|
102
|
+
/\bphp\s+artisan\s+serve\b/i,
|
|
103
|
+
/\bsymfony\s+server:start\b/i,
|
|
104
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview)\b.*\b(?:server|api|backend)\b/i,
|
|
105
|
+
/\b(?:server|api|backend)\b.*\b(?:dev|start|serve|preview)\b/i
|
|
106
|
+
];
|
|
107
|
+
if (matchesAny(value, backendServicePatterns)) {
|
|
108
|
+
return { kind: 'backend-service', longRunning: true };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const databaseServicePatterns = [
|
|
112
|
+
/\bpostgres(?:ql)?\b/i,
|
|
113
|
+
/\bmysql\b/i,
|
|
114
|
+
/\bmariadb\b/i,
|
|
115
|
+
/\bmongod\b/i,
|
|
116
|
+
/\bredis-server\b/i,
|
|
117
|
+
/\b(?:docker|docker-compose|docker compose)\s+.*\b(?:db|database|postgres|mysql|mongo|redis)\b/i,
|
|
118
|
+
/\b(?:db|database|postgres|mysql|mongo|redis)\b.*\b(?:start|up|serve|run)\b/i
|
|
119
|
+
];
|
|
120
|
+
if (matchesAny(value, databaseServicePatterns)) {
|
|
121
|
+
return { kind: 'database-service', longRunning: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const dockerServicePatterns = [
|
|
125
|
+
/\bdocker\s+compose\s+up\b/i,
|
|
126
|
+
/\bdocker-compose\s+up\b/i,
|
|
127
|
+
/\bdocker\s+run\b/i,
|
|
128
|
+
/\bdocker\s+start\b/i
|
|
129
|
+
];
|
|
130
|
+
if (matchesAny(value, dockerServicePatterns)) {
|
|
131
|
+
return { kind: 'docker-service', longRunning: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|preview|watch)\b/i.test(value) ||
|
|
136
|
+
/\b(?:vite|serve)\b/i.test(value) ||
|
|
137
|
+
/\b(?:watch|serve|server|dev|preview)\b/i.test(value)
|
|
138
|
+
) {
|
|
139
|
+
return { kind: 'service', longRunning: true };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (/\b(?:watch|serve|server|dev|preview)\b/i.test(value)) {
|
|
143
|
+
return { kind: 'service', longRunning: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { kind: 'generic', longRunning: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
29
149
|
export function isLikelyLongRunningCommand(command) {
|
|
30
|
-
const
|
|
31
|
-
return LONG_RUNNING_COMMAND_RE.test(
|
|
150
|
+
const { longRunning } = classifyCommandIntent(command);
|
|
151
|
+
return longRunning || LONG_RUNNING_COMMAND_RE.test(normalizeCommand(command)) || GENERIC_LONG_RUNNING_HINT_RE.test(normalizeCommand(command));
|
|
32
152
|
}
|
|
33
153
|
|
|
34
154
|
export function hasReadyOutput(text) {
|
package/src/core/tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import crypto from 'node:crypto';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import net from 'node:net';
|
|
6
6
|
import {
|
|
7
|
+
classifyCommandIntent,
|
|
7
8
|
hasReadyOutput,
|
|
8
9
|
isDangerousCommand,
|
|
9
10
|
isLikelyLongRunningCommand,
|
|
@@ -793,7 +794,16 @@ async function runCommand(root, config, args) {
|
|
|
793
794
|
throw new Error('run requires command');
|
|
794
795
|
}
|
|
795
796
|
if (isLikelyLongRunningCommand(command)) {
|
|
796
|
-
|
|
797
|
+
const intent = classifyCommandIntent(command);
|
|
798
|
+
const labelMap = {
|
|
799
|
+
'frontend-service': 'frontend service',
|
|
800
|
+
'backend-service': 'backend service',
|
|
801
|
+
'database-service': 'database service',
|
|
802
|
+
'docker-service': 'Docker service',
|
|
803
|
+
service: 'long-running service'
|
|
804
|
+
};
|
|
805
|
+
const label = labelMap[intent.kind] || 'long-running service';
|
|
806
|
+
throw new Error(`Command looks like a ${label}. Use start_service instead of run.`);
|
|
797
807
|
}
|
|
798
808
|
if (
|
|
799
809
|
!config.policy.allow_dangerous_commands &&
|
|
@@ -1727,7 +1737,8 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
|
1727
1737
|
type: 'function',
|
|
1728
1738
|
function: {
|
|
1729
1739
|
name: 'run',
|
|
1730
|
-
description:
|
|
1740
|
+
description:
|
|
1741
|
+
'Primary run tool. Execute a one-shot shell command in workspace such as install, build, test, or other finite tasks. Do not use for long-running services or watchers.',
|
|
1731
1742
|
parameters: {
|
|
1732
1743
|
type: 'object',
|
|
1733
1744
|
properties: {
|
|
@@ -1771,7 +1782,8 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
|
1771
1782
|
type: 'function',
|
|
1772
1783
|
function: {
|
|
1773
1784
|
name: 'start_service',
|
|
1774
|
-
description:
|
|
1785
|
+
description:
|
|
1786
|
+
'Start a long-running local service, such as a frontend, backend, database, or dev watcher, and return a compact service handle instead of blocking on process exit.',
|
|
1775
1787
|
parameters: {
|
|
1776
1788
|
type: 'object',
|
|
1777
1789
|
properties: {
|
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,6 +122,22 @@ 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: '正在执行技能',
|
|
126
143
|
toolFailed: (name) => `工具执行失败: ${name}`,
|
|
@@ -154,6 +171,7 @@ const TUI_COPY = {
|
|
|
154
171
|
skillRunning: '技能执行中',
|
|
155
172
|
skillCompleted: '技能已完成',
|
|
156
173
|
skillFailed: '技能执行失败',
|
|
174
|
+
autoSkillInjected: (names) => `自动启用技能: ${names.map((name) => `/${name}`).join(', ')}`,
|
|
157
175
|
compactingContext: '正在压缩上下文',
|
|
158
176
|
autoCompactTriggered: (mode, threshold) => `自动压缩已触发(${mode},阈值 ${threshold}%)`,
|
|
159
177
|
requestFailed: '请求失败',
|
|
@@ -230,6 +248,22 @@ const TUI_COPY = {
|
|
|
230
248
|
doingUpdateTask: 'Updating task',
|
|
231
249
|
doneGeneric: 'Completed tool',
|
|
232
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',
|
|
233
267
|
doneSkill: 'Completed skill',
|
|
234
268
|
doingSkill: 'Running skill',
|
|
235
269
|
toolFailed: (name) => `Tool failed: ${name}`,
|
|
@@ -263,6 +297,7 @@ const TUI_COPY = {
|
|
|
263
297
|
skillRunning: 'skill running',
|
|
264
298
|
skillCompleted: 'skill completed',
|
|
265
299
|
skillFailed: 'skill failed',
|
|
300
|
+
autoSkillInjected: (names) => `auto-enabled skills: ${names.map((name) => `/${name}`).join(', ')}`,
|
|
266
301
|
compactingContext: 'compacting context',
|
|
267
302
|
autoCompactTriggered: (mode, threshold) => `auto-compact triggered (${mode}, threshold ${threshold}%)`,
|
|
268
303
|
requestFailed: 'request failed',
|
|
@@ -308,6 +343,14 @@ function trimText(value, maxLen = 88) {
|
|
|
308
343
|
return `${text.slice(0, maxLen - 3)}...`;
|
|
309
344
|
}
|
|
310
345
|
|
|
346
|
+
function safeJsonParse(raw) {
|
|
347
|
+
try {
|
|
348
|
+
return JSON.parse(String(raw || '{}'));
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
311
354
|
function parseToolDisplayName(name) {
|
|
312
355
|
const raw = String(name || '').trim();
|
|
313
356
|
const match = raw.match(/^([^(]+)\((.*)\)$/);
|
|
@@ -318,14 +361,76 @@ function parseToolDisplayName(name) {
|
|
|
318
361
|
};
|
|
319
362
|
}
|
|
320
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
|
+
|
|
321
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
|
+
}
|
|
322
428
|
if ((activity?.type || 'tool') === 'skill') {
|
|
323
429
|
return {
|
|
324
430
|
primary: `Skill`,
|
|
325
431
|
secondary: `(${activity?.name || 'unknown'})`
|
|
326
432
|
};
|
|
327
433
|
}
|
|
328
|
-
const parsed = parseToolDisplayName(activity?.name);
|
|
329
434
|
const labels = {
|
|
330
435
|
read: 'Read',
|
|
331
436
|
edit: 'Edit',
|
|
@@ -351,6 +456,74 @@ function getActivityDisplayParts(activity) {
|
|
|
351
456
|
}
|
|
352
457
|
|
|
353
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
|
+
}
|
|
354
527
|
const { raw, base, target } = parseToolDisplayName(name);
|
|
355
528
|
const safeTarget = trimText(target, 72);
|
|
356
529
|
if (base === 'read') {
|
|
@@ -417,6 +590,21 @@ function describeSkillActivity(name, copy, { done = false, failed = false } = {}
|
|
|
417
590
|
return `${copy.toolActivity.doingSkill}: /${name}`;
|
|
418
591
|
}
|
|
419
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
|
+
|
|
420
608
|
function normalizeRuntimeStatus(status, copy) {
|
|
421
609
|
if (status && typeof status === 'object') {
|
|
422
610
|
return {
|
|
@@ -690,6 +878,188 @@ export function parsePlanProgressLine(text) {
|
|
|
690
878
|
};
|
|
691
879
|
}
|
|
692
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
|
+
|
|
693
1063
|
export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
|
|
694
1064
|
const source = Array.isArray(messages) ? messages : [];
|
|
695
1065
|
if (!planState || !planState.total) return source;
|
|
@@ -915,6 +1285,7 @@ function isCodeActivityName(name) {
|
|
|
915
1285
|
const parsed = parseToolDisplayName(name);
|
|
916
1286
|
return new Set([
|
|
917
1287
|
'edit',
|
|
1288
|
+
'write',
|
|
918
1289
|
'write_file',
|
|
919
1290
|
'patch',
|
|
920
1291
|
'replace_text',
|
|
@@ -943,6 +1314,23 @@ export function splitMessageRows(rows) {
|
|
|
943
1314
|
return { textRows, codeRows };
|
|
944
1315
|
}
|
|
945
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
|
+
|
|
946
1334
|
export function normalizeActivitySpacingRows(inputRows) {
|
|
947
1335
|
const rows = Array.isArray(inputRows) ? inputRows : [];
|
|
948
1336
|
const normalized = [];
|
|
@@ -1043,7 +1431,7 @@ export function mergeActivitySummary(previousSummary, nextSummary, activityName)
|
|
|
1043
1431
|
return lines.join('\n');
|
|
1044
1432
|
}
|
|
1045
1433
|
|
|
1046
|
-
function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
1434
|
+
function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
1047
1435
|
const rows = [];
|
|
1048
1436
|
const pushTextRows = (text) => {
|
|
1049
1437
|
const lines = String(text || '').split('\n');
|
|
@@ -1126,18 +1514,23 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
|
1126
1514
|
toolCalls.forEach((tool, idx) => pushActivityRows(tool, idx, toolCalls.length));
|
|
1127
1515
|
}
|
|
1128
1516
|
|
|
1517
|
+
const codeGenerationRows = getCodeGenerationActivityRows(msg);
|
|
1518
|
+
const generatingCodeRows = getGeneratingCodePlaceholderRows(msg, copy, contentWidth);
|
|
1519
|
+
const syntheticRows = [...codeGenerationRows, ...generatingCodeRows];
|
|
1129
1520
|
if (msg?.loading && (msg?.liveStatus || msg?.phase)) {
|
|
1521
|
+
const statusRows = [];
|
|
1130
1522
|
pushWrappedRow(
|
|
1131
|
-
|
|
1523
|
+
statusRows,
|
|
1132
1524
|
{
|
|
1133
1525
|
kind: 'status',
|
|
1134
1526
|
text: trimText(msg.liveStatus || msg.phase, 144)
|
|
1135
1527
|
},
|
|
1136
1528
|
Math.max(8, contentWidth - 2)
|
|
1137
1529
|
);
|
|
1530
|
+
syntheticRows.push(...statusRows);
|
|
1138
1531
|
}
|
|
1139
1532
|
|
|
1140
|
-
return normalizeActivitySpacingRows(rows);
|
|
1533
|
+
return normalizeActivitySpacingRows(insertRowsAfterLastCodeRow(rows, syntheticRows));
|
|
1141
1534
|
}
|
|
1142
1535
|
|
|
1143
1536
|
function renderMessageRow(msg, row, idx, loaderTick) {
|
|
@@ -1145,13 +1538,11 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
1145
1538
|
const activity = { type: row.activityType, name: row.name, status: row.status };
|
|
1146
1539
|
const display = getActivityDisplayParts(activity);
|
|
1147
1540
|
const dotColor =
|
|
1148
|
-
|
|
1149
|
-
?
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
? 'redBright'
|
|
1154
|
-
: 'greenBright';
|
|
1541
|
+
row.status === 'error' || row.status === 'blocked'
|
|
1542
|
+
? 'redBright'
|
|
1543
|
+
: row.status === 'done'
|
|
1544
|
+
? 'greenBright'
|
|
1545
|
+
: 'yellowBright';
|
|
1155
1546
|
const textColor =
|
|
1156
1547
|
activity.type === 'skill'
|
|
1157
1548
|
? row.status === 'error'
|
|
@@ -1159,7 +1550,8 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
1159
1550
|
: 'cyanBright'
|
|
1160
1551
|
: row.status === 'error' || row.status === 'blocked'
|
|
1161
1552
|
? 'redBright'
|
|
1162
|
-
: '
|
|
1553
|
+
: 'cyanBright';
|
|
1554
|
+
const durationText = formatActivityDurationText(row);
|
|
1163
1555
|
return h(
|
|
1164
1556
|
Box,
|
|
1165
1557
|
{ key: `row-tool-${msg.id}-${idx}` },
|
|
@@ -1168,7 +1560,7 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
1168
1560
|
h(Text, { color: 'gray' }, ' '),
|
|
1169
1561
|
h(Text, { color: textColor }, display.primary),
|
|
1170
1562
|
h(Text, { color: 'gray' }, display.secondary),
|
|
1171
|
-
|
|
1563
|
+
durationText ? h(Text, { color: row.statusColor }, ` ${durationText}`) : null
|
|
1172
1564
|
);
|
|
1173
1565
|
}
|
|
1174
1566
|
if (row.kind === 'activity-summary') {
|
|
@@ -1232,6 +1624,15 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
1232
1624
|
h(Text, { color: 'gray' }, row.text)
|
|
1233
1625
|
);
|
|
1234
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
|
+
}
|
|
1235
1636
|
return renderTextLine(msg, row.text, idx, row.color);
|
|
1236
1637
|
}
|
|
1237
1638
|
|
|
@@ -1349,11 +1750,12 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
|
|
|
1349
1750
|
return h(PlanSummaryBubble, { msg, copy });
|
|
1350
1751
|
}
|
|
1351
1752
|
const theme = roleStyle(msg.label);
|
|
1352
|
-
const allRows = buildMessageRows(msg, showToolDetails, contentWidth);
|
|
1753
|
+
const allRows = buildMessageRows(msg, showToolDetails, contentWidth, copy);
|
|
1353
1754
|
const start = rowWindow ? Math.max(0, rowWindow.start || 0) : 0;
|
|
1354
1755
|
const end = rowWindow ? Math.max(start, rowWindow.end || allRows.length) : allRows.length;
|
|
1355
1756
|
const visibleRows = allRows.slice(start, end);
|
|
1356
1757
|
const rendered = renderMessageRowsInOrder(msg, visibleRows, loaderTick, copy);
|
|
1758
|
+
const autoSkillBadge = formatAutoSkillBadge(msg.autoSkillNames, copy);
|
|
1357
1759
|
|
|
1358
1760
|
return h(
|
|
1359
1761
|
Box,
|
|
@@ -1377,7 +1779,9 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
|
|
|
1377
1779
|
null,
|
|
1378
1780
|
h(Text, { color: theme.badgeText, backgroundColor: theme.badgeBg }, ` ${messageLabel(msg.label, copy)} `)
|
|
1379
1781
|
),
|
|
1380
|
-
|
|
1782
|
+
autoSkillBadge
|
|
1783
|
+
? h(Text, { color: 'blueBright' }, autoSkillBadge)
|
|
1784
|
+
: h(Text, { color: theme.chrome }, ' ')
|
|
1381
1785
|
),
|
|
1382
1786
|
...rendered
|
|
1383
1787
|
)
|
|
@@ -1530,7 +1934,7 @@ function InputBar({
|
|
|
1530
1934
|
return h(
|
|
1531
1935
|
Box,
|
|
1532
1936
|
{
|
|
1533
|
-
marginTop:
|
|
1937
|
+
marginTop: 0,
|
|
1534
1938
|
flexDirection: 'column',
|
|
1535
1939
|
borderStyle: 'round',
|
|
1536
1940
|
borderColor: 'cyan',
|
|
@@ -1648,6 +2052,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1648
2052
|
const [lastKeyDebug, setLastKeyDebug] = useState('');
|
|
1649
2053
|
const [showToolDetails, setShowToolDetails] = useState(false);
|
|
1650
2054
|
const activeAssistantIdRef = useRef(null);
|
|
2055
|
+
const activeAssistantAutoSkillNamesRef = useRef([]);
|
|
2056
|
+
const streamedAssistantHandledRef = useRef(false);
|
|
1651
2057
|
const activeUserMessageIdRef = useRef(null);
|
|
1652
2058
|
const cursorIndexRef = useRef(0);
|
|
1653
2059
|
const inFlightRef = useRef(false);
|
|
@@ -1758,7 +2164,12 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1758
2164
|
} else {
|
|
1759
2165
|
segments.push({ type: 'text', text: delta });
|
|
1760
2166
|
}
|
|
1761
|
-
|
|
2167
|
+
const nextText = `${m.text}${delta}`;
|
|
2168
|
+
return {
|
|
2169
|
+
...m,
|
|
2170
|
+
text: nextText,
|
|
2171
|
+
segments
|
|
2172
|
+
};
|
|
1762
2173
|
})
|
|
1763
2174
|
);
|
|
1764
2175
|
};
|
|
@@ -1782,6 +2193,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1782
2193
|
const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
|
|
1783
2194
|
const activityType = toolEvent.type || 'tool';
|
|
1784
2195
|
const idx = findActivityUpdateIndex(toolCalls, toolEvent);
|
|
2196
|
+
const startedAt = toolEvent.status === 'running' ? Date.now() : undefined;
|
|
1785
2197
|
|
|
1786
2198
|
if (idx === -1) {
|
|
1787
2199
|
toolCalls.push({
|
|
@@ -1789,6 +2201,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1789
2201
|
id: toolEvent.id || '',
|
|
1790
2202
|
name: toolEvent.name,
|
|
1791
2203
|
status: toolEvent.status,
|
|
2204
|
+
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
2205
|
+
...(startedAt ? { startedAt } : {}),
|
|
1792
2206
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1793
2207
|
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
1794
2208
|
});
|
|
@@ -1798,6 +2212,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1798
2212
|
type: activityType,
|
|
1799
2213
|
id: toolEvent.id || toolCalls[idx].id,
|
|
1800
2214
|
status: toolEvent.status,
|
|
2215
|
+
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
2216
|
+
...(startedAt ? { startedAt } : {}),
|
|
1801
2217
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1802
2218
|
...(toolEvent.summary
|
|
1803
2219
|
? { summary: mergeActivitySummary(toolCalls[idx].summary, toolEvent.summary, toolEvent.name) }
|
|
@@ -1811,6 +2227,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1811
2227
|
id: toolEvent.id || '',
|
|
1812
2228
|
name: toolEvent.name,
|
|
1813
2229
|
status: toolEvent.status,
|
|
2230
|
+
...(toolEvent.arguments !== undefined ? { arguments: toolEvent.arguments } : {}),
|
|
2231
|
+
...(startedAt ? { startedAt } : {}),
|
|
1814
2232
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1815
2233
|
...(toolEvent.summary ? { summary: toolEvent.summary } : {})
|
|
1816
2234
|
};
|
|
@@ -1875,7 +2293,13 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1875
2293
|
if (!activeAssistantIdRef.current && result.text) {
|
|
1876
2294
|
setMessages((prev) => [
|
|
1877
2295
|
...prev,
|
|
1878
|
-
{
|
|
2296
|
+
{
|
|
2297
|
+
id: nextId(),
|
|
2298
|
+
label: 'coder',
|
|
2299
|
+
text: result.text,
|
|
2300
|
+
color: 'greenBright',
|
|
2301
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
2302
|
+
}
|
|
1879
2303
|
]);
|
|
1880
2304
|
}
|
|
1881
2305
|
return;
|
|
@@ -1899,8 +2323,38 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1899
2323
|
setMessages((prev) => prev.map((m) => (m.id === targetId ? { ...m, ...patch } : m)));
|
|
1900
2324
|
};
|
|
1901
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
|
+
|
|
1902
2348
|
const finalizeActiveAssistant = () => {
|
|
1903
|
-
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
|
+
});
|
|
1904
2358
|
};
|
|
1905
2359
|
|
|
1906
2360
|
const ensureActiveAssistant = () => {
|
|
@@ -1918,7 +2372,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1918
2372
|
segments: [],
|
|
1919
2373
|
loading: true,
|
|
1920
2374
|
phase: 'thinking',
|
|
1921
|
-
liveStatus: copy.runtime.modelThinking
|
|
2375
|
+
liveStatus: copy.runtime.modelThinking,
|
|
2376
|
+
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
1922
2377
|
}
|
|
1923
2378
|
]);
|
|
1924
2379
|
return aid;
|
|
@@ -1933,11 +2388,14 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1933
2388
|
setPlanState({ current: 0, total: 0, role: '', title: '', failed: false, steps: [] });
|
|
1934
2389
|
planTextBufferRef.current = '';
|
|
1935
2390
|
activeAssistantIdRef.current = null;
|
|
2391
|
+
activeAssistantAutoSkillNamesRef.current = [];
|
|
2392
|
+
streamedAssistantHandledRef.current = false;
|
|
1936
2393
|
deltaBufferRef.current = '';
|
|
1937
2394
|
|
|
1938
2395
|
runtime
|
|
1939
2396
|
.submit(line, (event) => {
|
|
1940
2397
|
if (event?.type === 'assistant:start') {
|
|
2398
|
+
streamedAssistantHandledRef.current = true;
|
|
1941
2399
|
setRuntimeStatus(makeStatus(copy.runtime.modelThinking, copy.runtime.requestDelivered, 'cyanBright'));
|
|
1942
2400
|
setInputStage('thinking');
|
|
1943
2401
|
updateMessageMeta(activeUserMessageIdRef.current, {
|
|
@@ -1958,8 +2416,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1958
2416
|
let liveStatus = copy.runtime.generatingReply;
|
|
1959
2417
|
if (targetId) {
|
|
1960
2418
|
const current = messagesRef.current?.find?.((m) => m.id === targetId);
|
|
1961
|
-
const
|
|
1962
|
-
if (
|
|
2419
|
+
const pendingToolCalls = Array.isArray(current?.pendingToolCalls) ? current.pendingToolCalls : [];
|
|
2420
|
+
if (pendingToolCalls.length > 0) {
|
|
1963
2421
|
liveStatus = copy.runtime.generatingCode;
|
|
1964
2422
|
}
|
|
1965
2423
|
}
|
|
@@ -1967,12 +2425,71 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1967
2425
|
})());
|
|
1968
2426
|
queueAssistantDelta(event.text);
|
|
1969
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
|
+
}
|
|
1970
2456
|
if (event?.type === 'assistant:response') {
|
|
1971
|
-
|
|
1972
|
-
|
|
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
|
+
}
|
|
1973
2465
|
flushAssistantDelta();
|
|
1974
|
-
|
|
1975
|
-
|
|
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) {
|
|
1976
2493
|
setMessages((prev) => [
|
|
1977
2494
|
...prev,
|
|
1978
2495
|
{ id: nextId(), label: 'coder', text: event.text, color: 'greenBright' }
|
|
@@ -1984,16 +2501,28 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1984
2501
|
const detail = describeToolActivity(event.name, copy);
|
|
1985
2502
|
setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'magentaBright'));
|
|
1986
2503
|
setInputStage('tooling');
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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
|
+
}
|
|
1992
2520
|
updateActivityStatusOnActiveAssistant({
|
|
1993
2521
|
type: 'tool',
|
|
1994
2522
|
id: event.id,
|
|
1995
2523
|
name: event.name,
|
|
1996
|
-
status: 'running'
|
|
2524
|
+
status: 'running',
|
|
2525
|
+
arguments: event.arguments
|
|
1997
2526
|
});
|
|
1998
2527
|
}
|
|
1999
2528
|
if (event?.type === 'tool:end') {
|
|
@@ -2007,7 +2536,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2007
2536
|
name: event.name,
|
|
2008
2537
|
status: 'done',
|
|
2009
2538
|
durationMs: event.durationMs,
|
|
2010
|
-
summary: event.summary
|
|
2539
|
+
summary: event.summary,
|
|
2540
|
+
arguments: event.arguments
|
|
2011
2541
|
});
|
|
2012
2542
|
}
|
|
2013
2543
|
if (event?.type === 'tool:blocked') {
|
|
@@ -2026,7 +2556,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2026
2556
|
type: 'tool',
|
|
2027
2557
|
id: event.id,
|
|
2028
2558
|
name: event.name,
|
|
2029
|
-
status: 'blocked'
|
|
2559
|
+
status: 'blocked',
|
|
2560
|
+
arguments: event.arguments
|
|
2030
2561
|
});
|
|
2031
2562
|
}
|
|
2032
2563
|
if (event?.type === 'tool:error') {
|
|
@@ -2047,7 +2578,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2047
2578
|
name: event.name,
|
|
2048
2579
|
status: 'error',
|
|
2049
2580
|
durationMs: event.durationMs,
|
|
2050
|
-
summary: event.summary
|
|
2581
|
+
summary: event.summary,
|
|
2582
|
+
arguments: event.arguments
|
|
2051
2583
|
});
|
|
2052
2584
|
}
|
|
2053
2585
|
if (event?.type === 'skill:start') {
|
|
@@ -2085,6 +2617,21 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2085
2617
|
summary: event.summary
|
|
2086
2618
|
});
|
|
2087
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
|
+
}
|
|
2088
2635
|
if (event?.type === 'compact:auto') {
|
|
2089
2636
|
setRuntimeStatus(makeStatus(copy.runtime.compactingContext, `auto compact ${event.mode}`, 'yellowBright'));
|
|
2090
2637
|
setMessages((prev) => [
|
|
@@ -2124,6 +2671,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2124
2671
|
}
|
|
2125
2672
|
syncRuntimeVisualState(result.type === 'noop' ? 'ready' : 'after');
|
|
2126
2673
|
if (result.type === 'noop') return;
|
|
2674
|
+
if (!shouldAppendAssistantResult(result, activeAssistantIdRef.current, streamedAssistantHandledRef.current)) return;
|
|
2127
2675
|
appendResultMessage(result);
|
|
2128
2676
|
})
|
|
2129
2677
|
.catch((err) => {
|
|
@@ -2150,6 +2698,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2150
2698
|
flushAssistantDelta();
|
|
2151
2699
|
finalizeActiveAssistant();
|
|
2152
2700
|
activeAssistantIdRef.current = null;
|
|
2701
|
+
streamedAssistantHandledRef.current = false;
|
|
2153
2702
|
activeUserMessageIdRef.current = null;
|
|
2154
2703
|
if (deltaFlushTimerRef.current) {
|
|
2155
2704
|
clearTimeout(deltaFlushTimerRef.current);
|