ideaco 1.1.5

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.
Files changed (159) hide show
  1. package/.dockerignore +33 -0
  2. package/.nvmrc +1 -0
  3. package/ARCHITECTURE.md +394 -0
  4. package/Dockerfile +50 -0
  5. package/LICENSE +29 -0
  6. package/README.md +206 -0
  7. package/bin/i18n.js +46 -0
  8. package/bin/ideaco.js +494 -0
  9. package/deploy.sh +15 -0
  10. package/docker-compose.yml +30 -0
  11. package/electron/main.cjs +986 -0
  12. package/electron/preload.cjs +14 -0
  13. package/electron/web-backends.cjs +854 -0
  14. package/jsconfig.json +8 -0
  15. package/next.config.mjs +34 -0
  16. package/package.json +134 -0
  17. package/postcss.config.mjs +6 -0
  18. package/public/demo/dashboard.png +0 -0
  19. package/public/demo/employee.png +0 -0
  20. package/public/demo/messages.png +0 -0
  21. package/public/demo/office.png +0 -0
  22. package/public/demo/requirement.png +0 -0
  23. package/public/logo.jpeg +0 -0
  24. package/public/logo.png +0 -0
  25. package/scripts/prepare-electron.js +67 -0
  26. package/scripts/release.js +76 -0
  27. package/src/app/api/agents/[agentId]/chat/route.js +70 -0
  28. package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
  29. package/src/app/api/agents/[agentId]/route.js +106 -0
  30. package/src/app/api/avatar/route.js +104 -0
  31. package/src/app/api/browse-dir/route.js +44 -0
  32. package/src/app/api/chat/route.js +265 -0
  33. package/src/app/api/company/factory-reset/route.js +43 -0
  34. package/src/app/api/company/route.js +82 -0
  35. package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
  36. package/src/app/api/departments/route.js +92 -0
  37. package/src/app/api/group-chat-loop/events/route.js +70 -0
  38. package/src/app/api/group-chat-loop/route.js +94 -0
  39. package/src/app/api/mailbox/route.js +100 -0
  40. package/src/app/api/messages/route.js +14 -0
  41. package/src/app/api/providers/[id]/configure/route.js +21 -0
  42. package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
  43. package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
  44. package/src/app/api/providers/route.js +11 -0
  45. package/src/app/api/requirements/route.js +242 -0
  46. package/src/app/api/secretary/route.js +65 -0
  47. package/src/app/api/system/cli-backends/route.js +91 -0
  48. package/src/app/api/system/cron/route.js +110 -0
  49. package/src/app/api/system/knowledge/route.js +104 -0
  50. package/src/app/api/system/plugins/route.js +40 -0
  51. package/src/app/api/system/skills/route.js +46 -0
  52. package/src/app/api/system/status/route.js +46 -0
  53. package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
  54. package/src/app/api/talent-market/[profileId]/route.js +17 -0
  55. package/src/app/api/talent-market/route.js +26 -0
  56. package/src/app/api/teams/route.js +773 -0
  57. package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
  58. package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
  59. package/src/app/globals.css +130 -0
  60. package/src/app/layout.jsx +40 -0
  61. package/src/app/page.jsx +97 -0
  62. package/src/components/AgentChatModal.jsx +164 -0
  63. package/src/components/AgentDetailModal.jsx +425 -0
  64. package/src/components/AgentSpyModal.jsx +481 -0
  65. package/src/components/AvatarGrid.jsx +29 -0
  66. package/src/components/BossProfileModal.jsx +162 -0
  67. package/src/components/CachedAvatar.jsx +77 -0
  68. package/src/components/ChatPanel.jsx +219 -0
  69. package/src/components/ChatShared.jsx +255 -0
  70. package/src/components/DepartmentDetail.jsx +842 -0
  71. package/src/components/DepartmentView.jsx +367 -0
  72. package/src/components/FileReference.jsx +260 -0
  73. package/src/components/FilesView.jsx +465 -0
  74. package/src/components/GroupChatView.jsx +799 -0
  75. package/src/components/Mailbox.jsx +926 -0
  76. package/src/components/MessagesView.jsx +112 -0
  77. package/src/components/OnboardingGuide.jsx +209 -0
  78. package/src/components/OrgTree.jsx +151 -0
  79. package/src/components/Overview.jsx +391 -0
  80. package/src/components/PixelOffice.jsx +2281 -0
  81. package/src/components/ProviderGrid.jsx +551 -0
  82. package/src/components/ProvidersBoard.jsx +16 -0
  83. package/src/components/RequirementDetail.jsx +1279 -0
  84. package/src/components/RequirementsBoard.jsx +187 -0
  85. package/src/components/SecretarySettings.jsx +295 -0
  86. package/src/components/SetupWizard.jsx +388 -0
  87. package/src/components/Sidebar.jsx +169 -0
  88. package/src/components/SystemMonitor.jsx +808 -0
  89. package/src/components/TalentMarket.jsx +183 -0
  90. package/src/components/TeamDetail.jsx +697 -0
  91. package/src/core/agent/base-agent.js +104 -0
  92. package/src/core/agent/chat-store.js +602 -0
  93. package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
  94. package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
  95. package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
  96. package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
  97. package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
  98. package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
  99. package/src/core/agent/cli-agent/backends/index.js +27 -0
  100. package/src/core/agent/cli-agent/backends/registry.js +580 -0
  101. package/src/core/agent/cli-agent/index.js +154 -0
  102. package/src/core/agent/index.js +60 -0
  103. package/src/core/agent/llm-agent/client.js +320 -0
  104. package/src/core/agent/llm-agent/index.js +97 -0
  105. package/src/core/agent/message-bus.js +211 -0
  106. package/src/core/agent/session.js +608 -0
  107. package/src/core/agent/tools.js +596 -0
  108. package/src/core/agent/web-agent/backends/base-backend.js +180 -0
  109. package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
  110. package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
  111. package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
  112. package/src/core/agent/web-agent/backends/index.js +91 -0
  113. package/src/core/agent/web-agent/index.js +278 -0
  114. package/src/core/agent/web-agent/web-client.js +407 -0
  115. package/src/core/employee/base-employee.js +1088 -0
  116. package/src/core/employee/index.js +35 -0
  117. package/src/core/employee/knowledge.js +327 -0
  118. package/src/core/employee/lifecycle.js +990 -0
  119. package/src/core/employee/memory/index.js +642 -0
  120. package/src/core/employee/memory/store.js +143 -0
  121. package/src/core/employee/performance.js +224 -0
  122. package/src/core/employee/secretary.js +625 -0
  123. package/src/core/employee/skills.js +398 -0
  124. package/src/core/index.js +38 -0
  125. package/src/core/organization/company.js +2600 -0
  126. package/src/core/organization/department.js +737 -0
  127. package/src/core/organization/group-chat-loop.js +264 -0
  128. package/src/core/organization/index.js +8 -0
  129. package/src/core/organization/persistence.js +111 -0
  130. package/src/core/organization/team.js +267 -0
  131. package/src/core/organization/workforce/hr.js +377 -0
  132. package/src/core/organization/workforce/providers.js +468 -0
  133. package/src/core/organization/workforce/role-archetypes.js +805 -0
  134. package/src/core/organization/workforce/talent-market.js +205 -0
  135. package/src/core/prompts.js +532 -0
  136. package/src/core/requirement.js +1789 -0
  137. package/src/core/system/audit.js +483 -0
  138. package/src/core/system/cron.js +449 -0
  139. package/src/core/system/index.js +7 -0
  140. package/src/core/system/plugin.js +2183 -0
  141. package/src/core/utils/json-parse.js +188 -0
  142. package/src/core/workspace.js +239 -0
  143. package/src/lib/api-i18n.js +211 -0
  144. package/src/lib/avatar.js +268 -0
  145. package/src/lib/client-store.js +1025 -0
  146. package/src/lib/config-validator.js +483 -0
  147. package/src/lib/format-time.js +22 -0
  148. package/src/lib/hooks.js +414 -0
  149. package/src/lib/i18n.js +134 -0
  150. package/src/lib/paths.js +23 -0
  151. package/src/lib/store.js +72 -0
  152. package/src/locales/de.js +393 -0
  153. package/src/locales/en.js +1054 -0
  154. package/src/locales/es.js +393 -0
  155. package/src/locales/fr.js +393 -0
  156. package/src/locales/ja.js +501 -0
  157. package/src/locales/ko.js +513 -0
  158. package/src/locales/zh.js +828 -0
  159. package/tailwind.config.mjs +11 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Web Chat Client — 通过 Electron 隐藏 BrowserWindow 的 DOM 交互与 Web AI 聊天。
