@kafca/agentdock 0.1.40 → 0.1.42

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 (63) hide show
  1. package/README.md +9 -0
  2. package/dist/renderer/assets/{Badge-CmP99fZV.js → Badge-D-WU4kUV.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BHxpB0Vz.js → Chat-BGkC5tAe.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-Cnhe-XvR.js → ChatMarkdown-CfbC1jRd.js} +1 -1
  5. package/dist/renderer/assets/{Config-CreR5RZX.js → Config-qQb4bX5D.js} +1 -1
  6. package/dist/renderer/assets/{CronList-OHg6JWsr.js → CronList-CKhggzYk.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-C083Q4zC.js → Dashboard-xn6cUy_n.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-Br-H8kqG.js → EmptyState-BdKBBUpr.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DKhVUY4_.js → KnowledgeDetail-Duk8IQP3.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-BM3TMxhj.js → KnowledgeHome-BfjX_4Sv.js} +1 -1
  11. package/dist/renderer/assets/{Logs-debYolKz.js → Logs-Bfq61QMn.js} +1 -1
  12. package/dist/renderer/assets/{Modal-BIetew5T.js → Modal-B3vlBmBr.js} +1 -1
  13. package/dist/renderer/assets/{Page-183Y5-ef.js → Page-CPsLOP3z.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-CB6-zRBY.js → ProjectDetail-D-g46wyF.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-BEUflZUB.js → ProjectList-BrQEKDQ8.js} +1 -1
  16. package/dist/renderer/assets/{Select-CNIQBVMx.js → Select-C4CFMQmF.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-CLpfIftJ.js → SessionChat-06Jny5_G.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-BUwfQcGl.js → SessionList-DPWIUbOg.js} +1 -1
  19. package/dist/renderer/assets/{ThreadChat-B9EcRcki.js → ThreadChat-BjtU-j9q.js} +1 -1
  20. package/dist/renderer/assets/{Workspace-D82il8Vz.js → Workspace-BcNmHuJx.js} +1 -1
  21. package/dist/renderer/assets/{arrow-left-T9D9J6Ln.js → arrow-left-DPzSmidS.js} +1 -1
  22. package/dist/renderer/assets/{book-open-Bfdr9s70.js → book-open-B0xhoRqZ.js} +1 -1
  23. package/dist/renderer/assets/{check-CPJsC_C4.js → check-BTJTyd0C.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-BDoIQdna.js → chevron-down-B6jJXlhi.js} +1 -1
  25. package/dist/renderer/assets/{circle-CRGTgPlR.js → circle-qRhJR9qR.js} +1 -1
  26. package/dist/renderer/assets/{desktop-DJ_OjjxL.js → desktop-DXmXjjUR.js} +1 -1
  27. package/dist/renderer/assets/{funnel-BISvzz6U.js → funnel-CbYKGTn8.js} +1 -1
  28. package/dist/renderer/assets/{heart-EllaLuEV.js → heart-DjwWJfkz.js} +1 -1
  29. package/dist/renderer/assets/{index-YUnN4zTb.js → index-BWmHJRhe.js} +2 -2
  30. package/dist/renderer/assets/{index-Db87Dhd_.js → index-CyZs_2NJ.js} +1 -1
  31. package/dist/renderer/assets/{pencil-w4F5pFB6.js → pencil-1dfAivmj.js} +1 -1
  32. package/dist/renderer/assets/{play-CNY6DepD.js → play-CB3w1G6S.js} +1 -1
  33. package/dist/renderer/assets/{plug-DnMzECJy.js → plug-Bvep5o3r.js} +1 -1
  34. package/dist/renderer/assets/{plus-D33HMCG5.js → plus-DJVg9m09.js} +1 -1
  35. package/dist/renderer/assets/projects-xnW0fTtq.js +1 -0
  36. package/dist/renderer/assets/{save-B0PXgtN_.js → save-xjJn3qf2.js} +1 -1
  37. package/dist/renderer/assets/{search-zNtpxpxG.js → search-BK3UrQxM.js} +1 -1
  38. package/dist/renderer/assets/{server-DIsU-ww0.js → server-Dyd2eXJu.js} +1 -1
  39. package/dist/renderer/assets/{session-utils-CxQ3KCei.js → session-utils-eXC0020s.js} +1 -1
  40. package/dist/renderer/assets/{sessions-DFDVZGz-.js → sessions-WLYpkyUt.js} +1 -1
  41. package/dist/renderer/assets/{shield-check-5hrgmi9Y.js → shield-check-C0N6aWzF.js} +1 -1
  42. package/dist/renderer/assets/{trash-2-DWftp5eL.js → trash-2-BGMTNJA6.js} +1 -1
  43. package/dist/renderer/assets/{user-CKjiW1bV.js → user-BcGMLyhu.js} +1 -1
  44. package/dist/renderer/assets/{wifi-off-DyBtTbdO.js → wifi-off-FhcQx0HK.js} +1 -1
  45. package/dist/renderer/index.html +1 -1
  46. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +27 -39
  47. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +25 -0
  48. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +13 -0
  49. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +7 -0
  50. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +31 -8
  51. package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +28 -0
  52. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +21 -11
  53. package/dist-electron/services/local-ai-core/src/channel/lark/post.js +7 -16
  54. package/dist-electron/services/local-ai-core/src/channel/lark/rendering/markdown.js +94 -0
  55. package/dist-electron/services/local-ai-core/src/channel/lark/rendering/messages.js +51 -0
  56. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +14 -0
  57. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +40 -4
  58. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +134 -0
  59. package/dist-electron/tests/electron/plugin-kernel.test.js +26 -0
  60. package/dist-electron/tests/integration/lark-rendering.test.js +123 -0
  61. package/dist-electron/tests/integration/local-core-refactor.test.js +335 -4
  62. package/package.json +1 -1
  63. package/dist/renderer/assets/projects-DcYpVq_W.js +0 -1
