@kafca/agentdock 0.1.45 → 0.1.47

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 (106) hide show
  1. package/README.md +11 -0
  2. package/dist/renderer/assets/{Badge-BIojwD6s.js → Badge-BYREM3t3.js} +1 -1
  3. package/dist/renderer/assets/Chat-Dtb5X7sB.js +2 -0
  4. package/dist/renderer/assets/{ChatMarkdown-DqszsPB_.js → ChatMarkdown-_slLloCh.js} +1 -1
  5. package/dist/renderer/assets/Config-pHichs7H.js +21 -0
  6. package/dist/renderer/assets/CronList-CJQMeCjz.js +1 -0
  7. package/dist/renderer/assets/Dashboard-CY77Imde.js +26 -0
  8. package/dist/renderer/assets/{EmptyState-BqCx1oWr.js → EmptyState-DCqmct2y.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-BwK8Gpfa.js → KnowledgeDetail-DFrxa-pv.js} +1 -1
  10. package/dist/renderer/assets/KnowledgeHome-DL-vAfrH.js +11 -0
  11. package/dist/renderer/assets/Logs-jw9Uv590.js +1 -0
  12. package/dist/renderer/assets/{Modal-D2U4r2V0.js → Modal-ChSm7tk6.js} +1 -1
  13. package/dist/renderer/assets/MonitorList-DrEwyS71.js +1 -0
  14. package/dist/renderer/assets/{Page-urf5GcT_.js → Page-DhD-zgMU.js} +1 -1
  15. package/dist/renderer/assets/ProjectDetail-BVSjpfpL.js +11 -0
  16. package/dist/renderer/assets/ProjectList-CHGbTJjk.js +1 -0
  17. package/dist/renderer/assets/{Select-CAscWpy5.js → Select-rHs9CSam.js} +1 -1
  18. package/dist/renderer/assets/SessionChat-CDc2FNMA.js +1 -0
  19. package/dist/renderer/assets/SessionList-Mr1uAnRW.js +1 -0
  20. package/dist/renderer/assets/ThreadChat-Detu5UfN.js +23 -0
  21. package/dist/renderer/assets/Workspace-MRRg8ndJ.js +19 -0
  22. package/dist/renderer/assets/{arrow-left-7HrNuS-V.js → arrow-left-BU2NB2LA.js} +1 -1
  23. package/dist/renderer/assets/{book-open-BDoAu82s.js → book-open-D-TGvWwa.js} +1 -1
  24. package/dist/renderer/assets/bot-IYNh8tH5.js +6 -0
  25. package/dist/renderer/assets/{check-DGlyq4xX.js → check-CCZ0JO_S.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-Bb_3EHq9.js → chevron-down-00bRZi15.js} +1 -1
  27. package/dist/renderer/assets/{circle-DIpqZnYw.js → circle-CpQ8x4k_.js} +1 -1
  28. package/dist/renderer/assets/{desktop-Ci1fnRDR.js → desktop-D0k2aGSj.js} +1 -1
  29. package/dist/renderer/assets/{desktop-Do_UrC-o.js → desktop-JQkhNOF4.js} +1 -1
  30. package/dist/renderer/assets/{funnel-Bbf0ERLP.js → funnel-UbcrCBCp.js} +1 -1
  31. package/dist/renderer/assets/{heart-CGqU-qfw.js → heart-DvLsV7tL.js} +1 -1
  32. package/dist/renderer/assets/{index-Df-Trovt.js → index-B2L4G-b_.js} +1 -1
  33. package/dist/renderer/assets/index-CO4fGqmv.css +11 -0
  34. package/dist/renderer/assets/{index-DwhTjLA1.js → index-dy-g2Bh4.js} +3 -3
  35. package/dist/renderer/assets/{pencil-CHiy1MvK.js → pencil-qBYOgW5M.js} +1 -1
  36. package/dist/renderer/assets/{play-CGYnmC4Q.js → play-Dn6b4AGj.js} +1 -1
  37. package/dist/renderer/assets/{plus-B_Q5-Oj7.js → plus-CaApUwEm.js} +1 -1
  38. package/dist/renderer/assets/projects-BwF5RlNV.js +1 -0
  39. package/dist/renderer/assets/save-CN6yvTfD.js +11 -0
  40. package/dist/renderer/assets/{search-D8Dkhsyq.js → search-Dn2MxYiO.js} +1 -1
  41. package/dist/renderer/assets/{server-Cd96R7WL.js → server-DpcVLHeh.js} +1 -1
  42. package/dist/renderer/assets/{session-utils-BNUbQBfF.js → session-utils-DnD2Q_y1.js} +1 -1
  43. package/dist/renderer/assets/{sessions-BACfV8s1.js → sessions-eK5D4FLt.js} +1 -1
  44. package/dist/renderer/assets/{shield-check-Cnn-yQUA.js → shield-check-DSSJkacw.js} +1 -1
  45. package/dist/renderer/assets/{trash-2-oWCxwQ9D.js → trash-2-DmYJlEvO.js} +1 -1
  46. package/dist/renderer/assets/user-Cvf_uiyQ.js +6 -0
  47. package/dist/renderer/assets/{wifi-off-DHxnNkdY.js → wifi-off-CCTloj-V.js} +1 -1
  48. package/dist/renderer/index.html +2 -2
  49. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +2 -1
  50. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +1 -1
  51. package/dist-electron/services/local-ai-core/src/agents/shared/launch-utils.js +22 -0
  52. package/dist-electron/services/local-ai-core/src/channel/lark/inbound.js +55 -1
  53. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +132 -348
  54. package/dist-electron/services/local-ai-core/src/channel/shared/base-channel-gateway.js +303 -0
  55. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +131 -382
  56. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +2 -1
  57. package/dist-electron/services/local-ai-core/src/runtime/channel-service.js +150 -0
  58. package/dist-electron/services/local-ai-core/src/runtime/env-utils.js +12 -0
  59. package/dist-electron/services/local-ai-core/src/runtime/external-service.js +234 -0
  60. package/dist-electron/services/local-ai-core/src/runtime/handlers/automation-handler.js +34 -0
  61. package/dist-electron/services/local-ai-core/src/runtime/handlers/capabilities-handler.js +12 -0
  62. package/dist-electron/services/local-ai-core/src/runtime/handlers/channel-handler.js +65 -0
  63. package/dist-electron/services/local-ai-core/src/runtime/handlers/external-handler.js +17 -0
  64. package/dist-electron/services/local-ai-core/src/runtime/handlers/knowledge-handler.js +65 -0
  65. package/dist-electron/services/local-ai-core/src/runtime/handlers/openai-handler.js +496 -0
  66. package/dist-electron/services/local-ai-core/src/runtime/handlers/provider-handler.js +24 -0
  67. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtime-handler.js +68 -0
  68. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtimes-handler.js +37 -0
  69. package/dist-electron/services/local-ai-core/src/runtime/handlers/scheduler-handler.js +34 -0
  70. package/dist-electron/services/local-ai-core/src/runtime/handlers/security-handler.js +49 -0
  71. package/dist-electron/services/local-ai-core/src/runtime/handlers/task-handler.js +27 -0
  72. package/dist-electron/services/local-ai-core/src/runtime/handlers/thread-handler.js +42 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/handlers/workspace-handler.js +29 -0
  74. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +40 -592
  75. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +96 -0
  76. package/dist-electron/services/local-ai-core/src/runtime/server.js +107 -1084
  77. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -1
  78. package/dist-electron/src/pages/Projects/project-provider-config.js +75 -0
  79. package/dist-electron/tests/contracts/architecture-docs.test.js +1 -0
  80. package/dist-electron/tests/electron/agent-runtime-detector.test.js +18 -0
  81. package/dist-electron/tests/electron/knowledge-skill-script.test.js +15 -5
  82. package/dist-electron/tests/electron/lac-cli.test.js +591 -688
  83. package/dist-electron/tests/electron/plugin-kernel.test.js +47 -31
  84. package/dist-electron/tests/electron/project-provider-config.test.js +71 -0
  85. package/dist-electron/tests/electron/workspace-task-store.test.js +19 -9
  86. package/dist-electron/tests/integration/lark-gateway.test.js +80 -0
  87. package/dist-electron/tests/integration/local-core-cors.test.js +22 -0
  88. package/package.json +1 -1
  89. package/dist/renderer/assets/Chat-wjBR3XEX.js +0 -2
  90. package/dist/renderer/assets/Config-BDCHs5A2.js +0 -21
  91. package/dist/renderer/assets/CronList-CEWQYTov.js +0 -1
  92. package/dist/renderer/assets/Dashboard-DrWftfk3.js +0 -26
  93. package/dist/renderer/assets/KnowledgeHome-hhDTZMDO.js +0 -11
  94. package/dist/renderer/assets/Logs-C9xPEhFT.js +0 -1
  95. package/dist/renderer/assets/MonitorList-CayKbfwJ.js +0 -1
  96. package/dist/renderer/assets/ProjectDetail-B1GyAr7a.js +0 -11
  97. package/dist/renderer/assets/ProjectList-BoipQypE.js +0 -1
  98. package/dist/renderer/assets/SessionChat-BAVy5221.js +0 -1
  99. package/dist/renderer/assets/SessionList-BZpuMvh0.js +0 -1
  100. package/dist/renderer/assets/ThreadChat-JX7m9biG.js +0 -23
  101. package/dist/renderer/assets/Workspace-CFW8otjU.js +0 -14
  102. package/dist/renderer/assets/index-RaTga2BC.css +0 -11
  103. package/dist/renderer/assets/plug-Mexu4gyV.js +0 -6
  104. package/dist/renderer/assets/projects-C6bbkC-O.js +0 -1
  105. package/dist/renderer/assets/save-CJ_QFPw0.js +0 -6
  106. package/dist/renderer/assets/user-BzE9BwsY.js +0 -11
