@kafca/agentdock 0.1.54 → 0.1.56

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 (69) hide show
  1. package/README.md +12 -0
  2. package/dist/renderer/assets/{Badge-Rs12sArE.js → Badge-DjcoXx2J.js} +1 -1
  3. package/dist/renderer/assets/{Chat-C9KfsjuN.js → Chat-C5Bkm9-M.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-C3m7Mxpl.js → ChatMarkdown-uc36EzyD.js} +1 -1
  5. package/dist/renderer/assets/{Config-BhIq2lQa.js → Config-DHtQuuLy.js} +1 -1
  6. package/dist/renderer/assets/{CronList-CenDzorW.js → CronList-BYUnhzSC.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-BmeP5ocP.js → Dashboard-BPnGMDg3.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BGONB4VS.js → EmptyState-DIZLiccW.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Ba3PuI7k.js → KnowledgeDetail-CJ9IYOsa.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-DkKXM7Bd.js → KnowledgeHome-BLGPwnBO.js} +1 -1
  11. package/dist/renderer/assets/{Logs-DTITWY-y.js → Logs-Dyd2Uwlt.js} +1 -1
  12. package/dist/renderer/assets/{Modal-BvFew60s.js → Modal-CQiUpfl4.js} +1 -1
  13. package/dist/renderer/assets/{MonitorList-DruqInIG.js → MonitorList-Byg3E3Gt.js} +1 -1
  14. package/dist/renderer/assets/{Page-CLjmm8c6.js → Page-7DxXvTMm.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-BCFaJig0.js → ProjectDetail-Dfr5s0Ja.js} +1 -1
  16. package/dist/renderer/assets/{ProjectList-Btg-mgwA.js → ProjectList-CDDQjAQ_.js} +1 -1
  17. package/dist/renderer/assets/{Select-CAwaG7ol.js → Select-D6z65IT3.js} +1 -1
  18. package/dist/renderer/assets/{SessionChat-CHPrm0Zs.js → SessionChat-s7CAaCPL.js} +1 -1
  19. package/dist/renderer/assets/{SessionList-0aGulton.js → SessionList-D-OycKKS.js} +1 -1
  20. package/dist/renderer/assets/{ThreadChat-BkSc0hKN.js → ThreadChat-CLdFynB5.js} +1 -1
  21. package/dist/renderer/assets/Workspace-xfT5z4j_.js +19 -0
  22. package/dist/renderer/assets/{arrow-left-Co19QKUc.js → arrow-left-CV5cL3f4.js} +1 -1
  23. package/dist/renderer/assets/{book-open-CtlUo2yw.js → book-open-DThvB7pj.js} +1 -1
  24. package/dist/renderer/assets/{bot-D-3jCeDr.js → bot-CWQ5-ovl.js} +1 -1
  25. package/dist/renderer/assets/{check-Cf5Y5H-1.js → check-DKM7zfWG.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-D1tfS-tx.js → chevron-down-Dfw2KU87.js} +1 -1
  27. package/dist/renderer/assets/{circle-DJOCyk2U.js → circle-CoP4RA4s.js} +1 -1
  28. package/dist/renderer/assets/{desktop-CmZMgSXn.js → desktop-CsDYrOOU.js} +1 -1
  29. package/dist/renderer/assets/{funnel-ZRCc1DCy.js → funnel-DpVLlG3g.js} +1 -1
  30. package/dist/renderer/assets/{heart-BuJEvv8x.js → heart-BMD36ejP.js} +1 -1
  31. package/dist/renderer/assets/{index-CDWUe2Sw.js → index-COxSYBjE.js} +2 -2
  32. package/dist/renderer/assets/{index-0gZsyozU.js → index-CTGTI-cm.js} +1 -1
  33. package/dist/renderer/assets/{pencil-sISh8tMs.js → pencil-Dv5WxhdC.js} +1 -1
  34. package/dist/renderer/assets/{play-CccQpZit.js → play-DnCnft83.js} +1 -1
  35. package/dist/renderer/assets/{plus-hxAkcfQ0.js → plus-DsxIagzS.js} +1 -1
  36. package/dist/renderer/assets/projects-DgjHcNgt.js +1 -0
  37. package/dist/renderer/assets/{save-By5b-_0Q.js → save-CST01lIc.js} +1 -1
  38. package/dist/renderer/assets/{search-CLzr87Rw.js → search-B_bqy6Wd.js} +1 -1
  39. package/dist/renderer/assets/{server-CubB1eW5.js → server-ecH4GNwa.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Cz45af_C.js → session-utils-Dd_WKMYW.js} +1 -1
  41. package/dist/renderer/assets/{sessions-C8np5fRp.js → sessions-BQ33Mv4k.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-CemAc1Ry.js → shield-check-D6lG5Aoc.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-DFKhii8D.js → trash-2-Cefngb6Q.js} +1 -1
  44. package/dist/renderer/assets/{user-CaR8Rru_.js → user-D_8RSH-T.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-C8miJu8i.js → wifi-off-DB9D-pOu.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +1 -1
  48. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +1 -1
  49. package/dist-electron/services/local-ai-core/src/acp/store/runtime-config-store.js +39 -28
  50. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +4 -16
  51. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +154 -65
  52. package/dist-electron/services/local-ai-core/src/channel/shared/channel-keys.js +16 -0
  53. package/dist-electron/services/local-ai-core/src/channel/shared/inbound-attachment-store.js +93 -0
  54. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +3 -16
  55. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +70 -35
  56. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +33 -5
  57. package/dist-electron/services/local-ai-core/src/runtime/channel-service.js +0 -40
  58. package/dist-electron/services/local-ai-core/src/runtime/external-service.js +1 -14
  59. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -14
  60. package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +16 -0
  61. package/dist-electron/services/local-ai-core/src/scheduler/channel-execution-policy.js +4 -6
  62. package/dist-electron/tests/electron/sandbox-config.test.js +5 -0
  63. package/dist-electron/tests/integration/channel-inbound-attachment-store.test.js +117 -0
  64. package/dist-electron/tests/integration/lark-gateway.test.js +81 -1
  65. package/dist-electron/tests/integration/local-core-errors.test.js +41 -0
  66. package/dist-electron/tests/integration/weixin-gateway.test.js +36 -0
  67. package/package.json +1 -1
  68. package/dist/renderer/assets/Workspace-DXJ3BGia.js +0 -19
  69. package/dist/renderer/assets/projects-CjdfXQ1l.js +0 -1
@@ -9,12 +9,15 @@ 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
11
  const node_crypto_2 = require("node:crypto");
12
+ const node_stream_1 = require("node:stream");
12
13
  const desktop_js_1 = require("../../../../../shared/desktop.js");
13
14
  const local_core_errors_js_1 = require("../../kernel/local-core-errors.js");
14
15
  const content_js_1 = require("../shared/content.js");
15
16
  const file_utils_js_1 = require("../shared/file-utils.js");
17
+ const inbound_attachment_store_js_1 = require("../shared/inbound-attachment-store.js");
16
18
  const thread_routing_js_1 = require("../shared/thread-routing.js");
17
19
  const base_channel_gateway_js_1 = require("../shared/base-channel-gateway.js");
20
+ const channel_keys_js_1 = require("../shared/channel-keys.js");
18
21
  const config_js_1 = require("./config.js");
19
22
  const transport_js_1 = require("./transport.js");
20
23
  // ==================== Constants ====================
@@ -109,6 +112,7 @@ function createWeixinAttachmentContentPart(att) {
109
112
  return {
110
113
  type: 'image',
111
114
  data: att.data,
115
+ ...(att.uri ? { uri: att.uri } : {}),
112
116
  mimeType: att.mimeType,
113
117
  fileName: att.name,
114
118
  };
@@ -148,6 +152,7 @@ function renderBridgeContentForWeixin(event) {
148
152
  class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGateway {
149
153
  processedInboundMessages = new Map();
150
154
  pollErrorLogWindows = new Map();
155
+ inboundAttachmentStore = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
151
156
  platform = 'weixin';
152
157
  constructor(options) {
153
158
  super(options);
@@ -168,7 +173,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
168
173
  }
169
174
  buildStatusObject(state, resolved) {
170
175
  this.options.store.expirePendingPairings();
171
- const platformKey = state.platformKey || (0, config_js_1.channelPlatformKey)('weixin', resolved.instanceId);
176
+ const platformKey = state.platformKey || (0, channel_keys_js_1.channelPlatformKey)('weixin', resolved.instanceId);
172
177
  const pairings = this.options.store.listPendingPairings(state.workspaceId)
173
178
  .filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
174
179
  const users = this.options.store.listAuthorizedUsers(state.workspaceId, platformKey);
@@ -294,8 +299,8 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
294
299
  return;
295
300
  }
296
301
  const routeInstanceId = route.instanceId || 'default';
297
- const routePlatformKey = route.platformKey || (0, config_js_1.channelPlatformKey)('weixin', routeInstanceId);
298
- const state = this.runtime.get((0, config_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
302
+ const routePlatformKey = route.platformKey || (0, channel_keys_js_1.channelPlatformKey)('weixin', routeInstanceId);
303
+ const state = this.runtime.get((0, channel_keys_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
299
304
  if (!state?.connected) {
300
305
  this.options.log?.(`localcore-weixin bridge event ignored because workspace is not connected: ${route.workspaceId}`);
301
306
  return;
@@ -372,7 +377,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
372
377
  }
373
378
  registerScheduledThreadBridge(input) {
374
379
  const instanceId = input.route.instanceId || getWeixinInstanceId(input.platform) || 'default';
375
- const platformKey = (0, config_js_1.channelPlatformKey)('weixin', instanceId);
380
+ const platformKey = (0, channel_keys_js_1.channelPlatformKey)('weixin', instanceId);
376
381
  const route = {
377
382
  workspaceId: input.workspaceId,
378
383
  instanceId,
@@ -470,7 +475,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
470
475
  async handleInboundMessage(input) {
471
476
  const msg = input;
472
477
  const instanceId = msg.instanceId || 'default';
473
- const platformKey = msg.platformKey || (0, config_js_1.channelPlatformKey)('weixin', instanceId);
478
+ const platformKey = msg.platformKey || (0, channel_keys_js_1.channelPlatformKey)('weixin', instanceId);
474
479
  if (this.isDuplicateInboundMessage(msg)) {
475
480
  this.options.log?.(`localcore-weixin skipped duplicate inbound message workspace=${msg.workspaceId} chat=${msg.chatId} id=${msg.contextToken || msg.messageId}`);
476
481
  return;
@@ -527,7 +532,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
527
532
  });
528
533
  this.notifyRuntimeStateChanged();
529
534
  }
530
- const state = this.runtime.get((0, config_js_1.runtimeKey)(msg.workspaceId, instanceId));
535
+ const state = this.runtime.get((0, channel_keys_js_1.runtimeKey)(msg.workspaceId, instanceId));
531
536
  if (state?.connected) {
532
537
  await this.sendTextMessage(state, msg.chatId, `**已收到消息**\n\n当前账号还未授权接入这个工作区。\n请在桌面端完成审批后再次发送消息。\n\n配对码:\`${pairingCode}\``, msg.contextToken);
533
538
  }
@@ -599,7 +604,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
599
604
  // ==================== Private: Bindings ====================
600
605
  // ==================== Private: Workspace Lifecycle ====================
601
606
  async startWorkspace(binding) {
602
- const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
607
+ const key = (0, channel_keys_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
603
608
  await this.stopWorkspaceKey(key);
604
609
  const status = {
605
610
  workspaceId: binding.workspaceId,
@@ -669,7 +674,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
669
674
  const isApiError = (resp.ret !== undefined && resp.ret !== 0) || (resp.errcode !== undefined && resp.errcode !== 0);
670
675
  if (isApiError) {
671
676
  consecutiveFailures++;
672
- const state = this.runtime.get((0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
677
+ const state = this.runtime.get((0, channel_keys_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
673
678
  const retryDelayMs = this.computeRetryDelay(consecutiveFailures);
674
679
  if (resp.errcode === -14 || resp.ret === -14) {
675
680
  if (state) {
@@ -693,7 +698,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
693
698
  continue;
694
699
  }
695
700
  consecutiveFailures = 0;
696
- const state = this.runtime.get((0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
701
+ const state = this.runtime.get((0, channel_keys_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
697
702
  if (state) {
698
703
  this.clearRuntimeError(state);
699
704
  state.status = 'running';
@@ -721,7 +726,16 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
721
726
  const attachmentParts = [];
722
727
  if (mediaItems.length > 0) {
723
728
  const uploadsDir = node_path_1.default.join(binding.stateDir, 'weixin-uploads');
729
+ const mayMaterializeAttachments = binding.allowFrom === '*'
730
+ || Boolean(this.options.store.getAuthorizedUser(binding.workspaceId, conversationId, binding.platformKey));
724
731
  for (const [idx, item] of mediaItems.entries()) {
732
+ const itemData = item.image_item ?? item.file_item ?? null;
733
+ const declaredName = String(itemData?.file_name ?? (item.type === transport_js_1.IMAGE_ITEM_TYPE ? 'image' : 'file'));
734
+ if (!mayMaterializeAttachments) {
735
+ attachmentText += attachmentText ? '\n' : '';
736
+ attachmentText += item.type === transport_js_1.IMAGE_ITEM_TYPE ? '[Image]' : `[File: ${declaredName}]`;
737
+ continue;
738
+ }
725
739
  try {
726
740
  const att = await this.downloadMediaItem(item, msgId, idx, uploadsDir, binding);
727
741
  if (att) {
@@ -762,7 +776,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
762
776
  return;
763
777
  consecutiveFailures++;
764
778
  const retryDelayMs = this.computeRetryDelay(consecutiveFailures);
765
- const state = this.runtime.get((0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
779
+ const state = this.runtime.get((0, channel_keys_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
766
780
  if (state) {
767
781
  state.consecutiveFailures = consecutiveFailures;
768
782
  state.nextRetryAt = new Date(Date.now() + retryDelayMs).toISOString();
@@ -886,37 +900,58 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
886
900
  aesKey = decoded.length === 16 ? decoded : decoded.length === 32 ? Buffer.from(decoded.toString('ascii'), 'hex') : undefined;
887
901
  }
888
902
  const cdnUrl = `${binding.cdnBaseUrl}/download?encrypted_query_param=${encodeURIComponent(encryptQueryParam)}`;
889
- const resp = await fetch(cdnUrl, { signal: AbortSignal.timeout(30_000) });
890
- if (!resp.ok)
891
- throw new Error(`CDN HTTP ${resp.status}`);
892
- const rawBuf = Buffer.from(await resp.arrayBuffer());
893
- if (rawBuf.length === 0)
894
- throw new Error('CDN returned empty data');
895
- let resultBuf;
896
- if (aesKey) {
897
- const decipher = node_crypto_1.default.createDecipheriv('aes-128-ecb', aesKey, null);
898
- decipher.setAutoPadding(true);
899
- resultBuf = Buffer.concat([decipher.update(rawBuf), decipher.final()]);
900
- }
901
- else {
902
- resultBuf = rawBuf;
903
- }
904
- const { ext, kind } = this.sniffExtAndKind(resultBuf);
905
903
  const declaredName = String(itemData?.file_name ?? (item.type === transport_js_1.IMAGE_ITEM_TYPE ? 'image' : 'file'));
906
- const safeName = declaredName.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 64);
907
- const safeMsgId = msgId.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 48);
908
- const fileName = `${safeMsgId}-${idx}-${safeName}${ext}`;
909
- const filePath = node_path_1.default.join(uploadsDir, fileName);
910
- node_fs_1.default.mkdirSync(uploadsDir, { recursive: true });
911
- node_fs_1.default.writeFileSync(filePath, resultBuf);
904
+ const stored = await this.inboundAttachmentStore.save({
905
+ directory: uploadsDir,
906
+ storedFileName: `${msgId}-${idx}-${declaredName}`,
907
+ displayFileName: declaredName,
908
+ maxBytes: WEIXIN_MAX_UPLOAD_FILE_SIZE,
909
+ includeBase64: ({ prefix }) => this.sniffExtAndKind(prefix).kind === 'image',
910
+ finalizeStoredFileName: ({ storedFileName, prefix }) => `${storedFileName}${this.sniffExtAndKind(prefix).ext}`,
911
+ source: {
912
+ open: async () => {
913
+ const resp = await fetch(cdnUrl, { signal: AbortSignal.timeout(30_000) });
914
+ if (!resp.ok)
915
+ throw new Error(`CDN HTTP ${resp.status}`);
916
+ if (!resp.body)
917
+ throw new Error('CDN returned no response body');
918
+ const rawStream = node_stream_1.Readable.fromWeb(resp.body);
919
+ if (!aesKey) {
920
+ return { stream: rawStream };
921
+ }
922
+ const decipher = node_crypto_1.default.createDecipheriv('aes-128-ecb', aesKey, null);
923
+ decipher.setAutoPadding(true);
924
+ return { stream: rawStream.pipe(decipher) };
925
+ },
926
+ },
927
+ });
928
+ const { ext, kind } = this.sniffExtAndKind(stored.prefix);
929
+ const uri = await this.resolveInboundAttachmentUri(binding, stored.path);
912
930
  return {
913
- path: filePath,
931
+ path: stored.path,
914
932
  kind,
915
933
  name: declaredName,
916
- data: kind === 'image' ? resultBuf.toString('base64') : undefined,
934
+ data: stored.data,
917
935
  mimeType: kind === 'image' ? this.mimeTypeForImageExt(ext) : undefined,
936
+ ...(uri ? { uri } : {}),
918
937
  };
919
938
  }
939
+ async resolveInboundAttachmentUri(binding, filePath) {
940
+ const sandbox = binding.project?.agent?.options?.sandbox;
941
+ if (!sandbox?.enabled) {
942
+ return (0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({ filePath });
943
+ }
944
+ const workspace = await this.options.getWorkspaceRouter().getWorkspaceRegistryEntry(binding.workspaceId).catch(() => undefined);
945
+ if (!workspace) {
946
+ return undefined;
947
+ }
948
+ return (0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({
949
+ filePath,
950
+ workspacePath: workspace.path,
951
+ sandboxEnabled: true,
952
+ sandboxWorkspacePath: sandbox.workspace_mount_path,
953
+ });
954
+ }
920
955
  mimeTypeForImageExt(ext) {
921
956
  if (ext === '.jpg')
922
957
  return 'image/jpeg';
@@ -1115,7 +1150,7 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
1115
1150
  return 60_000;
1116
1151
  }
1117
1152
  logPollError(binding, errorKey, message) {
1118
- const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
1153
+ const key = (0, channel_keys_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
1119
1154
  const current = this.pollErrorLogWindows.get(key);
1120
1155
  const now = Date.now();
1121
1156
  if (!current || current.errorKey !== errorKey || now - current.at >= ERROR_LOG_WINDOW_MS) {
@@ -137,11 +137,12 @@ class LocalCoreError extends Error {
137
137
  info;
138
138
  constructor(code, message, input = {}) {
139
139
  const defaults = DEFAULTS[code];
140
- super(message || defaults.userMessage);
140
+ const messageStr = coerceErrorMessage(message) || defaults.userMessage;
141
+ super(messageStr);
141
142
  this.name = 'LocalCoreError';
142
143
  this.info = {
143
144
  code,
144
- message: message || defaults.userMessage,
145
+ message: messageStr,
145
146
  userMessage: input.userMessage || defaults.userMessage,
146
147
  severity: input.severity || defaults.severity,
147
148
  retryable: input.retryable ?? defaults.retryable,
@@ -158,9 +159,9 @@ function toLocalCoreErrorInfo(error, fallbackCode = 'internal_error', details =
158
159
  }
159
160
  const maybeInfo = error?.info;
160
161
  if (maybeInfo?.code && maybeInfo.message) {
161
- return mergeDetails(maybeInfo, details);
162
+ return mergeDetails(ensureInfoMessageString(maybeInfo), details);
162
163
  }
163
- const message = error instanceof Error ? error.message : String(error || '');
164
+ const message = coerceErrorMessage(error instanceof Error ? error.message : error);
164
165
  const classifiedCode = classifyErrorMessage(message, fallbackCode);
165
166
  return new LocalCoreError(classifiedCode, message || DEFAULTS[classifiedCode].userMessage, {
166
167
  details: Object.keys(details).length ? details : undefined,
@@ -171,7 +172,8 @@ function formatUserError(info) {
171
172
  }
172
173
  function formatLogError(info) {
173
174
  const suffix = info.cause ? ` cause=${info.cause}` : '';
174
- return `${info.code}: ${info.message}${suffix}`;
175
+ const message = typeof info.message === 'string' ? info.message : safeStringify(info.message);
176
+ return `${info.code}: ${message}${suffix}`;
175
177
  }
176
178
  function errorInfoToHttpBody(info) {
177
179
  return {
@@ -253,3 +255,29 @@ function mergeDetails(info, details) {
253
255
  },
254
256
  };
255
257
  }
258
+ function ensureInfoMessageString(info) {
259
+ if (typeof info.message === 'string') {
260
+ return info;
261
+ }
262
+ return { ...info, message: safeStringify(info.message) };
263
+ }
264
+ function coerceErrorMessage(value) {
265
+ if (typeof value === 'string') {
266
+ return value;
267
+ }
268
+ if (value === null || value === undefined) {
269
+ return '';
270
+ }
271
+ return safeStringify(value);
272
+ }
273
+ function safeStringify(value) {
274
+ if (typeof value === 'string') {
275
+ return value;
276
+ }
277
+ try {
278
+ return JSON.stringify(value);
279
+ }
280
+ catch {
281
+ return String(value);
282
+ }
283
+ }
@@ -93,46 +93,6 @@ class ChannelService {
93
93
  }
94
94
  return runtime.checkQrCodeStatus(workspaceId, ticket, instanceId);
95
95
  }
96
- async getWeixinQrCode(workspaceId, instanceId) {
97
- return this.getQrCode('weixin', workspaceId, instanceId);
98
- }
99
- async checkWeixinQrCodeStatus(workspaceId, ticket, instanceId) {
100
- return this.checkQrCodeStatus('weixin', workspaceId, ticket, instanceId);
101
- }
102
- // Lark convenience wrappers
103
- async listLarkStatuses() {
104
- return this.listStatuses('lark');
105
- }
106
- async getLarkStatus(workspaceId, instanceId) {
107
- return this.getStatus('lark', workspaceId, instanceId);
108
- }
109
- async testLarkConnection(workspaceId, instanceId) {
110
- return this.testConnection('lark', workspaceId, instanceId);
111
- }
112
- async enableLark(workspaceId, instanceId) {
113
- return this.enable('lark', workspaceId, instanceId);
114
- }
115
- async disableLark(workspaceId, instanceId) {
116
- return this.disable('lark', workspaceId, instanceId);
117
- }
118
- async listLarkPendingPairings(workspaceId) {
119
- return this.listPendingPairings('lark', workspaceId);
120
- }
121
- async approveLarkPairing(code) {
122
- return this.approvePairing('lark', code);
123
- }
124
- async rejectLarkPairing(code) {
125
- return this.rejectPairing('lark', code);
126
- }
127
- async listLarkAuthorizedUsers(workspaceId) {
128
- return this.listAuthorizedUsers('lark', workspaceId);
129
- }
130
- async getLarkQrCode(workspaceId, instanceId) {
131
- return this.getQrCode('lark', workspaceId, instanceId);
132
- }
133
- async checkLarkQrCodeStatus(workspaceId, ticket, instanceId) {
134
- return this.checkQrCodeStatus('lark', workspaceId, ticket, instanceId);
135
- }
136
96
  async refreshBindings() {
137
97
  await Promise.all([...this.runtimes.values()].map((runtime) => runtime.refreshBindings?.()));
138
98
  }
@@ -175,20 +175,7 @@ class ExternalService {
175
175
  });
176
176
  }
177
177
  async readAndMigrateConfigFile() {
178
- const current = await this.deps.readRuntimeConfig();
179
- const migrated = (0, provider_config_migration_js_1.migrateLegacyProjectProvidersToStore)(current.config, this.store);
180
- if (!migrated.changed) {
181
- return current;
182
- }
183
- const saved = await this.deps.saveRuntimeConfig(migrated.config);
184
- return {
185
- ...saved,
186
- warnings: [
187
- ...(current.warnings || []),
188
- ...(saved.warnings || []),
189
- ...migrated.warnings,
190
- ],
191
- };
178
+ return (0, provider_config_migration_js_1.applyLegacyProviderMigration)(await this.deps.readRuntimeConfig(), this.store, (config) => this.deps.saveRuntimeConfig(config));
192
179
  }
193
180
  projectBasePath(userId, externalProjectId) {
194
181
  return (0, node_path_1.join)(this.workspaceRoot(), 'users', normalizeExternalSegment(userId, 'user'), 'projects', normalizeExternalSegment(externalProjectId, 'project'));
@@ -284,20 +284,7 @@ class LocalCoreController extends node_events_1.EventEmitter {
284
284
  }
285
285
  }
286
286
  async readAndMigrateRuntimeConfig() {
287
- const current = this.store.readRuntimeConfig();
288
- const migrated = (0, provider_config_migration_js_1.migrateLegacyProjectProvidersToStore)(current.config, this.store);
289
- if (!migrated.changed) {
290
- return current;
291
- }
292
- const saved = this.store.saveRuntimeConfig(migrated.config);
293
- return {
294
- ...saved,
295
- warnings: [
296
- ...(current.warnings || []),
297
- ...(saved.warnings || []),
298
- ...migrated.warnings,
299
- ],
300
- };
287
+ return (0, provider_config_migration_js_1.applyLegacyProviderMigration)(this.store.readRuntimeConfig(), this.store, (config) => this.store.saveRuntimeConfig(config));
301
288
  }
302
289
  }
303
290
  exports.LocalCoreController = LocalCoreController;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.migrateLegacyProjectProvidersToStore = migrateLegacyProjectProvidersToStore;
4
+ exports.applyLegacyProviderMigration = applyLegacyProviderMigration;
4
5
  const config_migration_js_1 = require("./config-migration.js");
5
6
  function migrateLegacyProjectProvidersToStore(input, store) {
6
7
  const config = cloneConfig(input);
@@ -27,6 +28,21 @@ function migrateLegacyProjectProvidersToStore(input, store) {
27
28
  }
28
29
  return { config, changed, warnings };
29
30
  }
31
+ async function applyLegacyProviderMigration(current, store, save) {
32
+ const migrated = migrateLegacyProjectProvidersToStore(current.config, store);
33
+ if (!migrated.changed) {
34
+ return current;
35
+ }
36
+ const saved = await save(migrated.config);
37
+ return {
38
+ ...saved,
39
+ warnings: [
40
+ ...(current.warnings || []),
41
+ ...(saved.warnings || []),
42
+ ...migrated.warnings,
43
+ ],
44
+ };
45
+ }
30
46
  function cloneConfig(input) {
31
47
  return JSON.parse(JSON.stringify(input || {}));
32
48
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createChannelExecutionPolicy = createChannelExecutionPolicy;
4
+ const definition_js_1 = require("../agents/shared/definition.js");
4
5
  const scheduled_bridge_session_js_1 = require("./scheduled-bridge-session.js");
5
6
  function createChannelExecutionPolicy(job, options, config) {
6
7
  if (job.executionMode === 'side-thread') {
@@ -79,16 +80,13 @@ class SideThreadChannelExecutionPolicy {
79
80
  this.bridgeSession = undefined;
80
81
  }
81
82
  async resolveTargetAgent(job) {
82
- const preferredAgent = normalizeAgentType(this.config.preferredAgentType?.(job));
83
+ const preferredAgent = (0, definition_js_1.normalizeAgentType)(this.config.preferredAgentType?.(job));
83
84
  if (preferredAgent) {
84
85
  return preferredAgent;
85
86
  }
86
- return normalizeAgentType(await this.options.workspaceRouter.getWorkspaceDefaultAgentType(job.workspaceId));
87
+ return (0, definition_js_1.normalizeAgentType)(await this.options.workspaceRouter.getWorkspaceDefaultAgentType(job.workspaceId));
87
88
  }
88
89
  threadMatchesTargetAgent(thread, targetAgent) {
89
- return normalizeAgentType(thread.agentType) === targetAgent;
90
+ return (0, definition_js_1.normalizeAgentType)(thread.agentType) === targetAgent;
90
91
  }
91
92
  }
92
- function normalizeAgentType(value) {
93
- return String(value || '').trim().toLowerCase();
94
- }
@@ -168,7 +168,9 @@ function launchConfig() {
168
168
  });
169
169
  (0, node_test_1.default)('workspace route launches sandbox proxy when sandbox is enabled', () => {
170
170
  const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-sandbox-'));
171
+ const previousUserDataDir = process.env.AI_WORKSTATION_USER_DATA_DIR;
171
172
  try {
173
+ delete process.env.AI_WORKSTATION_USER_DATA_DIR;
172
174
  (0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'runtime'), { recursive: true });
173
175
  const route = (0, workspace_route_config_js_1.toLocalCoreProjectConfig)(configState((0, node_path_1.join)(root, 'runtime', 'config.toml')), project({
174
176
  enabled: true,
@@ -186,6 +188,9 @@ function launchConfig() {
186
188
  strict_1.default.equal(JSON.parse(route.env.AGENTDOCK_SANDBOX_CONFIG).image, 'agentdock/pi-acp:local');
187
189
  }
188
190
  finally {
191
+ if (previousUserDataDir !== undefined) {
192
+ process.env.AI_WORKSTATION_USER_DATA_DIR = previousUserDataDir;
193
+ }
189
194
  (0, node_fs_1.rmSync)(root, { recursive: true, force: true });
190
195
  }
191
196
  });
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const node_stream_1 = require("node:stream");
12
+ const inbound_attachment_store_js_1 = require("../../services/local-ai-core/src/channel/shared/inbound-attachment-store.js");
13
+ (0, node_test_1.default)('inbound attachment store streams a platform source into an atomic local file', async () => {
14
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-store-'));
15
+ try {
16
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
17
+ const stored = await store.save({
18
+ directory: tempDir,
19
+ storedFileName: 'message/one-report.pdf',
20
+ displayFileName: 'report.pdf',
21
+ source: {
22
+ open: async () => ({
23
+ stream: node_stream_1.Readable.from([Buffer.from('hello '), Buffer.from('world')]),
24
+ mimeType: 'application/pdf',
25
+ }),
26
+ },
27
+ });
28
+ strict_1.default.equal(stored.path, (0, node_path_1.join)(tempDir, 'message_one-report.pdf'));
29
+ strict_1.default.equal(stored.fileName, 'report.pdf');
30
+ strict_1.default.equal(stored.mimeType, 'application/pdf');
31
+ strict_1.default.equal(stored.size, 11);
32
+ strict_1.default.equal((0, node_fs_1.readFileSync)(stored.path, 'utf8'), 'hello world');
33
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), ['message_one-report.pdf']);
34
+ }
35
+ finally {
36
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
37
+ }
38
+ });
39
+ (0, node_test_1.default)('inbound attachment store removes partial files when the size limit is exceeded', async () => {
40
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-limit-'));
41
+ try {
42
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
43
+ await strict_1.default.rejects(() => store.save({
44
+ directory: tempDir,
45
+ storedFileName: 'large.bin',
46
+ displayFileName: 'large.bin',
47
+ maxBytes: 4,
48
+ source: {
49
+ open: async () => ({ stream: node_stream_1.Readable.from([Buffer.from('12345')]) }),
50
+ },
51
+ }), /exceeds 4 bytes/);
52
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), []);
53
+ }
54
+ finally {
55
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
56
+ }
57
+ });
58
+ (0, node_test_1.default)('inbound attachment store finalizes the filename before commit and can return base64', async () => {
59
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-finalize-'));
60
+ try {
61
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
62
+ const stored = await store.save({
63
+ directory: tempDir,
64
+ storedFileName: 'image-1',
65
+ displayFileName: 'image.png',
66
+ includeBase64: true,
67
+ finalizeStoredFileName: ({ storedFileName, prefix }) => {
68
+ strict_1.default.deepEqual(prefix.subarray(0, 4), Buffer.from([0x89, 0x50, 0x4e, 0x47]));
69
+ return `${storedFileName}.png`;
70
+ },
71
+ source: {
72
+ open: async () => ({ stream: node_stream_1.Readable.from([Buffer.from([0x89, 0x50, 0x4e, 0x47, 1, 2])]) }),
73
+ },
74
+ });
75
+ strict_1.default.equal(stored.path, (0, node_path_1.join)(tempDir, 'image-1.png'));
76
+ strict_1.default.equal(stored.data, 'iVBORwEC');
77
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), ['image-1.png']);
78
+ }
79
+ finally {
80
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
81
+ }
82
+ });
83
+ (0, node_test_1.default)('inbound attachment store removes temporary data when filename finalization fails', async () => {
84
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'channel-inbound-finalize-error-'));
85
+ try {
86
+ const store = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
87
+ await strict_1.default.rejects(() => store.save({
88
+ directory: tempDir,
89
+ storedFileName: 'image-1',
90
+ displayFileName: 'image.png',
91
+ finalizeStoredFileName: () => {
92
+ throw new Error('cannot identify image');
93
+ },
94
+ source: {
95
+ open: async () => ({ stream: node_stream_1.Readable.from([Buffer.from('image')]) }),
96
+ },
97
+ }), /cannot identify image/);
98
+ strict_1.default.deepEqual((0, node_fs_1.readdirSync)(tempDir), []);
99
+ }
100
+ finally {
101
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
102
+ }
103
+ });
104
+ (0, node_test_1.default)('inbound attachment URI maps workspace files into sandbox and hides external files', () => {
105
+ strict_1.default.equal((0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({
106
+ filePath: '/host/project/.agentdock/image.png',
107
+ workspacePath: '/host/project',
108
+ sandboxEnabled: true,
109
+ sandboxWorkspacePath: '/workspace',
110
+ }), 'file:///workspace/.agentdock/image.png');
111
+ strict_1.default.equal((0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({
112
+ filePath: '/host/state/image.png',
113
+ workspacePath: '/host/project',
114
+ sandboxEnabled: true,
115
+ sandboxWorkspacePath: '/workspace',
116
+ }), undefined);
117
+ });