@kafca/agentdock 0.1.55 → 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 +7 -0
- package/dist/renderer/assets/{Badge-Bg_nNvBL.js → Badge-DjcoXx2J.js} +1 -1
- package/dist/renderer/assets/{Chat-BJveQQKu.js → Chat-C5Bkm9-M.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-Bqn2HJ6f.js → ChatMarkdown-uc36EzyD.js} +1 -1
- package/dist/renderer/assets/{Config-CMtZDRAE.js → Config-DHtQuuLy.js} +1 -1
- package/dist/renderer/assets/{CronList-C-ILB_Ae.js → CronList-BYUnhzSC.js} +1 -1
- package/dist/renderer/assets/{Dashboard-CsKBSQIe.js → Dashboard-BPnGMDg3.js} +2 -2
- package/dist/renderer/assets/{EmptyState-C60g3RMj.js → EmptyState-DIZLiccW.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-CC5C7JYe.js → KnowledgeDetail-CJ9IYOsa.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-3rlvxgWf.js → KnowledgeHome-BLGPwnBO.js} +1 -1
- package/dist/renderer/assets/{Logs-CktemGN0.js → Logs-Dyd2Uwlt.js} +1 -1
- package/dist/renderer/assets/{Modal-BQUW9LSf.js → Modal-CQiUpfl4.js} +1 -1
- package/dist/renderer/assets/{MonitorList-B0hxjuOZ.js → MonitorList-Byg3E3Gt.js} +1 -1
- package/dist/renderer/assets/{Page-DL_Nv2i5.js → Page-7DxXvTMm.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-Djr9-MyT.js → ProjectDetail-Dfr5s0Ja.js} +1 -1
- package/dist/renderer/assets/{ProjectList-BKl4eW7q.js → ProjectList-CDDQjAQ_.js} +1 -1
- package/dist/renderer/assets/{Select-CfRkos3z.js → Select-D6z65IT3.js} +1 -1
- package/dist/renderer/assets/{SessionChat-Dxv8D5DW.js → SessionChat-s7CAaCPL.js} +1 -1
- package/dist/renderer/assets/{SessionList-BJ2GjXvR.js → SessionList-D-OycKKS.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-CAsIl9qf.js → ThreadChat-CLdFynB5.js} +1 -1
- package/dist/renderer/assets/{Workspace-CaUhiGBX.js → Workspace-xfT5z4j_.js} +1 -1
- package/dist/renderer/assets/{arrow-left-D1DbXrLA.js → arrow-left-CV5cL3f4.js} +1 -1
- package/dist/renderer/assets/{book-open-BxC1z1C1.js → book-open-DThvB7pj.js} +1 -1
- package/dist/renderer/assets/{bot-Fw3IzhJA.js → bot-CWQ5-ovl.js} +1 -1
- package/dist/renderer/assets/{check-5zCTgp3A.js → check-DKM7zfWG.js} +1 -1
- package/dist/renderer/assets/{chevron-down-N3PxTNxH.js → chevron-down-Dfw2KU87.js} +1 -1
- package/dist/renderer/assets/{circle-C_chUflo.js → circle-CoP4RA4s.js} +1 -1
- package/dist/renderer/assets/{desktop-DkQTZNhe.js → desktop-CsDYrOOU.js} +1 -1
- package/dist/renderer/assets/{funnel-B8ZAv6I7.js → funnel-DpVLlG3g.js} +1 -1
- package/dist/renderer/assets/{heart-M9A3E4Ed.js → heart-BMD36ejP.js} +1 -1
- package/dist/renderer/assets/{index-DJ8w4e0l.js → index-COxSYBjE.js} +2 -2
- package/dist/renderer/assets/{index-Bwv855ID.js → index-CTGTI-cm.js} +1 -1
- package/dist/renderer/assets/{pencil-X44UaxwR.js → pencil-Dv5WxhdC.js} +1 -1
- package/dist/renderer/assets/{play-ujo-De38.js → play-DnCnft83.js} +1 -1
- package/dist/renderer/assets/{plus-BHgVdG_A.js → plus-DsxIagzS.js} +1 -1
- package/dist/renderer/assets/projects-DgjHcNgt.js +1 -0
- package/dist/renderer/assets/{save-Cz6giKrE.js → save-CST01lIc.js} +1 -1
- package/dist/renderer/assets/{search-DKWvTqi5.js → search-B_bqy6Wd.js} +1 -1
- package/dist/renderer/assets/{server-BsuHcYl4.js → server-ecH4GNwa.js} +1 -1
- package/dist/renderer/assets/{session-utils-DnAWL3eo.js → session-utils-Dd_WKMYW.js} +1 -1
- package/dist/renderer/assets/{sessions-DptxO_BE.js → sessions-BQ33Mv4k.js} +1 -1
- package/dist/renderer/assets/{shield-check-V82O8AC1.js → shield-check-D6lG5Aoc.js} +1 -1
- package/dist/renderer/assets/{trash-2-_Fgwk8mc.js → trash-2-Cefngb6Q.js} +1 -1
- package/dist/renderer/assets/{user-DQKTaUcR.js → user-D_8RSH-T.js} +1 -1
- package/dist/renderer/assets/{wifi-off-vjH-MD2w.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/channel/lark/local-core-lark-gateway.js +110 -80
- 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/local-core-weixin-gateway.js +58 -24
- package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +33 -5
- package/dist-electron/tests/integration/channel-inbound-attachment-store.test.js +117 -0
- package/dist-electron/tests/integration/lark-gateway.test.js +54 -0
- 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/projects-DB1FLZ3A.js +0 -1
package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js
CHANGED
|
@@ -9,10 +9,12 @@ 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");
|
|
18
20
|
const channel_keys_js_1 = require("../shared/channel-keys.js");
|
|
@@ -110,6 +112,7 @@ function createWeixinAttachmentContentPart(att) {
|
|
|
110
112
|
return {
|
|
111
113
|
type: 'image',
|
|
112
114
|
data: att.data,
|
|
115
|
+
...(att.uri ? { uri: att.uri } : {}),
|
|
113
116
|
mimeType: att.mimeType,
|
|
114
117
|
fileName: att.name,
|
|
115
118
|
};
|
|
@@ -149,6 +152,7 @@ function renderBridgeContentForWeixin(event) {
|
|
|
149
152
|
class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGateway {
|
|
150
153
|
processedInboundMessages = new Map();
|
|
151
154
|
pollErrorLogWindows = new Map();
|
|
155
|
+
inboundAttachmentStore = new inbound_attachment_store_js_1.FileSystemInboundAttachmentStore();
|
|
152
156
|
platform = 'weixin';
|
|
153
157
|
constructor(options) {
|
|
154
158
|
super(options);
|
|
@@ -722,7 +726,16 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
|
|
|
722
726
|
const attachmentParts = [];
|
|
723
727
|
if (mediaItems.length > 0) {
|
|
724
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));
|
|
725
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
|
+
}
|
|
726
739
|
try {
|
|
727
740
|
const att = await this.downloadMediaItem(item, msgId, idx, uploadsDir, binding);
|
|
728
741
|
if (att) {
|
|
@@ -887,37 +900,58 @@ class LocalCoreWeixinGateway extends base_channel_gateway_js_1.BaseChannelGatewa
|
|
|
887
900
|
aesKey = decoded.length === 16 ? decoded : decoded.length === 32 ? Buffer.from(decoded.toString('ascii'), 'hex') : undefined;
|
|
888
901
|
}
|
|
889
902
|
const cdnUrl = `${binding.cdnBaseUrl}/download?encrypted_query_param=${encodeURIComponent(encryptQueryParam)}`;
|
|
890
|
-
const resp = await fetch(cdnUrl, { signal: AbortSignal.timeout(30_000) });
|
|
891
|
-
if (!resp.ok)
|
|
892
|
-
throw new Error(`CDN HTTP ${resp.status}`);
|
|
893
|
-
const rawBuf = Buffer.from(await resp.arrayBuffer());
|
|
894
|
-
if (rawBuf.length === 0)
|
|
895
|
-
throw new Error('CDN returned empty data');
|
|
896
|
-
let resultBuf;
|
|
897
|
-
if (aesKey) {
|
|
898
|
-
const decipher = node_crypto_1.default.createDecipheriv('aes-128-ecb', aesKey, null);
|
|
899
|
-
decipher.setAutoPadding(true);
|
|
900
|
-
resultBuf = Buffer.concat([decipher.update(rawBuf), decipher.final()]);
|
|
901
|
-
}
|
|
902
|
-
else {
|
|
903
|
-
resultBuf = rawBuf;
|
|
904
|
-
}
|
|
905
|
-
const { ext, kind } = this.sniffExtAndKind(resultBuf);
|
|
906
903
|
const declaredName = String(itemData?.file_name ?? (item.type === transport_js_1.IMAGE_ITEM_TYPE ? 'image' : 'file'));
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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);
|
|
913
930
|
return {
|
|
914
|
-
path:
|
|
931
|
+
path: stored.path,
|
|
915
932
|
kind,
|
|
916
933
|
name: declaredName,
|
|
917
|
-
data:
|
|
934
|
+
data: stored.data,
|
|
918
935
|
mimeType: kind === 'image' ? this.mimeTypeForImageExt(ext) : undefined,
|
|
936
|
+
...(uri ? { uri } : {}),
|
|
919
937
|
};
|
|
920
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
|
+
}
|
|
921
955
|
mimeTypeForImageExt(ext) {
|
|
922
956
|
if (ext === '.jpg')
|
|
923
957
|
return 'image/jpeg';
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
});
|
|
@@ -9,6 +9,7 @@ const node_fs_1 = require("node:fs");
|
|
|
9
9
|
const node_os_1 = require("node:os");
|
|
10
10
|
const node_path_1 = require("node:path");
|
|
11
11
|
const node_stream_1 = require("node:stream");
|
|
12
|
+
const node_url_1 = require("node:url");
|
|
12
13
|
const local_core_lark_gateway_js_1 = require("../../services/local-ai-core/src/channel/lark/local-core-lark-gateway.js");
|
|
13
14
|
const runtime_state_js_1 = require("../../services/local-ai-core/src/channel/lark/runtime-state.js");
|
|
14
15
|
function extractLarkCreatedMessage(request) {
|
|
@@ -1174,6 +1175,7 @@ function findLarkPostMdText(content) {
|
|
|
1174
1175
|
});
|
|
1175
1176
|
(0, node_test_1.default)('lark image messages are downloaded and forwarded as generic channel image parts', async () => {
|
|
1176
1177
|
const sentMessages = [];
|
|
1178
|
+
const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'lark-image-receive-'));
|
|
1177
1179
|
const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
1178
1180
|
const client = {
|
|
1179
1181
|
im: {
|
|
@@ -1230,6 +1232,7 @@ function findLarkPostMdText(content) {
|
|
|
1230
1232
|
}],
|
|
1231
1233
|
}),
|
|
1232
1234
|
getWorkspaceRouter: () => ({
|
|
1235
|
+
getWorkspaceRegistryEntry: async () => ({ path: tempDir }),
|
|
1233
1236
|
getThreadSessionKey: (threadId) => `session:${threadId}`,
|
|
1234
1237
|
sendThreadMessage: async (threadId, content) => {
|
|
1235
1238
|
sentMessages.push({ threadId, content });
|
|
@@ -1266,6 +1269,10 @@ function findLarkPostMdText(content) {
|
|
|
1266
1269
|
strict_1.default.deepEqual(sentMessages[0]?.content?.contentParts?.map((part) => part.type), ['text', 'image']);
|
|
1267
1270
|
strict_1.default.equal(sentMessages[0]?.content?.contentParts?.[1]?.mimeType, 'image/png');
|
|
1268
1271
|
strict_1.default.equal(sentMessages[0]?.content?.contentParts?.[1]?.data, pngBytes.toString('base64'));
|
|
1272
|
+
const imagePath = (0, node_path_1.join)(tempDir, '.agentdock', 'channel-uploads', 'lark', 'default', 'msg-image-1-img-key-1.png');
|
|
1273
|
+
strict_1.default.equal(sentMessages[0]?.content?.contentParts?.[1]?.uri, (0, node_url_1.pathToFileURL)(imagePath).href);
|
|
1274
|
+
strict_1.default.deepEqual((0, node_fs_1.readFileSync)(imagePath), pngBytes);
|
|
1275
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
1269
1276
|
});
|
|
1270
1277
|
(0, node_test_1.default)('lark file messages are downloaded and forwarded as generic channel file parts', async () => {
|
|
1271
1278
|
const sentMessages = [];
|
|
@@ -1368,6 +1375,53 @@ function findLarkPostMdText(content) {
|
|
|
1368
1375
|
strict_1.default.deepEqual((0, node_fs_1.readFileSync)(sentMessages[0]?.content?.contentParts?.[1]?.path), fileBytes);
|
|
1369
1376
|
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
1370
1377
|
});
|
|
1378
|
+
(0, node_test_1.default)('lark file messages are not downloaded before the sender is authorized', async () => {
|
|
1379
|
+
let resourceDownloads = 0;
|
|
1380
|
+
let inboundMessage;
|
|
1381
|
+
const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
|
|
1382
|
+
store: {
|
|
1383
|
+
getAuthorizedUser: () => undefined,
|
|
1384
|
+
},
|
|
1385
|
+
readConfig: async () => null,
|
|
1386
|
+
getWorkspaceRouter: () => ({}),
|
|
1387
|
+
eventBus: { emit: () => { }, on: () => () => { } },
|
|
1388
|
+
});
|
|
1389
|
+
const internals = gateway;
|
|
1390
|
+
internals.runtime.set('default', {
|
|
1391
|
+
workspaceId: 'default',
|
|
1392
|
+
enabled: true,
|
|
1393
|
+
status: 'running',
|
|
1394
|
+
connected: true,
|
|
1395
|
+
autoApprove: false,
|
|
1396
|
+
client: {
|
|
1397
|
+
im: {
|
|
1398
|
+
messageResource: {
|
|
1399
|
+
get: async () => {
|
|
1400
|
+
resourceDownloads += 1;
|
|
1401
|
+
throw new Error('should not download');
|
|
1402
|
+
},
|
|
1403
|
+
},
|
|
1404
|
+
},
|
|
1405
|
+
},
|
|
1406
|
+
});
|
|
1407
|
+
internals.handleInboundMessage = async (message) => {
|
|
1408
|
+
inboundMessage = message;
|
|
1409
|
+
};
|
|
1410
|
+
await internals.handleMessageEvent('default', {
|
|
1411
|
+
event: {
|
|
1412
|
+
sender: { sender_id: { user_id: 'user-1' } },
|
|
1413
|
+
message: {
|
|
1414
|
+
message_id: 'msg-file-unauthorized',
|
|
1415
|
+
message_type: 'file',
|
|
1416
|
+
chat_id: 'chat-1',
|
|
1417
|
+
content: JSON.stringify({ file_key: 'file-key-1', file_name: 'private.pdf' }),
|
|
1418
|
+
},
|
|
1419
|
+
},
|
|
1420
|
+
});
|
|
1421
|
+
strict_1.default.equal(resourceDownloads, 0);
|
|
1422
|
+
strict_1.default.equal(inboundMessage.text, '[File: private.pdf]');
|
|
1423
|
+
strict_1.default.deepEqual(inboundMessage.contentParts, [{ type: 'text', text: '[File: private.pdf]' }]);
|
|
1424
|
+
});
|
|
1371
1425
|
(0, node_test_1.default)('lark group text messages strip the bot mention before dispatching', async () => {
|
|
1372
1426
|
const sentMessages = [];
|
|
1373
1427
|
const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
|
|
@@ -0,0 +1,41 @@
|
|
|
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 local_core_errors_js_1 = require("../../services/local-ai-core/src/kernel/local-core-errors.js");
|
|
9
|
+
(0, node_test_1.default)('LocalCoreError coerces non-string message into a string', () => {
|
|
10
|
+
const err = new local_core_errors_js_1.LocalCoreError('internal_error', { weird: 'shape' });
|
|
11
|
+
strict_1.default.equal(typeof err.info.message, 'string');
|
|
12
|
+
strict_1.default.equal(err.message, err.info.message);
|
|
13
|
+
strict_1.default.ok(err.info.message.includes('"weird"'));
|
|
14
|
+
const line = (0, local_core_errors_js_1.formatLogError)(err.info);
|
|
15
|
+
strict_1.default.ok(!line.includes('[object Object]'), `log line leaked [object Object]: ${line}`);
|
|
16
|
+
});
|
|
17
|
+
(0, node_test_1.default)('toLocalCoreErrorInfo stringifies Error whose message is an object', () => {
|
|
18
|
+
const raw = new Error('');
|
|
19
|
+
raw.message = { nested: 'value' };
|
|
20
|
+
const info = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(raw);
|
|
21
|
+
strict_1.default.equal(typeof info.message, 'string');
|
|
22
|
+
const line = (0, local_core_errors_js_1.formatLogError)(info);
|
|
23
|
+
strict_1.default.ok(!line.includes('[object Object]'), `log line leaked [object Object]: ${line}`);
|
|
24
|
+
});
|
|
25
|
+
(0, node_test_1.default)('toLocalCoreErrorInfo preserves runtime_protocol_timeout classification for timeout string', () => {
|
|
26
|
+
const info = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(new Error('Timed out waiting for ACP session/prompt after 900000ms'));
|
|
27
|
+
strict_1.default.equal(info.code, 'runtime_protocol_timeout');
|
|
28
|
+
strict_1.default.equal(typeof info.message, 'string');
|
|
29
|
+
});
|
|
30
|
+
(0, node_test_1.default)('formatLogError renders cause suffix when present', () => {
|
|
31
|
+
const err = new local_core_errors_js_1.LocalCoreError('runtime_exited', 'child crashed', { cause: 'SIGTERM' });
|
|
32
|
+
const line = (0, local_core_errors_js_1.formatLogError)(err.info);
|
|
33
|
+
strict_1.default.equal(line, 'runtime_exited: child crashed cause=SIGTERM');
|
|
34
|
+
});
|
|
35
|
+
(0, node_test_1.default)('toLocalCoreErrorInfo carries details through LocalCoreError instances', () => {
|
|
36
|
+
const base = new local_core_errors_js_1.LocalCoreError('runtime_protocol_timeout', 'Timed out waiting for ACP session/prompt after 100ms');
|
|
37
|
+
const info = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(base, 'internal_error', { threadId: 'thread-x', runtimeId: 'hermes' });
|
|
38
|
+
strict_1.default.equal(info.code, 'runtime_protocol_timeout');
|
|
39
|
+
strict_1.default.equal(info.details?.threadId, 'thread-x');
|
|
40
|
+
strict_1.default.equal(info.details?.runtimeId, 'hermes');
|
|
41
|
+
});
|
|
@@ -5,10 +5,44 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const node_test_1 = __importDefault(require("node:test"));
|
|
7
7
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_os_1 = require("node:os");
|
|
10
11
|
const node_path_1 = require("node:path");
|
|
12
|
+
const node_url_1 = require("node:url");
|
|
11
13
|
const local_core_weixin_gateway_js_1 = require("../../services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js");
|
|
14
|
+
(0, node_test_1.default)('weixin inbound attachment source streams AES-decrypted files into the shared store', async () => {
|
|
15
|
+
const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'weixin-file-receive-'));
|
|
16
|
+
const originalFetch = globalThis.fetch;
|
|
17
|
+
try {
|
|
18
|
+
const plaintext = Buffer.from('%PDF streamed file');
|
|
19
|
+
const aesKey = node_crypto_1.default.randomBytes(16);
|
|
20
|
+
const cipher = node_crypto_1.default.createCipheriv('aes-128-ecb', aesKey, null);
|
|
21
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
22
|
+
globalThis.fetch = (async () => new Response(encrypted));
|
|
23
|
+
const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
|
|
24
|
+
store: {},
|
|
25
|
+
readConfig: async () => null,
|
|
26
|
+
getWorkspaceRouter: () => ({}),
|
|
27
|
+
eventBus: { emit: () => { }, on: () => () => { } },
|
|
28
|
+
});
|
|
29
|
+
const downloaded = await gateway.downloadMediaItem({
|
|
30
|
+
type: 4,
|
|
31
|
+
file_item: {
|
|
32
|
+
file_name: 'report.pdf',
|
|
33
|
+
aeskey: aesKey.toString('hex'),
|
|
34
|
+
media: { encrypt_query_param: 'download-token' },
|
|
35
|
+
},
|
|
36
|
+
}, 'msg-1', 0, tempDir, { cdnBaseUrl: 'https://weixin.example' });
|
|
37
|
+
strict_1.default.equal(downloaded.kind, 'file');
|
|
38
|
+
strict_1.default.deepEqual((0, node_fs_1.readFileSync)(downloaded.path), plaintext);
|
|
39
|
+
strict_1.default.equal(downloaded.data, undefined);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
globalThis.fetch = originalFetch;
|
|
43
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
12
46
|
(0, node_test_1.default)('weixin channel can encrypt, upload, and send a local file', async () => {
|
|
13
47
|
const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'weixin-file-send-'));
|
|
14
48
|
const originalFetch = globalThis.fetch;
|
|
@@ -388,6 +422,7 @@ const local_core_weixin_gateway_js_1 = require("../../services/local-ai-core/src
|
|
|
388
422
|
(0, node_test_1.default)('weixin downloaded image attachment keeps image data content part', () => {
|
|
389
423
|
const part = (0, local_core_weixin_gateway_js_1.createWeixinAttachmentContentPart)({
|
|
390
424
|
path: '/tmp/image.png',
|
|
425
|
+
uri: (0, node_url_1.pathToFileURL)('/tmp/image.png').href,
|
|
391
426
|
kind: 'image',
|
|
392
427
|
name: 'image.png',
|
|
393
428
|
data: 'aW1n',
|
|
@@ -396,6 +431,7 @@ const local_core_weixin_gateway_js_1 = require("../../services/local-ai-core/src
|
|
|
396
431
|
strict_1.default.deepEqual(part, {
|
|
397
432
|
type: 'image',
|
|
398
433
|
data: 'aW1n',
|
|
434
|
+
uri: (0, node_url_1.pathToFileURL)('/tmp/image.png').href,
|
|
399
435
|
mimeType: 'image/png',
|
|
400
436
|
fileName: 'image.png',
|
|
401
437
|
});
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{D as t}from"./index-DJ8w4e0l.js";const e=()=>t.get("/projects"),r=o=>t.get(`/projects/${o}`);export{r as g,e as l};
|