@@ -1,18 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildLarkPostContent = buildLarkPostContent;
4
- function buildLarkPostContent(markdown) {
5
- return {
6
- zh_cn: {
7
- content: [[{
8
- tag: 'md',
9
- text: sanitizeLarkPostMarkdown(String(markdown || '').trim() || ' '),
10
- }]],
11
- },
12
- };
13
- }
14
- function sanitizeLarkPostMarkdown(markdown) {
15
- return markdown.replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, label, href) => {
16
- return /^https?:\/\//i.test(href) ? match : `${label} (${href})`;
17
- });
18
- }
3
+ exports.shouldUseLarkMarkdownCard = exports.renderLarkTextMessage = exports.normalizeMarkdownTablesForLarkPost = exports.buildLarkPostContent = exports.buildLarkMarkdownCardContent = void 0;
4
+ var messages_js_1 = require("./rendering/messages.js");
5
+ Object.defineProperty(exports, "buildLarkMarkdownCardContent", { enumerable: true, get: function () { return messages_js_1.buildLarkMarkdownCardContent; } });
6
+ Object.defineProperty(exports, "buildLarkPostContent", { enumerable: true, get: function () { return messages_js_1.buildLarkPostContent; } });
7
+ Object.defineProperty(exports, "normalizeMarkdownTablesForLarkPost", { enumerable: true, get: function () { return messages_js_1.normalizeMarkdownTablesForLarkPost; } });
8
+ Object.defineProperty(exports, "renderLarkTextMessage", { enumerable: true, get: function () { return messages_js_1.renderLarkTextMessage; } });
9
+ Object.defineProperty(exports, "shouldUseLarkMarkdownCard", { enumerable: true, get: function () { return messages_js_1.shouldUseLarkMarkdownCard; } });
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LARK_CARD_MARKDOWN_TABLE_LIMIT = void 0;
4
+ exports.sanitizeLarkMarkdown = sanitizeLarkMarkdown;
5
+ exports.countMarkdownTables = countMarkdownTables;
6
+ exports.shouldUseLarkMarkdownCard = shouldUseLarkMarkdownCard;
7
+ exports.normalizeMarkdownTablesForLarkPost = normalizeMarkdownTablesForLarkPost;
8
+ exports.LARK_CARD_MARKDOWN_TABLE_LIMIT = 5;
9
+ function sanitizeLarkMarkdown(markdown) {
10
+ return markdown.replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, label, href) => {
11
+ return /^https?:\/\//i.test(href) ? match : `${label} (${href})`;
12
+ });
13
+ }
14
+ function countMarkdownTables(markdown) {
15
+ const lines = String(markdown || '').split('\n');
16
+ let count = 0;
17
+ let inTable = false;
18
+ for (let index = 0; index < lines.length; index += 1) {
19
+ const isTableStart = isMarkdownTableLine(lines[index]) && isMarkdownTableSeparatorLine(lines[index + 1]);
20
+ const isTableBody = inTable && isMarkdownTableLine(lines[index]);
21
+ if (isTableStart && !inTable) {
22
+ count += 1;
23
+ inTable = true;
24
+ continue;
25
+ }
26
+ if (!isTableBody) {
27
+ inTable = false;
28
+ }
29
+ }
30
+ return count;
31
+ }
32
+ function shouldUseLarkMarkdownCard(markdown) {
33
+ const tableCount = countMarkdownTables(markdown);
34
+ return tableCount > 0 && tableCount <= exports.LARK_CARD_MARKDOWN_TABLE_LIMIT;
35
+ }
36
+ function normalizeMarkdownTablesForLarkPost(markdown) {
37
+ const lines = markdown.split('\n');
38
+ const output = [];
39
+ for (let index = 0; index < lines.length; index += 1) {
40
+ if (isMarkdownTableLine(lines[index]) && isMarkdownTableSeparatorLine(lines[index + 1])) {
41
+ const tableLines = [lines[index], lines[index + 1]];
42
+ index += 2;
43
+ while (index < lines.length && isMarkdownTableLine(lines[index])) {
44
+ tableLines.push(lines[index]);
45
+ index += 1;
46
+ }
47
+ index -= 1;
48
+ output.push(renderMarkdownTableAsList(tableLines));
49
+ continue;
50
+ }
51
+ output.push(lines[index]);
52
+ }
53
+ return output.join('\n');
54
+ }
55
+ function isMarkdownTableLine(line) {
56
+ const trimmed = String(line || '').trim();
57
+ return trimmed.length > 1 && trimmed.startsWith('|') && trimmed.endsWith('|');
58
+ }
59
+ function isMarkdownTableSeparatorLine(line) {
60
+ if (!isMarkdownTableLine(line)) {
61
+ return false;
62
+ }
63
+ const cells = splitMarkdownTableRow(String(line));
64
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
65
+ }
66
+ function splitMarkdownTableRow(line) {
67
+ return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((cell) => cell.trim());
68
+ }
69
+ function renderMarkdownTableAsList(tableLines) {
70
+ const headers = splitMarkdownTableRow(tableLines[0] || '');
71
+ const bodyRows = tableLines.slice(2).map(splitMarkdownTableRow);
72
+ if (headers.length === 0 || bodyRows.length === 0) {
73
+ return tableLines.join('\n');
74
+ }
75
+ return bodyRows
76
+ .map((cells, rowIndex) => renderMarkdownTableRow(headers, cells, rowIndex))
77
+ .join('\n');
78
+ }
79
+ function renderMarkdownTableRow(headers, cells, rowIndex) {
80
+ const hasNumberColumn = headers[0]?.trim() === '#';
81
+ const explicitNumber = hasNumberColumn ? cells[0]?.trim() : '';
82
+ const prefix = explicitNumber && /^\d+$/.test(explicitNumber) ? `${explicitNumber}.` : `${rowIndex + 1}.`;
83
+ const startIndex = hasNumberColumn ? 1 : 0;
84
+ const parts = [];
85
+ for (let index = startIndex; index < headers.length; index += 1) {
86
+ const header = headers[index]?.trim();
87
+ const value = cells[index]?.trim();
88
+ if (!header || !value) {
89
+ continue;
90
+ }
91
+ parts.push(`${header}: ${value}`);
92
+ }
93
+ return parts.length > 0 ? `${prefix} ${parts.join(';')}` : `${prefix} ${cells.join(';')}`;
94
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shouldUseLarkMarkdownCard = exports.normalizeMarkdownTablesForLarkPost = void 0;
4
+ exports.renderLarkTextMessage = renderLarkTextMessage;
5
+ exports.buildLarkPostContent = buildLarkPostContent;
6
+ exports.buildLarkMarkdownCardContent = buildLarkMarkdownCardContent;
7
+ const markdown_js_1 = require("./markdown.js");
8
+ Object.defineProperty(exports, "normalizeMarkdownTablesForLarkPost", { enumerable: true, get: function () { return markdown_js_1.normalizeMarkdownTablesForLarkPost; } });
9
+ Object.defineProperty(exports, "shouldUseLarkMarkdownCard", { enumerable: true, get: function () { return markdown_js_1.shouldUseLarkMarkdownCard; } });
10
+ function renderLarkTextMessage(markdown) {
11
+ const tableCount = (0, markdown_js_1.countMarkdownTables)(markdown);
12
+ if ((0, markdown_js_1.shouldUseLarkMarkdownCard)(markdown)) {
13
+ return {
14
+ msgType: 'interactive',
15
+ content: buildLarkMarkdownCardContent(markdown),
16
+ renderKind: 'markdown_card',
17
+ reason: 'markdown_table',
18
+ tableCount,
19
+ };
20
+ }
21
+ return {
22
+ msgType: 'post',
23
+ content: buildLarkPostContent(markdown),
24
+ renderKind: 'post_md',
25
+ reason: tableCount > 0 ? 'table_limit_fallback' : 'plain_markdown',
26
+ tableCount,
27
+ };
28
+ }
29
+ function buildLarkPostContent(markdown) {
30
+ const normalized = (0, markdown_js_1.normalizeMarkdownTablesForLarkPost)(String(markdown || '').trim() || ' ');
31
+ return {
32
+ zh_cn: {
33
+ content: [[{
34
+ tag: 'md',
35
+ text: (0, markdown_js_1.sanitizeLarkMarkdown)(normalized),
36
+ }]],
37
+ },
38
+ };
39
+ }
40
+ function buildLarkMarkdownCardContent(markdown) {
41
+ return {
42
+ schema: '2.0',
43
+ config: { wide_screen_mode: true },
44
+ body: {
45
+ elements: [{
46
+ tag: 'markdown',
47
+ content: (0, markdown_js_1.sanitizeLarkMarkdown)(String(markdown || '').trim() || ' '),
48
+ }],
49
+ },
50
+ };
51
+ }
@@ -114,6 +114,10 @@ function consumeLarkBridgeEvent(turn, event, options) {
114
114
  pushUniqueLarkTurnLine(turn.statusLines, content.slice(3).trim());
115
115
  return;
116
116
  }
117
+ if (bridgeKind === 'assistant' && event.messageId) {
118
+ turn.previewText = content;
119
+ return;
120
+ }
117
121
  turn.finalText = content;
118
122
  turn.previewText = content;
119
123
  }
