@kafca/agentdock 0.1.24 → 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 (83) hide show
  1. package/README.md +2 -0
  2. package/dist/renderer/assets/{Badge-CXcUMqb-.js → Badge-Bn2h-Qkf.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BRxdPzi-.js → Chat-Cgo9aneg.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-sWGBIfIb.js → ChatMarkdown-CYzHfSiR.js} +1 -1
  5. package/dist/renderer/assets/{Config-DqxuxIGg.js → Config-DljB2-eP.js} +1 -1
  6. package/dist/renderer/assets/{CronList-OgHxbti5.js → CronList-DeVbiY9a.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-DmklXiH1.js → Dashboard-fSqx5sm8.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BvDmi8dq.js → EmptyState-DgqKDwlq.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Bj59Hdpo.js → KnowledgeDetail-u8s2SmPV.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-CigQ-nSo.js → KnowledgeHome-BdQw8nbY.js} +1 -1
  11. package/dist/renderer/assets/{Logs-Bfc7gBpY.js → Logs-DSbndMpy.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CfmiFknz.js → Modal-0d0EmAuK.js} +1 -1
  13. package/dist/renderer/assets/{Page-BxtLEw_b.js → Page-CVbOH_Kh.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-CcUphxGm.js → ProjectDetail-C91tF7uA.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-CVkvDIfy.js → ProjectList-D78Pc-BQ.js} +1 -1
  16. package/dist/renderer/assets/{Select-DgQMZ0MD.js → Select-BbKzAIuO.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-CRmGKfBx.js → SessionChat-BERvL5e4.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-D0E8XbYw.js → SessionList-Bomz335M.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-CLr3amU2.js +23 -0
  20. package/dist/renderer/assets/{Workspace-Q4g4rlRT.js → Workspace-In91z4W2.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-BySN_zhF.js → arrow-left-FBnVKKBl.js} +1 -1
  22. package/dist/renderer/assets/{book-open-BGlrnrTc.js → book-open-4_CuP6Sa.js} +1 -1
  23. package/dist/renderer/assets/{check-BiJogEa5.js → check-vsoVjvac.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-D2fHRJv6.js → chevron-down-DFpmFTJW.js} +1 -1
  25. package/dist/renderer/assets/{circle-CIcjixmI.js → circle-vRULegER.js} +1 -1
  26. package/dist/renderer/assets/{desktop-DAXY9Bxv.js → desktop-BkMW-h5p.js} +2 -2
  27. package/dist/renderer/assets/{desktop-DyA_NYew.js → desktop-ul0Tpzyc.js} +1 -1
  28. package/dist/renderer/assets/{funnel-BUzwb-id.js → funnel-Gu0E_hJ_.js} +1 -1
  29. package/dist/renderer/assets/{heart-DZEQ1z_V.js → heart-DsdofOHf.js} +1 -1
  30. package/dist/renderer/assets/{index-CB_KyLOr.js → index-BD3aCyBa.js} +3 -3
  31. package/dist/renderer/assets/{index-DgsIo4vJ.js → index-pJUFobU7.js} +1 -1
  32. package/dist/renderer/assets/{pencil-DGL4agcl.js → pencil-DbKmV8nD.js} +1 -1
  33. package/dist/renderer/assets/{play-DzasCLM4.js → play-BDMsRxSm.js} +1 -1
  34. package/dist/renderer/assets/{plug-dubNMe_m.js → plug-t2dMl78-.js} +1 -1
  35. package/dist/renderer/assets/{plus-DN4kML4k.js → plus-BUb6zOAe.js} +1 -1
  36. package/dist/renderer/assets/projects-4F6D_Q8t.js +1 -0
  37. package/dist/renderer/assets/{save-CNIc9tjl.js → save-C-_5rQdv.js} +1 -1
  38. package/dist/renderer/assets/{search-C0EGbF3I.js → search-R3ekHFPM.js} +1 -1
  39. package/dist/renderer/assets/{server-BMxQ4zmc.js → server-BTuvHf5n.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Bf5O-iMi.js → session-utils-ClX6VPFG.js} +1 -1
  41. package/dist/renderer/assets/{sessions-BMSs6rEI.js → sessions-AFKIC-ub.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-i3Tf1TUz.js → shield-check-CqMW8CHe.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-SKrcp18f.js → trash-2-BNLcHyUw.js} +1 -1
  44. package/dist/renderer/assets/{user-cNUZddjZ.js → user-BCCN8FYK.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Dyb84ANR.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 +8 -0
  50. package/dist-electron/electron/lac-cli.test.js +248 -0
  51. package/dist-electron/electron/local-core-refactor.test.js +666 -3
  52. package/dist-electron/electron/plugin-kernel.test.js +108 -0
  53. package/dist-electron/electron/renderer-packaging.test.js +77 -0
  54. package/dist-electron/electron/workspace-task-store.test.js +57 -0
  55. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +67 -6
  56. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-permission-lifecycle.js +76 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +201 -0
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +29 -0
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +30 -18
  60. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +112 -210
  61. package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +60 -0
  62. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +15 -3
  63. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +11 -0
  64. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +13 -1
  65. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +8 -1
  66. package/dist-electron/services/local-ai-core/src/cli/lac.js +3 -1
  67. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +1 -0
  68. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +9 -0
  69. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +221 -10
  70. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +39 -9
  71. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +1 -0
  72. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +1 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +439 -0
  74. package/dist-electron/services/local-ai-core/src/runtime/server.js +208 -303
  75. package/dist-electron/shared/desktop.js +7 -3
  76. package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +187 -0
  77. package/dist-electron/src/pages/Threads/thread-chat-model.js +1 -0
  78. package/dist-electron/src/pages/Threads/thread-chat-page-state.js +96 -0
  79. package/dist-electron/src/pages/Threads/thread-chat-permission.js +18 -0
  80. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +277 -0
  81. package/package.json +4 -2
  82. package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +0 -23
  83. package/dist/renderer/assets/projects-migPtfRe.js +0 -1
@@ -2,7 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalCoreAcpTurnCoordinator = void 0;
4
4
  const desktop_js_1 = require("../../../../shared/desktop.js");
5
+ const local_core_acp_progress_js_1 = require("./local-core-acp-progress.js");
6
+ const local_core_acp_permission_lifecycle_js_1 = require("./local-core-acp-permission-lifecycle.js");
5
7
  const workspace_acp_permissions_js_1 = require("./workspace-acp-permissions.js");
8
+ const local_core_slash_commands_js_1 = require("./local-core-slash-commands.js");
6
9
  class LocalCoreAcpTurnCoordinator {
7
10
  options;
8
11
  constructor(options) {
@@ -14,7 +17,7 @@ class LocalCoreAcpTurnCoordinator {
14
17
  if (!currentTurn || !currentRunId) {
15
18
  return;
16
19
  }
17
- const pending = this.getToolCallsInOrder(currentTurn)
20
+ const pending = (0, local_core_acp_progress_js_1.getToolCallsInOrder)(currentTurn)
18
21
  .filter((toolCall) => !toolCall.emitted);
19
22
  if (pending.length === 0) {
20
23
  const title = currentTurn.pendingToolCallTitle?.trim();
@@ -25,14 +28,26 @@ class LocalCoreAcpTurnCoordinator {
25
28
  currentTurn.pendingToolCallTitle = undefined;
26
29
  currentTurn.pendingToolCallId = undefined;
27
30
  currentTurn.pendingToolCallDetail = undefined;
28
- this.emitProgress(session, currentRunId, `🔧 ${title}`, messageId);
31
+ this.emitProgress(session, currentRunId, `🔧 ${title}`, messageId, createToolCallPayload({
32
+ id: currentTurn.activeToolCallKey,
33
+ name: title,
34
+ status: 'running',
35
+ content: '',
36
+ }));
29
37
  return;
30
38
  }
31
39
  for (const toolCall of pending) {
32
40
  toolCall.emitted = true;
33
- this.emitProgress(session, currentRunId, `🔧 ${toolCall.title}`, toolCall.messageId);
41
+ this.emitProgress(session, currentRunId, `🔧 ${toolCall.title}`, toolCall.messageId, createToolCallPayload({
42
+ id: toolCall.key,
43
+ name: toolCall.title,
44
+ status: 'running',
45
+ input: toolCall.input,
46
+ detail: toolCall.detail,
47
+ content: '',
48
+ }));
34
49
  }
35
- this.syncLegacyPendingToolCall(currentTurn, this.resolveFallbackToolCall(currentTurn));
50
+ (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, (0, local_core_acp_progress_js_1.resolveFallbackToolCall)(currentTurn));
36
51
  }
37
52
  getPendingPermissionRequest(session, detail) {
38
53
  const runId = session?.currentRunId;
@@ -80,22 +95,29 @@ class LocalCoreAcpTurnCoordinator {
80
95
  });
81
96
  return;
82
97
  }
83
- const options = Array.isArray(payload.params?.options)
84
- ? payload.params.options
85
- .map((option) => ({
86
- optionId: String(option?.optionId || '').trim(),
87
- name: String(option?.name || option?.optionId || '').trim(),
88
- kind: String(option?.kind || '').trim(),
89
- normalizedAction: (0, workspace_acp_permissions_js_1.normalizePermissionOptionAction)({
90
- optionId: option?.optionId,
91
- name: option?.name,
92
- kind: option?.kind,
93
- }),
94
- }))
95
- .filter((option) => option.optionId)
96
- : [];
98
+ const options = (0, local_core_acp_permission_lifecycle_js_1.parsePermissionOptions)(payload.params?.options);
97
99
  const toolTitle = (0, workspace_acp_permissions_js_1.formatToolCallContent)(payload.params?.toolCall);
98
- const isSchedulerAdd = isSchedulerAddCommand(toolTitle);
100
+ if ((this.options.getThreadAgentMode?.(session.threadId) || local_core_slash_commands_js_1.DEFAULT_AGENT_MODE) === 'bypassPermissions') {
101
+ const selected = options.find((option) => option.normalizedAction === 'allow all')
102
+ || options.find((option) => option.normalizedAction === 'allow')
103
+ || options.find((option) => option.normalizedAction !== 'deny');
104
+ this.options.sendRaw(session, {
105
+ jsonrpc: '2.0',
106
+ id: payload.id,
107
+ result: {
108
+ outcome: selected
109
+ ? {
110
+ outcome: 'selected',
111
+ optionId: selected.optionId,
112
+ }
113
+ : {
114
+ outcome: 'cancelled',
115
+ },
116
+ },
117
+ });
118
+ return;
119
+ }
120
+ const isSchedulerAdd = (0, local_core_acp_permission_lifecycle_js_1.isSchedulerAddCommand)(toolTitle);
99
121
  if (isSchedulerAdd && session.schedulerJobCreatedByRun.get(currentRunId)) {
100
122
  this.options.sendRaw(session, {
101
123
  jsonrpc: '2.0',
@@ -117,45 +139,27 @@ class LocalCoreAcpTurnCoordinator {
117
139
  return;
118
140
  }
119
141
  const buttonRows = (0, workspace_acp_permissions_js_1.toPermissionButtonRows)(options, desktop_js_1.normalizeDesktopBridgeButtonOption);
120
- const permissionRequest = {
142
+ const approvalId = this.options.createApprovalRequest?.((0, local_core_acp_permission_lifecycle_js_1.createPermissionApprovalInput)({
143
+ threadId: session.threadId,
144
+ runId: currentRunId,
145
+ toolTitle,
146
+ options,
147
+ }));
148
+ const permissionRequest = (0, local_core_acp_permission_lifecycle_js_1.createRunningPermissionRequest)({
121
149
  requestId: payload.id,
122
150
  toolTitle,
123
- isSchedulerAdd,
124
151
  options,
125
- };
126
- const approvalId = this.options.createApprovalRequest?.({
127
- threadId: session.threadId,
152
+ approvalId,
153
+ });
154
+ (0, local_core_acp_permission_lifecycle_js_1.applyPendingPermissionRequest)({
155
+ session,
128
156
  runId: currentRunId,
129
- title: toolTitle ? `Approve ${toolTitle}` : 'Approve agent action',
130
- description: toolTitle || 'Agent requested permission before continuing.',
131
- command: toolTitle,
132
- options,
157
+ permissionRequest,
158
+ resolveFallbackToolCall: local_core_acp_progress_js_1.resolveFallbackToolCall,
159
+ syncLegacyPendingToolCall: local_core_acp_progress_js_1.syncLegacyPendingToolCall,
133
160
  });
134
- if (approvalId) {
135
- permissionRequest.approvalId = approvalId;
136
- }
137
- session.pendingPermissionByRun.set(currentRunId, permissionRequest);
138
- if (session.currentTurn) {
139
- session.currentTurn.permission = permissionRequest;
140
- if (toolTitle && toolTitle !== 'Permission required before continuing.') {
141
- session.currentTurn.pendingToolCallDetail = toolTitle;
142
- const toolCall = this.resolveFallbackToolCall(session.currentTurn);
143
- if (toolCall) {
144
- toolCall.detail = toolTitle;
145
- this.syncLegacyPendingToolCall(session.currentTurn, toolCall);
146
- }
147
- }
148
- }
149
161
  this.options.updateRunStatus(currentRunId, session.threadId, 'awaiting_input');
150
- const permissionPrompt = [
151
- '等待工具确认',
152
- '',
153
- toolTitle,
154
- '',
155
- '请选择一个选项继续执行。',
156
- '',
157
- '若按钮没有显示,请直接回复:allow all / allow / deny',
158
- ].join('\n');
162
+ const permissionPrompt = (0, local_core_acp_permission_lifecycle_js_1.createPermissionPrompt)(toolTitle);
159
163
  this.options.appendMessage(session.threadId, 'assistant', permissionPrompt, 'progress');
160
164
  this.options.emitBridge({
161
165
  type: 'buttons',
@@ -181,24 +185,16 @@ class LocalCoreAcpTurnCoordinator {
181
185
  if (update.content?.type !== 'text') {
182
186
  return;
183
187
  }
184
- currentTurn.assistantText += String(update.content.text || '');
185
- if (!currentTurn.previewStarted) {
186
- currentTurn.previewStarted = true;
187
- this.options.emitBridge({
188
- type: 'preview_start',
189
- sessionKey: session.bridgeSessionKey,
190
- replyCtx: currentRunId,
191
- previewHandle: currentTurn.previewHandle,
192
- content: currentTurn.assistantText,
193
- });
188
+ const projection = (0, local_core_acp_progress_js_1.applyAssistantMessageChunk)(currentTurn, String(update.content.text || ''));
189
+ if (!projection) {
194
190
  return;
195
191
  }
196
192
  this.options.emitBridge({
197
- type: 'update_message',
193
+ type: projection.bridgeType,
198
194
  sessionKey: session.bridgeSessionKey,
199
195
  replyCtx: currentRunId,
200
- previewHandle: currentTurn.previewHandle,
201
- content: currentTurn.assistantText,
196
+ previewHandle: projection.previewHandle,
197
+ content: projection.content,
202
198
  });
203
199
  return;
204
200
  }
@@ -207,92 +203,64 @@ class LocalCoreAcpTurnCoordinator {
207
203
  if (update.content?.type !== 'text') {
208
204
  return;
209
205
  }
210
- const thoughtChunk = String(update.content.text || '');
211
- if (!thoughtChunk) {
206
+ const projection = (0, local_core_acp_progress_js_1.applyThoughtChunk)(currentTurn, String(update.content.text || ''));
207
+ if (!projection) {
212
208
  return;
213
209
  }
214
- currentTurn.thoughtText += thoughtChunk;
215
- const content = `💭 ${currentTurn.thoughtText.trim()}`;
216
210
  if (this.options.upsertMessage) {
217
- this.options.upsertMessage(session.threadId, currentTurn.thoughtMessageId, 'assistant', content, 'progress');
218
- }
219
- if (!currentTurn.thoughtPreviewStarted) {
220
- currentTurn.thoughtPreviewStarted = true;
221
- this.options.emitBridge({
222
- type: 'preview_start',
223
- sessionKey: session.bridgeSessionKey,
224
- replyCtx: currentRunId,
225
- previewHandle: currentTurn.thoughtPreviewHandle,
226
- content,
227
- });
228
- return;
211
+ this.options.upsertMessage(session.threadId, projection.messageId, 'assistant', projection.content, 'progress');
229
212
  }
230
213
  this.options.emitBridge({
231
- type: 'update_message',
214
+ type: projection.bridgeType,
232
215
  sessionKey: session.bridgeSessionKey,
233
216
  replyCtx: currentRunId,
234
- previewHandle: currentTurn.thoughtPreviewHandle,
235
- content,
217
+ previewHandle: projection.previewHandle,
218
+ content: projection.content,
236
219
  });
237
220
  return;
238
221
  }
239
222
  case 'tool_call': {
240
- const title = String(update.title || 'Running tool').trim();
241
- const nextSequence = (currentTurn.toolCallSequence || 0) + 1;
242
- currentTurn.toolCallSequence = nextSequence;
243
- const key = this.extractToolCallKey(update) || `sequence:${nextSequence}`;
244
- const toolCall = {
245
- key,
246
- title,
247
- messageId: `${currentRunId}-tool-${nextSequence}`,
248
- sequence: nextSequence,
249
- emitted: false,
250
- };
251
- currentTurn.pendingToolCalls = {
252
- ...(currentTurn.pendingToolCalls || {}),
253
- [key]: toolCall,
254
- };
255
- currentTurn.pendingToolCallOrder = [
256
- ...(currentTurn.pendingToolCallOrder || []).filter((item) => item !== key),
257
- key,
258
- ];
259
- currentTurn.activeToolCallKey = key;
260
- this.syncLegacyPendingToolCall(currentTurn, toolCall);
223
+ const toolCall = (0, local_core_acp_progress_js_1.registerPendingToolCall)({ currentTurn, runId: currentRunId, update });
224
+ (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, toolCall);
261
225
  return;
262
226
  }
263
227
  case 'tool_call_update': {
264
228
  const title = String(update.title || 'Tool update').trim();
265
229
  const status = String(update.status || '').trim();
266
- const content = this.extractToolUpdateContent(update.content);
267
- if (isSchedulerAddCommand(title) && /Created scheduler job\b/.test(content)) {
230
+ const content = (0, local_core_acp_progress_js_1.extractToolUpdateContent)(update.content);
231
+ if ((0, local_core_acp_permission_lifecycle_js_1.isSchedulerAddCommand)(title) && /Created scheduler job\b/.test(content)) {
268
232
  session.schedulerJobCreatedByRun.set(currentRunId, true);
269
233
  }
270
- const toolCall = this.resolveToolCallForUpdate(currentTurn, update);
234
+ const toolCall = (0, local_core_acp_progress_js_1.resolveToolCallForUpdate)(currentTurn, update);
235
+ const updateInput = (0, local_core_acp_progress_js_1.extractToolCallInput)(update);
236
+ if (toolCall && updateInput !== undefined) {
237
+ toolCall.input = updateInput;
238
+ }
271
239
  const toolName = toolCall?.title.trim() || currentTurn.pendingToolCallTitle?.trim();
272
240
  const messageId = toolCall?.messageId || currentTurn.pendingToolCallId;
273
241
  const priorDetail = toolCall ? toolCall.detail?.trim() : currentTurn.pendingToolCallDetail?.trim();
274
- if (this.isEmptyRunningToolUpdate(title, status, content)) {
242
+ if ((0, local_core_acp_progress_js_1.isEmptyRunningToolUpdate)({ title, status, content })) {
275
243
  if (toolCall) {
276
- this.deleteToolCall(currentTurn, toolCall.key);
244
+ (0, local_core_acp_progress_js_1.deletePendingToolCall)(currentTurn, toolCall.key);
277
245
  }
278
246
  else {
279
247
  currentTurn.pendingToolCallTitle = undefined;
280
248
  currentTurn.pendingToolCallId = undefined;
281
249
  currentTurn.pendingToolCallDetail = undefined;
282
250
  }
283
- this.syncLegacyPendingToolCall(currentTurn, this.resolveFallbackToolCall(currentTurn));
251
+ (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, (0, local_core_acp_progress_js_1.resolveFallbackToolCall)(currentTurn));
284
252
  return;
285
253
  }
286
- const displayTitle = this.resolveToolUpdateDisplayTitle(title, status, priorDetail);
287
- if (!this.isTerminalToolStatus(status) && displayTitle && !/^tool update$/i.test(displayTitle)) {
254
+ const displayTitle = (0, local_core_acp_progress_js_1.resolveToolUpdateDisplayTitle)({ title, status, priorDetail });
255
+ if (!(0, local_core_acp_progress_js_1.isTerminalToolStatus)(status) && displayTitle && !/^tool update$/i.test(displayTitle)) {
288
256
  if (toolCall) {
289
257
  toolCall.detail = displayTitle;
290
258
  }
291
259
  currentTurn.pendingToolCallDetail = displayTitle;
292
260
  }
293
- if (this.isTerminalToolStatus(status)) {
261
+ if ((0, local_core_acp_progress_js_1.isTerminalToolStatus)(status)) {
294
262
  if (toolCall) {
295
- this.deleteToolCall(currentTurn, toolCall.key);
263
+ (0, local_core_acp_progress_js_1.deletePendingToolCall)(currentTurn, toolCall.key);
296
264
  }
297
265
  else {
298
266
  currentTurn.pendingToolCallTitle = undefined;
@@ -300,8 +268,16 @@ class LocalCoreAcpTurnCoordinator {
300
268
  currentTurn.pendingToolCallDetail = undefined;
301
269
  }
302
270
  }
303
- this.emitProgress(session, currentRunId, this.formatToolProgressMessage(toolName, displayTitle, status, content), messageId);
304
- this.syncLegacyPendingToolCall(currentTurn, this.resolveFallbackToolCall(currentTurn));
271
+ const toolCallPayload = createToolCallPayload({
272
+ id: toolCall?.key || currentTurn.activeToolCallKey,
273
+ name: toolName || displayTitle || 'Tool update',
274
+ status,
275
+ input: updateInput === undefined ? toolCall?.input : updateInput,
276
+ detail: displayTitle,
277
+ content,
278
+ });
279
+ this.emitProgress(session, currentRunId, (0, local_core_acp_progress_js_1.formatToolProgressMessage)({ toolName, title: displayTitle, status, content }), messageId, toolCallPayload);
280
+ (0, local_core_acp_progress_js_1.syncLegacyPendingToolCall)(currentTurn, (0, local_core_acp_progress_js_1.resolveFallbackToolCall)(currentTurn));
305
281
  return;
306
282
  }
307
283
  case 'plan': {
@@ -310,11 +286,10 @@ class LocalCoreAcpTurnCoordinator {
310
286
  if (entries.length === 0) {
311
287
  return;
312
288
  }
313
- const summary = entries
314
- .map((entry) => String(entry?.content || '').trim())
315
- .filter(Boolean)
316
- .join(' | ');
317
- const content = `💭 ${summary}`;
289
+ const content = (0, local_core_acp_progress_js_1.formatPlanProgress)(entries);
290
+ if (!content) {
291
+ return;
292
+ }
318
293
  this.options.appendMessage(session.threadId, 'assistant', content, 'progress');
319
294
  this.options.emitBridge({
320
295
  type: 'reply',
@@ -328,12 +303,12 @@ class LocalCoreAcpTurnCoordinator {
328
303
  return;
329
304
  }
330
305
  }
331
- emitProgress(session, currentRunId, content, messageId) {
306
+ emitProgress(session, currentRunId, content, messageId, toolCall) {
332
307
  if (messageId && this.options.upsertMessage) {
333
- this.options.upsertMessage(session.threadId, messageId, 'assistant', content, 'progress');
308
+ this.options.upsertMessage(session.threadId, messageId, 'assistant', content, 'progress', toolCall);
334
309
  }
335
310
  else {
336
- this.options.appendMessage(session.threadId, 'assistant', content, 'progress');
311
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress', toolCall);
337
312
  }
338
313
  this.options.emitBridge({
339
314
  type: 'reply',
@@ -341,93 +316,20 @@ class LocalCoreAcpTurnCoordinator {
341
316
  replyCtx: currentRunId,
342
317
  messageId,
343
318
  content,
319
+ toolCall,
344
320
  });
345
321
  }
346
- extractToolUpdateContent(content) {
347
- return Array.isArray(content)
348
- ? content
349
- .map((entry) => entry?.type === 'content' && entry?.content?.type === 'text'
350
- ? String(entry.content.text || '')
351
- : '')
352
- .filter(Boolean)
353
- .join('\n')
354
- : '';
355
- }
356
- formatToolProgressMessage(toolName, title, status, content) {
357
- const detail = [title, status, content].filter(Boolean).join(' - ');
358
- return toolName ? `🔧 ${toolName}: ${detail || 'Tool update'}` : `🔧 ${detail || 'Tool update'}`;
359
- }
360
- resolveToolUpdateDisplayTitle(title, status, priorDetail) {
361
- if (priorDetail && (this.isTerminalToolStatus(status) || /^tool update$/i.test(title.trim()))) {
362
- return priorDetail;
363
- }
364
- if (this.isTerminalToolStatus(status) && /^tool update$/i.test(title.trim())) {
365
- return '';
366
- }
367
- return title;
368
- }
369
- isEmptyRunningToolUpdate(title, status, content) {
370
- return !content.trim() &&
371
- /^running$/i.test(status) &&
372
- (!title.trim() || /^tool update$/i.test(title));
373
- }
374
- isTerminalToolStatus(status) {
375
- return /^(completed|failed|error|cancelled|canceled)$/i.test(status.trim());
376
- }
377
- extractToolCallKey(update) {
378
- for (const key of ['toolCallId', 'tool_call_id', 'callId', 'call_id', 'invocationId', 'invocation_id', 'id']) {
379
- const value = update[key];
380
- if (typeof value === 'string' || typeof value === 'number') {
381
- const normalized = String(value).trim();
382
- if (normalized) {
383
- return normalized;
384
- }
385
- }
386
- }
387
- return '';
388
- }
389
- resolveToolCallForUpdate(currentTurn, update) {
390
- const explicitKey = this.extractToolCallKey(update);
391
- if (explicitKey && currentTurn.pendingToolCalls?.[explicitKey]) {
392
- currentTurn.activeToolCallKey = explicitKey;
393
- return currentTurn.pendingToolCalls[explicitKey];
394
- }
395
- return this.resolveFallbackToolCall(currentTurn);
396
- }
397
- resolveFallbackToolCall(currentTurn) {
398
- const active = currentTurn.activeToolCallKey
399
- ? currentTurn.pendingToolCalls?.[currentTurn.activeToolCallKey]
400
- : undefined;
401
- if (active) {
402
- return active;
403
- }
404
- const ordered = this.getToolCallsInOrder(currentTurn);
405
- return ordered[ordered.length - 1];
406
- }
407
- getToolCallsInOrder(currentTurn) {
408
- const toolCalls = currentTurn.pendingToolCalls || {};
409
- const orderedKeys = currentTurn.pendingToolCallOrder || [];
410
- return orderedKeys
411
- .map((key) => toolCalls[key])
412
- .filter((toolCall) => Boolean(toolCall));
413
- }
414
- deleteToolCall(currentTurn, key) {
415
- if (currentTurn.pendingToolCalls) {
416
- delete currentTurn.pendingToolCalls[key];
417
- }
418
- currentTurn.pendingToolCallOrder = (currentTurn.pendingToolCallOrder || []).filter((item) => item !== key);
419
- if (currentTurn.activeToolCallKey === key) {
420
- currentTurn.activeToolCallKey = undefined;
421
- }
422
- }
423
- syncLegacyPendingToolCall(currentTurn, toolCall) {
424
- currentTurn.pendingToolCallTitle = toolCall?.title;
425
- currentTurn.pendingToolCallId = toolCall?.messageId;
426
- currentTurn.pendingToolCallDetail = toolCall?.detail;
427
- currentTurn.activeToolCallKey = toolCall?.key;
428
- }
429
322
  }
430
323
  exports.LocalCoreAcpTurnCoordinator = LocalCoreAcpTurnCoordinator;
431
- function isSchedulerAddCommand(value) {
432
- return /\blac\s+scheduler\s+add\b/.test(String(value || ''));
324
+ function createToolCallPayload(input) {
325
+ const status = input.status.trim() || 'running';
326
+ return {
327
+ id: input.id,
328
+ name: input.name.trim() || 'Tool call',
329
+ status,
330
+ input: input.input,
331
+ output: input.content,
332
+ detail: input.detail?.trim() || undefined,
333
+ label: /^running$/i.test(status) ? '工具调用' : '工具结果',
334
+ };
433
335
  }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_AGENT_MODE = void 0;
4
+ exports.parseSlashCommand = parseSlashCommand;
5
+ exports.normalizeAgentMode = normalizeAgentMode;
6
+ exports.formatAgentMode = formatAgentMode;
7
+ exports.modeHelpText = modeHelpText;
8
+ exports.DEFAULT_AGENT_MODE = 'default';
9
+ const MODE_ALIASES = {
10
+ auto: 'auto',
11
+ default: 'default',
12
+ acceptedits: 'acceptEdits',
13
+ 'accept-edits': 'acceptEdits',
14
+ accept_edits: 'acceptEdits',
15
+ dontask: 'dontAsk',
16
+ "don'task": 'dontAsk',
17
+ 'dont-ask': 'dontAsk',
18
+ dont_ask: 'dontAsk',
19
+ plan: 'plan',
20
+ bypass: 'bypassPermissions',
21
+ bypasspermissions: 'bypassPermissions',
22
+ 'bypass-permissions': 'bypassPermissions',
23
+ bypass_permissions: 'bypassPermissions',
24
+ yolo: 'bypassPermissions',
25
+ };
26
+ const MODE_LABELS = {
27
+ auto: 'auto',
28
+ default: 'default',
29
+ acceptEdits: 'acceptEdits',
30
+ dontAsk: 'dontAsk',
31
+ plan: 'plan',
32
+ bypassPermissions: 'yolo',
33
+ };
34
+ function parseSlashCommand(text) {
35
+ const normalized = String(text || '').trim();
36
+ if (!normalized.startsWith('/')) {
37
+ return null;
38
+ }
39
+ const [name = '', ...args] = normalized.slice(1).split(/\s+/);
40
+ const commandName = name.trim().toLowerCase();
41
+ if (!commandName) {
42
+ return null;
43
+ }
44
+ return { name: commandName, args };
45
+ }
46
+ function normalizeAgentMode(mode) {
47
+ const key = String(mode || '').trim().toLowerCase();
48
+ return MODE_ALIASES[key] || '';
49
+ }
50
+ function formatAgentMode(mode) {
51
+ const normalized = normalizeAgentMode(mode) || mode || exports.DEFAULT_AGENT_MODE;
52
+ return MODE_LABELS[normalized] || normalized;
53
+ }
54
+ function modeHelpText(currentMode) {
55
+ return [
56
+ `当前模式:${formatAgentMode(currentMode)}`,
57
+ '可用模式:default, auto, acceptEdits, dontAsk, plan, yolo。',
58
+ '`/mode yolo` 会跳过工具权限申请;使用 `/mode default` 可恢复默认权限。'
59
+ ].join('\n');
60
+ }
@@ -47,6 +47,8 @@ const registration_js_1 = require("./registration.js");
47
47
  const runtime_state_js_1 = require("./runtime-state.js");
48
48
  const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
49
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;
50
52
  class LocalCoreLarkGateway extends node_events_1.EventEmitter {
51
53
  options;
52
54
  // Lark returns 200340 when card action events are not enabled in the app's
@@ -246,6 +248,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
246
248
  const file = await (0, file_utils_js_1.prepareChannelFile)({
247
249
  path: part.path,
248
250
  fileName: part.fileName,
251
+ workspacePath: typeof part.metadata?.workspacePath === 'string' ? part.metadata.workspacePath : undefined,
249
252
  maxBytes: LARK_MAX_UPLOAD_FILE_SIZE,
250
253
  platformLabel: 'Lark',
251
254
  });
@@ -475,9 +478,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
475
478
  }
476
479
  const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, rendered);
477
480
  const shouldThrottle = event.type === 'update_message' &&
478
- rendered.isFinal &&
479
481
  existingMessageId &&
480
- 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);
481
483
  this.options.log?.(`localcore-lark bridge event type=${event.type} sessionKey=${sessionKey} hasMessageId=${Boolean(existingMessageId)} throttle=${shouldThrottle}`);
482
484
  if (shouldThrottle) {
483
485
  return;
@@ -620,6 +622,10 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
620
622
  if (slashCommand?.name === 'new') {
621
623
  const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'Lark'} ${new Date().toLocaleTimeString()}`;
622
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
+ }
623
629
  const now = new Date().toISOString();
624
630
  this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
625
631
  this.options.store.upsertPlatformThreadBinding({
@@ -650,7 +656,9 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
650
656
  return { paired: true, threadId };
651
657
  }
652
658
  this.options.store.clearPlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId);
653
- const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
659
+ const wrappedText = slashCommand
660
+ ? input.text
661
+ : (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
654
662
  await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
655
663
  return { paired: true, threadId };
656
664
  }
@@ -1095,6 +1103,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1095
1103
  return this.larkModulePromise;
1096
1104
  }
1097
1105
  async sendTextAsCard(state, chatId, text, buttonRows = [], sessionKey, threadId) {
1106
+ const startedAt = Date.now();
1098
1107
  const response = await state.client.im.message.create({
1099
1108
  params: {
1100
1109
  receive_id_type: this.resolveReceiveIdType(chatId),
@@ -1105,6 +1114,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1105
1114
  content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1106
1115
  },
1107
1116
  });
1117
+ this.options.log?.(`localcore-lark card create took ${Date.now() - startedAt}ms textBytes=${Buffer.byteLength(text || '', 'utf8')}`);
1108
1118
  return String(response?.data?.message_id || '').trim();
1109
1119
  }
1110
1120
  resolveReceiveIdType(receiveId) {
@@ -1135,6 +1145,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1135
1145
  }
1136
1146
  }
1137
1147
  async patchTextCard(state, messageId, text, buttonRows = [], sessionKey, threadId) {
1148
+ const startedAt = Date.now();
1138
1149
  await state.client.im.message.patch({
1139
1150
  path: {
1140
1151
  message_id: messageId,
@@ -1143,6 +1154,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1143
1154
  content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1144
1155
  },
1145
1156
  });
1157
+ this.options.log?.(`localcore-lark card patch took ${Date.now() - startedAt}ms message=${messageId} textBytes=${Buffer.byteLength(text || '', 'utf8')}`);
1146
1158
  }
1147
1159
  generatePairingCode() {
1148
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('📤 ')) {
@@ -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';