@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.421",
3
+ "version": "0.1.423",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * web-bridge.js — Bridge between web UI and Yeaft Unify Engine.
3
3
  *
4
- * Handles `unify_chat` messages from the server, runs queries through
5
- * the Engine, and streams `unify_output` events back.
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
- * The session is lazily initialized on first use and reused across queries.
8
- * Engine internally manages conversation history and persistence.
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
- skipMCP: true,
37
- skipSkills: true,
76
+ // Enable all features — no lazy shortcuts
77
+ skipMCP: false,
78
+ skipSkills: false,
38
79
  });
39
80
 
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 },
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 query events → unify_output messages
56
- for await (const event of session.engine.query({ prompt, signal: currentAbort.signal })) {
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
- sendToServer({
60
- type: 'unify_output',
61
- event: { type: 'text_delta', text: event.text },
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
- sendToServer({
67
- type: 'unify_output',
68
- event: {
69
- type: 'error',
70
- message: event.error?.message || 'Unknown error',
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
- // Signal completion
83
- sendToServer({
84
- type: 'unify_output',
85
- event: { type: 'done' },
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
- sendToServer({
93
- type: 'unify_output',
94
- event: { type: 'error', message: err.message || 'Session error' },
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
+ }