@yeaft/webchat-agent 0.1.420 → 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.
@@ -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, mode } = msg;
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
- // Skip prestart for unify mode — it doesn't use Claude CLI
421
- if (mode !== 'unify') {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.420",
3
+ "version": "0.1.421",
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,204 +1,99 @@
1
1
  /**
2
- * web-bridge.js — Bridge between Yeaft Unify Engine and the Web UI
2
+ * web-bridge.js — Bridge between web UI and Yeaft Unify Engine.
3
3
  *
4
- * Translates Unify Engine streaming events into the sendOutput wire format
5
- * that the existing web client already understands. This means the UI needs
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
- * Architecture: Agent-side only. UI and Server are transparent relays.
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 user message for a Unify-mode conversation.
22
+ * Handle a unify_chat message from the web UI.
22
23
  *
23
- * Called from conversation.js handleUserInput() when state.mode === 'unify'.
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 handleUnifyInput(msg) {
30
- const { conversationId, prompt } = msg;
31
- const state = ctx.conversations.get(conversationId);
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
- // ─── Lazy-init Unify Session ──────────────────────────────
39
- if (!state._unifySession) {
40
- try {
41
- console.log(`[Unify] Loading session for ${conversationId}...`);
42
- state._unifySession = await loadSession({
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
- state._unifyMessages = [];
47
- console.log(`[Unify] Session loaded for ${conversationId} (model: ${state._unifySession.config.model})`);
48
- } catch (err) {
49
- console.error(`[Unify] Failed to load session for ${conversationId}:`, err);
50
- sendOutput(conversationId, {
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
- // ─── Send user message to UI ──────────────────────────────
72
- sendOutput(conversationId, {
73
- type: 'user',
74
- message: {
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
- // Track messages for multi-turn context
81
- state._unifyMessages.push({ role: 'user', content: prompt });
53
+ currentAbort = new AbortController();
82
54
 
83
- // ─── Run Engine query and stream back ─────────────────────
84
- let fullText = '';
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
- fullText += event.text;
95
- sendOutput(conversationId, {
96
- type: 'assistant',
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
- hasError = true;
146
- sendOutput(conversationId, {
147
- type: 'assistant',
148
- message: {
149
- role: 'assistant',
150
- content: [{
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
- case 'stop':
159
- case 'turn_start':
160
- case 'turn_end':
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
- } catch (err) {
170
- hasError = true;
171
- console.error(`[Unify] Query error for ${conversationId}:`, err);
172
- sendOutput(conversationId, {
173
- type: 'assistant',
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
- // ─── Track assistant response for multi-turn ──────────────
185
- if (fullText) {
186
- state._unifyMessages.push({ role: 'assistant', content: fullText });
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
  }