@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
@@ -41,9 +41,11 @@ const local_core_errors_js_1 = require("../../kernel/local-core-errors.js");
41
41
  const desktop_js_1 = require("../../../../../shared/desktop.js");
42
42
  const content_js_1 = require("../shared/content.js");
43
43
  const file_utils_js_1 = require("../shared/file-utils.js");
44
+ const inbound_attachment_store_js_1 = require("../shared/inbound-attachment-store.js");
44
45
  const thread_routing_js_1 = require("../shared/thread-routing.js");
45
46
  const base_channel_gateway_js_1 = require("../shared/base-channel-gateway.js");
46
47
  const cards_js_1 = require("./cards.js");
48
+ const channel_keys_js_1 = require("../shared/channel-keys.js");
47
49
  const config_js_1 = require("./config.js");
48
50
  const registration_js_1 = require("./registration.js");
49
51
  const messages_js_1 = require("./rendering/messages.js");
@@ -53,6 +55,7 @@ const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
53
55
  const LARK_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
54
56
  const LARK_FINAL_PATCH_INTERVAL_MS = 900;
55
57
  const LARK_PROGRESS_PATCH_INTERVAL_MS = 3000;
58
+ const LARK_EMPTY_RENDER_LOG_WINDOW_MS = 5 * 60 * 1000;
56
59
  class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway {
57
60
  // Lark returns 200340 when card action events are not enabled in the app's
58
61
  // event subscription. Keep card actions opt-in so text approval always works.
@@ -60,6 +63,8 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
60
63
  // Keep permission state in a dedicated card to avoid mixing order in the main reply card.
61
64
  mirrorPermissionStateInMainCard = false;
62
65
  larkModulePromise = null;
66
+ emptyRenderLogWindows = new Map();
67
+ inboundAttachmentStore = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
63
68
  platform = 'lark';
64
69
  constructor(options) {
65
70
  super(options);
@@ -129,7 +134,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
129
134
  }
130
135
  registerScheduledThreadBridge(input) {
131
136
  const instanceId = input.route.instanceId || getLarkInstanceId(input.platform) || 'default';
132
- const platformKey = (0, config_js_1.channelPlatformKey)('lark', instanceId);
137
+ const platformKey = (0, channel_keys_js_1.channelPlatformKey)('lark', instanceId);
133
138
  const route = {
134
139
  workspaceId: input.workspaceId,
135
140
  instanceId,
@@ -277,8 +282,8 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
277
282
  return;
278
283
  }
279
284
  const routeInstanceId = route.instanceId || 'default';
280
- const routePlatformKey = route.platformKey || (0, config_js_1.channelPlatformKey)('lark', routeInstanceId);
281
- const state = this.runtime.get((0, config_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
285
+ const routePlatformKey = route.platformKey || (0, channel_keys_js_1.channelPlatformKey)('lark', routeInstanceId);
286
+ const state = this.runtime.get((0, channel_keys_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
282
287
  if (!state?.client || !state.connected) {
283
288
  this.options.log?.(`localcore-lark bridge event ignored because workspace is not connected: ${route.workspaceId}`);
284
289
  return;
@@ -353,7 +358,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
353
358
  const renderedMessages = (0, runtime_state_js_1.renderLarkBridgeEventMessages)(turn, event);
354
359
  for (const renderedMessage of renderedMessages) {
355
360
  if (!renderedMessage.text && renderedMessage.buttonRows.length === 0) {
356
- this.options.log?.(`localcore-lark bridge event produced empty render for sessionKey=${sessionKey} type=${event.type}`);
361
+ this.logEmptyRender(sessionKey, event.type);
357
362
  continue;
358
363
  }
359
364
  const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, renderedMessage);
@@ -408,7 +413,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
408
413
  async handleInboundMessage(input) {
409
414
  const msg = input;
410
415
  const instanceId = msg.instanceId || 'default';
411
- const platformKey = msg.platformKey || (0, config_js_1.channelPlatformKey)('lark', instanceId);
416
+ const platformKey = msg.platformKey || (0, channel_keys_js_1.channelPlatformKey)('lark', instanceId);
412
417
  this.options.eventBus.emit({
413
418
  type: 'platform.message.received',
414
419
  payload: {
@@ -562,7 +567,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
562
567
  }
563
568
  }
564
569
  async startWorkspace(binding) {
565
- const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
570
+ const key = (0, channel_keys_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
566
571
  await this.stopWorkspaceKey(key);
567
572
  const status = {
568
573
  workspaceId: binding.workspaceId,
@@ -576,6 +581,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
576
581
  autoApprove: binding.autoApprove,
577
582
  cardActionsEnabled: binding.cardActionsEnabled,
578
583
  groupReplyAll: binding.groupReplyAll,
584
+ downloadsDir: binding.downloadsDir,
579
585
  botOpenId: binding.botOpenId,
580
586
  };
581
587
  this.runtime.set(key, status);
@@ -652,7 +658,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
652
658
  async handleMessageEvent(workspaceId, instanceIdOrData, platformKeyOrData, maybeData) {
653
659
  const legacyCall = typeof instanceIdOrData === 'object';
654
660
  const instanceId = legacyCall ? 'default' : instanceIdOrData;
655
- const platformKey = legacyCall ? 'lark' : String(platformKeyOrData || (0, config_js_1.channelPlatformKey)('lark', instanceId));
661
+ const platformKey = legacyCall ? 'lark' : String(platformKeyOrData || (0, channel_keys_js_1.channelPlatformKey)('lark', instanceId));
656
662
  const data = (legacyCall ? instanceIdOrData : maybeData) || {};
657
663
  const runtimeState = this.resolveRuntimeState(workspaceId, instanceId).state;
658
664
  const normalized = (0, inbound_js_1.normalizeLarkInboundMessageEvent)(data, {
@@ -665,6 +671,8 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
665
671
  return;
666
672
  }
667
673
  const { message, parsedContent, messageType, chatType, mentions, text, platformUserId, chatId, displayName, messageId, } = normalized.message;
674
+ const mayMaterializeAttachments = Boolean(runtimeState?.autoApprove
675
+ || this.options.store.getAuthorizedUser(workspaceId, platformUserId, platformKey));
668
676
  const contentParts = [];
669
677
  if (text) {
670
678
  contentParts.push({ type: 'text', text });
@@ -672,26 +680,37 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
672
680
  if (messageType === 'image') {
673
681
  const imageKey = String(parsedContent.image_key || parsedContent.file_key || '').trim();
674
682
  if (imageKey) {
675
- try {
676
- contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey, instanceId));
683
+ if (!mayMaterializeAttachments) {
684
+ contentParts.push({ type: 'text', text: '[Image]' });
677
685
  }
678
- catch (error) {
679
- const errorText = `[Image download failed: ${error instanceof Error ? error.message : String(error)}]`;
680
- contentParts.push({ type: 'text', text: errorText });
681
- this.options.log?.(`localcore-lark image download failed for ${workspaceId}: message=${String(message.message_id || '')} imageKey=${imageKey} error=${error instanceof Error ? error.message : String(error)}`);
686
+ else {
687
+ try {
688
+ contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey, instanceId));
689
+ }
690
+ catch (error) {
691
+ const errorText = `[Image download failed: ${error instanceof Error ? error.message : String(error)}]`;
692
+ contentParts.push({ type: 'text', text: errorText });
693
+ this.options.log?.(`localcore-lark image download failed for ${workspaceId}: message=${String(message.message_id || '')} imageKey=${imageKey} error=${error instanceof Error ? error.message : String(error)}`);
694
+ }
682
695
  }
683
696
  }
684
697
  }
685
698
  if (messageType === 'file') {
686
699
  const fileKey = String(parsedContent.file_key || '').trim();
687
700
  if (fileKey) {
688
- try {
689
- contentParts.push(await this.downloadMessageFile(workspaceId, String(message.message_id || ''), fileKey, String(parsedContent.file_name || parsedContent.name || '').trim(), instanceId));
701
+ if (!mayMaterializeAttachments) {
702
+ const inboundFileName = String(parsedContent.file_name || parsedContent.name || '').trim();
703
+ contentParts.push({ type: 'text', text: inboundFileName ? `[File: ${inboundFileName}]` : '[File]' });
690
704
  }
691
- catch (error) {
692
- const errorText = `[File download failed: ${error instanceof Error ? error.message : String(error)}]`;
693
- contentParts.push({ type: 'text', text: errorText });
694
- this.options.log?.(`localcore-lark file download failed for ${workspaceId}: message=${String(message.message_id || '')} fileKey=${fileKey} error=${error instanceof Error ? error.message : String(error)}`);
705
+ else {
706
+ try {
707
+ contentParts.push(await this.downloadMessageFile(workspaceId, String(message.message_id || ''), fileKey, String(parsedContent.file_name || parsedContent.name || '').trim(), instanceId));
708
+ }
709
+ catch (error) {
710
+ const errorText = `[File download failed: ${error instanceof Error ? error.message : String(error)}]`;
711
+ contentParts.push({ type: 'text', text: errorText });
712
+ this.options.log?.(`localcore-lark file download failed for ${workspaceId}: message=${String(message.message_id || '')} fileKey=${fileKey} error=${error instanceof Error ? error.message : String(error)}`);
713
+ }
695
714
  }
696
715
  }
697
716
  }
@@ -721,25 +740,44 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
721
740
  if (!messageId) {
722
741
  throw new Error('Lark image message is missing message_id');
723
742
  }
724
- const resource = await state.client.im.messageResource.get({
725
- path: {
726
- message_id: messageId,
727
- file_key: imageKey,
728
- },
729
- params: {
730
- type: 'image',
743
+ const downloadsDir = await this.resolveInboundDownloadsDir(workspaceId, instanceId, state.downloadsDir);
744
+ const stored = await this.inboundAttachmentStore.save({
745
+ directory: downloadsDir,
746
+ storedFileName: `${messageId}-${imageKey}`,
747
+ displayFileName: imageKey,
748
+ maxBytes: LARK_MAX_UPLOAD_FILE_SIZE,
749
+ includeBase64: true,
750
+ finalizeStoredFileName: ({ storedFileName, prefix }) => `${storedFileName}.${this.sniffImageExtension(prefix)}`,
751
+ source: {
752
+ open: async () => {
753
+ const resource = await state.client.im.messageResource.get({
754
+ path: {
755
+ message_id: messageId,
756
+ file_key: imageKey,
757
+ },
758
+ params: {
759
+ type: 'image',
760
+ },
761
+ });
762
+ const stream = resource?.getReadableStream?.();
763
+ if (!stream || typeof stream.pipe !== 'function') {
764
+ throw new Error('Lark image resource did not provide a readable stream');
765
+ }
766
+ return {
767
+ stream,
768
+ mimeType: this.extractHeaderMimeType(resource?.headers),
769
+ };
770
+ },
731
771
  },
732
772
  });
733
- const buffer = await this.readLarkResourceBuffer(resource);
734
- if (buffer.length === 0) {
735
- throw new Error('Lark image resource is empty');
736
- }
737
- const headerMime = this.extractHeaderMimeType(resource?.headers);
773
+ const extension = (0, node_path_1.extname)(stored.path).slice(1) || 'bin';
774
+ const uri = await this.resolveInboundAttachmentUri(workspaceId, stored.path, instanceId);
738
775
  return {
739
776
  type: 'image',
740
- data: buffer.toString('base64'),
741
- mimeType: headerMime || this.sniffImageMimeType(buffer),
742
- fileName: `${imageKey}.${this.sniffImageExtension(buffer)}`,
777
+ data: stored.data || '',
778
+ ...(uri ? { uri } : {}),
779
+ mimeType: stored.mimeType || this.sniffImageMimeType(stored.prefix),
780
+ fileName: `${imageKey}.${extension}`,
743
781
  };
744
782
  }
745
783
  async downloadMessageFile(workspaceId, messageId, fileKey, fileName, instanceId) {
@@ -750,27 +788,76 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
750
788
  if (!messageId) {
751
789
  throw new Error('Lark file message is missing message_id');
752
790
  }
753
- const resource = await state.client.im.messageResource.get({
754
- path: {
755
- message_id: messageId,
756
- file_key: fileKey,
757
- },
758
- params: {
759
- type: 'file',
791
+ const downloadsDir = await this.resolveInboundDownloadsDir(workspaceId, instanceId, state.downloadsDir);
792
+ const displayFileName = fileName || fileKey;
793
+ const stored = await this.inboundAttachmentStore.save({
794
+ directory: downloadsDir,
795
+ storedFileName: `${messageId}-${displayFileName}`,
796
+ displayFileName,
797
+ maxBytes: LARK_MAX_UPLOAD_FILE_SIZE,
798
+ source: {
799
+ open: async () => {
800
+ const resource = await state.client.im.messageResource.get({
801
+ path: {
802
+ message_id: messageId,
803
+ file_key: fileKey,
804
+ },
805
+ params: {
806
+ type: 'file',
807
+ },
808
+ });
809
+ const stream = resource?.getReadableStream?.();
810
+ if (!stream || typeof stream.pipe !== 'function') {
811
+ throw new Error('Lark file resource did not provide a readable stream');
812
+ }
813
+ return {
814
+ stream,
815
+ mimeType: this.extractHeaderMimeType(resource?.headers) || 'application/octet-stream',
816
+ };
817
+ },
760
818
  },
761
819
  });
762
- const buffer = await this.readLarkResourceBuffer(resource);
763
- if (buffer.length === 0) {
764
- throw new Error('Lark file resource is empty');
765
- }
766
820
  return {
767
821
  type: 'file',
768
- data: buffer.toString('base64'),
769
- mimeType: this.extractHeaderMimeType(resource?.headers) || 'application/octet-stream',
770
- fileName: fileName || fileKey,
771
- size: buffer.length,
822
+ path: stored.path,
823
+ fileName: stored.fileName,
824
+ mimeType: stored.mimeType,
825
+ size: stored.size,
772
826
  };
773
827
  }
828
+ async resolveInboundDownloadsDir(workspaceId, instanceId = 'default', runtimeDownloadsDir) {
829
+ const binding = runtimeDownloadsDir === undefined
830
+ ? await this.getBinding(workspaceId, instanceId)
831
+ : undefined;
832
+ const configuredDir = String(runtimeDownloadsDir ?? binding?.downloadsDir ?? '').trim();
833
+ if (configuredDir && (0, node_path_1.isAbsolute)(configuredDir)) {
834
+ return configuredDir;
835
+ }
836
+ const workspace = await this.options.getWorkspaceRouter().getWorkspaceRegistryEntry(workspaceId);
837
+ return configuredDir
838
+ ? (0, node_path_1.resolve)(workspace.path, configuredDir)
839
+ : (0, node_path_1.join)(workspace.path, '.agentdock', 'channel-uploads', 'lark', instanceId);
840
+ }
841
+ async resolveInboundAttachmentUri(workspaceId, filePath, instanceId = 'default') {
842
+ const binding = await this.getBinding(workspaceId, instanceId).catch(() => undefined);
843
+ if (!binding) {
844
+ return undefined;
845
+ }
846
+ const sandbox = binding.project.agent?.options?.sandbox;
847
+ if (!sandbox?.enabled) {
848
+ return (0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({ filePath });
849
+ }
850
+ const workspace = await this.options.getWorkspaceRouter().getWorkspaceRegistryEntry(workspaceId).catch(() => undefined);
851
+ if (!workspace) {
852
+ return undefined;
853
+ }
854
+ return (0, inbound_attachment_store_js_1.resolveInboundAttachmentUri)({
855
+ filePath,
856
+ workspacePath: workspace.path,
857
+ sandboxEnabled: true,
858
+ sandboxWorkspacePath: sandbox.workspace_mount_path,
859
+ });
860
+ }
774
861
  summarizeInboundContentParts(parts) {
775
862
  const text = parts
776
863
  .filter((part) => part.type === 'text')
@@ -794,21 +881,6 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
794
881
  .join('\n');
795
882
  return attachmentSummary;
796
883
  }
797
- async readLarkResourceBuffer(resource) {
798
- const readable = resource?.getReadableStream?.();
799
- if (!readable || typeof readable.on !== 'function') {
800
- throw new Error('Lark resource did not provide a readable stream');
801
- }
802
- const chunks = [];
803
- await new Promise((resolve, reject) => {
804
- readable.on('data', (chunk) => {
805
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
806
- });
807
- readable.on('error', reject);
808
- readable.on('end', resolve);
809
- });
810
- return Buffer.concat(chunks);
811
- }
812
884
  extractHeaderMimeType(headers) {
813
885
  const value = headers && typeof headers === 'object'
814
886
  ? headers['content-type'] || headers['Content-Type']
@@ -913,7 +985,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
913
985
  this.options.store.expirePendingPairings();
914
986
  const resolved = this.resolveRuntimeState(state.workspaceId, state.instanceId);
915
987
  const binding = resolved.state;
916
- const platformKey = binding?.platformKey || (0, config_js_1.channelPlatformKey)('lark', resolved.instanceId);
988
+ const platformKey = binding?.platformKey || (0, channel_keys_js_1.channelPlatformKey)('lark', resolved.instanceId);
917
989
  const pairings = this.options.store.listPendingPairings(state.workspaceId)
918
990
  .filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
919
991
  const users = this.options.store.listAuthorizedUsers(state.workspaceId, platformKey);
@@ -947,6 +1019,7 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
947
1019
  autoApprove: binding.autoApprove,
948
1020
  cardActionsEnabled: binding.cardActionsEnabled,
949
1021
  groupReplyAll: binding.groupReplyAll,
1022
+ downloadsDir: binding.downloadsDir,
950
1023
  botOpenId: binding.botOpenId,
951
1024
  };
952
1025
  }
@@ -1247,6 +1320,22 @@ class LocalCoreLarkGateway extends base_channel_gateway_js_1.BaseChannelGateway
1247
1320
  this.options.log?.(`localcore-lark acknowledgement reaction failed for message=${messageId}: ${error instanceof Error ? error.message : String(error)}`);
1248
1321
  }
1249
1322
  }
1323
+ logEmptyRender(sessionKey, type) {
1324
+ const key = `${sessionKey}|${type}`;
1325
+ const now = Date.now();
1326
+ const lastAt = this.emptyRenderLogWindows.get(key);
1327
+ if (lastAt !== undefined && now - lastAt < LARK_EMPTY_RENDER_LOG_WINDOW_MS) {
1328
+ return;
1329
+ }
1330
+ this.emptyRenderLogWindows.set(key, now);
1331
+ if (this.emptyRenderLogWindows.size > 1000) {
1332
+ const oldestKey = this.emptyRenderLogWindows.keys().next().value;
1333
+ if (oldestKey) {
1334
+ this.emptyRenderLogWindows.delete(oldestKey);
1335
+ }
1336
+ }
1337
+ this.options.log?.(`localcore-lark bridge event produced empty render for sessionKey=${sessionKey} type=${type}`);
1338
+ }
1250
1339
  }
1251
1340
  exports.LocalCoreLarkGateway = LocalCoreLarkGateway;
1252
1341
  function getLarkInstanceId(platform) {
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeChannelInstanceId = normalizeChannelInstanceId;
4
+ exports.channelPlatformKey = channelPlatformKey;
5
+ exports.runtimeKey = runtimeKey;
6
+ function normalizeChannelInstanceId(value, fallback) {
7
+ const raw = String(value || '').trim();
8
+ const normalized = raw.replace(/[^a-zA-Z0-9._-]/g, '-').replace(/-+/g, '-').slice(0, 80);
9
+ return normalized || fallback;
10
+ }
11
+ function channelPlatformKey(platform, instanceId) {
12
+ return instanceId === 'default' ? platform : `${platform}:${instanceId}`;
13
+ }
14
+ function runtimeKey(workspaceId, instanceId) {
15
+ return `${workspaceId}::${instanceId}`;
16
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileSystemInboundAttachmentStore = void 0;
4
+ exports.sanitizeInboundFilePart = sanitizeInboundFilePart;
5
+ exports.resolveInboundAttachmentUri = resolveInboundAttachmentUri;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const node_fs_1 = require("node:fs");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = require("node:path");
10
+ const node_stream_1 = require("node:stream");
11
+ const promises_2 = require("node:stream/promises");
12
+ const node_url_1 = require("node:url");
13
+ class FileSystemInboundAttachmentStore {
14
+ async save(input) {
15
+ const storedFileName = sanitizeInboundFilePart(input.storedFileName, 'file');
16
+ const temporaryPath = (0, node_path_1.join)(input.directory, `.${storedFileName}.${(0, node_crypto_1.randomUUID)()}.part`);
17
+ await (0, promises_1.mkdir)(input.directory, { recursive: true });
18
+ const opened = await input.source.open();
19
+ let size = 0;
20
+ let prefix = Buffer.alloc(0);
21
+ const sizeLimiter = new node_stream_1.Transform({
22
+ transform(chunk, _encoding, callback) {
23
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
24
+ if (prefix.length < 16) {
25
+ prefix = Buffer.concat([prefix, buffer.subarray(0, 16 - prefix.length)]);
26
+ }
27
+ size += buffer.length;
28
+ if (input.maxBytes && size > input.maxBytes) {
29
+ callback(new Error(`Inbound attachment exceeds ${input.maxBytes} bytes`));
30
+ return;
31
+ }
32
+ callback(null, buffer);
33
+ },
34
+ });
35
+ let filePath = '';
36
+ try {
37
+ await (0, promises_2.pipeline)(opened.stream, sizeLimiter, (0, node_fs_1.createWriteStream)(temporaryPath, { flags: 'wx' }));
38
+ if (size === 0) {
39
+ throw new Error('Inbound attachment is empty');
40
+ }
41
+ const finalizedName = input.finalizeStoredFileName
42
+ ? input.finalizeStoredFileName({ storedFileName, prefix })
43
+ : storedFileName;
44
+ filePath = (0, node_path_1.join)(input.directory, sanitizeInboundFilePart(finalizedName, storedFileName));
45
+ await (0, promises_1.rename)(temporaryPath, filePath);
46
+ const includeBase64 = typeof input.includeBase64 === 'function'
47
+ ? input.includeBase64({ prefix })
48
+ : input.includeBase64;
49
+ const data = includeBase64
50
+ ? (await (0, promises_1.readFile)(filePath)).toString('base64')
51
+ : undefined;
52
+ const fileStat = await (0, promises_1.stat)(filePath);
53
+ return {
54
+ path: filePath,
55
+ fileName: input.displayFileName,
56
+ mimeType: opened.mimeType,
57
+ size: fileStat.size,
58
+ data,
59
+ prefix,
60
+ };
61
+ }
62
+ catch (error) {
63
+ await (0, promises_1.rm)(temporaryPath, { force: true }).catch(() => undefined);
64
+ if (filePath) {
65
+ await (0, promises_1.rm)(filePath, { force: true }).catch(() => undefined);
66
+ }
67
+ throw error;
68
+ }
69
+ }
70
+ }
71
+ exports.FileSystemInboundAttachmentStore = FileSystemInboundAttachmentStore;
72
+ function sanitizeInboundFilePart(value, fallback) {
73
+ return String(value || fallback)
74
+ .replace(/[^a-zA-Z0-9._-]/g, '_')
75
+ .slice(0, 240) || fallback;
76
+ }
77
+ function resolveInboundAttachmentUri(input) {
78
+ if (!input.sandboxEnabled) {
79
+ return (0, node_url_1.pathToFileURL)(input.filePath).href;
80
+ }
81
+ const workspacePath = String(input.workspacePath || '').trim();
82
+ if (!workspacePath) {
83
+ return undefined;
84
+ }
85
+ const relativePath = (0, node_path_1.relative)((0, node_path_1.resolve)(workspacePath), (0, node_path_1.resolve)(input.filePath));
86
+ if (!relativePath || relativePath.startsWith('..') || (0, node_path_1.isAbsolute)(relativePath)) {
87
+ return relativePath === ''
88
+ ? new URL(node_path_1.posix.resolve(input.sandboxWorkspacePath || '/workspace'), 'file:///').href
89
+ : undefined;
90
+ }
91
+ const sandboxRelativePath = relativePath.split(node_path_1.sep).join('/');
92
+ return new URL(node_path_1.posix.resolve(input.sandboxWorkspacePath || '/workspace', sandboxRelativePath), 'file:///').href;
93
+ }
@@ -4,9 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.LONG_POLL_TIMEOUT_MS = exports.DEFAULT_CDN_BASE_URL = exports.DEFAULT_BASE_URL = void 0;
7
- exports.normalizeChannelInstanceId = normalizeChannelInstanceId;
8
- exports.channelPlatformKey = channelPlatformKey;
9
- exports.runtimeKey = runtimeKey;
10
7
  exports.getDefaultWeixinStateDir = getDefaultWeixinStateDir;
11
8
  exports.safeFilePart = safeFilePart;
12
9
  exports.getWeixinBufPath = getWeixinBufPath;
@@ -19,20 +16,10 @@ exports.collectWeixinWorkspaceBindings = collectWeixinWorkspaceBindings;
19
16
  const node_fs_1 = __importDefault(require("node:fs"));
20
17
  const node_path_1 = __importDefault(require("node:path"));
21
18
  const desktop_js_1 = require("../../../../../shared/desktop.js");
19
+ const channel_keys_js_1 = require("../shared/channel-keys.js");
22
20
  exports.DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
23
21
  exports.DEFAULT_CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';
24
22
  exports.LONG_POLL_TIMEOUT_MS = 35_000;
25
- function normalizeChannelInstanceId(value, fallback) {
26
- const raw = String(value || '').trim();
27
- const normalized = raw.replace(/[^a-zA-Z0-9._-]/g, '-').replace(/-+/g, '-').slice(0, 80);
28
- return normalized || fallback;
29
- }
30
- function channelPlatformKey(platform, instanceId) {
31
- return instanceId === 'default' ? platform : `${platform}:${instanceId}`;
32
- }
33
- function runtimeKey(workspaceId, instanceId) {
34
- return `${workspaceId}::${instanceId}`;
35
- }
36
23
  function getDefaultWeixinStateDir() {
37
24
  return node_path_1.default.join(process.cwd(), 'weixin-monitor');
38
25
  }
@@ -95,7 +82,7 @@ function collectWeixinWorkspaceBindings(config) {
95
82
  }))
96
83
  .filter((p) => p.platformType === 'weixin')
97
84
  .map((p, index) => {
98
- const instanceId = normalizeChannelInstanceId(p.options.instance_id || p.options.id, index === 0 ? 'default' : `weixin-${index + 1}`);
85
+ const instanceId = (0, channel_keys_js_1.normalizeChannelInstanceId)(p.options.instance_id || p.options.id, index === 0 ? 'default' : `weixin-${index + 1}`);
99
86
  const stateDir = String(p.options.state_dir || getDefaultWeixinStateDir()).trim();
100
87
  const credentials = loadWeixinCredentials(project.name, stateDir, instanceId);
101
88
  const configuredToken = String(p.options.token || '').trim();
@@ -105,7 +92,7 @@ function collectWeixinWorkspaceBindings(config) {
105
92
  workspaceId: project.name,
106
93
  instanceId,
107
94
  displayName: String(p.options.name || p.options.display_name || `WeChat ${index + 1}`).trim(),
108
- platformKey: channelPlatformKey('weixin', instanceId),
95
+ platformKey: (0, channel_keys_js_1.channelPlatformKey)('weixin', instanceId),
109
96
  token: configuredToken || credentials?.token || '',
110
97
  accountId,
111
98
  baseUrl: configuredBaseUrl || credentials?.baseUrl || exports.DEFAULT_BASE_URL,