@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.
Files changed (57) hide show
  1. package/README.md +7 -0
  2. package/dist/renderer/assets/{Badge-Bg_nNvBL.js → Badge-DjcoXx2J.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BJveQQKu.js → Chat-C5Bkm9-M.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-Bqn2HJ6f.js → ChatMarkdown-uc36EzyD.js} +1 -1
  5. package/dist/renderer/assets/{Config-CMtZDRAE.js → Config-DHtQuuLy.js} +1 -1
  6. package/dist/renderer/assets/{CronList-C-ILB_Ae.js → CronList-BYUnhzSC.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-CsKBSQIe.js → Dashboard-BPnGMDg3.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-C60g3RMj.js → EmptyState-DIZLiccW.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-CC5C7JYe.js → KnowledgeDetail-CJ9IYOsa.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-3rlvxgWf.js → KnowledgeHome-BLGPwnBO.js} +1 -1
  11. package/dist/renderer/assets/{Logs-CktemGN0.js → Logs-Dyd2Uwlt.js} +1 -1
  12. package/dist/renderer/assets/{Modal-BQUW9LSf.js → Modal-CQiUpfl4.js} +1 -1
  13. package/dist/renderer/assets/{MonitorList-B0hxjuOZ.js → MonitorList-Byg3E3Gt.js} +1 -1
  14. package/dist/renderer/assets/{Page-DL_Nv2i5.js → Page-7DxXvTMm.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-Djr9-MyT.js → ProjectDetail-Dfr5s0Ja.js} +1 -1
  16. package/dist/renderer/assets/{ProjectList-BKl4eW7q.js → ProjectList-CDDQjAQ_.js} +1 -1
  17. package/dist/renderer/assets/{Select-CfRkos3z.js → Select-D6z65IT3.js} +1 -1
  18. package/dist/renderer/assets/{SessionChat-Dxv8D5DW.js → SessionChat-s7CAaCPL.js} +1 -1
  19. package/dist/renderer/assets/{SessionList-BJ2GjXvR.js → SessionList-D-OycKKS.js} +1 -1
  20. package/dist/renderer/assets/{ThreadChat-CAsIl9qf.js → ThreadChat-CLdFynB5.js} +1 -1
  21. package/dist/renderer/assets/{Workspace-CaUhiGBX.js → Workspace-xfT5z4j_.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-D1DbXrLA.js → arrow-left-CV5cL3f4.js} +1 -1
  23. package/dist/renderer/assets/{book-open-BxC1z1C1.js → book-open-DThvB7pj.js} +1 -1
  24. package/dist/renderer/assets/{bot-Fw3IzhJA.js → bot-CWQ5-ovl.js} +1 -1
  25. package/dist/renderer/assets/{check-5zCTgp3A.js → check-DKM7zfWG.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-N3PxTNxH.js → chevron-down-Dfw2KU87.js} +1 -1
  27. package/dist/renderer/assets/{circle-C_chUflo.js → circle-CoP4RA4s.js} +1 -1
  28. package/dist/renderer/assets/{desktop-DkQTZNhe.js → desktop-CsDYrOOU.js} +1 -1
  29. package/dist/renderer/assets/{funnel-B8ZAv6I7.js → funnel-DpVLlG3g.js} +1 -1
  30. package/dist/renderer/assets/{heart-M9A3E4Ed.js → heart-BMD36ejP.js} +1 -1
  31. package/dist/renderer/assets/{index-DJ8w4e0l.js → index-COxSYBjE.js} +2 -2
  32. package/dist/renderer/assets/{index-Bwv855ID.js → index-CTGTI-cm.js} +1 -1
  33. package/dist/renderer/assets/{pencil-X44UaxwR.js → pencil-Dv5WxhdC.js} +1 -1
  34. package/dist/renderer/assets/{play-ujo-De38.js → play-DnCnft83.js} +1 -1
  35. package/dist/renderer/assets/{plus-BHgVdG_A.js → plus-DsxIagzS.js} +1 -1
  36. package/dist/renderer/assets/projects-DgjHcNgt.js +1 -0
  37. package/dist/renderer/assets/{save-Cz6giKrE.js → save-CST01lIc.js} +1 -1
  38. package/dist/renderer/assets/{search-DKWvTqi5.js → search-B_bqy6Wd.js} +1 -1
  39. package/dist/renderer/assets/{server-BsuHcYl4.js → server-ecH4GNwa.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-DnAWL3eo.js → session-utils-Dd_WKMYW.js} +1 -1
  41. package/dist/renderer/assets/{sessions-DptxO_BE.js → sessions-BQ33Mv4k.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-V82O8AC1.js → shield-check-D6lG5Aoc.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-_Fgwk8mc.js → trash-2-Cefngb6Q.js} +1 -1
  44. package/dist/renderer/assets/{user-DQKTaUcR.js → user-D_8RSH-T.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-vjH-MD2w.js → wifi-off-DB9D-pOu.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +1 -1
  48. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +110 -80
  49. package/dist-electron/services/local-ai-core/src/channel/shared/inbound-attachment-store.js +93 -0
  50. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +58 -24
  51. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +33 -5
  52. package/dist-electron/tests/integration/channel-inbound-attachment-store.test.js +117 -0
  53. package/dist-electron/tests/integration/lark-gateway.test.js +54 -0
  54. package/dist-electron/tests/integration/local-core-errors.test.js +41 -0
  55. package/dist-electron/tests/integration/weixin-gateway.test.js +36 -0
  56. package/package.json +1 -1
  57. package/dist/renderer/assets/projects-DB1FLZ3A.js +0 -1
@@ -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 safeName = declaredName.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 64);
908
- const safeMsgId = msgId.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 48);
909
- const fileName = `${safeMsgId}-${idx}-${safeName}${ext}`;
910
- const filePath = node_path_1.default.join(uploadsDir, fileName);
911
- node_fs_1.default.mkdirSync(uploadsDir, { recursive: true });
912
- node_fs_1.default.writeFileSync(filePath, resultBuf);
904
+ const stored = await this.inboundAttachmentStore.save({
905
+ directory: uploadsDir,
906
+ storedFileName: `${msgId}-${idx}-${declaredName}`,
907
+ displayFileName: declaredName,
908
+ maxBytes: WEIXIN_MAX_UPLOAD_FILE_SIZE,
909
+ includeBase64: ({ prefix }) => this.sniffExtAndKind(prefix).kind === 'image',
910
+ finalizeStoredFileName: ({ storedFileName, prefix }) => `${storedFileName}${this.sniffExtAndKind(prefix).ext}`,
911
+ source: {
912
+ open: async () => {
913
+ const resp = await fetch(cdnUrl, { signal: AbortSignal.timeout(30_000) });
914
+ if (!resp.ok)
915
+ throw new Error(`CDN HTTP ${resp.status}`);
916
+ if (!resp.body)
917
+ throw new Error('CDN returned no response body');
918
+ const rawStream = node_stream_1.Readable.fromWeb(resp.body);
919
+ if (!aesKey) {
920
+ return { stream: rawStream };
921
+ }
922
+ const decipher = node_crypto_1.default.createDecipheriv('aes-128-ecb', aesKey, null);
923
+ decipher.setAutoPadding(true);
924
+ return { stream: rawStream.pipe(decipher) };
925
+ },
926
+ },
927
+ });
928
+ const { ext, kind } = this.sniffExtAndKind(stored.prefix);
929
+ const uri = await this.resolveInboundAttachmentUri(binding, stored.path);
913
930
  return {
914
- path: filePath,
931
+ path: stored.path,
915
932
  kind,
916
933
  name: declaredName,
917
- data: kind === 'image' ? resultBuf.toString('base64') : undefined,
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
- super(message || defaults.userMessage);
140
+ const messageStr = coerceErrorMessage(message) || defaults.userMessage;
141
+ super(messageStr);
141
142
  this.name = 'LocalCoreError';
142
143
  this.info = {
143
144
  code,
144
- message: message || defaults.userMessage,
145
+ message: messageStr,
145
146
  userMessage: input.userMessage || defaults.userMessage,
146
147
  severity: input.severity || defaults.severity,
147
148
  retryable: input.retryable ?? defaults.retryable,
@@ -158,9 +159,9 @@ function toLocalCoreErrorInfo(error, fallbackCode = 'internal_error', details =
158
159
  }
159
160
  const maybeInfo = error?.info;
160
161
  if (maybeInfo?.code && maybeInfo.message) {
161
- return mergeDetails(maybeInfo, details);
162
+ return mergeDetails(ensureInfoMessageString(maybeInfo), details);
162
163
  }
163
- const message = error instanceof Error ? error.message : String(error || '');
164
+ const message = coerceErrorMessage(error instanceof Error ? error.message : error);
164
165
  const classifiedCode = classifyErrorMessage(message, fallbackCode);
165
166
  return new LocalCoreError(classifiedCode, message || DEFAULTS[classifiedCode].userMessage, {
166
167
  details: Object.keys(details).length ? details : undefined,
@@ -171,7 +172,8 @@ function formatUserError(info) {
171
172
  }
172
173
  function formatLogError(info) {
173
174
  const suffix = info.cause ? ` cause=${info.cause}` : '';
174
- return `${info.code}: ${info.message}${suffix}`;
175
+ const message = typeof info.message === 'string' ? info.message : safeStringify(info.message);
176
+ return `${info.code}: ${message}${suffix}`;
175
177
  }
176
178
  function errorInfoToHttpBody(info) {
177
179
  return {
@@ -253,3 +255,29 @@ function mergeDetails(info, details) {
253
255
  },
254
256
  };
255
257
  }
258
+ function ensureInfoMessageString(info) {
259
+ if (typeof info.message === 'string') {
260
+ return info;
261
+ }
262
+ return { ...info, message: safeStringify(info.message) };
263
+ }
264
+ function coerceErrorMessage(value) {
265
+ if (typeof value === 'string') {
266
+ return value;
267
+ }
268
+ if (value === null || value === undefined) {
269
+ return '';
270
+ }
271
+ return safeStringify(value);
272
+ }
273
+ function safeStringify(value) {
274
+ if (typeof value === 'string') {
275
+ return value;
276
+ }
277
+ try {
278
+ return JSON.stringify(value);
279
+ }
280
+ catch {
281
+ return String(value);
282
+ }
283
+ }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@kafca/agentdock",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "license": "PolyForm-Noncommercial-1.0.0",
5
5
  "type": "module",
6
6
  "main": "dist-electron/electron/main.js",
@@ -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};