@yeaft/webchat-agent 1.0.532 → 1.0.534

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.
@@ -26,7 +26,7 @@ import { sendToServer, flushMessageBuffer } from './buffer.js';
26
26
  import { sendAgentMetricsSnapshot } from '../metrics.js';
27
27
  import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
28
28
  import { loadMcpServers, updateMcpConfig } from '../mcp.js';
29
- import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getPluginConfig, updatePluginConfig, getTelemetrySettings, updateTelemetrySettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
29
+ import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getPluginConfig, updatePluginConfig, getTelemetrySettings, updateTelemetrySettings, getWorkCenterFeatureSettings, updateWorkCenterFeatureSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
30
30
  import { loadConfig } from '../yeaft/config.js';
31
31
  import { mutateAgentConfig } from '../yeaft/config-store.js';
32
32
  import { discoverLlmModels } from '../llm-model-discovery.js';
@@ -96,6 +96,68 @@ export function applyRegisteredTransport(msg) {
96
96
  }
97
97
  }
98
98
 
99
+ let workCenterFeatureTransition = Promise.resolve();
100
+
101
+ export async function applyWorkCenterFeatureUpdate(msg, dependencies = {}) {
102
+ const run = async () => {
103
+ const update = dependencies.updateWorkCenterFeatureSettings || updateWorkCenterFeatureSettings;
104
+ const getSettings = dependencies.getWorkCenterFeatureSettings || getWorkCenterFeatureSettings;
105
+ const bridge = dependencies.bridge || await import('../yeaft/work-center/bridge.js');
106
+ const refreshCapabilities = dependencies.refreshAgentCapabilities || ctx.refreshAgentCapabilities;
107
+ const yeaftDir = dependencies.yeaftDir ?? ctx.CONFIG?.yeaftDir;
108
+ // Read inside the transition queue so the previous request is observable.
109
+ const previous = getSettings(yeaftDir);
110
+ const persisted = update(msg.settings || {}, yeaftDir);
111
+ if (persisted.error) return { ...persisted, persisted: false, effective: previous.enabled === true };
112
+
113
+ bridge.setWorkCenterFeatureEnabled(persisted.enabled);
114
+ try {
115
+ if (persisted.enabled) await bridge.bootWorkCenter();
116
+ else await bridge.shutdownWorkCenter();
117
+ if (ctx.CONFIG) ctx.CONFIG.workCenterEnabled = persisted.enabled;
118
+ ctx.workCenterStartupError = null;
119
+ await refreshCapabilities?.();
120
+ return {
121
+ ...persisted,
122
+ persisted: true,
123
+ effective: persisted.enabled,
124
+ sessionTools: persisted.enabled ? 'new_sessions_only' : 'disabled_immediately',
125
+ };
126
+ } catch (error) {
127
+ const rollback = previous.error ? previous : update({ enabled: previous.enabled === true }, yeaftDir);
128
+ const restored = !rollback.error;
129
+ let rollbackRuntimeError = null;
130
+ if (restored) {
131
+ bridge.setWorkCenterFeatureEnabled(previous.enabled === true);
132
+ try {
133
+ if (previous.enabled === true) await bridge.bootWorkCenter();
134
+ else await bridge.shutdownWorkCenter();
135
+ } catch (rollbackError) {
136
+ rollbackRuntimeError = rollbackError?.message || String(rollbackError);
137
+ bridge.setWorkCenterFeatureEnabled(false);
138
+ }
139
+ if (ctx.CONFIG) ctx.CONFIG.workCenterEnabled = previous.enabled === true && !rollbackRuntimeError;
140
+ try { await refreshCapabilities?.(); } catch { /* original transition error remains authoritative */ }
141
+ } else {
142
+ bridge.setWorkCenterFeatureEnabled(false);
143
+ if (ctx.CONFIG) ctx.CONFIG.workCenterEnabled = false;
144
+ }
145
+ const effective = restored && !rollbackRuntimeError ? previous.enabled === true : false;
146
+ return {
147
+ ...persisted,
148
+ enabled: restored ? previous.enabled === true : persisted.enabled,
149
+ error: `Failed to apply Work Center runtime transition: ${error?.message || error}${rollbackRuntimeError ? `; rollback runtime failed: ${rollbackRuntimeError}` : ''}`,
150
+ persisted: !restored,
151
+ effective,
152
+ rolledBack: restored && !rollbackRuntimeError,
153
+ };
154
+ }
155
+ };
156
+ const result = workCenterFeatureTransition.then(run, run);
157
+ workCenterFeatureTransition = result.catch(() => {});
158
+ return result;
159
+ }
160
+
99
161
  export async function handleMessage(msg) {
100
162
  if (await handleBrowserRuntimeMessage(msg)) return;
101
163
  switch (msg.type) {
@@ -532,6 +594,28 @@ export async function handleMessage(msg) {
532
594
  break;
533
595
  }
534
596
 
597
+
598
+ case 'get_work_center_feature_settings': {
599
+ const settings = getWorkCenterFeatureSettings(ctx.CONFIG?.yeaftDir);
600
+ sendToServer({
601
+ type: 'work_center_feature_settings', requestId: msg.requestId, clientId: msg.clientId, ...settings,
602
+ effective: ctx.CONFIG?.workCenterEnabled === true,
603
+ ...(ctx.workCenterStartupError ? { runtimeError: ctx.workCenterStartupError } : {}),
604
+ });
605
+ break;
606
+ }
607
+
608
+ case 'update_work_center_feature_settings': {
609
+ let result;
610
+ try {
611
+ result = await applyWorkCenterFeatureUpdate(msg);
612
+ } catch (error) {
613
+ result = { error: `Failed to apply Work Center setting: ${error?.message || error}`, persisted: false, effective: ctx.CONFIG?.workCenterEnabled === true };
614
+ }
615
+ sendToServer({ type: 'work_center_feature_settings_updated', requestId: msg.requestId, clientId: msg.clientId, ...result });
616
+ break;
617
+ }
618
+
535
619
  // Search settings (web-search backend + Tavily key) — read/write the
536
620
  // `search` section of config.json. `get_tavily_usage` hits Tavily's
537
621
  // /usage endpoint with the saved key and is fired from the UI only
package/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  } from './upgrade-command.js';
26
26
  import { loadConfig as loadYeaftConfig } from './yeaft/config.js';
27
27
  import { initYeaftDir } from './yeaft/init.js';
28
- import { isWorkCenterEnabled } from './yeaft/work-center/feature.js';
28
+ import { isWorkCenterEnabled, startWorkCenterFeature } from './yeaft/work-center/feature.js';
29
29
  import { updateBrowserRuntimeSettings } from './yeaft/config-api.js';
30
30
  import { bootBrowserRuntime, shutdownBrowserRuntime } from './browser-runtime/index.js';
31
31
  import {
@@ -139,7 +139,7 @@ const CONFIG = {
139
139
  workDir: WORK_DIR,
140
140
  yeaftDir: YEAFT_DIR,
141
141
  telemetry: loadYeaftConfig({ dir: YEAFT_DIR }).telemetry,
142
- workCenterEnabled: isWorkCenterEnabled(),
142
+ workCenterEnabled: isWorkCenterEnabled(process.env, loadYeaftConfig({ dir: YEAFT_DIR })),
143
143
  reconnectInterval: fileConfig.reconnectInterval,
144
144
  agentSecret,
145
145
  // 显式禁用的工具(非 MCP 相关)
@@ -177,9 +177,9 @@ async function detectCapabilities() {
177
177
  // agent build can speak plaintext WS frames. New servers see this and
178
178
  // flip `agent.encryptOutbound = false`, stopping outbound encryption
179
179
  // to this peer. Old servers ignore the unknown capability token.
180
- const capabilities = ['background_tasks', 'file_editor', 'ping_session', 'plaintext-ok', 'workbench_session_routes', 'workbench_request_correlation', 'workbench_terminal_cleanup_fence', 'workbench_file_content_chunks', 'workbench_video_stream', 'session_history_search', 'session_history_outline', 'session_history_window_prefetch', 'file_reference_resolution', 'response_image_preview', 'yeaft_plugins', 'yeaft_managed_skills', 'settings_request_correlation'];
180
+ const capabilities = ['background_tasks', 'file_editor', 'ping_session', 'plaintext-ok', 'workbench_session_routes', 'workbench_request_correlation', 'workbench_terminal_cleanup_fence', 'workbench_file_content_chunks', 'workbench_video_stream', 'session_history_search', 'session_history_outline', 'session_history_window_prefetch', 'file_reference_resolution', 'response_image_preview', 'yeaft_plugins', 'yeaft_managed_skills', 'settings_request_correlation', 'work_center_feature_settings'];
181
181
  capabilities.push(getAgentUpgradeCapability());
182
- if (isWorkCenterEnabled()) {
182
+ if (ctx.CONFIG?.workCenterEnabled === true) {
183
183
  capabilities.push('work_center', 'work_center_message_v2');
184
184
  if (process.platform === 'linux') capabilities.push('work_item_attachments');
185
185
  }
@@ -193,6 +193,12 @@ async function detectCapabilities() {
193
193
  return capabilities;
194
194
  }
195
195
 
196
+ ctx.refreshAgentCapabilities = async () => {
197
+ ctx.agentCapabilities = await detectCapabilities();
198
+ ctx.sendToServer?.({ type: 'agent_capabilities_updated', capabilities: ctx.agentCapabilities });
199
+ return ctx.agentCapabilities;
200
+ };
201
+
196
202
  // 确保依赖已安装。node-pty 已被 @homebridge/node-pty-prebuilt-multiarch
197
203
  // 取代(regular dep + 全平台预编译),不再需要 optionalDependency 的特判。
198
204
  async function ensureDependencies() {
@@ -459,7 +465,6 @@ process.on('SIGTERM', async () => {
459
465
  } catch (err) {
460
466
  console.warn(`[BrowserRuntime] startup probe failed: ${err?.message || err}`);
461
467
  }
462
- ctx.agentCapabilities = await detectCapabilities();
463
468
  // Prime the models.dev community catalog so the Yeaft engine's *synchronous*
464
469
  // hot path (engine.js / config.js / cli.js all read context-window inline)
465
470
  // can resolve real per-model limits without bubbling async up through every
@@ -472,16 +477,21 @@ process.on('SIGTERM', async () => {
472
477
  } catch (err) {
473
478
  console.warn(`[Agent] models.dev prime failed (will use config/defaults): ${err?.message || err}`);
474
479
  }
475
- if (isWorkCenterEnabled()) {
476
- try {
477
- const { bootWorkCenter } = await import('./yeaft/work-center/bridge.js');
478
- await bootWorkCenter();
480
+ if (ctx.CONFIG?.workCenterEnabled === true) {
481
+ const bridge = await import('./yeaft/work-center/bridge.js');
482
+ const startup = await startWorkCenterFeature(true, bridge.bootWorkCenter);
483
+ ctx.CONFIG.workCenterEnabled = startup.effective;
484
+ ctx.workCenterStartupError = startup.runtimeError;
485
+ if (startup.effective) {
479
486
  console.log('[Agent] Work Center watcher started');
480
- } catch (err) {
481
- console.warn(`[Agent] Work Center failed to start: ${err?.message || err}`);
487
+ } else {
488
+ bridge.setWorkCenterFeatureEnabled(false);
489
+ console.warn(`[Agent] Work Center failed to start: ${startup.runtimeError}`);
482
490
  }
483
491
  } else {
484
492
  console.log('[Agent] Work Center disabled; watcher not started');
485
493
  }
494
+ // Runtime capabilities must reflect the effective startup state.
495
+ ctx.agentCapabilities = await detectCapabilities();
486
496
  connect();
487
497
  })();
@@ -196,6 +196,23 @@ export const yeaftProjectDb = {
196
196
  })();
197
197
  },
198
198
 
199
+ /**
200
+ * Assign a newly registered fork to the source's Project. The caller owns the
201
+ * Session-registration transaction and replay fence; this is one insert, not
202
+ * moveSession's independent transaction or a user-requested Project move.
203
+ */
204
+ inheritSessionProject(userId, agentId, sourceSessionId, targetSessionId) {
205
+ const ownerId = requireUserId(userId);
206
+ const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
207
+ const sourceId = requireId(sourceSessionId, 'invalid_session_id', 'Source Session id');
208
+ const targetId = requireId(targetSessionId, 'invalid_session_id', 'Fork Session id');
209
+ if (sourceId === targetId) throw new YeaftProjectDbError('invalid_session_id', 'Fork must have a new identity');
210
+ const project = stmts.getYeaftProjectForSession.get(ownerId, targetAgentId, sourceId);
211
+ if (!project) return null;
212
+ stmts.insertYeaftProjectSessionMembership.run(ownerId, project.id, targetAgentId, targetId, Date.now());
213
+ return project.id;
214
+ },
215
+
199
216
  reconcileAgentSessions(userId, agentId, sessionIds) {
200
217
  const ownerId = requireUserId(userId);
201
218
  const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
@@ -83,6 +83,30 @@ function syncYeaftSessionMetadata(agentId, agent, event) {
83
83
  }
84
84
  return { ...event, sessions: decorateYeaftSessionsWithPinned(agentId, event.sessions) };
85
85
  }
86
+ // A fork is a new Session on the same Agent, not a new Project. Resolve
87
+ // membership here (never from browser/Agent project metadata) before the
88
+ // successful acknowledgement can focus the new Session in the browser.
89
+ if (event.ok && ownerId && op === 'copy' && event.sourceSessionId && event.session?.id) {
90
+ try {
91
+ if (event.sourceSessionId === event.session.id) throw new Error('Fork must have a new Session identity');
92
+ transaction(() => {
93
+ // Replayed acknowledgements must not undo a later user Project move.
94
+ if (yeaftSessionDb.getForAgent(ownerId, agentId, event.session.id)) return;
95
+ yeaftSessionDb.upsertFromSnapshot(ownerId, agentId, event.session);
96
+ yeaftProjectDb.inheritSessionProject(ownerId, agentId, event.sourceSessionId, event.session.id);
97
+ })();
98
+ } catch (e) {
99
+ console.warn('[Server] Fork Project inheritance failed:', e?.message || e);
100
+ return {
101
+ ...event,
102
+ ok: false,
103
+ error: {
104
+ code: 'project_inheritance_failed',
105
+ message: `Fork ${event.session.id} was created, but its Project could not be saved.`,
106
+ },
107
+ };
108
+ }
109
+ }
86
110
  if (!event.ok || !ownerId || !sessionId || (op !== 'archive' && op !== 'delete')) return event;
87
111
 
88
112
  if (op === 'archive') {
@@ -409,14 +409,20 @@ export async function handleAgentSync(agentId, agent, msg) {
409
409
  // Local telemetry settings relay. Only bounded config is forwarded;
410
410
  // trace payloads stay on the Agent.
411
411
  case 'telemetry_settings':
412
- case 'telemetry_settings_updated': {
412
+ case 'telemetry_settings_updated':
413
+ case 'work_center_feature_settings':
414
+ case 'work_center_feature_settings_updated': {
413
415
  const operation = msg.type === 'yeaft_plugins_updated'
414
416
  ? 'plugins:update'
415
417
  : msg.type === 'yeaft_plugins'
416
418
  ? 'plugins:load'
417
419
  : msg.type === 'telemetry_settings_updated'
418
420
  ? 'telemetry:update'
419
- : 'telemetry:load';
421
+ : msg.type === 'telemetry_settings'
422
+ ? 'telemetry:load'
423
+ : msg.type === 'work_center_feature_settings_updated'
424
+ ? 'work-center-feature:update'
425
+ : 'work-center-feature:load';
420
426
  await sendAgentSettingsReply(agentId, agent, operation, msg, { ...msg, agentId });
421
427
  break;
422
428
  }
@@ -19,6 +19,8 @@ import {
19
19
  // builds without this capability may still inherit the installed package cwd.
20
20
  export const SAFE_REMOTE_UPGRADE_CAPABILITY = 'remote_upgrade_safe';
21
21
  export const CONTAINER_AGENT_CAPABILITY = 'container_agent';
22
+ export const WORK_CENTER_FEATURE_SETTINGS_CAPABILITY = 'work_center_feature_settings';
23
+ export const WORK_CENTER_FEATURE_SETTINGS_UNSUPPORTED_ERROR = 'This Agent must be upgraded to manage Work Center settings.';
22
24
  export const CONTAINER_IMAGE_UPGRADE_REASON = 'container_image_upgrade_required';
23
25
  export { YEAFT_PLUGINS_CAPABILITY, YEAFT_PLUGINS_UNSUPPORTED_ERROR };
24
26
 
@@ -52,6 +54,19 @@ async function forwardRegisteredAgentRequest({ client, clientId, agentId, operat
52
54
  return false;
53
55
  }
54
56
 
57
+
58
+ async function rejectUnsupportedWorkCenterFeatureSettings(client, msg, agentId) {
59
+ await sendToWebClient(client, {
60
+ type: msg.type === 'update_work_center_feature_settings'
61
+ ? 'work_center_feature_settings_updated'
62
+ : 'work_center_feature_settings',
63
+ agentId,
64
+ requestId: msg.requestId || null,
65
+ error: WORK_CENTER_FEATURE_SETTINGS_UNSUPPORTED_ERROR,
66
+ unsupported: true,
67
+ });
68
+ }
69
+
55
70
  export function requiresManualUpgradeBridge(capabilities, platform = null) {
56
71
  if (Array.isArray(capabilities) && capabilities.includes(SAFE_REMOTE_UPGRADE_CAPABILITY)) return false;
57
72
  const normalizedPlatform = typeof platform === 'string' ? platform.trim().toLowerCase() : '';
@@ -352,6 +367,31 @@ export async function handleClientMisc(clientId, client, msg, checkAgentAccess)
352
367
  break;
353
368
  }
354
369
 
370
+
371
+ case 'get_work_center_feature_settings': {
372
+ const a = msg.agentId || client.currentAgent;
373
+ if (!a || !await checkAgentAccess(a)) break;
374
+ if (!agents.get(a)?.capabilities?.includes(WORK_CENTER_FEATURE_SETTINGS_CAPABILITY)) {
375
+ await rejectUnsupportedWorkCenterFeatureSettings(client, msg, a);
376
+ break;
377
+ }
378
+ await forwardRegisteredAgentRequest({ client, clientId, agentId: a, operation: 'work-center-feature:load', requestId: msg.requestId,
379
+ message: { type: 'get_work_center_feature_settings', requestId: msg.requestId, clientId }, responseType: 'work_center_feature_settings' });
380
+ break;
381
+ }
382
+
383
+ case 'update_work_center_feature_settings': {
384
+ const a = msg.agentId || client.currentAgent;
385
+ if (!a || !await checkAgentAccess(a)) break;
386
+ if (!agents.get(a)?.capabilities?.includes(WORK_CENTER_FEATURE_SETTINGS_CAPABILITY)) {
387
+ await rejectUnsupportedWorkCenterFeatureSettings(client, msg, a);
388
+ break;
389
+ }
390
+ await forwardRegisteredAgentRequest({ client, clientId, agentId: a, operation: 'work-center-feature:update', requestId: msg.requestId,
391
+ message: { type: 'update_work_center_feature_settings', requestId: msg.requestId, clientId, settings: msg.settings || {} }, responseType: 'work_center_feature_settings_updated' });
392
+ break;
393
+ }
394
+
355
395
  // Search settings (web-search backend + Tavily key + on-demand usage probe).
356
396
  // Mirrors the get/update_yeaft_settings pair: the agent owns the
357
397
  // config file, server is just a relay.
@@ -316,6 +316,8 @@ function handleAgentDisconnect(agentId, agentName, ws) {
316
316
  restart: 'restart_agent_ack', dream: 'dream_enabled_changed', upgrade: 'upgrade_agent_ack',
317
317
  'plugins:load': 'yeaft_plugins', 'plugins:update': 'yeaft_plugins_updated',
318
318
  'telemetry:load': 'telemetry_settings', 'telemetry:update': 'telemetry_settings_updated',
319
+ 'work-center-feature:load': 'work_center_feature_settings',
320
+ 'work-center-feature:update': 'work_center_feature_settings_updated',
319
321
  };
320
322
  void sendToWebClient(client, {
321
323
  type: responseTypes[pending.operation] || 'agent_request_error',
@@ -1 +1 @@
1
- {"version":"1.0.532"}
1
+ {"version":"1.0.534"}