@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.
- package/README.md +12 -0
- package/dist/renderer/assets/{Badge-Rs12sArE.js → Badge-DjcoXx2J.js} +1 -1
- package/dist/renderer/assets/{Chat-C9KfsjuN.js → Chat-C5Bkm9-M.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-C3m7Mxpl.js → ChatMarkdown-uc36EzyD.js} +1 -1
- package/dist/renderer/assets/{Config-BhIq2lQa.js → Config-DHtQuuLy.js} +1 -1
- package/dist/renderer/assets/{CronList-CenDzorW.js → CronList-BYUnhzSC.js} +1 -1
- package/dist/renderer/assets/{Dashboard-BmeP5ocP.js → Dashboard-BPnGMDg3.js} +2 -2
- package/dist/renderer/assets/{EmptyState-BGONB4VS.js → EmptyState-DIZLiccW.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-Ba3PuI7k.js → KnowledgeDetail-CJ9IYOsa.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-DkKXM7Bd.js → KnowledgeHome-BLGPwnBO.js} +1 -1
- package/dist/renderer/assets/{Logs-DTITWY-y.js → Logs-Dyd2Uwlt.js} +1 -1
- package/dist/renderer/assets/{Modal-BvFew60s.js → Modal-CQiUpfl4.js} +1 -1
- package/dist/renderer/assets/{MonitorList-DruqInIG.js → MonitorList-Byg3E3Gt.js} +1 -1
- package/dist/renderer/assets/{Page-CLjmm8c6.js → Page-7DxXvTMm.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-BCFaJig0.js → ProjectDetail-Dfr5s0Ja.js} +1 -1
- package/dist/renderer/assets/{ProjectList-Btg-mgwA.js → ProjectList-CDDQjAQ_.js} +1 -1
- package/dist/renderer/assets/{Select-CAwaG7ol.js → Select-D6z65IT3.js} +1 -1
- package/dist/renderer/assets/{SessionChat-CHPrm0Zs.js → SessionChat-s7CAaCPL.js} +1 -1
- package/dist/renderer/assets/{SessionList-0aGulton.js → SessionList-D-OycKKS.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-BkSc0hKN.js → ThreadChat-CLdFynB5.js} +1 -1
- package/dist/renderer/assets/Workspace-xfT5z4j_.js +19 -0
- package/dist/renderer/assets/{arrow-left-Co19QKUc.js → arrow-left-CV5cL3f4.js} +1 -1
- package/dist/renderer/assets/{book-open-CtlUo2yw.js → book-open-DThvB7pj.js} +1 -1
- package/dist/renderer/assets/{bot-D-3jCeDr.js → bot-CWQ5-ovl.js} +1 -1
- package/dist/renderer/assets/{check-Cf5Y5H-1.js → check-DKM7zfWG.js} +1 -1
- package/dist/renderer/assets/{chevron-down-D1tfS-tx.js → chevron-down-Dfw2KU87.js} +1 -1
- package/dist/renderer/assets/{circle-DJOCyk2U.js → circle-CoP4RA4s.js} +1 -1
- package/dist/renderer/assets/{desktop-CmZMgSXn.js → desktop-CsDYrOOU.js} +1 -1
- package/dist/renderer/assets/{funnel-ZRCc1DCy.js → funnel-DpVLlG3g.js} +1 -1
- package/dist/renderer/assets/{heart-BuJEvv8x.js → heart-BMD36ejP.js} +1 -1
- package/dist/renderer/assets/{index-CDWUe2Sw.js → index-COxSYBjE.js} +2 -2
- package/dist/renderer/assets/{index-0gZsyozU.js → index-CTGTI-cm.js} +1 -1
- package/dist/renderer/assets/{pencil-sISh8tMs.js → pencil-Dv5WxhdC.js} +1 -1
- package/dist/renderer/assets/{play-CccQpZit.js → play-DnCnft83.js} +1 -1
- package/dist/renderer/assets/{plus-hxAkcfQ0.js → plus-DsxIagzS.js} +1 -1
- package/dist/renderer/assets/projects-DgjHcNgt.js +1 -0
- package/dist/renderer/assets/{save-By5b-_0Q.js → save-CST01lIc.js} +1 -1
- package/dist/renderer/assets/{search-CLzr87Rw.js → search-B_bqy6Wd.js} +1 -1
- package/dist/renderer/assets/{server-CubB1eW5.js → server-ecH4GNwa.js} +1 -1
- package/dist/renderer/assets/{session-utils-Cz45af_C.js → session-utils-Dd_WKMYW.js} +1 -1
- package/dist/renderer/assets/{sessions-C8np5fRp.js → sessions-BQ33Mv4k.js} +1 -1
- package/dist/renderer/assets/{shield-check-CemAc1Ry.js → shield-check-D6lG5Aoc.js} +1 -1
- package/dist/renderer/assets/{trash-2-DFKhii8D.js → trash-2-Cefngb6Q.js} +1 -1
- package/dist/renderer/assets/{user-CaR8Rru_.js → user-D_8RSH-T.js} +1 -1
- package/dist/renderer/assets/{wifi-off-C8miJu8i.js → wifi-off-DB9D-pOu.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +1 -1
- package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +1 -1
- package/dist-electron/services/local-ai-core/src/acp/store/runtime-config-store.js +39 -28
- package/dist-electron/services/local-ai-core/src/channel/lark/config.js +4 -16
- package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +154 -65
- package/dist-electron/services/local-ai-core/src/channel/shared/channel-keys.js +16 -0
- package/dist-electron/services/local-ai-core/src/channel/shared/inbound-attachment-store.js +93 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +3 -16
- package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +70 -35
- package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +33 -5
- package/dist-electron/services/local-ai-core/src/runtime/channel-service.js +0 -40
- package/dist-electron/services/local-ai-core/src/runtime/external-service.js +1 -14
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -14
- package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +16 -0
- package/dist-electron/services/local-ai-core/src/scheduler/channel-execution-policy.js +4 -6
- package/dist-electron/tests/electron/sandbox-config.test.js +5 -0
- package/dist-electron/tests/integration/channel-inbound-attachment-store.test.js +117 -0
- package/dist-electron/tests/integration/lark-gateway.test.js +81 -1
- package/dist-electron/tests/integration/local-core-errors.test.js +41 -0
- package/dist-electron/tests/integration/weixin-gateway.test.js +36 -0
- package/package.json +1 -1
- package/dist/renderer/assets/Workspace-DXJ3BGia.js +0 -19
- 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,
|
|
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,
|
|
281
|
-
const state = this.runtime.get((0,
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
676
|
-
contentParts.push(
|
|
683
|
+
if (!mayMaterializeAttachments) {
|
|
684
|
+
contentParts.push({ type: 'text', text: '[Image]' });
|
|
677
685
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
689
|
-
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
|
734
|
-
|
|
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:
|
|
741
|
-
|
|
742
|
-
|
|
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
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
size:
|
|
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,
|
|
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,
|