3
+ *
4
+ * 架构重构:
5
+ * - 本文件只负责通用的通信层(proxy 发现、请求转发、会话管理)
6
+ * - ChatGPT 专属逻辑(选择器、DOM 脚本等)已迁移到 backends/chatgpt/
7
+ * - 每个员工(sessionId)在 Electron 侧会获得独立的 BrowserWindow,避免并发冲突
8
+ * - 新增 web 后端(Claude、DeepSeek 等)只需在 backends/ 下新增文件夹
9
+ *
10
+ * 通信链路:
11
+ * WebClient.chat() → HTTP POST 到 Electron proxy → IPC → 独立 BrowserWindow → DOM 交互
12
+ */
13
+
14
+ import { auditLogger, AuditCategory, AuditLevel } from '../../system/audit.js';
15
+ import { webBackendRegistry } from './backends/index.js';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import os from 'os';
19
+
20
+ // --- Proxy port 发现 ---
21
+ let _proxyPort = null;
22
+
23
+ function _getProxyPort() {
24
+ if (_proxyPort) return _proxyPort;
25
+
26
+ // 1. 环境变量(Electron 启动 Next.js 时注入)
27
+ if (process.env.CHATGPT_PROXY_PORT) {
28
+ _proxyPort = parseInt(process.env.CHATGPT_PROXY_PORT, 10);
29
+ if (_proxyPort > 0) return _proxyPort;
30
+ }
31
+
32
+ // 2. 临时文件(Electron 主进程写入)
33
+ const candidatePaths = [
34
+ path.join(os.tmpdir(), 'ideaco-chatgpt-proxy-port'),
35
+ path.join(os.homedir(), '.ideaco-chatgpt-proxy-port'),
36
+ ];
37
+ for (const tmpFile of candidatePaths) {
38
+ try {
39
+ if (fs.existsSync(tmpFile)) {
40
+ const port = parseInt(fs.readFileSync(tmpFile, 'utf8').trim(), 10);
41
+ if (port > 0) {
42
+ _proxyPort = port;
43
+ return _proxyPort;
44
+ }
45
+ }
46
+ } catch { /* ignore */ }
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * 通过 Electron proxy 发送 DOM 聊天请求。
54
+ *
55
+ * @param {string} message - 消息内容
56
+ * @param {string} model - 模型名称
57
+ * @param {object} options - { timeoutMs, newConversation, sessionId, backendId }
58
+ */
59
+ async function _domChat(message, model, { timeoutMs = 120000, newConversation = false, sessionId = null, backendId = 'chatgpt' } = {}) {
60
+ let proxyPort = _getProxyPort();
61
+ if (!proxyPort) {
62
+ throw new Error(
63
+ 'ChatGPT proxy not available. ' +
64
+ `ENV CHATGPT_PROXY_PORT=${process.env.CHATGPT_PROXY_PORT || '(not set)'}. ` +
65
+ 'Make sure you are running inside Electron, or set the CHATGPT_PROXY_PORT env var.'
66
+ );
67
+ }
68
+
69
+ const payload = JSON.stringify({
70
+ url: '__dom_chat__',
71
+ method: 'DOM_CHAT',
72
+ body: JSON.stringify({ message, model, timeoutMs, newConversation, sessionId, backendId }),
73
+ });
74
+
75
+ let res;
76
+ try {
77
+ res = await fetch(`http://127.0.0.1:${proxyPort}`, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: payload,
81
+ });
82
+ } catch (fetchErr) {
83
+ // 端口可能过期(应用重启),清缓存重试一次
84
+ _proxyPort = null;
85
+ proxyPort = _getProxyPort();
86
+ if (proxyPort) {
87
+ try {
88
+ res = await fetch(`http://127.0.0.1:${proxyPort}`, {
89
+ method: 'POST',
90
+ headers: { 'Content-Type': 'application/json' },
91
+ body: payload,
92
+ });
93
+ } catch (retryErr) {
94
+ throw new Error(
95
+ `Cannot reach proxy at 127.0.0.1:${proxyPort} — ${retryErr.message}. ` +
96
+ 'The Electron proxy server may not be running. Try restarting the app.'
97
+ );
98
+ }
99
+ } else {
100
+ throw new Error(
101
+ `Cannot reach proxy — ${fetchErr.message}. ` +
102
+ 'The Electron proxy server may not be running. Try restarting the app.'
103
+ );
104
+ }
105
+ }
106
+
107
+ if (res.status !== 200) {
108
+ throw new Error(`DOM chat proxy returned ${res.status}`);
109
+ }
110
+
111
+ const data = JSON.parse(await res.text());
112
+
113
+ if (data.error && data.error !== 'timeout_partial') {
114
+ throw new Error(`DOM chat failed: ${data.error}`);
115
+ }
116
+
117
+ if (!data.text) {
118
+ throw new Error('DOM chat returned empty response');
119
+ }
120
+
121
+ return data.text;
122
+ }
123
+
124
+ /**
125
+ * BaseWebClient — Web 聊天客户端基类
126
+ *
127
+ * 管理会话状态、消息构建、与 Electron proxy 的通信。
128
+ * 所有 web 后端(ChatGPT、Claude、DeepSeek 等)共享这套通信机制,
129
+ * 差异化的 DOM 交互由各自的 backend 处理。
130
+ */
131
+ class BaseWebClient {
132
+ /**
133
+ * @param {string} backendId - 后端标识,如 'chatgpt'
134
+ */
135
+ constructor(backendId) {
136
+ this._backendId = backendId;
137
+ this._cookieRefresher = null;
138
+ // 每个 session 的对话状态:sessionId → { hasActiveConversation, messageCount, currentContext }
139
+ this._sessions = new Map();
140
+ }
141
+
142
+ get backendId() {
143
+ return this._backendId;
144
+ }
145
+
146
+ /**
147
+ * 是否运行在 Electron 代理环境中
148
+ */
149
+ get _useProxy() {
150
+ return !!_getProxyPort();
151
+ }
152
+
153
+ /**
154
+ * 获取或创建指定 sessionId 的会话状态
155
+ */
156
+ _getSession(sessionId) {
157
+ if (!sessionId) sessionId = '__default__';
158
+ if (!this._sessions.has(sessionId)) {
159
+ this._sessions.set(sessionId, {
160
+ hasActiveConversation: false,
161
+ messageCount: 0,
162
+ currentContext: null,
163
+ });
164
+ }
165
+ return this._sessions.get(sessionId);
166
+ }
167
+
168
+ /**
169
+ * 重置会话状态
170
+ * @param {string} [sessionId] - 指定则只重置该会话,否则重置全部
171
+ */
172
+ resetConversation(sessionId) {
173
+ if (sessionId) {
174
+ this._sessions.delete(sessionId);
175
+ } else {
176
+ this._sessions.clear();
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 检查会话是否需要新开对话(消息数过多)
182
+ */
183
+ needsNewSession(sessionId, maxMessages = 50) {
184
+ const session = this._getSession(sessionId);
185
+ return session.messageCount >= maxMessages;
186
+ }
187
+
188
+ /**
189
+ * 发送聊天消息
190
+ * 每个 sessionId 在 Electron 侧拥有独立的 BrowserWindow 和对话线程。
191
+ */
192
+ async chat(messages, options = {}) {
193
+ if (!this._useProxy) {
194
+ throw new Error('Web client requires Electron environment');
195
+ }
196
+
197
+ const model = options.model || 'auto';
198
+ const sessionId = options.sessionId || null;
199
+ const startTime = Date.now();
200
+
201
+ const lastUserMsg = messages.filter(m => m.role === 'user').pop();
202
+ if (!lastUserMsg) {
203
+ throw new Error('No user message found in messages array');
204
+ }
205
+
206
+ const session = this._getSession(sessionId);
207
+
208
+ // 判断是否需要新建对话
209
+ const forceNew = options.newConversation === true;
210
+ const forceReuse = options.newConversation === false;
211
+ const needNewConversation = forceNew || (!forceReuse && !session.hasActiveConversation);
212
+
213
+ // 根据对话状态构建 DOM 消息
214
+ const domMessage = needNewConversation
215
+ ? this._buildFirstMessage(messages)
216
+ : this._buildFollowUpMessage(messages);
217
+
218
+ const content = await _domChat(domMessage, model, {
219
+ timeoutMs: options.timeoutMs,
220
+ newConversation: needNewConversation,
221
+ sessionId: sessionId,
222
+ backendId: this._backendId,
223
+ });
224
+
225
+ // 更新会话状态
226
+ session.hasActiveConversation = true;
227
+ session.messageCount++;
228
+
229
+ const latency = Date.now() - startTime;
230
+ console.log(`[${this._backendId}-web] DOM chat success (session=${sessionId || 'default'}, ${content.length} chars, ${latency}ms, reused=${!needNewConversation}, msgCount=${session.messageCount})`);
231
+
232
+ auditLogger.log({
233
+ category: AuditCategory.LLM_REQUEST,
234
+ level: AuditLevel.INFO,
235
+ agentId: options._agentId || 'system',
236
+ agentName: options._agentName || '',
237
+ action: `Web chat: ${this._backendId} (${model}) - ${latency}ms`,
238
+ details: { provider: `${this._backendId}-web`, model, latency, mode: 'dom', reused: !needNewConversation, sessionId },
239
+ });
240
+
241
+ return {
242
+ content,
243
+ toolCalls: null,
244
+ finishReason: 'stop',
245
+ usage: {},
246
+ };
247
+ }
248
+
249
+ /**
250
+ * 构建新对话的第一条消息(包含 system prompt + 完整上下文)
251
+ */
252
+ _buildFirstMessage(messages) {
253
+ const parts = [];
254
+
255
+ const systemMsgs = messages.filter(m => m.role === 'system');
256
+ if (systemMsgs.length > 0) {
257
+ parts.push('[System Instructions]\n' + systemMsgs.map(m => m.content).join('\n\n'));
258
+ }
259
+
260
+ const nonSystem = messages.filter(m => m.role !== 'system');
261
+ if (nonSystem.length > 1) {
262
+ const history = nonSystem.slice(0, -1);
263
+ if (history.length > 0) {
264
+ parts.push('[Conversation History]');
265
+ for (const msg of history) {
266
+ const role = msg.role === 'assistant' ? 'Assistant' : 'User';
267
+ parts.push(`${role}: ${msg.content}`);
268
+ }
269
+ }
270
+ }
271
+
272
+ const lastUser = nonSystem[nonSystem.length - 1];
273
+ if (lastUser) {
274
+ if (parts.length > 0) {
275
+ parts.push('[Current Message]');
276
+ }
277
+ parts.push(lastUser.content);
278
+ }
279
+
280
+ return parts.join('\n\n');
281
+ }
282
+
283
+ /**
284
+ * 构建跟进消息(上下文已在对话中,通常只发最新的用户消息)
285
+ * 如果有 system 消息(动态上下文,如秘书的实时公司状态),需要带上。
286
+ */
287
+ _buildFollowUpMessage(messages) {
288
+ const systemMsgs = messages.filter(m => m.role === 'system');
289
+ const nonSystem = messages.filter(m => m.role !== 'system');
290
+ const lastUser = nonSystem[nonSystem.length - 1];
291
+
292
+ if (systemMsgs.length > 0) {
293
+ const parts = [];
294
+ parts.push('[Context Update]\n' + systemMsgs.map(m => m.content).join('\n\n'));
295
+
296
+ if (nonSystem.length > 1) {
297
+ const history = nonSystem.slice(0, -1);
298
+ if (history.length > 0) {
299
+ parts.push('[Conversation History]');
300
+ for (const msg of history) {
301
+ const role = msg.role === 'assistant' ? 'Assistant' : 'User';
302
+ parts.push(`${role}: ${msg.content}`);
303
+ }
304
+ }
305
+ }
306
+
307
+ if (lastUser) {
308
+ parts.push('[Current Message]');
309
+ parts.push(lastUser.content);
310
+ }
311
+ return parts.join('\n\n');
312
+ }
313
+
314
+ return lastUser ? lastUser.content : '';
315
+ }
316
+
317
+ /**
318
+ * 测试连接
319
+ */
320
+ async testConnection() {
321
+ if (!this._useProxy) {
322
+ return { ok: false, error: 'Not running in Electron' };
323
+ }
324
+ return { ok: true, proxyMode: true, mode: 'dom', backend: this._backendId };
325
+ }
326
+ }
327
+
328
+ /**
329
+ * WebClientRegistry — 管理所有 web 聊天客户端
330
+ *
331
+ * 通过 providerId 查找对应的客户端实例。
332
+ * 每个后端(chatgpt、claude、deepseek)对应一个 BaseWebClient 实例。
333
+ */
334
+ export class WebClientRegistry {
335
+ constructor() {
336
+ this.clients = new Map();
337
+ // 为每个已注册的后端创建客户端
338
+ for (const backend of webBackendRegistry.getAll()) {
339
+ this.clients.set(backend.id, new BaseWebClient(backend.id));
340
+ }
341
+ }
342
+
343
+ /**
344
+ * 根据 providerId 获取客户端
345
+ * 兼容旧的命名方式(如 'web-chatgpt-xxx')
346
+ */
347
+ getClient(providerId) {
348
+ // 精确匹配
349
+ if (this.clients.has(providerId)) {
350
+ return this.clients.get(providerId);
351
+ }
352
+ // 模糊匹配(兼容 'web-chatgpt-xxx' 格式)
353
+ for (const [id, client] of this.clients) {
354
+ if (providerId.includes(id)) {
355
+ return client;
356
+ }
357
+ }
358
+ return null;
359
+ }
360
+
361
+ /**
362
+ * No-op:DOM 模式使用 Electron 的 Chromium session cookies。
363
+ * 保留用于向后兼容。
364
+ */
365
+ configureCookie(_providerId, _cookie) {
366
+ // No-op
367
+ }
368
+
369
+ setCookieRefresher(providerId, refresher) {
370
+ const client = this.getClient(providerId);
371
+ if (client) {
372
+ client._cookieRefresher = refresher;
373
+ }
374
+ }
375
+
376
+ async testConnection(providerId) {
377
+ const client = this.getClient(providerId);
378
+ if (!client) return { ok: false, error: 'Unknown web provider' };
379
+ return await client.testConnection();
380
+ }
381
+
382
+ async chat(providerId, messages, options = {}) {
383
+ const client = this.getClient(providerId);
384
+ if (!client) throw new Error(`No web client for: ${providerId}`);
385
+ return await client.chat(messages, options);
386
+ }
387
+
388
+ /**
389
+ * 重置会话
390
+ */
391
+ resetConversation(providerId, sessionId) {
392
+ const client = this.getClient(providerId);
393
+ if (client) client.resetConversation(sessionId);
394
+ }
395
+
396
+ /**
397
+ * 检查是否需要新建会话
398
+ */
399
+ needsNewSession(providerId, sessionId, maxMessages) {
400
+ const client = this.getClient(providerId);
401
+ if (!client) return false;
402
+ return client.needsNewSession(sessionId, maxMessages);
403
+ }
404
+ }
405
+
406
+ // 全局单例
407
+ export const webClientRegistry = new WebClientRegistry();