@yeaft/webchat-agent 0.1.421 → 0.1.423
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 +9 -1
- package/package.json +1 -1
- package/unify/web-bridge.js +230 -33
|
@@ -25,7 +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
|
+
import { handleUnifyChat, handleUnifyModeSwitch, resetUnifySession } from '../unify/web-bridge.js';
|
|
29
29
|
|
|
30
30
|
export async function handleMessage(msg) {
|
|
31
31
|
switch (msg.type) {
|
|
@@ -324,5 +324,13 @@ export async function handleMessage(msg) {
|
|
|
324
324
|
case 'unify_chat':
|
|
325
325
|
await handleUnifyChat(msg);
|
|
326
326
|
break;
|
|
327
|
+
|
|
328
|
+
case 'unify_mode_switch':
|
|
329
|
+
handleUnifyModeSwitch(msg);
|
|
330
|
+
break;
|
|
331
|
+
|
|
332
|
+
case 'unify_reset':
|
|
333
|
+
await resetUnifySession();
|
|
334
|
+
break;
|
|
327
335
|
}
|
|
328
336
|
}
|
package/package.json
CHANGED
package/unify/web-bridge.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* web-bridge.js — Bridge between web UI and Yeaft Unify Engine.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Translates Engine events into claude_output-format messages so the
|
|
5
|
+
* frontend can fully reuse the standard Chat rendering pipeline
|
|
6
|
+
* (MessageList, AssistantTurn, ToolLine, AskCard, waiting cat, etc.).
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* Engine
|
|
8
|
+
* Architecture:
|
|
9
|
+
* 1. On first use, loadSession() initialises Engine with skills + MCP enabled.
|
|
10
|
+
* 2. A virtual conversationId ('unify-<ts>') is assigned per session.
|
|
11
|
+
* 3. Engine.query() yields events → translated into unify_output messages
|
|
12
|
+
* that carry { conversationId, data } in claude_output format.
|
|
13
|
+
* 4. The frontend's handleUnifyOutput dispatches them through handleClaudeOutput.
|
|
9
14
|
*/
|
|
10
15
|
|
|
11
16
|
import { loadSession } from './session.js';
|
|
@@ -18,33 +23,76 @@ let session = null;
|
|
|
18
23
|
/** @type {AbortController | null} */
|
|
19
24
|
let currentAbort = null;
|
|
20
25
|
|
|
26
|
+
/** Virtual conversationId for the Unify session */
|
|
27
|
+
let unifyConversationId = null;
|
|
28
|
+
|
|
29
|
+
/** Current query mode: 'chat' or 'work' */
|
|
30
|
+
let currentMode = 'chat';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Send a unify_output message carrying claude_output-format data.
|
|
34
|
+
* The server forwards this as-is to the web client.
|
|
35
|
+
* The frontend's handleUnifyOutput will dispatch via handleClaudeOutput.
|
|
36
|
+
*/
|
|
37
|
+
function sendUnifyOutput(data) {
|
|
38
|
+
sendToServer({
|
|
39
|
+
type: 'unify_output',
|
|
40
|
+
conversationId: unifyConversationId,
|
|
41
|
+
data,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Send a unify_output event (non-claude_output metadata).
|
|
47
|
+
*/
|
|
48
|
+
function sendUnifyEvent(event) {
|
|
49
|
+
sendToServer({
|
|
50
|
+
type: 'unify_output',
|
|
51
|
+
conversationId: unifyConversationId,
|
|
52
|
+
event,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
21
56
|
/**
|
|
22
57
|
* Handle a unify_chat message from the web UI.
|
|
23
58
|
*
|
|
24
|
-
* @param {{ prompt: string, userId?: string, username?: string }} msg
|
|
59
|
+
* @param {{ prompt: string, mode?: string, userId?: string, username?: string }} msg
|
|
25
60
|
*/
|
|
26
61
|
export async function handleUnifyChat(msg) {
|
|
27
|
-
const { prompt } = msg;
|
|
62
|
+
const { prompt, mode } = msg;
|
|
28
63
|
if (!prompt?.trim()) return;
|
|
29
64
|
|
|
65
|
+
// Update mode if provided
|
|
66
|
+
if (mode === 'chat' || mode === 'work') {
|
|
67
|
+
currentMode = mode;
|
|
68
|
+
}
|
|
69
|
+
|
|
30
70
|
try {
|
|
31
|
-
// Lazy-init session (reuse across queries — Engine manages history)
|
|
71
|
+
// ─── Lazy-init session (reuse across queries — Engine manages history) ──
|
|
32
72
|
if (!session) {
|
|
33
73
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
34
74
|
session = await loadSession({
|
|
35
75
|
...(yeaftDir && { dir: yeaftDir }),
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
// Enable all features — no lazy shortcuts
|
|
77
|
+
skipMCP: false,
|
|
78
|
+
skipSkills: false,
|
|
38
79
|
});
|
|
39
80
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
81
|
+
// Create a stable conversationId for the Unify session
|
|
82
|
+
unifyConversationId = `unify-${Date.now()}`;
|
|
83
|
+
|
|
84
|
+
// Notify UI: session is ready with model info + conversationId
|
|
85
|
+
sendUnifyEvent({
|
|
86
|
+
type: 'session_ready',
|
|
87
|
+
conversationId: unifyConversationId,
|
|
88
|
+
model: session.config.model,
|
|
89
|
+
skills: session.status.skills,
|
|
90
|
+
mcpServers: session.status.mcpServers,
|
|
91
|
+
tools: session.status.tools,
|
|
44
92
|
});
|
|
45
93
|
}
|
|
46
94
|
|
|
47
|
-
// Cancel any in-flight query
|
|
95
|
+
// ─── Cancel any in-flight query ──
|
|
48
96
|
if (currentAbort) {
|
|
49
97
|
currentAbort.abort();
|
|
50
98
|
currentAbort = null;
|
|
@@ -52,48 +100,197 @@ export async function handleUnifyChat(msg) {
|
|
|
52
100
|
|
|
53
101
|
currentAbort = new AbortController();
|
|
54
102
|
|
|
55
|
-
// Stream
|
|
56
|
-
for await (const event of session.engine.query({
|
|
103
|
+
// ─── Stream Engine events → claude_output format ──
|
|
104
|
+
for await (const event of session.engine.query({
|
|
105
|
+
prompt,
|
|
106
|
+
mode: currentMode,
|
|
107
|
+
signal: currentAbort.signal,
|
|
108
|
+
})) {
|
|
57
109
|
switch (event.type) {
|
|
110
|
+
// ── Text streaming ──
|
|
58
111
|
case 'text_delta':
|
|
59
|
-
|
|
60
|
-
type: '
|
|
61
|
-
|
|
112
|
+
sendUnifyOutput({
|
|
113
|
+
type: 'assistant',
|
|
114
|
+
message: {
|
|
115
|
+
content: [{ type: 'text', text: event.text }],
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
// ── Thinking streaming (extended thinking) ──
|
|
121
|
+
case 'thinking_delta':
|
|
122
|
+
// Currently not rendered in UI, but forward for future use
|
|
123
|
+
sendUnifyEvent({ type: 'thinking_delta', text: event.text });
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
// ── Tool call announced by LLM ──
|
|
127
|
+
case 'tool_call':
|
|
128
|
+
// Finish any in-progress text streaming so UI shows typing dots
|
|
129
|
+
sendUnifyOutput({
|
|
130
|
+
type: 'assistant',
|
|
131
|
+
message: { content: [] },
|
|
62
132
|
});
|
|
133
|
+
// Send tool_use block
|
|
134
|
+
sendUnifyOutput({
|
|
135
|
+
type: 'assistant',
|
|
136
|
+
message: {
|
|
137
|
+
content: [{
|
|
138
|
+
type: 'tool_use',
|
|
139
|
+
id: event.id,
|
|
140
|
+
name: event.name,
|
|
141
|
+
input: event.input,
|
|
142
|
+
}],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
// ── Tool execution started ──
|
|
148
|
+
case 'tool_start':
|
|
149
|
+
// Tool is running — the UI already shows it from tool_use block above
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
// ── Tool execution completed ──
|
|
153
|
+
case 'tool_end':
|
|
154
|
+
// Send tool_result as a user message (matches Claude CLI format)
|
|
155
|
+
sendUnifyOutput({
|
|
156
|
+
type: 'user',
|
|
157
|
+
tool_use_result: [{
|
|
158
|
+
type: 'tool_result',
|
|
159
|
+
tool_use_id: event.id,
|
|
160
|
+
content: event.output || '',
|
|
161
|
+
is_error: event.isError || false,
|
|
162
|
+
}],
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
// ── Turn boundaries ──
|
|
167
|
+
case 'turn_start':
|
|
168
|
+
// No UI action needed
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case 'turn_end':
|
|
172
|
+
// Don't send result/done here — wait for the outermost loop to finish
|
|
63
173
|
break;
|
|
64
174
|
|
|
175
|
+
// ── Token usage ──
|
|
176
|
+
case 'usage':
|
|
177
|
+
sendUnifyEvent({
|
|
178
|
+
type: 'context_usage',
|
|
179
|
+
inputTokens: event.inputTokens,
|
|
180
|
+
outputTokens: event.outputTokens,
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
// ── Stop reason from LLM ──
|
|
185
|
+
case 'stop':
|
|
186
|
+
// Intermediate signal — final done is sent after the loop
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
// ── Memory recall ──
|
|
190
|
+
case 'recall':
|
|
191
|
+
sendUnifyEvent({
|
|
192
|
+
type: 'recall',
|
|
193
|
+
entryCount: event.entryCount,
|
|
194
|
+
cached: event.cached,
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
// ── Context consolidation ──
|
|
199
|
+
case 'consolidate':
|
|
200
|
+
sendUnifyEvent({
|
|
201
|
+
type: 'consolidate',
|
|
202
|
+
archivedCount: event.archivedCount,
|
|
203
|
+
extractedCount: event.extractedCount,
|
|
204
|
+
});
|
|
205
|
+
break;
|
|
206
|
+
|
|
207
|
+
// ── Model fallback ──
|
|
208
|
+
case 'fallback':
|
|
209
|
+
sendUnifyEvent({
|
|
210
|
+
type: 'fallback',
|
|
211
|
+
from: event.from,
|
|
212
|
+
to: event.to,
|
|
213
|
+
reason: event.reason,
|
|
214
|
+
});
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
// ── Errors ──
|
|
65
218
|
case 'error':
|
|
66
|
-
|
|
67
|
-
type: '
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
219
|
+
sendUnifyOutput({
|
|
220
|
+
type: 'assistant',
|
|
221
|
+
message: {
|
|
222
|
+
content: [{
|
|
223
|
+
type: 'text',
|
|
224
|
+
text: `⚠️ Error: ${event.error?.message || 'Unknown error'}`,
|
|
225
|
+
}],
|
|
71
226
|
},
|
|
72
227
|
});
|
|
73
228
|
break;
|
|
74
229
|
|
|
75
|
-
// Silently consume other events (tool_call, thinking_delta, usage, etc.)
|
|
76
|
-
// These can be surfaced later as the Unify UI grows.
|
|
77
230
|
default:
|
|
231
|
+
// Silently consume unknown events
|
|
78
232
|
break;
|
|
79
233
|
}
|
|
80
234
|
}
|
|
81
235
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
236
|
+
// ─── Query complete — signal turn end ──
|
|
237
|
+
// Finish any streaming text
|
|
238
|
+
sendUnifyOutput({
|
|
239
|
+
type: 'assistant',
|
|
240
|
+
message: { content: [] },
|
|
241
|
+
});
|
|
242
|
+
// Send result to clear processing state
|
|
243
|
+
sendUnifyOutput({
|
|
244
|
+
type: 'result',
|
|
245
|
+
result_text: '',
|
|
86
246
|
});
|
|
247
|
+
|
|
87
248
|
} catch (err) {
|
|
88
249
|
// Don't report abort errors
|
|
89
250
|
if (err.name === 'AbortError') return;
|
|
90
251
|
|
|
91
252
|
console.error('[Unify] query error:', err.message);
|
|
92
|
-
|
|
93
|
-
type: '
|
|
94
|
-
|
|
253
|
+
sendUnifyOutput({
|
|
254
|
+
type: 'assistant',
|
|
255
|
+
message: {
|
|
256
|
+
content: [{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: `⚠️ Session error: ${err.message}`,
|
|
259
|
+
}],
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
// Still send result to clear processing state
|
|
263
|
+
sendUnifyOutput({
|
|
264
|
+
type: 'result',
|
|
265
|
+
result_text: '',
|
|
95
266
|
});
|
|
96
267
|
} finally {
|
|
97
268
|
currentAbort = null;
|
|
98
269
|
}
|
|
99
270
|
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Handle mode switch from the web UI.
|
|
274
|
+
* @param {{ mode: 'chat' | 'work' }} msg
|
|
275
|
+
*/
|
|
276
|
+
export function handleUnifyModeSwitch(msg) {
|
|
277
|
+
if (msg.mode === 'chat' || msg.mode === 'work') {
|
|
278
|
+
currentMode = msg.mode;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Reset Unify session (for clear messages).
|
|
284
|
+
*/
|
|
285
|
+
export async function resetUnifySession() {
|
|
286
|
+
if (currentAbort) {
|
|
287
|
+
currentAbort.abort();
|
|
288
|
+
currentAbort = null;
|
|
289
|
+
}
|
|
290
|
+
if (session) {
|
|
291
|
+
await session.shutdown();
|
|
292
|
+
session = null;
|
|
293
|
+
}
|
|
294
|
+
unifyConversationId = null;
|
|
295
|
+
currentMode = 'chat';
|
|
296
|
+
}
|