@kafca/agentdock 0.1.23 → 0.1.25

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 (93) hide show
  1. package/README.md +3 -0
  2. package/dist/renderer/assets/{Badge-C2K5apfV.js → Badge-Bn2h-Qkf.js} +1 -1
  3. package/dist/renderer/assets/{Chat-DI2YeVKb.js → Chat-Cgo9aneg.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DMAr15Oi.js → ChatMarkdown-CYzHfSiR.js} +1 -1
  5. package/dist/renderer/assets/{Config-7rP3EuUf.js → Config-DljB2-eP.js} +1 -1
  6. package/dist/renderer/assets/{CronList-81QslYE8.js → CronList-DeVbiY9a.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-Db2T7rSZ.js → Dashboard-fSqx5sm8.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BBJ3cTvB.js → EmptyState-DgqKDwlq.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Dz_6flG3.js → KnowledgeDetail-u8s2SmPV.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-DIh046mN.js → KnowledgeHome-BdQw8nbY.js} +1 -1
  11. package/dist/renderer/assets/{Logs-BXBtHJLf.js → Logs-DSbndMpy.js} +1 -1
  12. package/dist/renderer/assets/{Modal-BB2Av_Xs.js → Modal-0d0EmAuK.js} +1 -1
  13. package/dist/renderer/assets/{Page-DPSdvMgU.js → Page-CVbOH_Kh.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-C6EWGvGg.js → ProjectDetail-C91tF7uA.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-BBo5ygnS.js → ProjectList-D78Pc-BQ.js} +1 -1
  16. package/dist/renderer/assets/{Select-CNX-Tx_O.js → Select-BbKzAIuO.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-BIn46jqZ.js → SessionChat-BERvL5e4.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-CZt6f1Rd.js → SessionList-Bomz335M.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-CLr3amU2.js +23 -0
  20. package/dist/renderer/assets/{Workspace-C_4tdpjX.js → Workspace-In91z4W2.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-cHr5wkpq.js → arrow-left-FBnVKKBl.js} +1 -1
  22. package/dist/renderer/assets/{book-open-4nx5C8T-.js → book-open-4_CuP6Sa.js} +1 -1
  23. package/dist/renderer/assets/{check-exTS-R-x.js → check-vsoVjvac.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-DSvxXQa0.js → chevron-down-DFpmFTJW.js} +1 -1
  25. package/dist/renderer/assets/{circle-DyvEovdw.js → circle-vRULegER.js} +1 -1
  26. package/dist/renderer/assets/desktop-BkMW-h5p.js +4 -0
  27. package/dist/renderer/assets/{desktop-B-KVT10D.js → desktop-ul0Tpzyc.js} +1 -1
  28. package/dist/renderer/assets/{funnel-DA9qVdTy.js → funnel-Gu0E_hJ_.js} +1 -1
  29. package/dist/renderer/assets/{heart-DuEV0ZRJ.js → heart-DsdofOHf.js} +1 -1
  30. package/dist/renderer/assets/{index-Biz-aexG.js → index-BD3aCyBa.js} +3 -3
  31. package/dist/renderer/assets/{index-BgflqkCN.js → index-pJUFobU7.js} +1 -1
  32. package/dist/renderer/assets/{pencil-DBDxeNmO.js → pencil-DbKmV8nD.js} +1 -1
  33. package/dist/renderer/assets/{play-IzlP83_7.js → play-BDMsRxSm.js} +1 -1
  34. package/dist/renderer/assets/{plug-CuT-qFnW.js → plug-t2dMl78-.js} +1 -1
  35. package/dist/renderer/assets/{plus-DEqgkn63.js → plus-BUb6zOAe.js} +1 -1
  36. package/dist/renderer/assets/projects-4F6D_Q8t.js +1 -0
  37. package/dist/renderer/assets/{save-D4sRo07y.js → save-C-_5rQdv.js} +1 -1
  38. package/dist/renderer/assets/{search-DbPZLuV8.js → search-R3ekHFPM.js} +1 -1
  39. package/dist/renderer/assets/{server-D1jUQmwN.js → server-BTuvHf5n.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Aife8ep2.js → session-utils-ClX6VPFG.js} +1 -1
  41. package/dist/renderer/assets/{sessions-DaH3H7gC.js → sessions-AFKIC-ub.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-BGwUFWNr.js → shield-check-CqMW8CHe.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-CIFBioBa.js → trash-2-BNLcHyUw.js} +1 -1
  44. package/dist/renderer/assets/{user-CnKcvHdo.js → user-BCCN8FYK.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-ClO53WGa.js → wifi-off-BsNsx3oX.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/agent-runtime-detector.test.js +64 -0
  48. package/dist-electron/electron/architecture-docs.test.js +56 -0
  49. package/dist-electron/electron/channel-content.test.js +37 -0
  50. package/dist-electron/electron/lac-cli.test.js +297 -4
  51. package/dist-electron/electron/local-core-acp-content.test.js +39 -0
  52. package/dist-electron/electron/local-core-contracts.test.js +67 -0
  53. package/dist-electron/electron/local-core-refactor.test.js +789 -3
  54. package/dist-electron/electron/plugin-kernel.test.js +108 -0
  55. package/dist-electron/electron/renderer-packaging.test.js +77 -0
  56. package/dist-electron/electron/workspace-task-store.test.js +181 -0
  57. package/dist-electron/packages/contracts/src/local-core.js +110 -0
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +67 -6
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-content.js +25 -4
  60. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-permission-lifecycle.js +76 -0
  61. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +201 -0
  62. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +3 -2
  63. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +29 -0
  64. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +74 -46
  65. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +112 -210
  66. package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +60 -0
  67. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +5 -48
  68. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +83 -28
  69. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +11 -0
  70. package/dist-electron/services/local-ai-core/src/channel/shared/content.js +23 -0
  71. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +13 -1
  72. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +31 -29
  73. package/dist-electron/services/local-ai-core/src/cli/lac.js +22 -17
  74. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +1 -0
  75. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -0
  76. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +221 -10
  77. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +39 -9
  78. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -0
  79. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +1 -0
  80. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +439 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/server.js +208 -303
  82. package/dist-electron/services/local-ai-core/src/scheduler/job-id.js +15 -0
  83. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +30 -5
  84. package/dist-electron/shared/desktop.js +34 -8
  85. package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +187 -0
  86. package/dist-electron/src/pages/Threads/thread-chat-model.js +58 -0
  87. package/dist-electron/src/pages/Threads/thread-chat-page-state.js +96 -0
  88. package/dist-electron/src/pages/Threads/thread-chat-permission.js +18 -0
  89. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +347 -0
  90. package/package.json +4 -2
  91. package/dist/renderer/assets/ThreadChat-BotD0gKQ.js +0 -23
  92. package/dist/renderer/assets/desktop-CVibRyAw.js +0 -4
  93. package/dist/renderer/assets/projects-DDtDN1rc.js +0 -1
@@ -39,6 +39,7 @@ const node_crypto_1 = require("node:crypto");
39
39
  const node_fs_1 = require("node:fs");
40
40
  const node_path_1 = require("node:path");
41
41
  const desktop_js_1 = require("../../../../../shared/desktop.js");
42
+ const content_js_1 = require("../shared/content.js");
42
43
  const file_utils_js_1 = require("../shared/file-utils.js");
43
44
  const cards_js_1 = require("./cards.js");
44
45
  const config_js_1 = require("./config.js");
@@ -46,6 +47,8 @@ const registration_js_1 = require("./registration.js");
46
47
  const runtime_state_js_1 = require("./runtime-state.js");
47
48
  const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
48
49
  const LARK_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
50
+ const LARK_FINAL_PATCH_INTERVAL_MS = 900;
51
+ const LARK_PROGRESS_PATCH_INTERVAL_MS = 3000;
49
52
  class LocalCoreLarkGateway extends node_events_1.EventEmitter {
50
53
  options;
51
54
  // Lark returns 200340 when card action events are not enabled in the app's
@@ -245,6 +248,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
245
248
  const file = await (0, file_utils_js_1.prepareChannelFile)({
246
249
  path: part.path,
247
250
  fileName: part.fileName,
251
+ workspacePath: typeof part.metadata?.workspacePath === 'string' ? part.metadata.workspacePath : undefined,
248
252
  maxBytes: LARK_MAX_UPLOAD_FILE_SIZE,
249
253
  platformLabel: 'Lark',
250
254
  });
@@ -474,9 +478,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
474
478
  }
475
479
  const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, rendered);
476
480
  const shouldThrottle = event.type === 'update_message' &&
477
- rendered.isFinal &&
478
481
  existingMessageId &&
479
- Date.now() - (turn.lastPatchedAtByMessageId[existingMessageId] || 0) < 900;
482
+ Date.now() - (turn.lastPatchedAtByMessageId[existingMessageId] || 0) < (rendered.isFinal ? LARK_FINAL_PATCH_INTERVAL_MS : LARK_PROGRESS_PATCH_INTERVAL_MS);
480
483
  this.options.log?.(`localcore-lark bridge event type=${event.type} sessionKey=${sessionKey} hasMessageId=${Boolean(existingMessageId)} throttle=${shouldThrottle}`);
481
484
  if (shouldThrottle) {
482
485
  return;
@@ -619,6 +622,10 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
619
622
  if (slashCommand?.name === 'new') {
620
623
  const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'Lark'} ${new Date().toLocaleTimeString()}`;
621
624
  const nextThread = await router.createThread(input.workspaceId, title);
625
+ const inheritedMode = this.options.store.getThreadRow?.(threadId)?.agent_mode || '';
626
+ if (inheritedMode && inheritedMode !== 'default') {
627
+ this.options.store.updateThreadAgentMode?.(nextThread.id, inheritedMode);
628
+ }
622
629
  const now = new Date().toISOString();
623
630
  this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
624
631
  this.options.store.upsertPlatformThreadBinding({
@@ -649,8 +656,10 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
649
656
  return { paired: true, threadId };
650
657
  }
651
658
  this.options.store.clearPlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId);
652
- const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
653
- await router.sendThreadMessage(threadId, this.createThreadMessageInput(wrappedText, input.contentParts));
659
+ const wrappedText = slashCommand
660
+ ? input.text
661
+ : (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
662
+ await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
654
663
  return { paired: true, threadId };
655
664
  }
656
665
  close() {
@@ -857,15 +866,24 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
857
866
  }
858
867
  }
859
868
  }
869
+ if (messageType === 'file') {
870
+ const fileKey = String(parsedContent.file_key || '').trim();
871
+ if (fileKey) {
872
+ try {
873
+ contentParts.push(await this.downloadMessageFile(workspaceId, String(message.message_id || ''), fileKey, String(parsedContent.file_name || parsedContent.name || '').trim(), instanceId));
874
+ }
875
+ catch (error) {
876
+ const errorText = `[File download failed: ${error instanceof Error ? error.message : String(error)}]`;
877
+ contentParts.push({ type: 'text', text: errorText });
878
+ this.options.log?.(`localcore-lark file download failed for ${workspaceId}: message=${String(message.message_id || '')} fileKey=${fileKey} error=${error instanceof Error ? error.message : String(error)}`);
879
+ }
880
+ }
881
+ }
860
882
  if (contentParts.length === 0) {
861
883
  this.options.log?.(`localcore-lark ignored unsupported message for ${workspaceId}: type=${String(message.message_type || 'unknown')} contentKeys=${JSON.stringify(Object.keys(parsedContent))}`);
862
884
  return;
863
885
  }
864
- const displayText = text || (contentParts.some((part) => part.type === 'image') ? '[Image]' : contentParts
865
- .filter((part) => part.type === 'text')
866
- .map((part) => part.text)
867
- .join('\n\n')
868
- .trim());
886
+ const displayText = text || this.summarizeInboundContentParts(contentParts);
869
887
  const platformUserId = String(sender.sender_id?.user_id || sender.sender_id?.open_id || '').trim();
870
888
  const chatId = String(message.chat_id || platformUserId).trim();
871
889
  if (!platformUserId || !chatId) {
@@ -887,25 +905,6 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
887
905
  contentParts,
888
906
  });
889
907
  }
890
- createWrappedContentParts(wrappedText, parts) {
891
- const nonTextParts = Array.isArray(parts)
892
- ? parts.filter((part) => part.type !== 'text')
893
- : [];
894
- return [
895
- { type: 'text', text: wrappedText },
896
- ...nonTextParts,
897
- ];
898
- }
899
- createThreadMessageInput(wrappedText, parts) {
900
- const hasNonTextPart = Array.isArray(parts) && parts.some((part) => part.type !== 'text');
901
- if (!hasNonTextPart) {
902
- return wrappedText;
903
- }
904
- return {
905
- displayText: wrappedText,
906
- contentParts: this.createWrappedContentParts(wrappedText, parts),
907
- };
908
- }
909
908
  async downloadMessageImage(workspaceId, messageId, imageKey, instanceId) {
910
909
  const state = this.resolveRuntimeState(workspaceId, instanceId).state;
911
910
  if (!state?.client) {
@@ -935,6 +934,58 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
935
934
  fileName: `${imageKey}.${this.sniffImageExtension(buffer)}`,
936
935
  };
937
936
  }
937
+ async downloadMessageFile(workspaceId, messageId, fileKey, fileName, instanceId) {
938
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
939
+ if (!state?.client) {
940
+ throw new Error('Lark client is not connected');
941
+ }
942
+ if (!messageId) {
943
+ throw new Error('Lark file message is missing message_id');
944
+ }
945
+ const resource = await state.client.im.messageResource.get({
946
+ path: {
947
+ message_id: messageId,
948
+ file_key: fileKey,
949
+ },
950
+ params: {
951
+ type: 'file',
952
+ },
953
+ });
954
+ const buffer = await this.readLarkResourceBuffer(resource);
955
+ if (buffer.length === 0) {
956
+ throw new Error('Lark file resource is empty');
957
+ }
958
+ return {
959
+ type: 'file',
960
+ data: buffer.toString('base64'),
961
+ mimeType: this.extractHeaderMimeType(resource?.headers) || 'application/octet-stream',
962
+ fileName: fileName || fileKey,
963
+ size: buffer.length,
964
+ };
965
+ }
966
+ summarizeInboundContentParts(parts) {
967
+ const text = parts
968
+ .filter((part) => part.type === 'text')
969
+ .map((part) => part.text)
970
+ .filter(Boolean)
971
+ .join('\n\n')
972
+ .trim();
973
+ if (text)
974
+ return text;
975
+ const attachmentSummary = parts
976
+ .map((part) => {
977
+ if (part.type === 'image') {
978
+ return '[Image]';
979
+ }
980
+ if (part.type === 'file') {
981
+ return part.fileName ? `[File: ${part.fileName}]` : '[File]';
982
+ }
983
+ return '';
984
+ })
985
+ .filter(Boolean)
986
+ .join('\n');
987
+ return attachmentSummary;
988
+ }
938
989
  async readLarkResourceBuffer(resource) {
939
990
  const readable = resource?.getReadableStream?.();
940
991
  if (!readable || typeof readable.on !== 'function') {
@@ -1052,6 +1103,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1052
1103
  return this.larkModulePromise;
1053
1104
  }
1054
1105
  async sendTextAsCard(state, chatId, text, buttonRows = [], sessionKey, threadId) {
1106
+ const startedAt = Date.now();
1055
1107
  const response = await state.client.im.message.create({
1056
1108
  params: {
1057
1109
  receive_id_type: this.resolveReceiveIdType(chatId),
@@ -1062,6 +1114,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1062
1114
  content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1063
1115
  },
1064
1116
  });
1117
+ this.options.log?.(`localcore-lark card create took ${Date.now() - startedAt}ms textBytes=${Buffer.byteLength(text || '', 'utf8')}`);
1065
1118
  return String(response?.data?.message_id || '').trim();
1066
1119
  }
1067
1120
  resolveReceiveIdType(receiveId) {
@@ -1092,6 +1145,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1092
1145
  }
1093
1146
  }
1094
1147
  async patchTextCard(state, messageId, text, buttonRows = [], sessionKey, threadId) {
1148
+ const startedAt = Date.now();
1095
1149
  await state.client.im.message.patch({
1096
1150
  path: {
1097
1151
  message_id: messageId,
@@ -1100,6 +1154,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1100
1154
  content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1101
1155
  },
1102
1156
  });
1157
+ this.options.log?.(`localcore-lark card patch took ${Date.now() - startedAt}ms message=${messageId} textBytes=${Buffer.byteLength(text || '', 'utf8')}`);
1103
1158
  }
1104
1159
  generatePairingCode() {
1105
1160
  return String((0, node_crypto_1.randomInt)(100000, 1000000));
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createLarkTurnState = createLarkTurnState;
4
4
  exports.foldToolResultForLark = foldToolResultForLark;
5
+ exports.isPendingToolProgressForLark = isPendingToolProgressForLark;
5
6
  exports.consumeLarkBridgeEvent = consumeLarkBridgeEvent;
6
7
  exports.renderLarkBridgeEventMessage = renderLarkBridgeEventMessage;
7
8
  exports.getLarkRenderedMessageId = getLarkRenderedMessageId;
@@ -33,6 +34,10 @@ function foldToolResultForLark(content) {
33
34
  return normalized;
34
35
  return parts.slice(0, 2).join(' - ');
35
36
  }
37
+ function isPendingToolProgressForLark(content) {
38
+ const normalized = foldToolResultForLark(content).toLowerCase();
39
+ return normalized.startsWith('🔧 ') && normalized.includes(' - pending');
40
+ }
36
41
  function consumeLarkBridgeEvent(turn, event, options) {
37
42
  const content = foldToolResultForLark(String(event.content || '').trim());
38
43
  if (event.type === 'typing_start') {
@@ -98,6 +103,9 @@ function renderLarkBridgeEventMessage(turn, event) {
98
103
  return renderProgressMessage(event.previewHandle || 'thinking-preview', content);
99
104
  }
100
105
  if (content.startsWith('🔧 ')) {
106
+ if (isPendingToolProgressForLark(content)) {
107
+ return { key: 'noop', text: '', buttonRows: [], isFinal: false };
108
+ }
101
109
  return renderProgressMessage(progressKey('tool', event), content);
102
110
  }
103
111
  return {
@@ -116,6 +124,9 @@ function renderLarkBridgeEventMessage(turn, event) {
116
124
  return renderProgressMessage(progressKey('thinking', event), content);
117
125
  }
118
126
  if (content.startsWith('🔧 ')) {
127
+ if (isPendingToolProgressForLark(content)) {
128
+ return { key: 'noop', text: '', buttonRows: [], isFinal: false };
129
+ }
119
130
  return renderProgressMessage(progressKey('tool', event), content);
120
131
  }
121
132
  if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWrappedChannelContentParts = createWrappedChannelContentParts;
4
+ exports.createChannelThreadMessageInput = createChannelThreadMessageInput;
5
+ function createWrappedChannelContentParts(wrappedText, parts) {
6
+ const nonTextParts = Array.isArray(parts)
7
+ ? parts.filter((part) => part.type !== 'text')
8
+ : [];
9
+ return [
10
+ { type: 'text', text: wrappedText },
11
+ ...nonTextParts,
12
+ ];
13
+ }
14
+ function createChannelThreadMessageInput(wrappedText, parts) {
15
+ const hasNonTextPart = Array.isArray(parts) && parts.some((part) => part.type !== 'text');
16
+ if (!hasNonTextPart) {
17
+ return wrappedText;
18
+ }
19
+ return {
20
+ displayText: wrappedText,
21
+ contentParts: createWrappedChannelContentParts(wrappedText, parts),
22
+ };
23
+ }
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareChannelFile = prepareChannelFile;
4
+ exports.resolveChannelFilePath = resolveChannelFilePath;
4
5
  exports.sanitizeChannelFileName = sanitizeChannelFileName;
5
6
  const promises_1 = require("node:fs/promises");
6
7
  const node_path_1 = require("node:path");
7
8
  async function prepareChannelFile(input) {
8
- const filePath = String(input.path || '').trim();
9
+ const filePath = resolveChannelFilePath(input.path, input.workspacePath);
9
10
  if (!filePath) {
10
11
  throw new Error('Missing file path');
11
12
  }
@@ -27,6 +28,17 @@ async function prepareChannelFile(input) {
27
28
  fileSize: fileStat.size,
28
29
  };
29
30
  }
31
+ function resolveChannelFilePath(filePath, workspacePath) {
32
+ const normalized = String(filePath || '').trim();
33
+ if (!normalized) {
34
+ return '';
35
+ }
36
+ if ((0, node_path_1.isAbsolute)(normalized)) {
37
+ return normalized;
38
+ }
39
+ const workspaceRoot = String(workspacePath || '').trim();
40
+ return workspaceRoot ? (0, node_path_1.resolve)(workspaceRoot, normalized) : normalized;
41
+ }
30
42
  function sanitizeChannelFileName(fileName) {
31
43
  const normalized = String(fileName || '').trim().replace(/[\/\\]+/g, '_');
32
44
  return normalized || 'file';
@@ -4,12 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.LocalCoreWeixinGateway = void 0;
7
+ exports.createWeixinAttachmentContentPart = createWeixinAttachmentContentPart;
7
8
  const node_crypto_1 = __importDefault(require("node:crypto"));
8
9
  const node_fs_1 = __importDefault(require("node:fs"));
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const node_events_1 = require("node:events");
11
12
  const node_crypto_2 = require("node:crypto");
12
13
  const desktop_js_1 = require("../../../../../shared/desktop.js");
14
+ const content_js_1 = require("../shared/content.js");
13
15
  const file_utils_js_1 = require("../shared/file-utils.js");
14
16
  const config_js_1 = require("./config.js");
15
17
  const transport_js_1 = require("./transport.js");
@@ -97,6 +99,23 @@ function splitTextByUtf8Bytes(text, maxBytes) {
97
99
  pushCurrent();
98
100
  return chunks;
99
101
  }
102
+ function createWeixinAttachmentContentPart(att) {
103
+ if (att.kind === 'image') {
104
+ if (!att.data)
105
+ return null;
106
+ return {
107
+ type: 'image',
108
+ data: att.data,
109
+ mimeType: att.mimeType,
110
+ fileName: att.name,
111
+ };
112
+ }
113
+ return {
114
+ type: 'file',
115
+ path: att.path,
116
+ fileName: att.name,
117
+ };
118
+ }
100
119
  function truncateTextByUtf8Bytes(text, maxBytes) {
101
120
  const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
102
121
  if (utf8ByteLength(normalized) <= maxBytes)
@@ -624,6 +643,10 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
624
643
  if (slashCommand?.name === 'new') {
625
644
  const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'WeChat'} ${new Date().toLocaleTimeString()}`;
626
645
  const nextThread = await router.createThread(input.workspaceId, title);
646
+ const inheritedMode = this.options.store.getThreadRow?.(threadId)?.agent_mode || '';
647
+ if (inheritedMode && inheritedMode !== 'default') {
648
+ this.options.store.updateThreadAgentMode?.(nextThread.id, inheritedMode);
649
+ }
627
650
  const now = new Date().toISOString();
628
651
  this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
629
652
  this.options.store.upsertPlatformThreadBinding({
@@ -657,8 +680,10 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
657
680
  return { paired: true, threadId };
658
681
  }
659
682
  this.options.store.updatePlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId, input.contextToken || input.messageId, platformKey);
660
- const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
661
- await router.sendThreadMessage(threadId, this.createThreadMessageInput(wrappedText, input.contentParts));
683
+ const wrappedText = slashCommand
684
+ ? input.text
685
+ : (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
686
+ await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
662
687
  return { paired: true, threadId };
663
688
  }
664
689
  // ==================== Private: Bindings ====================
@@ -825,14 +850,9 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
825
850
  if (att) {
826
851
  attachmentText += attachmentText ? '\n' : '';
827
852
  attachmentText += att.kind === 'image' ? `[Image: ${att.path}]` : `[File "${att.name}": ${att.path}]`;
828
- if (att.kind === 'image' && att.data) {
829
- attachmentParts.push({
830
- type: 'image',
831
- data: att.data,
832
- mimeType: att.mimeType,
833
- fileName: att.name,
834
- });
835
- }
853
+ const part = createWeixinAttachmentContentPart(att);
854
+ if (part)
855
+ attachmentParts.push(part);
836
856
  }
837
857
  }
838
858
  catch (dlErr) {
@@ -906,6 +926,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
906
926
  const file = await (0, file_utils_js_1.prepareChannelFile)({
907
927
  path: part.path,
908
928
  fileName: part.fileName,
929
+ workspacePath: typeof part.metadata?.workspacePath === 'string' ? part.metadata.workspacePath : undefined,
909
930
  maxBytes: WEIXIN_MAX_UPLOAD_FILE_SIZE,
910
931
  platformLabel: 'WeChat',
911
932
  });
@@ -1017,25 +1038,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
1017
1038
  mimeType: kind === 'image' ? this.mimeTypeForImageExt(ext) : undefined,
1018
1039
  };
1019
1040
  }