@@ -148,6 +152,9 @@ function renderLarkBridgeEventMessage(turn, event) {
148
152
  if (bridgeKind === 'status') {
149
153
  return renderProgressMessage(progressKey('status', event), content);
150
154
  }
155
+ if (bridgeKind === 'assistant' && event.messageId) {
156
+ return renderAssistantSegmentMessage(progressKey('assistant', event), content);
157
+ }
151
158
  return {
152
159
  key: 'final',
153
160
  text: content,
@@ -217,6 +224,13 @@ function renderToolMessage(key, text) {
217
224
  updatePolicy: 'create-only',
218
225
  };
219
226
  }
227
+ function renderAssistantSegmentMessage(key, text) {
228
+ return {
229
+ ...renderProgressMessage(key, text),
230
+ delivery: 'message',
231
+ updatePolicy: 'create-only',
232
+ };
233
+ }
220
234
  function progressKey(prefix, event) {
221
235
  const stableId = String(event.messageId || event.previewHandle || '').trim();
222
236
  if (stableId) {
@@ -48,6 +48,7 @@ class WorkspaceRouter {
48
48
  return this.schedulerBridge.deleteJob(jobId);
49
49
  },
50
50
  },
51
+ getAgentTypes: () => this.options.getCapabilities().snapshot.agents.map((agent) => agent.agentType),
51
52
  log: options.log,
52
53
  });