@@ -8,15 +8,13 @@ exports.createWeixinAttachmentContentPart = createWeixinAttachmentContentPart;
8
8
  const node_crypto_1 = __importDefault(require("node:crypto"));
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
- const node_events_1 = require("node:events");
12
11
  const node_crypto_2 = require("node:crypto");
13
12
  const desktop_js_1 = require("../../../../../shared/desktop.js");
14
13
  const local_core_errors_js_1 = require("../../kernel/local-core-errors.js");
15
14
  const content_js_1 = require("../shared/content.js");
16
15
  const file_utils_js_1 = require("../shared/file-utils.js");
17
- const session_command_runtime_js_1 = require("../shared/session-command-runtime.js");
18
16
  const thread_routing_js_1 = require("../shared/thread-routing.js");
19
- const thread_slash_command_dispatcher_js_1 = require("../../thread/thread-slash-command-dispatcher.js");
17
+ const base_channel_gateway_js_1 = require("../shared/base-channel-gateway.js");
20
18
  const config_js_1 = require("./config.js");
21
19
  const transport_js_1 = require("./transport.js");
22
20
  // ==================== Constants ====================
@@ -147,169 +145,99 @@ function renderBridgeContentForWeixin(event) {
147
145
  return [name, status].filter(Boolean).join(' - ');
148
146
  }
