@yeaft/webchat-agent 0.1.419 → 0.1.421
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/connection/message-router.js +6 -0
- package/conversation.js +5 -18
- package/package.json +1 -1
- package/unify/web-bridge.js +63 -168
|
@@ -25,6 +25,7 @@ import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
|
25
25
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
26
26
|
import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
27
27
|
import { getLlmConfig, updateLlmConfig } from '../unify/config-api.js';
|
|
28
|
+
import { handleUnifyChat } from '../unify/web-bridge.js';
|
|
28
29
|
|
|
29
30
|
export async function handleMessage(msg) {
|
|
30
31
|
switch (msg.type) {
|
|
@@ -318,5 +319,10 @@ export async function handleMessage(msg) {
|
|
|
318
319
|
sendToServer({ type: 'llm_config_updated', ...result });
|
|
319
320
|
break;
|
|
320
321
|
}
|
|
322
|
+
|
|
323
|
+
// Unify — independent chat via Engine
|
|
324
|
+
case 'unify_chat':
|
|
325
|
+
await handleUnifyChat(msg);
|
|
326
|
+
break;
|
|
321
327
|
}
|
|
322
328
|
}
|
package/conversation.js
CHANGED
|
@@ -6,7 +6,6 @@ import { query } from './sdk/index.js';
|
|
|
6
6
|
import { loadSessionHistory } from './history.js';
|
|
7
7
|
import { startClaudeQuery } from './claude.js';
|
|
8
8
|
import { crewSessions, loadCrewIndex } from './crew.js';
|
|
9
|
-
import { handleUnifyInput } from './unify/web-bridge.js';
|
|
10
9
|
|
|
11
10
|
// 不支持的斜杠命令(真正需要交互式 CLI 的命令)
|
|
12
11
|
const UNSUPPORTED_SLASH_COMMANDS = ['/help', '/bug', '/login', '/logout', '/terminal-setup', '/vim', '/config'];
|
|
@@ -294,8 +293,7 @@ export async function sendConversationList() {
|
|
|
294
293
|
createdAt: state.createdAt,
|
|
295
294
|
processing: !!state.turnActive,
|
|
296
295
|
userId: state.userId,
|
|
297
|
-
username: state.username
|
|
298
|
-
mode: state.mode || null
|
|
296
|
+
username: state.username
|
|
299
297
|
};
|
|
300
298
|
list.push(entry);
|
|
301
299
|
}
|
|
@@ -356,7 +354,7 @@ export function sendError(conversationId, message) {
|
|
|
356
354
|
|
|
357
355
|
// 创建新的 conversation (延迟启动 Claude,等待用户发送第一条消息)
|
|
358
356
|
export async function createConversation(msg) {
|
|
359
|
-
const { conversationId, workDir, userId, username, disallowedTools
|
|
357
|
+
const { conversationId, workDir, userId, username, disallowedTools } = msg;
|
|
360
358
|
const effectiveWorkDir = workDir || ctx.CONFIG.workDir;
|
|
361
359
|
|
|
362
360
|
console.log(`Creating conversation: ${conversationId} in ${effectiveWorkDir} (lazy start)`);
|
|
@@ -377,7 +375,6 @@ export async function createConversation(msg) {
|
|
|
377
375
|
userId,
|
|
378
376
|
username,
|
|
379
377
|
disallowedTools: disallowedTools || null, // null = 使用全局默认
|
|
380
|
-
mode: mode || 'claude', // 'claude' (default) or 'unify'
|
|
381
378
|
usage: {
|
|
382
379
|
inputTokens: 0,
|
|
383
380
|
outputTokens: 0,
|
|
@@ -393,8 +390,7 @@ export async function createConversation(msg) {
|
|
|
393
390
|
workDir: effectiveWorkDir,
|
|
394
391
|
userId,
|
|
395
392
|
username,
|
|
396
|
-
disallowedTools: disallowedTools || null
|
|
397
|
-
mode: mode || 'claude'
|
|
393
|
+
disallowedTools: disallowedTools || null
|
|
398
394
|
});
|
|
399
395
|
|
|
400
396
|
// 立即发送 agent 级别的 MCP servers 列表(从 ~/.claude.json 读取的)
|
|
@@ -417,11 +413,8 @@ export async function createConversation(msg) {
|
|
|
417
413
|
sendConversationList();
|
|
418
414
|
|
|
419
415
|
// ★ Prestart Claude CLI in background to eagerly fetch skills/tools/model
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
// Fire-and-forget: failure just degrades to lazy-start behavior
|
|
423
|
-
prestartClaude(conversationId, effectiveWorkDir, null);
|
|
424
|
-
}
|
|
416
|
+
// Fire-and-forget: failure just degrades to lazy-start behavior
|
|
417
|
+
prestartClaude(conversationId, effectiveWorkDir, null);
|
|
425
418
|
}
|
|
426
419
|
|
|
427
420
|
// Resume 历史 conversation (延迟启动 Claude,等待用户发送第一条消息)
|
|
@@ -669,12 +662,6 @@ export async function handleCancelExecution(msg) {
|
|
|
669
662
|
export async function handleUserInput(msg) {
|
|
670
663
|
const { conversationId, prompt, workDir, claudeSessionId } = msg;
|
|
671
664
|
|
|
672
|
-
// ★ Unify mode: route to Unify Engine instead of Claude CLI
|
|
673
|
-
const state = ctx.conversations.get(conversationId);
|
|
674
|
-
if (state?.mode === 'unify') {
|
|
675
|
-
return handleUnifyInput(msg);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
665
|
// 解析斜杠命令
|
|
679
666
|
const slashCommand = parseSlashCommand(prompt);
|
|
680
667
|
|
package/package.json
CHANGED
package/unify/web-bridge.js
CHANGED
|
@@ -1,204 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* web-bridge.js — Bridge between
|
|
2
|
+
* web-bridge.js — Bridge between web UI and Yeaft Unify Engine.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* zero rendering changes — Unify conversations look identical to Claude ones.
|
|
4
|
+
* Handles `unify_chat` messages from the server, runs queries through
|
|
5
|
+
* the Engine, and streams `unify_output` events back.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Lifecycle per conversation:
|
|
11
|
-
* 1. First user message → lazy-load a Unify Session (loadSession)
|
|
12
|
-
* 2. Each user message → engine.query({ prompt }) → stream events → sendOutput
|
|
13
|
-
* 3. On turn complete → send turn_completed so UI exits processing state
|
|
7
|
+
* The session is lazily initialized on first use and reused across queries.
|
|
8
|
+
* Engine internally manages conversation history and persistence.
|
|
14
9
|
*/
|
|
15
10
|
|
|
16
|
-
import ctx from '../context.js';
|
|
17
|
-
import { sendOutput, sendConversationList } from '../conversation.js';
|
|
18
11
|
import { loadSession } from './session.js';
|
|
12
|
+
import { sendToServer } from '../connection/buffer.js';
|
|
13
|
+
import ctx from '../context.js';
|
|
14
|
+
|
|
15
|
+
/** @type {import('./session.js').Session | null} */
|
|
16
|
+
let session = null;
|
|
17
|
+
|
|
18
|
+
/** @type {AbortController | null} */
|
|
19
|
+
let currentAbort = null;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Handle a
|
|
22
|
+
* Handle a unify_chat message from the web UI.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
* Reuses the same sendOutput() wire format as Claude conversations so the
|
|
25
|
-
* web client renders messages identically.
|
|
26
|
-
*
|
|
27
|
-
* @param {{ conversationId: string, prompt: string }} msg
|
|
24
|
+
* @param {{ prompt: string, userId?: string, username?: string }} msg
|
|
28
25
|
*/
|
|
29
|
-
export async function
|
|
30
|
-
const {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!state) {
|
|
34
|
-
console.error(`[Unify] No conversation state for ${conversationId}`);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
26
|
+
export async function handleUnifyChat(msg) {
|
|
27
|
+
const { prompt } = msg;
|
|
28
|
+
if (!prompt?.trim()) return;
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
try {
|
|
31
|
+
// Lazy-init session (reuse across queries — Engine manages history)
|
|
32
|
+
if (!session) {
|
|
33
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
34
|
+
session = await loadSession({
|
|
35
|
+
...(yeaftDir && { dir: yeaftDir }),
|
|
43
36
|
skipMCP: true,
|
|
44
37
|
skipSkills: true,
|
|
45
38
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
type: 'assistant',
|
|
52
|
-
message: {
|
|
53
|
-
role: 'assistant',
|
|
54
|
-
content: [{
|
|
55
|
-
type: 'text',
|
|
56
|
-
text: `Unify session initialization failed: ${err.message}`
|
|
57
|
-
}]
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
ctx.sendToServer({
|
|
61
|
-
type: 'turn_completed',
|
|
62
|
-
conversationId,
|
|
63
|
-
workDir: state.workDir
|
|
39
|
+
|
|
40
|
+
// Send model info to UI so the badge updates
|
|
41
|
+
sendToServer({
|
|
42
|
+
type: 'unify_output',
|
|
43
|
+
event: { type: 'model_info', model: session.config.model },
|
|
64
44
|
});
|
|
65
|
-
return;
|
|
66
45
|
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const session = state._unifySession;
|
|
70
46
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
role: 'user',
|
|
76
|
-
content: prompt
|
|
47
|
+
// Cancel any in-flight query
|
|
48
|
+
if (currentAbort) {
|
|
49
|
+
currentAbort.abort();
|
|
50
|
+
currentAbort = null;
|
|
77
51
|
}
|
|
78
|
-
});
|
|
79
52
|
|
|
80
|
-
|
|
81
|
-
state._unifyMessages.push({ role: 'user', content: prompt });
|
|
53
|
+
currentAbort = new AbortController();
|
|
82
54
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let hasError = false;
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
for await (const event of session.engine.query({
|
|
89
|
-
prompt,
|
|
90
|
-
messages: state._unifyMessages.slice(0, -1), // pass history (excluding current prompt which engine adds)
|
|
91
|
-
})) {
|
|
55
|
+
// Stream query events → unify_output messages
|
|
56
|
+
for await (const event of session.engine.query({ prompt, signal: currentAbort.signal })) {
|
|
92
57
|
switch (event.type) {
|
|
93
58
|
case 'text_delta':
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
type: '
|
|
97
|
-
message: {
|
|
98
|
-
role: 'assistant',
|
|
99
|
-
content: [{
|
|
100
|
-
type: 'text',
|
|
101
|
-
text: event.text
|
|
102
|
-
}]
|
|
103
|
-
},
|
|
104
|
-
subtype: 'text_delta'
|
|
105
|
-
});
|
|
106
|
-
break;
|
|
107
|
-
|
|
108
|
-
case 'tool_call':
|
|
109
|
-
sendOutput(conversationId, {
|
|
110
|
-
type: 'assistant',
|
|
111
|
-
message: {
|
|
112
|
-
role: 'assistant',
|
|
113
|
-
content: [{
|
|
114
|
-
type: 'tool_use',
|
|
115
|
-
id: event.id,
|
|
116
|
-
name: event.name,
|
|
117
|
-
input: event.input
|
|
118
|
-
}]
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case 'tool_result':
|
|
124
|
-
sendOutput(conversationId, {
|
|
125
|
-
type: 'assistant',
|
|
126
|
-
message: {
|
|
127
|
-
role: 'tool',
|
|
128
|
-
tool_use_id: event.toolCallId,
|
|
129
|
-
content: typeof event.result === 'string'
|
|
130
|
-
? event.result
|
|
131
|
-
: JSON.stringify(event.result)
|
|
132
|
-
}
|
|
59
|
+
sendToServer({
|
|
60
|
+
type: 'unify_output',
|
|
61
|
+
event: { type: 'text_delta', text: event.text },
|
|
133
62
|
});
|
|
134
63
|
break;
|
|
135
64
|
|
|
136
|
-
case 'usage':
|
|
137
|
-
// Update conversation usage tracking
|
|
138
|
-
if (state.usage) {
|
|
139
|
-
state.usage.inputTokens += event.inputTokens || 0;
|
|
140
|
-
state.usage.outputTokens += event.outputTokens || 0;
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
|
|
144
65
|
case 'error':
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
type: 'text',
|
|
152
|
-
text: `Error: ${event.error?.message || 'Unknown error'}`
|
|
153
|
-
}]
|
|
154
|
-
}
|
|
66
|
+
sendToServer({
|
|
67
|
+
type: 'unify_output',
|
|
68
|
+
event: {
|
|
69
|
+
type: 'error',
|
|
70
|
+
message: event.error?.message || 'Unknown error',
|
|
71
|
+
},
|
|
155
72
|
});
|
|
156
73
|
break;
|
|
157
74
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
case 'recall':
|
|
162
|
-
case 'consolidate':
|
|
163
|
-
case 'fallback':
|
|
164
|
-
case 'thinking_delta':
|
|
165
|
-
// These are engine-internal events; no UI action needed
|
|
75
|
+
// Silently consume other events (tool_call, thinking_delta, usage, etc.)
|
|
76
|
+
// These can be surfaced later as the Unify UI grows.
|
|
77
|
+
default:
|
|
166
78
|
break;
|
|
167
79
|
}
|
|
168
80
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
type: '
|
|
174
|
-
message: {
|
|
175
|
-
role: 'assistant',
|
|
176
|
-
content: [{
|
|
177
|
-
type: 'text',
|
|
178
|
-
text: `Unify query error: ${err.message}`
|
|
179
|
-
}]
|
|
180
|
-
}
|
|
81
|
+
|
|
82
|
+
// Signal completion
|
|
83
|
+
sendToServer({
|
|
84
|
+
type: 'unify_output',
|
|
85
|
+
event: { type: 'done' },
|
|
181
86
|
});
|
|
182
|
-
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
// Don't report abort errors
|
|
89
|
+
if (err.name === 'AbortError') return;
|
|
183
90
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
91
|
+
console.error('[Unify] query error:', err.message);
|
|
92
|
+
sendToServer({
|
|
93
|
+
type: 'unify_output',
|
|
94
|
+
event: { type: 'error', message: err.message || 'Session error' },
|
|
95
|
+
});
|
|
96
|
+
} finally {
|
|
97
|
+
currentAbort = null;
|
|
187
98
|
}
|
|
188
|
-
|
|
189
|
-
// ─── Send result + turn_completed ─────────────────────────
|
|
190
|
-
// Emit a result message to match Claude's wire format (UI uses this for state management)
|
|
191
|
-
sendOutput(conversationId, {
|
|
192
|
-
type: 'result',
|
|
193
|
-
subtype: hasError ? 'error' : 'success',
|
|
194
|
-
result_text: fullText
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
ctx.sendToServer({
|
|
198
|
-
type: 'turn_completed',
|
|
199
|
-
conversationId,
|
|
200
|
-
workDir: state.workDir
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
sendConversationList();
|
|
204
99
|
}
|