53
54
  }
@@ -203,12 +204,14 @@ class WorkspaceRouter {
203
204
  }
204
205
  async sendThreadMessage(threadId, content, options) {
205
206
  const { workspaceId } = (0, workspace_thread_id_js_1.decodeThreadId)(threadId);
206
- const route = await this.getWorkspaceRoute(workspaceId);
207
+ const route = isLocalSlashCommand(content)
208
+ ? await this.getWorkspaceRoute(workspaceId)
209
+ : await this.getThreadWorkspaceRoute(threadId, workspaceId);
207
210
  return this.localCoreAcp.sendThreadMessage(threadId, content, route.config, options);
208
211
  }
209
212
  async sendThreadAction(threadId, content) {
210
213
  const { workspaceId } = (0, workspace_thread_id_js_1.decodeThreadId)(threadId);
211
- const route = await this.getWorkspaceRoute(workspaceId);
214
+ const route = await this.getThreadWorkspaceRoute(threadId, workspaceId);
212
215
  return this.localCoreAcp.sendThreadAction(threadId, content, route.config);
213
216
  }
214
217
  async interruptRun(runId) {
@@ -425,11 +428,21 @@ class WorkspaceRouter {
425
428
  await this.localCoreAcp.deleteThread(thread.id).catch(() => ({ deleted: false }));
426
429
  }
427
430
  }
