@yeaft/webchat-agent 0.1.418 → 0.1.419

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/conversation.js CHANGED
@@ -6,6 +6,7 @@ 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';
9
10
 
10
11
  // 不支持的斜杠命令(真正需要交互式 CLI 的命令)
11
12
  const UNSUPPORTED_SLASH_COMMANDS = ['/help', '/bug', '/login', '/logout', '/terminal-setup', '/vim', '/config'];
@@ -293,7 +294,8 @@ export async function sendConversationList() {
293
294
  createdAt: state.createdAt,
294
295
  processing: !!state.turnActive,
295
296
  userId: state.userId,
296
- username: state.username
297
+ username: state.username,
298
+ mode: state.mode || null
297
299
  };
298
300
  list.push(entry);
299
301
  }
@@ -354,7 +356,7 @@ export function sendError(conversationId, message) {
354
356
 
355
357
  // 创建新的 conversation (延迟启动 Claude,等待用户发送第一条消息)
356
358
  export async function createConversation(msg) {
357
- const { conversationId, workDir, userId, username, disallowedTools } = msg;
359
+ const { conversationId, workDir, userId, username, disallowedTools, mode } = msg;
358
360
  const effectiveWorkDir = workDir || ctx.CONFIG.workDir;
359
361
 
360
362
  console.log(`Creating conversation: ${conversationId} in ${effectiveWorkDir} (lazy start)`);
@@ -375,6 +377,7 @@ export async function createConversation(msg) {
375
377
  userId,
376
378
  username,
377
379
  disallowedTools: disallowedTools || null, // null = 使用全局默认
380
+ mode: mode || 'claude', // 'claude' (default) or 'unify'
378
381
  usage: {
379
382
  inputTokens: 0,
380
383
  outputTokens: 0,
@@ -390,7 +393,8 @@ export async function createConversation(msg) {
390
393
  workDir: effectiveWorkDir,
391
394
  userId,
392
395
  username,
393
- disallowedTools: disallowedTools || null
396
+ disallowedTools: disallowedTools || null,
397
+ mode: mode || 'claude'
394
398
  });
395
399
 
396
400
  // 立即发送 agent 级别的 MCP servers 列表(从 ~/.claude.json 读取的)
@@ -413,8 +417,11 @@ export async function createConversation(msg) {
413
417
  sendConversationList();
414
418
 
415
419
  // ★ Prestart Claude CLI in background to eagerly fetch skills/tools/model
416
- // Fire-and-forget: failure just degrades to lazy-start behavior
417
- prestartClaude(conversationId, effectiveWorkDir, null);
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
+ }
418
425
  }
419
426
 
420
427
  // Resume 历史 conversation (延迟启动 Claude,等待用户发送第一条消息)
@@ -662,6 +669,12 @@ export async function handleCancelExecution(msg) {
662
669
  export async function handleUserInput(msg) {
663
670
  const { conversationId, prompt, workDir, claudeSessionId } = msg;
664
671
 
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
+
665
678
  // 解析斜杠命令
666
679
  const slashCommand = parseSlashCommand(prompt);
667
680
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.418",
3
+ "version": "0.1.419",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,204 @@
1
+ /**
2
+ * web-bridge.js — Bridge between Yeaft Unify Engine and the Web UI
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.
7
+ *
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
14
+ */
15
+
16
+ import ctx from '../context.js';
17
+ import { sendOutput, sendConversationList } from '../conversation.js';
18
+ import { loadSession } from './session.js';
19
+
20
+ /**
21
+ * Handle a user message for a Unify-mode conversation.
22
+ *
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
28
+ */
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
+ }
37
+
38
+ // ─── Lazy-init Unify Session ──────────────────────────────
39
+ if (!state._unifySession) {
40
+ try {
41
+ console.log(`[Unify] Loading session for ${conversationId}...`);
42
+ state._unifySession = await loadSession({
43
+ skipMCP: true,
44
+ skipSkills: true,
45
+ });
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
64
+ });
65
+ return;
66
+ }
67
+ }
68
+
69
+ const session = state._unifySession;
70
+
71
+ // ─── Send user message to UI ──────────────────────────────
72
+ sendOutput(conversationId, {
73
+ type: 'user',
74
+ message: {
75
+ role: 'user',
76
+ content: prompt
77
+ }
78
+ });
79
+
80
+ // Track messages for multi-turn context
81
+ state._unifyMessages.push({ role: 'user', content: prompt });
82
+
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
+ })) {
92
+ switch (event.type) {
93
+ 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
+ }
133
+ });
134
+ break;
135
+
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
+ 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
+ }
155
+ });
156
+ break;
157
+
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
166
+ break;
167
+ }
168
+ }
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
+ }
181
+ });
182
+ }
183
+
184
+ // ─── Track assistant response for multi-turn ──────────────
185
+ if (fullText) {
186
+ state._unifyMessages.push({ role: 'assistant', content: fullText });
187
+ }
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
+ }