1020
- createWrappedContentParts(wrappedText, parts) {
1021
- const nonTextParts = Array.isArray(parts)
1022
- ? parts.filter((part) => part.type !== 'text')
1023
- : [];
1024
- return [
1025
- { type: 'text', text: wrappedText },
1026
- ...nonTextParts,
1027
- ];
1028
- }
1029
- createThreadMessageInput(wrappedText, parts) {
1030
- const hasNonTextPart = Array.isArray(parts) && parts.some((part) => part.type !== 'text');
1031
- if (!hasNonTextPart) {
1032
- return wrappedText;
1033
- }
1034
- return {
1035
- displayText: wrappedText,
1036
- contentParts: this.createWrappedContentParts(wrappedText, parts),
1037
- };
1038
- }
1039
1041
  mimeTypeForImageExt(ext) {
1040
1042
  if (ext === '.jpg')
1041
1043
  return 'image/jpeg';
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runCli = runCli;
7
7
  const node_process_1 = __importDefault(require("node:process"));
8
+ const index_js_1 = require("../../../../packages/contracts/src/index.js");
9
+ const job_id_js_1 = require("../scheduler/job-id.js");
8
10
  const DEFAULT_BASE_URL = 'http://127.0.0.1:9831/api/local/v1';
9
11
  async function runCli(argv, env = node_process_1.default.env, io = node_process_1.default) {
10
12
  try {
@@ -51,7 +53,8 @@ async function runCli(argv, env = node_process_1.default.env, io = node_process_
51
53
  async function handleChannelSendFile(flags, env, io, json) {
52
54
  const context = resolveContext(flags, env);
53
55
  const filePath = getRequiredFlag(flags, 'path');
54
- const platform = getFlag(flags, 'platform') || context.platform;
56
+ const rawPlatform = getFlag(flags, 'platform') || context.platform;
57
+ const platform = rawPlatform ? (0, index_js_1.normalizeChannelPlatform)(rawPlatform) : '';
55
58
  if (!platform) {
56
59
  throw new Error('channel send-file requires a platform. Set LOCAL_AI_PLATFORM or pass --platform.');
57
60
  }
@@ -72,6 +75,7 @@ async function handleChannelSendFile(flags, env, io, json) {
72
75
  type: 'file',
73
76
  path: filePath,
74
77
  fileName: getFlag(flags, 'name') || undefined,
78
+ metadata: context.workspacePath ? { workspacePath: context.workspacePath } : undefined,
75
79
  }],
76
80
  });
77
81
  const file = result.attachments?.[0];
@@ -113,8 +117,8 @@ async function handleAdd(flags, env, io, json) {
113
117
  description,
114
118
  enabled: true,
115
119
  });
116
- print(json, io.stdout, job, [
117
- `Created scheduler job ${job.id}`,
120
+ print(json, io.stdout, presentJob(job), [
121
+ `Created scheduler job ${(0, job_id_js_1.toPublicScheduledJobId)(job.id)}`,
118
122
  `Schedule: ${job.cronExpr || ''}`,
119
123
  `Execution mode: ${job.executionMode}`,
120
124
  `Description: ${job.description}`,
@@ -132,7 +136,7 @@ async function handleList(flags, env, io, json) {
132
136
  const jobs = threadId
133
137
  ? response.jobs.filter((job) => job.route.threadId === threadId)
134
138
  : response.jobs;
135
- print(json, io.stdout, { jobs }, jobs.length === 0 ? 'No scheduler jobs.' : jobs.map(formatJobLine).join('\n'));
139
+ print(json, io.stdout, { jobs: jobs.map(presentJob) }, jobs.length === 0 ? 'No scheduler jobs.' : jobs.map(formatJobLine).join('\n'));
136
140
  return 0;
137
141
  }
138
142
  async function handleInfo(jobId, flags, env, io, json) {
@@ -142,7 +146,7 @@ async function handleInfo(jobId, flags, env, io, json) {
142
146
  const context = resolveContext(flags, env);
143
147
  const job = await request(context.baseUrl, 'GET', `/scheduler/jobs/${encodeURIComponent(jobId)}`);
144
148
  const runs = await request(context.baseUrl, 'GET', `/scheduler/jobs/${encodeURIComponent(jobId)}/runs`);
145
- print(json, io.stdout, { job, runs: runs.runs }, formatJobDetails(job, runs.runs[0]));
149
+ print(json, io.stdout, { job: presentJob(job), runs: runs.runs }, formatJobDetails(job, runs.runs[0]));
146
150
  return 0;
147
151
  }
148
152
  async function handleEdit(jobId, flags, env, io, json) {
@@ -168,14 +172,14 @@ async function handleEdit(jobId, flags, env, io, json) {
168
172
  input.enabled = enabled;
169
173
  }
170
174
  if (executionMode) {
171
- input.executionMode = parseExecutionMode(executionMode);
175
+ input.executionMode = (0, index_js_1.normalizeScheduledJobExecutionMode)(executionMode);
172
176
  }
173
177
  if (Object.keys(input).length === 0) {
174
178
  throw new Error('scheduler edit requires at least one editable field.');
175
179
  }
176
180
  const context = resolveContext(flags, env);
177
181
  const job = await request(context.baseUrl, 'PATCH', `/scheduler/jobs/${encodeURIComponent(jobId)}`, input);
178
- print(json, io.stdout, job, `Updated scheduler job ${job.id}`);
182
+ print(json, io.stdout, presentJob(job), `Updated scheduler job ${(0, job_id_js_1.toPublicScheduledJobId)(job.id)}`);
179
183
  return 0;
180
184
  }
181
185
  async function handleDelete(jobId, flags, env, io, json) {
@@ -223,6 +227,7 @@ function resolveContext(flags, env) {
223
227
  return {
224
228
  baseUrl: getFlag(flags, 'base-url') || String(env.LOCAL_AI_CORE_BASE || DEFAULT_BASE_URL),
225
229
  workspaceId: getFlag(flags, 'workspace') || String(env.LOCAL_AI_WORKSPACE_ID || ''),
230
+ workspacePath: getFlag(flags, 'workspace-path') || String(env.LOCAL_AI_WORKSPACE_PATH || ''),
226
231
  threadId: normalizeMaybeBooleanFlag(getFlag(flags, 'thread')) || String(env.LOCAL_AI_THREAD_ID || ''),
227
232
  platform,
228
233
  routeType: String(env.LOCAL_AI_ROUTE_TYPE || '') || (platform === 'lark' && chatId && platformUserId ? 'channel.chat' : ''),
@@ -284,13 +289,7 @@ function normalizeMaybeBooleanFlag(value) {
284
289
  return value === 'true' ? '' : value;
285
290
  }
286
291
  function getExecutionMode(flags) {
287
- return parseExecutionMode(getFlag(flags, 'execution-mode') || 'same-thread');
288
- }
289
- function parseExecutionMode(value) {
290
- if (value === 'same-thread' || value === 'side-thread') {
291
- return value;
292
- }
293
- throw new Error('Flag --execution-mode must be same-thread or side-thread');
292
+ return (0, index_js_1.normalizeScheduledJobExecutionMode)(getFlag(flags, 'execution-mode'));
294
293
  }
295
294
  function print(asJson, output, payload, text) {
296
295
  output.write(asJson ? `${JSON.stringify(payload, null, 2)}\n` : `${text}\n`);
@@ -304,16 +303,16 @@ function printUsage(output) {
304
303
  ' lac scheduler edit <job-id> [--cron "<expr>"] [--message "<text>"] [--desc "<label>"] [--enabled true|false] [--execution-mode same-thread|side-thread] [--json]',
305
304
  ' lac scheduler del <job-id> [--json]',
306
305
  ' lac scheduler run <job-id> [--json]',
307
- ' lac channel send-file --path "<file>" [--target <chat-or-user-id>] [--workspace <id>] [--platform lark] [--name <filename>] [--json]',
306
+ ' lac channel send-file --path "<file>" [--target <chat-or-user-id>] [--workspace <id>] [--workspace-path <path>] [--platform lark] [--name <filename>] [--json]',
308
307
  ].join('\n') + '\n');
309
308
  }
310
309
  function formatJobLine(job) {
311
310
  const schedule = job.triggerType === 'cron' ? job.cronExpr || '' : job.runAt || '';
312
- return `${job.id} | ${job.enabled ? 'enabled' : 'disabled'} | ${job.executionMode} | ${schedule} | ${job.description}`;
311
+ return `${(0, job_id_js_1.toPublicScheduledJobId)(job.id)} | ${job.enabled ? 'enabled' : 'disabled'} | ${job.executionMode} | ${schedule} | ${job.description}`;
313
312
  }
314
313
  function formatJobDetails(job, latestRun) {
315
314
  return [
316
- `Job: ${job.id}`,
315
+ `Job: ${(0, job_id_js_1.toPublicScheduledJobId)(job.id)}`,
317
316
  `Workspace: ${job.workspaceId}`,
318
317
  `Platform: ${job.platform}`,
319
318
  `Thread: ${job.route.threadId || ''}`,
@@ -325,6 +324,12 @@ function formatJobDetails(job, latestRun) {
325
324
  latestRun ? `Latest run: ${latestRun.status} @ ${latestRun.triggeredAt}` : 'Latest run: none',
326
325
  ].join('\n');
327
326
  }
327
+ function presentJob(job) {
328
+ return {
329
+ ...job,
330
+ id: (0, job_id_js_1.toPublicScheduledJobId)(job.id),
331
+ };
332
+ }
328
333
  function formatError(error) {
329
334
  return error instanceof Error ? error.message : String(error);
330
335
  }
@@ -163,6 +163,7 @@ function bootstrapLocalCoreRuntime(options) {
163
163
  }
164
164
  const agentPlugins = [
165
165
  localCoreAgentPlugin,
166
+ (0, agent_localcore_acp_plugin_js_1.createBuiltinPiAgentPlugin)(),
166
167
  (0, agent_localcore_acp_plugin_js_1.createBuiltinOpencodeAgentPlugin)(),
167
168
  (0, agent_localcore_acp_plugin_js_1.createBuiltinCodexAgentPlugin)(),
168
169
  (0, agent_localcore_acp_plugin_js_1.createBuiltinClaudeCodeAgentPlugin)(),
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createBuiltinLocalCoreAcpAgentPlugin = createBuiltinLocalCoreAcpAgentPlugin;
4
4
  exports.createBuiltinOpencodeAgentPlugin = createBuiltinOpencodeAgentPlugin;
5
+ exports.createBuiltinPiAgentPlugin = createBuiltinPiAgentPlugin;
5
6
  exports.createBuiltinCodexAgentPlugin = createBuiltinCodexAgentPlugin;
6
7
  exports.createBuiltinClaudeCodeAgentPlugin = createBuiltinClaudeCodeAgentPlugin;
7
8
  exports.createBuiltinStaticAgentCapabilityPlugin = createBuiltinStaticAgentCapabilityPlugin;
@@ -86,6 +87,14 @@ function createBuiltinOpencodeAgentPlugin() {
86
87
  displayName: 'OpenCode',
87
88
  });
88
89
  }
90
+ function createBuiltinPiAgentPlugin() {
91
+ return createBuiltinAgentPlugin({
92
+ pluginId: 'builtin.agent-pi',
93
+ agentType: 'pi',
94
+ match: (normalizedAgentType) => normalizedAgentType === 'pi',
95
+ displayName: 'Pi',
96
+ });
97
+ }
89
98
  function createBuiltinCodexAgentPlugin() {
90
99
  return createBuiltinAgentPlugin({
91
100
  pluginId: 'builtin.agent-codex',