428
- async getWorkspaceRoute(workspaceId) {
431
+ async getThreadWorkspaceRoute(threadId, workspaceId) {
432
+ const defaultRoute = await this.getWorkspaceRoute(workspaceId);
433
+ const row = this.store.getThreadRow(threadId);
434
+ const threadAgentType = String(row?.agent_type || '').trim().toLowerCase();
435
+ if (!threadAgentType || threadAgentType === defaultRoute.agentType) {
436
+ return defaultRoute;
437
+ }
438
+ return this.getWorkspaceRoute(workspaceId, threadAgentType);
439
+ }
440
+ async getWorkspaceRoute(workspaceId, agentTypeOverride = '') {
429
441
  const configState = await this.options.readConfigState();
430
442
  const projects = Array.isArray(configState.parsed?.projects) ? configState.parsed.projects : [];
431
443
  const matched = projects.find((project) => String(project?.name || '').trim() === workspaceId);
432
- const route = matched ? this.resolveProjectRoute(configState, matched) : null;
444
+ const project = matched && agentTypeOverride ? withAgentTypeOverride(matched, agentTypeOverride) : matched;
445
+ const route = project ? this.resolveProjectRoute(configState, project) : null;
433
446
  if (!matched || !route) {
434
447
  throw new Error(`Workspace "${workspaceId}" is not configured as a Local AI Core ACP workspace.`);
435
448
  }
@@ -512,6 +525,29 @@ function inferWorkspacePath(project) {
512
525
  }
513
526
  return '';
514
527
  }
528
+ function withAgentTypeOverride(project, agentType) {
529
+ const options = project.agent?.options && typeof project.agent.options === 'object'
530
+ ? { ...project.agent.options }
531
+ : {};
532
+ delete options.command;
533
+ delete options.args;
534
+ return {
535
+ ...project,
536
+ agent: {
537
+ ...(project.agent || {}),
538
+ type: agentType,
539
+ options,
540
+ },
541
+ };
542
+ }
543
+ function isLocalSlashCommand(content) {
544
+ const text = typeof content === 'string' ? content : String(content.displayText || '');
545
+ const normalized = text.trim().toLowerCase();
546
+ return normalized === '/agent'
547
+ || normalized.startsWith('/agent ')
548
+ || normalized === '/mode'
549
+ || normalized.startsWith('/mode ');
550
+ }
515
551
  function workspaceHealth(path) {
516
552
  if (!path) {
517
553
  return {
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ThreadCommandService = void 0;
4
+ const local_core_slash_commands_js_1 = require("../acp/local-core-slash-commands.js");
5
+ const index_js_1 = require("../agents/index.js");
6
+ class ThreadCommandService {
7
+ options;
8
+ constructor(options) {
9
+ this.options = options;
10
+ }
11
+ async execute(input) {
12
+ const command = (0, local_core_slash_commands_js_1.parseSlashCommand)(input.content);
13
+ if (!command) {
14
+ return { handled: false, displayText: '' };
15
+ }
16
+ if (command.name === 'mode') {
17
+ return {
18
+ handled: true,
19
+ displayText: await this.executeModeCommand(input.threadId, input.workspaceId, command.args),
20
+ };
21
+ }
22
+ if (command.name === 'agent') {
23
+ return {
24
+ handled: true,
25
+ displayText: this.executeAgentCommand(input.threadId, input.workspaceId, command.args, input.defaultAgentType),
26
+ };
27
+ }
28
+ return { handled: false, displayText: '' };
29
+ }
30
+ async executeModeCommand(threadId, workspaceId, args) {
31
+ const row = this.options.getThreadRow(threadId);
32
+ const currentMode = row?.agent_mode || local_core_slash_commands_js_1.DEFAULT_AGENT_MODE;
33
+ const requested = args.join(' ').trim();
34
+ if (!requested) {
35
+ return (0, local_core_slash_commands_js_1.modeHelpText)(currentMode);
36
+ }
37
+ const mode = (0, local_core_slash_commands_js_1.normalizeAgentMode)(requested);
38
+ if (!mode) {
39
+ return `未知模式:${requested}\n\n${(0, local_core_slash_commands_js_1.modeHelpText)(currentMode)}`;
40
+ }
41
+ this.options.updateThreadAgentMode(threadId, mode);
42
+ try {
43
+ await this.options.setThreadMode?.(threadId, mode);
44
+ }
45
+ catch (error) {
46
+ this.options.log?.(`localcore-acp mode sync failed for ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
47
+ }
48
+ this.options.createAuditEvent({
49
+ type: 'permission.changed',
50
+ workspaceId,
51
+ actor: 'local',
52
+ summary: `Thread agent mode changed to ${mode}.`,
53
+ metadata: { threadId, mode },
54
+ });
55
+ if (mode === 'bypassPermissions') {
56
+ return '已切换到 yolo 模式:后续工具调用会跳过权限申请。使用 `/mode default` 可恢复默认权限。';
57
+ }
58
+ return `已切换到 ${(0, local_core_slash_commands_js_1.formatAgentMode)(mode)} 模式。`;
59
+ }
60
+ executeAgentCommand(threadId, workspaceId, args, defaultAgentType) {
61
+ const row = this.options.getThreadRow(threadId);
62
+ const currentAgent = (0, local_core_slash_commands_js_1.normalizeAgentCommandTarget)(row?.agent_type || defaultAgentType);
63
+ const defaultAgent = (0, local_core_slash_commands_js_1.normalizeAgentCommandTarget)(defaultAgentType);
64
+ const availableAgents = (0, local_core_slash_commands_js_1.formatAgentList)(this.options.getAgentTypes?.() || [defaultAgent, currentAgent]);
65
+ const [rawAction = '', ...rest] = args;
66
+ const action = String(rawAction || '').trim().toLowerCase();
67
+ if (!action || action === 'current') {
68
+ return [
69
+ `当前线程 Agent:${currentAgent}`,
70
+ `来源:${currentAgent === defaultAgent ? '默认设置' : '线程设置'}`,
71
+ `默认 Agent:${defaultAgent}`,
72
+ '使用 `/agent list` 查看可用 Agent,或 `/agent use <agent-id>` 切换。',
73
+ ].join('\n');
74
+ }
75
+ if (action === 'list') {
76
+ return (0, local_core_slash_commands_js_1.agentHelpText)({ currentAgent, defaultAgent, availableAgents });
77
+ }
78
+ if (action === 'reset') {
79
+ if (currentAgent === defaultAgent) {
80
+ return `当前线程已经使用默认 Agent:${defaultAgent}。`;
81
+ }
82
+ const activeRun = this.hasActiveRun(threadId);
83
+ this.options.updateThreadAgentType(threadId, defaultAgent);
84
+ if (!activeRun) {
85
+ this.options.closeThreadSession?.(threadId);
86
+ }
87
+ this.options.createAuditEvent({
88
+ type: 'agent.changed',
89
+ workspaceId,
90
+ actor: 'local',
91
+ summary: `Thread agent reset to ${defaultAgent}.`,
92
+ metadata: { threadId, agentType: defaultAgent, previousAgentType: currentAgent },
93
+ });
94
+ return `已清除当前线程 Agent 设置。\n当前线程将回到默认 Agent:${defaultAgent}。`;
95
+ }
96
+ const rawRequested = action === 'use' ? rest.join(' ').trim() : args.join(' ').trim();
97
+ const requestedAgent = (0, local_core_slash_commands_js_1.normalizeAgentCommandTarget)(rawRequested);
98
+ if (!requestedAgent) {
99
+ return (0, local_core_slash_commands_js_1.agentHelpText)({ currentAgent, defaultAgent, availableAgents });
100
+ }
101
+ const definition = (0, index_js_1.resolveAgentRuntimeDefinition)(requestedAgent);
102
+ if (!definition) {
103
+ return `未知 Agent:${rawRequested}\n可用 Agent:${availableAgents.length ? availableAgents.join(', ') : '无'}`;
104
+ }
105
+ const canonicalAgent = definition.agentType;
106
+ if (!availableAgents.includes(canonicalAgent)) {
107
+ return `Agent "${canonicalAgent}" 当前不可用。\n可用 Agent:${availableAgents.length ? availableAgents.join(', ') : '无'}`;
108
+ }
109
+ if (canonicalAgent === currentAgent) {
110
+ return `当前线程已经使用 Agent:${canonicalAgent}。`;
111
+ }
112
+ const activeRun = this.hasActiveRun(threadId);
113
+ this.options.updateThreadAgentType(threadId, canonicalAgent);
114
+ if (!activeRun) {
115
+ this.options.closeThreadSession?.(threadId);
116
+ }
117
+ const runningNote = activeRun
118
+ ? `\n当前正在运行的任务仍会继续使用 ${currentAgent},下一轮开始生效。`
119
+ : '';
120
+ this.options.createAuditEvent({
121
+ type: 'agent.changed',
122
+ workspaceId,
123
+ actor: 'local',
124
+ summary: `Thread agent changed to ${canonicalAgent}.`,
125
+ metadata: { threadId, agentType: canonicalAgent, previousAgentType: currentAgent },
126
+ });
127
+ return `已将当前线程 Agent 切换为 ${canonicalAgent}。\n后续消息将使用 ${canonicalAgent} 处理。${runningNote}`;
128
+ }
129
+ hasActiveRun(threadId) {
130
+ const latestRun = this.options.getLatestRunForThread(threadId);
131
+ return Boolean(latestRun && ['queued', 'running', 'awaiting_input'].includes(latestRun.status));
132
+ }
133
+ }
134
+ exports.ThreadCommandService = ThreadCommandService;
@@ -302,6 +302,32 @@ type = "codex"
302
302
  (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
303
303
  }
304
304
  });
305
+ (0, node_test_1.default)('thread slash agent reset resolves the workspace default agent through the router', async () => {
306
+ const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-agent-reset-'));
307
+ try {
308
+ const runtime = (0, bootstrap_js_2.bootstrapLocalCoreRuntime)({
309
+ userDataPath,
310
+ enableKnowledge: false,
311
+ });
312
+ await runtime.state.saveRawConfigFile(`
313
+ [[projects]]
314
+ name = "agent-workspace"
315
+
316
+ [projects.agent]
317
+ type = "codex"
318
+ `);
319
+ const thread = await runtime.workspaceRouter.createThread('agent-workspace', 'Agent reset');
320
+ await runtime.workspaceRouter.sendThreadMessage(thread.id, '/agent use pi');
321
+ strict_1.default.equal(runtime.store.getThreadRow(thread.id)?.agent_type, 'pi');
322
+ await runtime.workspaceRouter.sendThreadMessage(thread.id, '/agent reset');
323
+ strict_1.default.equal(runtime.store.getThreadRow(thread.id)?.agent_type, 'codex');
324
+ strict_1.default.match(runtime.store.getThread(thread.id, []).messages.at(-1)?.content || '', /回到默认 Agent:codex/);
325
+ await runtime.stop();
326
+ }
327
+ finally {
328
+ (0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
329
+ }
330
+ });
305
331
  (0, node_test_1.default)('hermes agent runtime routes projects through hermes ACP command', async () => {
306
332
  const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-kernel-'));
307
333
  try {
@@ -0,0 +1,123 @@
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_lark_gateway_js_1 = require("../../services/local-ai-core/src/channel/lark/local-core-lark-gateway.js");
9
+ const messages_js_1 = require("../../services/local-ai-core/src/channel/lark/rendering/messages.js");
10
+ function findLarkPostMdText(content) {
11
+ for (const line of content.zh_cn?.content || []) {
12
+ for (const item of Array.isArray(line) ? line : []) {
13
+ if (item?.tag === 'md') {
14
+ return String(item.text || '');
15
+ }
16
+ }
17
+ }
18
+ return '';
19
+ }
20
+ function tableMarkdown(index) {
21
+ return [
22
+ `| # | 标题 | 状态 | 一句话 |`,
23
+ `|---|------|------|--------|`,
24
+ `| ${index} | **标题 ${index}** | 待处理 | 摘要 ${index} |`,
25
+ ].join('\n');
26
+ }
27
+ (0, node_test_1.default)('lark text renderer keeps plain markdown on post messages', () => {
28
+ const rendered = (0, messages_js_1.renderLarkTextMessage)('### 标题\n\n普通回复');
29
+ strict_1.default.equal(rendered.msgType, 'post');
30
+ strict_1.default.equal(rendered.renderKind, 'post_md');
31
+ strict_1.default.equal(rendered.reason, 'plain_markdown');
32
+ strict_1.default.equal(rendered.tableCount, 0);
33
+ strict_1.default.equal(findLarkPostMdText(rendered.content), '### 标题\n\n普通回复');
34
+ });
35
+ (0, node_test_1.default)('lark text renderer sends markdown tables through schema 2.0 cards', () => {
36
+ const markdown = [
37
+ '### 各收件内容摘要',
38
+ '',
39
+ tableMarkdown(1),
40
+ ].join('\n');
41
+ const rendered = (0, messages_js_1.renderLarkTextMessage)(markdown);
42
+ strict_1.default.equal(rendered.msgType, 'interactive');
43
+ strict_1.default.equal(rendered.renderKind, 'markdown_card');
44
+ strict_1.default.equal(rendered.reason, 'markdown_table');
45
+ strict_1.default.equal(rendered.tableCount, 1);
46
+ strict_1.default.equal(rendered.content.schema, '2.0');
47
+ strict_1.default.equal(rendered.content.body?.elements?.[0]?.tag, 'markdown');
48
+ strict_1.default.match(rendered.content.body?.elements?.[0]?.content || '', /\| 1 \| \*\*标题 1\*\* \| 待处理 \|/);
49
+ });
50
+ (0, node_test_1.default)('lark post fallback renders markdown table rows as visible text', () => {
51
+ const content = (0, messages_js_1.buildLarkPostContent)([
52
+ '### 各收件内容摘要',
53
+ '',
54
+ tableMarkdown(1),
55
+ tableMarkdown(2),
56
+ tableMarkdown(3),
57
+ tableMarkdown(4),
58
+ tableMarkdown(5),
59
+ tableMarkdown(6),
60
+ '',
61
+ '需要我对哪个文件做进一步处理?',
62
+ ].join('\n'));
63
+ const text = findLarkPostMdText(content);
64
+ strict_1.default.doesNotMatch(text, /\|---\|/);
65
+ strict_1.default.match(text, /1\. 标题: \*\*标题 1\*\*;状态: 待处理;一句话: 摘要 1/);
66
+ strict_1.default.match(text, /6\. 标题: \*\*标题 6\*\*;状态: 待处理;一句话: 摘要 6/);
67
+ strict_1.default.match(text, /需要我对哪个文件做进一步处理?/);
68
+ });
69
+ (0, node_test_1.default)('lark text renderer falls back to post when card table count exceeds the platform limit', () => {
70
+ const rendered = (0, messages_js_1.renderLarkTextMessage)([
71
+ tableMarkdown(1),
72
+ tableMarkdown(2),
73
+ tableMarkdown(3),
74
+ tableMarkdown(4),
75
+ tableMarkdown(5),
76
+ tableMarkdown(6),
77
+ ].join('\n\n'));
78
+ strict_1.default.equal(rendered.msgType, 'post');
79
+ strict_1.default.equal(rendered.renderKind, 'post_md');
80
+ strict_1.default.equal(rendered.reason, 'table_limit_fallback');
81
+ strict_1.default.equal(rendered.tableCount, 6);
82
+ strict_1.default.match(findLarkPostMdText(rendered.content), /6\. 标题: \*\*标题 6\*\*/);
83
+ });
84
+ (0, node_test_1.default)('lark text renderer sanitizes non-http markdown links', () => {
85
+ const rendered = (0, messages_js_1.renderLarkTextMessage)('[本地文件](file:///tmp/a.md) 和 [官网](https://example.com)');
86
+ const text = findLarkPostMdText(rendered.content);
87
+ strict_1.default.match(text, /本地文件 \(file:\/\/\/tmp\/a\.md\)/);
88
+ strict_1.default.match(text, /\[官网\]\(https:\/\/example\.com\)/);
89
+ });
90
+ (0, node_test_1.default)('lark gateway sends rendered text message payloads without owning render strategy', async () => {
91
+ const requests = [];
92
+ const logs = [];
93
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
94
+ store: {},
95
+ readConfig: async () => null,
96
+ getWorkspaceRouter: () => ({}),
97
+ eventBus: { emit: () => { }, on: () => () => { } },
98
+ log: (line) => logs.push(line),
99
+ });
100
+ const result = await gateway.sendTextAsMessage({
101
+ client: {
102
+ im: {
103
+ message: {
104
+ create: async (request) => {
105
+ requests.push(request);
106
+ return { data: { message_id: 'lark-msg-1' } };
107
+ },
108
+ },
109
+ },
110
+ },
111
+ }, 'chat-1', [
112
+ '### 各收件内容摘要',
113
+ '',
114
+ tableMarkdown(1),
115
+ ].join('\n'));
116
+ const request = requests[0];
117
+ const content = JSON.parse(String(request?.data?.content || '{}'));
118
+ strict_1.default.equal(result.messageId, 'lark-msg-1');
119
+ strict_1.default.equal(result.renderKind, 'markdown_card');
120
+ strict_1.default.equal(request?.data?.msg_type, 'interactive');
121
+ strict_1.default.equal(content.schema, '2.0');
122
+ strict_1.default.match(logs.join('\n'), /msgType=interactive reason=markdown_table tableCount=1/);
123
+ });