149
147
  // ==================== Gateway Class ====================
150
- class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
151
- options;
152
- runtime = new Map();
153
- threadRouting = new Map();
154
- outboundEventChains = new Map();
155
- outboundTurns = new Map();
148
+ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGateway {
156
149
  processedInboundMessages = new Map();
157
- mutedThreadBridgeCounts = new Map();
158
150
  pollErrorLogWindows = new Map();
159
- sessionCommandRuntime;
160
151
  platform = 'weixin';
161
- routeType = 'channel.chat';
162
152
  constructor(options) {
163
- super();
164
- this.options = options;
165
- const slashCommands = new thread_slash_command_dispatcher_js_1.ThreadSlashCommandDispatcher({
166
- session: {
167
- listThreads: (workspaceId) => this.options.getWorkspaceRouter().listThreads(workspaceId),
168
- getThread: (threadId) => this.options.getWorkspaceRouter().getThread(threadId),
169
- createThread: (workspaceId, title) => this.options.getWorkspaceRouter().createThread(workspaceId, title),
170
- renameThread: (threadId, title) => this.options.getWorkspaceRouter().renameThread(threadId, title),
171
- deleteThread: (threadId) => this.options.getWorkspaceRouter().deleteThread(threadId),
172
- },
173
- thread: {
174
- getThreadRow: (threadId) => this.options.store.getThreadRow(threadId),
175
- updateThreadAgentMode: (threadId, mode) => this.options.store.updateThreadAgentMode(threadId, mode),
176
- updateThreadAgentType: (threadId, agentType) => this.options.store.updateThreadAgentType(threadId, agentType),
177
- getLatestRunForThread: (threadId) => this.options.store.getLatestRunForThread(threadId),
178
- createAuditEvent: (input) => this.options.store.createAuditEvent(input),
179
- getAgentTypes: () => this.options.getWorkspaceRouter().getAgentTypes(),
180
- setThreadMode: (threadId, mode) => this.options.getWorkspaceRouter().setThreadMode(threadId, mode),
181
- closeThreadSession: (threadId) => this.options.getWorkspaceRouter().closeThreadSession(threadId),
182
- interruptRun: (runId) => this.options.getWorkspaceRouter().interruptRun(runId),
183
- log: this.options.log,
184
- },
185
- });
186
- this.sessionCommandRuntime = new session_command_runtime_js_1.ChannelSessionCommandRuntime({
187
- dispatcher: slashCommands,
188
- store: this.options.store,
189
- getThreadSessionKey: (threadId) => this.options.getWorkspaceRouter().getThreadSessionKey(threadId),
190
- setThreadRoute: (sessionKey, route) => {
191
- this.threadRouting.set(sessionKey, route);
192
- },
193
- createRoute: (input, threadId) => ({
194
- workspaceId: input.workspaceId,
195
- instanceId: input.instanceId,
196
- platformKey: input.platformKey,
197
- platformUserId: input.platformUserId,
198
- chatId: input.chatId,
199
- threadId,
200
- }),
201
- sendResult: (input, result) => this.sendSessionCommandResult(input, result),
202
- });
203
- }
204
- // ==================== Lifecycle ====================
205
- async refreshBindings() {
206
- const config = await this.options.readConfig();
207
- const bindings = this.collectBindings(config);
208
- const nextKeys = new Set(bindings.map((b) => (0, config_js_1.runtimeKey)(b.workspaceId, b.instanceId)));
209
- for (const key of [...this.runtime.keys()]) {
210
- if (!nextKeys.has(key)) {
211
- await this.stopWorkspaceKey(key);
212
- }
213
- }
214
- for (const binding of bindings) {
215
- const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
216
- const current = this.runtime.get(key);
217
- if (!binding.enabled) {
218
- if (current) {
219
- await this.stopWorkspaceKey(key);
220
- }
221
- else {
222
- this.runtime.set(key, {
223
- workspaceId: binding.workspaceId,
224
- instanceId: binding.instanceId,
225
- displayName: binding.displayName,
226
- platformKey: binding.platformKey,
227
- enabled: false,
228
- status: 'disabled',
229
- connected: false,
230
- accountId: binding.accountId,
231
- });
232
- }
233
- continue;
234
- }
235
- if (current?.status === 'running' && current.accountId === binding.accountId) {
236
- continue;
237
- }
238
- await this.startWorkspace(binding);
239
- }
240
- this.notifyRuntimeStateChanged();
241
- }
242
- async testConnection(workspaceId, instanceId) {
243
- const binding = await this.getBinding(workspaceId, instanceId);
244
- try {
245
- const bufPath = (0, config_js_1.getWeixinBufPath)(binding);
246
- node_fs_1.default.accessSync(bufPath);
247
- return { success: true, platform: 'weixin', workspaceId, instanceId: binding.instanceId, appId: binding.accountId };
248
- }
249
- catch {
250
- return { success: false, platform: 'weixin', workspaceId, instanceId: binding.instanceId, error: 'No sync buf found for account. Ensure the plugin has been connected at least once.' };
251
- }
153
+ super(options);
252
154
  }
253
- async enable(workspaceId, instanceId) {
254
- const binding = await this.getBinding(workspaceId, instanceId);
255
- await this.startWorkspace(binding);
256
- return this.getStatus(workspaceId, binding.instanceId);
155
+ // ==================== Abstract implementations ====================
156
+ makeThreadRoute(input, threadId) {
157
+ return {
158
+ workspaceId: input.workspaceId,
159
+ instanceId: input.instanceId,
160
+ platformKey: input.platformKey,
161
+ platformUserId: input.platformUserId,
162
+ chatId: input.chatId,
163
+ threadId,
164
+ };
257
165
  }
258
- async disable(workspaceId, instanceId) {
259
- await this.stopWorkspace(workspaceId, instanceId);
260
- return this.getStatus(workspaceId, instanceId);
166
+ collectBindings(config) {
167
+ return (0, config_js_1.collectWeixinWorkspaceBindings)(config);
261
168
  }
262
- getStatus(workspaceId, instanceId) {
169
+ buildStatusObject(state, resolved) {
263
170
  this.options.store.expirePendingPairings();
264
- const resolved = this.resolveRuntimeState(workspaceId, instanceId);
265
- const binding = resolved.state;
266
- const platformKey = binding?.platformKey || (0, config_js_1.channelPlatformKey)('weixin', resolved.instanceId);
267
- const pairings = this.options.store.listPendingPairings(workspaceId)
171
+ const platformKey = state.platformKey || (0, config_js_1.channelPlatformKey)('weixin', resolved.instanceId);
172
+ const pairings = this.options.store.listPendingPairings(state.workspaceId)
268
173
  .filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
269
- const users = this.options.store.listAuthorizedUsers(workspaceId, platformKey);
174
+ const users = this.options.store.listAuthorizedUsers(state.workspaceId, platformKey);
270
175
  return {
271
- workspaceId,
176
+ workspaceId: state.workspaceId,
272
177
  platform: 'weixin',
273
178
  instanceId: resolved.instanceId,
274
- displayName: binding?.displayName,
275
- enabled: Boolean(binding?.enabled),
276
- connected: Boolean(binding?.connected),
277
- status: binding?.status || 'disabled',
278
- appId: binding?.accountId || '',
279
- lastError: binding?.lastError,
280
- lastErrorInfo: binding?.lastErrorInfo,
281
- lastErrorAt: binding?.lastErrorAt,
282
- consecutiveFailures: binding?.consecutiveFailures,
283
- nextRetryAt: binding?.nextRetryAt,
284
- connectedAt: binding?.connectedAt,
179
+ displayName: state.displayName,
180
+ enabled: Boolean(state.enabled),
181
+ connected: Boolean(state.connected),
182
+ status: state.status || 'disabled',
183
+ appId: state.accountId || '',
184
+ lastError: state.lastError,
185
+ lastErrorInfo: state.lastErrorInfo,
186
+ lastErrorAt: state.lastErrorAt,
187
+ consecutiveFailures: state.consecutiveFailures,
188
+ nextRetryAt: state.nextRetryAt,
189
+ connectedAt: state.connectedAt,
285
190
  pendingPairings: pairings.length,
286
191
  authorizedUsers: users.length,
287
192
  };
288
193
  }
289
- listStatuses() {
290
- return [...this.runtime.values()]
291
- .sort((a, b) => `${a.workspaceId}:${a.instanceId}`.localeCompare(`${b.workspaceId}:${b.instanceId}`))
292
- .map((state) => this.getStatus(state.workspaceId, state.instanceId));
293
- }
294
- listPendingPairings(workspaceId) {
295
- this.options.store.expirePendingPairings();
296
- return this.options.store
297
- .listPairingRequests(workspaceId)
298
- .filter((item) => item.platform === 'weixin' || item.platform.startsWith('weixin:'))
299
- .filter((item) => item.status === 'pending' && item.expiresAt >= new Date().toISOString());
194
+ createDisabledState(binding) {
195
+ return {
196
+ workspaceId: binding.workspaceId,
197
+ instanceId: binding.instanceId,
198
+ displayName: binding.displayName,
199
+ platformKey: binding.platformKey,
200
+ enabled: false,
201
+ status: 'disabled',
202
+ connected: false,
203
+ accountId: binding.accountId,
204
+ };
300
205
  }
301
- listAuthorizedUsers(workspaceId) {
302
- return this.options.store.listAuthorizedUsers(workspaceId)
303
- .filter((item) => item.platform === 'weixin' || item.platform.startsWith('weixin:'));
206
+ async stopWorkspaceTransport(state) {
207
+ try {
208
+ state.abortController?.abort();
209
+ }
210
+ catch { }
304
211
  }
305
- async start() {
306
- await this.refreshBindings();
212
+ resetStateToStopped(state, _key) {
213
+ Object.assign(state, {
214
+ enabled: false,
215
+ status: 'stopped',
216
+ connected: false,
217
+ });
307
218
  }
308
- async stop() {
309
- this.close();
219
+ async sendSessionCommandResult(input, result) {
220
+ const state = this.resolveRuntimeState(input.workspaceId, input.instanceId).state;
221
+ if (!state?.connected)
222
+ return;
223
+ try {
224
+ await this.sendTextMessage(state, input.chatId, result.displayText);
225
+ }
226
+ catch (error) {
227
+ this.options.log?.(`localcore-weixin session command result failed: ${error instanceof Error ? error.message : String(error)}`);
228
+ }
310
229
  }
311
- close() {
312
- return Promise.all([...this.runtime.keys()].map((id) => this.stopWorkspaceKey(id))).then(() => undefined);
230
+ // ==================== Lifecycle (platform-specific) ====================
231
+ async testConnection(workspaceId, instanceId) {
232
+ const binding = await this.getBinding(workspaceId, instanceId);
233
+ try {
234
+ const bufPath = (0, config_js_1.getWeixinBufPath)(binding);
235
+ node_fs_1.default.accessSync(bufPath);
236
+ return { success: true, platform: 'weixin', workspaceId, instanceId: binding.instanceId, appId: binding.accountId };
237
+ }
238
+ catch {
239
+ return { success: false, platform: 'weixin', workspaceId, instanceId: binding.instanceId, error: 'No sync buf found for account. Ensure the plugin has been connected at least once.' };
240
+ }
313
241
  }
314
242
  // ==================== QR Code Login ====================
315
243
  async getQrCode(workspaceId, instanceId) {
@@ -354,62 +282,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
354
282
  };
355
283
  }
356
284
  // ==================== Pairing ====================
357
- approvePairing(code) {
358
- this.options.store.expirePendingPairings();
359
- const pairing = this.options.store.getPairingRequest(code);
360
- if (!pairing)
361
- throw new Error(`Pairing code not found: ${code}`);
362
- if (pairing.platform !== 'weixin' && !pairing.platform.startsWith('weixin:'))
363
- throw new Error(`Pairing code ${code} is not a WeChat pairing`);
364
- if (pairing.status !== 'pending')
365
- throw new Error(`Pairing code ${code} is already ${pairing.status}`);
366
- if (pairing.expires_at < new Date().toISOString()) {
367
- this.options.store.updatePairingStatus(code, 'expired');
368
- throw new Error(`Pairing code ${code} has expired`);
369
- }
370
- const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id, pairing.platform);
371
- const userId = existing?.id || `wx-user-${(0, node_crypto_2.randomUUID)()}`;
372
- const authorizedAt = new Date().toISOString();
373
- this.options.store.createAuthorizedUser({
374
- id: userId,
375
- workspace_id: pairing.workspace_id,
376
- platform: pairing.platform,
377
- platform_user_id: pairing.platform_user_id,
378
- chat_id: pairing.chat_id,
379
- display_name: pairing.display_name,
380
- thread_id: existing?.thread_id || null,
381
- authorized_at: authorizedAt,
382
- });
383
- this.options.store.updatePairingStatus(code, 'approved');
384
- this.notifyRuntimeStateChanged();
385
- const user = this.options.store.listAuthorizedUsers(pairing.workspace_id, pairing.platform).find((e) => e.id === userId);
386
- if (!user)
387
- throw new Error('Authorized user lookup failed after approval');
388
- return user;
389
- }
390
- rejectPairing(code) {
391
- const pairing = this.options.store.getPairingRequest(code);
392
- if (!pairing)
393
- throw new Error(`Pairing code not found: ${code}`);
394
- if (pairing.platform !== 'weixin' && !pairing.platform.startsWith('weixin:'))
395
- throw new Error(`Pairing code ${code} is not a WeChat pairing`);
396
- this.options.store.updatePairingStatus(code, 'rejected');
397
- this.notifyRuntimeStateChanged();
398
- return { rejected: true };
399
- }
400
285
  // ==================== Bridge Event Handling ====================
401
- muteThreadBridge(threadId) {
402
- const current = this.mutedThreadBridgeCounts.get(threadId) || 0;
403
- this.mutedThreadBridgeCounts.set(threadId, current + 1);
404
- }
405
- unmuteThreadBridge(threadId) {
406
- const current = this.mutedThreadBridgeCounts.get(threadId) || 0;
407
- if (current <= 1) {
408
- this.mutedThreadBridgeCounts.delete(threadId);
409
- return;
410
- }
411
- this.mutedThreadBridgeCounts.set(threadId, current - 1);
412
- }
413
286
  async onBridgeEvent(event) {
414
287
  if (!event.sessionKey) {
415
288
  this.options.log?.(`localcore-weixin bridge event ignored without sessionKey: ${event.type}`);
@@ -497,25 +370,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
497
370
  this.outboundEventChains.set(sessionKey, current);
498
371
  await current;
499
372
  }
500
- async sendScheduledCard(workspaceId, chatId, text) {
501
- return this.sendScheduledMessage(workspaceId, { type: 'channel.chat', channelId: chatId }, text);
502
- }
503
- async sendScheduledMessage(workspaceId, route, text) {
504
- const state = this.resolveRuntimeState(workspaceId, route.instanceId).state;
505
- if (!state?.connected) {
506
- this.options.log?.(`localcore-weixin scheduled message skipped: workspace not connected: ${workspaceId}`);
507
- return '';
508
- }
509
- try {
510
- const stripped = stripHtml(text);
511
- await this.sendTextMessage(state, route.channelId, stripped);
512
- return `wx_sched_${(0, node_crypto_2.randomUUID)()}`;
513
- }
514
- catch (error) {
515
- this.options.log?.(`localcore-weixin scheduled message failed for ${workspaceId}: ${formatError(error)}`);
516
- return '';
517
- }
518
- }
519
373
  registerScheduledThreadBridge(input) {
520
374
  const instanceId = input.route.instanceId || getWeixinInstanceId(input.platform) || 'default';
521
375
  const platformKey = (0, config_js_1.channelPlatformKey)('weixin', instanceId);
@@ -614,136 +468,126 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
614
468
  }
615
469
  // ==================== Inbound Message Handling ====================
616
470
  async handleInboundMessage(input) {
617
- const instanceId = input.instanceId || 'default';
618
- const platformKey = input.platformKey || (0, config_js_1.channelPlatformKey)('weixin', instanceId);
619
- if (this.isDuplicateInboundMessage(input)) {
620
- this.options.log?.(`localcore-weixin skipped duplicate inbound message workspace=${input.workspaceId} chat=${input.chatId} id=${input.contextToken || input.messageId}`);
621
- return { paired: true, duplicate: true };
471
+ const msg = input;
472
+ const instanceId = msg.instanceId || 'default';
473
+ const platformKey = msg.platformKey || (0, config_js_1.channelPlatformKey)('weixin', instanceId);
474
+ if (this.isDuplicateInboundMessage(msg)) {
475
+ this.options.log?.(`localcore-weixin skipped duplicate inbound message workspace=${msg.workspaceId} chat=${msg.chatId} id=${msg.contextToken || msg.messageId}`);
476
+ return;
622
477
  }
623
478
  this.options.eventBus.emit({
624
479
  type: 'platform.message.received',
625
480
  payload: {
626
481
  platform: this.platform,
627
- workspaceId: input.workspaceId,
628
- participantId: input.platformUserId,
629
- channelId: input.chatId,
630
- displayName: input.displayName,
631
- text: input.text,
632
- messageId: input.messageId,
482
+ workspaceId: msg.workspaceId,
483
+ participantId: msg.platformUserId,
484
+ channelId: msg.chatId,
485
+ displayName: msg.displayName,
486
+ text: msg.text,
487
+ messageId: msg.messageId,
633
488
  },
634
489
  });
635
490
  this.options.store.expirePendingPairings();
636
- const binding = await this.getBinding(input.workspaceId, instanceId);
637
- let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
491
+ const binding = await this.getBinding(msg.workspaceId, instanceId);
492
+ let authorized = this.options.store.getAuthorizedUser(msg.workspaceId, msg.platformUserId, platformKey);
638
493
  if (!authorized) {
639
494
  if (binding.allowFrom === '*') {
640
495
  const authorizedAt = new Date().toISOString();
641
496
  this.options.store.createAuthorizedUser({
642
497
  id: `wx-user-${(0, node_crypto_2.randomUUID)()}`,
643
- workspace_id: input.workspaceId,
498
+ workspace_id: msg.workspaceId,
644
499
  platform: platformKey,
645
- platform_user_id: input.platformUserId,
646
- chat_id: input.chatId,
647
- display_name: input.displayName,
500
+ platform_user_id: msg.platformUserId,
501
+ chat_id: msg.chatId,
502
+ display_name: msg.displayName,
648
503
  thread_id: null,
649
504
  authorized_at: authorizedAt,
650
505
  });
651
- authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
652
- this.options.log?.(`localcore-weixin auto-approved user for ${input.workspaceId}: ${input.platformUserId}`);
506
+ authorized = this.options.store.getAuthorizedUser(msg.workspaceId, msg.platformUserId, platformKey);
507
+ this.options.log?.(`localcore-weixin auto-approved user for ${msg.workspaceId}: ${msg.platformUserId}`);
653
508
  this.notifyRuntimeStateChanged();
654
509
  }
655
510
  }
656
511
  if (!authorized) {
657
- const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform === platformKey && item.platform_user_id === input.platformUserId && item.chat_id === input.chatId && item.status === 'pending');
512
+ const existingPending = this.options.store.listPendingPairings(msg.workspaceId).find((item) => item.platform === platformKey && item.platform_user_id === msg.platformUserId && item.chat_id === msg.chatId && item.status === 'pending');
658
513
  let pairingCode = existingPending?.code || '';
659
514
  if (!existingPending) {
660
515
  const now = new Date();
661
516
  pairingCode = String((0, node_crypto_2.randomInt)(100000, 1000000));
662
517
  this.options.store.createPairingRequest({
663
518
  code: pairingCode,
664
- workspace_id: input.workspaceId,
519
+ workspace_id: msg.workspaceId,
665
520
  platform: platformKey,
666
- platform_user_id: input.platformUserId,
667
- chat_id: input.chatId,
668
- display_name: input.displayName,
521
+ platform_user_id: msg.platformUserId,
522
+ chat_id: msg.chatId,
523
+ display_name: msg.displayName,
669
524
  requested_at: now.toISOString(),
670
525
  expires_at: new Date(now.getTime() + PAIRING_EXPIRY_MS).toISOString(),
671
526
  status: 'pending',
672
527
  });
673
528
  this.notifyRuntimeStateChanged();
674
529
  }
675
- const state = this.runtime.get((0, config_js_1.runtimeKey)(input.workspaceId, instanceId));
530
+ const state = this.runtime.get((0, config_js_1.runtimeKey)(msg.workspaceId, instanceId));
676
531
  if (state?.connected) {
677
- await this.sendTextMessage(state, input.chatId, `**已收到消息**\n\n当前账号还未授权接入这个工作区。\n请在桌面端完成审批后再次发送消息。\n\n配对码:\`${pairingCode}\``, input.contextToken);
532
+ await this.sendTextMessage(state, msg.chatId, `**已收到消息**\n\n当前账号还未授权接入这个工作区。\n请在桌面端完成审批后再次发送消息。\n\n配对码:\`${pairingCode}\``, msg.contextToken);
678
533
  }
679
- return { paired: false };
534
+ return;
680
535
  }
681
536
  const router = this.options.getWorkspaceRouter();
682
537
  let { threadId } = await (0, thread_routing_js_1.resolveChannelThreadRoute)({
683
538
  store: this.options.store,
684
539
  router,
685
- workspaceId: input.workspaceId,
540
+ workspaceId: msg.workspaceId,
686
541
  platformKey,
687
- chatId: input.chatId,
688
- platformUserId: input.platformUserId,
689
- displayName: input.displayName,
542
+ chatId: msg.chatId,
543
+ platformUserId: msg.platformUserId,
544
+ displayName: msg.displayName,
690
545
  fallbackTitlePrefix: 'WeChat',
691
546
  authorized,
692
547
  });
693
- const normalizedText = String(input.text || '').trim().toLowerCase();
694
- const permissionThreadId = (normalizedText === 'allow' || normalizedText === 'allow all' || normalizedText === 'deny') ? this.findAwaitingPermissionThreadId(input.workspaceId, input.chatId, input.platformUserId, input.platformKey) : '';
548
+ const normalizedText = String(msg.text || '').trim().toLowerCase();
549
+ const permissionThreadId = (normalizedText === 'allow' || normalizedText === 'allow all' || normalizedText === 'deny') ? this.findAwaitingPermissionThreadId(msg.workspaceId, msg.chatId, msg.platformUserId, msg.platformKey) : '';
695
550
  if (permissionThreadId && permissionThreadId !== threadId) {
696
551
  threadId = permissionThreadId;
697
552
  }
698
553
  const effectiveSessionKey = router.getThreadSessionKey(threadId);
699
554
  this.threadRouting.set(effectiveSessionKey, {
700
- workspaceId: input.workspaceId,
555
+ workspaceId: msg.workspaceId,
701
556
  instanceId,
702
557
  platformKey,
703
- platformUserId: input.platformUserId,
704
- chatId: input.chatId,
558
+ platformUserId: msg.platformUserId,
559
+ chatId: msg.chatId,
705
560
  threadId,
706
561
  });
707
562
  // Handle slash commands
708
- const slashCommand = this.parseSlashCommand(input.text);
563
+ const slashCommand = this.parseSlashCommand(msg.text);
709
564
  const sessionCommand = await this.executeSessionCommand({
710
- workspaceId: input.workspaceId,
565
+ workspaceId: msg.workspaceId,
711
566
  currentThreadId: threadId,
712
- text: input.text,
713
- defaultTitle: `${input.displayName || 'WeChat'} ${new Date().toLocaleTimeString()}`,
714
- defaultAgentType: slashCommand ? await this.resolveDefaultAgentType(input.workspaceId, threadId) : '',
715
- chatId: input.chatId,
716
- platformUserId: input.platformUserId,
567
+ text: msg.text,
568
+ defaultTitle: `${msg.displayName || 'WeChat'} ${new Date().toLocaleTimeString()}`,
569
+ defaultAgentType: slashCommand ? await this.resolveDefaultAgentType(msg.workspaceId, threadId) : '',
570
+ chatId: msg.chatId,
571
+ platformUserId: msg.platformUserId,
717
572
  platformKey,
718
573
  instanceId,
719
- contextToken: input.contextToken,
574
+ contextToken: msg.contextToken,
720
575
  });
721
576
  if (sessionCommand.handled) {
722
- return { paired: true, threadId: sessionCommand.threadId || threadId };
577
+ return;
723
578
  }
724
579
  const latestRun = this.options.store.getLatestRunForThread(threadId);
725
580
  if ((normalizedText === 'allow' || normalizedText === 'allow all' || normalizedText === 'deny')
726
581
  && latestRun?.status === 'awaiting_input') {
727
- await router.sendThreadAction(threadId, input.text);
728
- return { paired: true, threadId };
729
- }
730
- this.options.store.updatePlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId, input.contextToken || input.messageId, platformKey);
731
- const wrappedText = slashCommand
732
- ? input.text
733
- : (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
734
- await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
735
- return { paired: true, threadId };
736
- }
737
- async executeSessionCommand(input) {
738
- return this.sessionCommandRuntime.execute(input);
739
- }
740
- async sendSessionCommandResult(input, result) {
741
- const state = this.resolveRuntimeState(input.workspaceId, input.instanceId).state;
742
- if (!state?.connected) {
743
- this.options.log?.(`localcore-weixin session command response skipped: workspace not connected: ${input.workspaceId}`);
582
+ await router.sendThreadAction(threadId, msg.text);
744
583
  return;
745
584
  }
746
- await this.sendTextMessage(state, input.chatId, result.displayText, input.contextToken);
585
+ this.options.store.updatePlatformThreadMessageId(msg.workspaceId, msg.chatId, msg.platformUserId, msg.contextToken || msg.messageId, platformKey);
586
+ const wrappedText = slashCommand
587
+ ? msg.text
588
+ : (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(msg.text);
589
+ await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, msg.contentParts));
590
+ return;
747
591
  }
748
592
  async resolveDefaultAgentType(workspaceId, threadId) {
749
593
  const router = this.options.getWorkspaceRouter();
@@ -753,28 +597,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
753
597
  return this.options.store.getThreadRow(threadId)?.agent_type || 'codex';
754
598
  }
755
599
  // ==================== Private: Bindings ====================
756
- resolveRuntimeState(workspaceId, instanceId) {
757
- if (instanceId) {
758
- const state = this.runtime.get((0, config_js_1.runtimeKey)(workspaceId, instanceId)) || (instanceId === 'default' ? this.runtime.get(workspaceId) : undefined);
759
- return { instanceId, state };
760
- }
761
- const states = [...this.runtime.values()].filter((entry) => entry.workspaceId === workspaceId);
762
- const state = states.find((entry) => entry.instanceId === 'default') || states[0];
763
- return { instanceId: state?.instanceId || 'default', state: state || this.runtime.get(workspaceId) };
764
- }
765
- async getBinding(workspaceId, instanceId) {
766
- const config = await this.options.readConfig();
767
- const bindings = this.collectBindings(config).filter((e) => e.workspaceId === workspaceId);
768
- const binding = instanceId
769
- ? bindings.find((entry) => entry.instanceId === instanceId)
770
- : bindings.find((entry) => entry.instanceId === 'default') || bindings[0];
771
- if (!binding)
772
- throw new Error(`No WeChat binding configured for workspace "${workspaceId}"${instanceId ? ` instance "${instanceId}"` : ''}`);
773
- return binding;
774
- }
775
- collectBindings(config) {
776
- return (0, config_js_1.collectWeixinWorkspaceBindings)(config);
777
- }
778
600
  // ==================== Private: Workspace Lifecycle ====================
779
601
  async startWorkspace(binding) {
780
602
  const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
@@ -815,11 +637,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
815
637
  if (!abortController.signal.aborted) {
816
638
  status.status = 'error';
817
639
  status.connected = false;
818
- this.setRuntimeError(status, err, 'internal_error', {
819
- workspaceId: binding.workspaceId,
820
- instanceId: binding.instanceId,
821
- runtimeId: 'weixin',
822
- });
640
+ this.setRuntimeError(status, (0, local_core_errors_js_1.toLocalCoreErrorInfo)(err));
823
641
  this.notifyRuntimeStateChanged();
824
642
  }
825
643
  });
@@ -827,38 +645,10 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
827
645
  catch (error) {
828
646
  status.status = 'error';
829
647
  status.connected = false;
830
- this.setRuntimeError(status, error, 'internal_error', {
831
- workspaceId: binding.workspaceId,
832
- instanceId: binding.instanceId,
833
- runtimeId: 'weixin',
834
- });
648
+ this.setRuntimeError(status, (0, local_core_errors_js_1.toLocalCoreErrorInfo)(error));
835
649
  }
836
650
  this.notifyRuntimeStateChanged();
837
651
  }
838
- async stopWorkspace(workspaceId, instanceId) {
839
- const resolved = this.resolveRuntimeState(workspaceId, instanceId);
840
- await this.stopWorkspaceKey((0, config_js_1.runtimeKey)(workspaceId, resolved.instanceId));
841
- }
842
- async stopWorkspaceKey(key) {
843
- const state = this.runtime.get(key);
844
- if (!state)
845
- return;
846
- try {
847
- state.abortController?.abort();
848
- }
849
- catch { }
850
- this.runtime.set(key, {
851
- workspaceId: state.workspaceId,
852
- instanceId: state.instanceId,
853
- displayName: state.displayName,
854
- platformKey: state.platformKey,
855
- enabled: false,
856
- status: 'stopped',
857
- connected: false,
858
- accountId: state.accountId,
859
- });
860
- this.notifyRuntimeStateChanged();
861
- }
862
652
  // ==================== Private: Long-poll Monitor ====================
863
653
  normalizeQrStatus(status) {
864
654
  const normalized = String(status || 'wait').trim().toLowerCase();
@@ -887,17 +677,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
887
677
  state.connected = false;
888
678
  state.consecutiveFailures = consecutiveFailures;
889
679
  state.nextRetryAt = new Date(Date.now() + retryDelayMs).toISOString();
890
- this.setRuntimeError(state, new local_core_errors_js_1.LocalCoreError('channel_session_expired', 'WeChat login expired. Generate and scan a new QR code.', {
891
- suggestedAction: 'Generate and scan a new WeChat QR code.',
892
- details: {
893
- errcode: resp.errcode ?? resp.ret ?? -14,
894
- },
895
- }), 'channel_session_expired', {
896
- workspaceId: binding.workspaceId,
897
- instanceId: binding.instanceId,
898
- runtimeId: 'weixin',
899
- errcode: resp.errcode ?? resp.ret ?? -14,
900
- });
680
+ this.setRuntimeError(state, (0, local_core_errors_js_1.toLocalCoreErrorInfo)(new local_core_errors_js_1.LocalCoreError('channel_session_expired', 'WeChat login expired.')));
901
681
  this.notifyRuntimeStateChanged();
902
682
  }
903
683
  }
@@ -986,11 +766,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
986
766
  if (state) {
987
767
  state.consecutiveFailures = consecutiveFailures;
988
768
  state.nextRetryAt = new Date(Date.now() + retryDelayMs).toISOString();
989
- this.setRuntimeError(state, err, 'internal_error', {
990
- workspaceId: binding.workspaceId,
991
- instanceId: binding.instanceId,
992
- runtimeId: 'weixin',
993
- });
769
+ this.setRuntimeError(state, (0, local_core_errors_js_1.toLocalCoreErrorInfo)(err));
994
770
  this.notifyRuntimeStateChanged();
995
771
  }
996
772
  this.logPollError(binding, formatError(err), `localcore-weixin getUpdates error for ${binding.workspaceId} (${consecutiveFailures}): ${formatError(err)}`);
@@ -1327,33 +1103,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1327
1103
  return null;
1328
1104
  return { name: name.trim().toLowerCase(), args };
1329
1105
  }
1330
- notifyRuntimeStateChanged() {
1331
- this.options.eventBus.emit({
1332
- type: 'runtime.state.changed',
1333
- payload: { reason: 'channel-bindings' },
1334
- });
1335
- }
1336
- clearRuntimeError(state) {
1337
- state.lastError = undefined;
1338
- state.lastErrorInfo = undefined;
1339
- state.lastErrorAt = undefined;
1340
- state.consecutiveFailures = 0;
1341
- state.nextRetryAt = undefined;
1342
- }
1343
- setRuntimeError(state, error, fallbackCode, context) {
1344
- const errorInfo = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(error, fallbackCode, context);
1345
- state.lastError = errorInfo.message;
1346
- state.lastErrorInfo = errorInfo;
1347
- state.lastErrorAt = new Date().toISOString();
1348
- this.options.eventBus.emit({
1349
- type: 'localcore.error',
1350
- payload: {
1351
- scope: 'channel.weixin',
1352
- errorInfo,
1353
- context,
1354
- },
1355
- });
1356
- }
1357
1106
  computeRetryDelay(failures) {
1358
1107
  if (failures <= 1)
1359
1108
  return RETRY_DELAY_MS;