@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
package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js
CHANGED
|
@@ -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,
|
|
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,
|
|
298
|
-
const state = this.runtime.get((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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:
|
|
931
|
+
path: stored.path,
|
|
914
932
|
kind,
|
|
915
933
|
name: declaredName,
|
|
916
|
-
data:
|
|
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,
|
|
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
|
-
|
|
140
|
+
const messageStr = coerceErrorMessage(message) || defaults.userMessage;
|
|
141
|
+
super(messageStr);
|
|
141
142
|
this.name = 'LocalCoreError';
|
|
142
143
|
this.info = {
|
|
143
144
|
code,
|
|
144
|
-
message:
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|