@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.
- package/README.md +9 -0
- package/dist/renderer/assets/{Badge-CmP99fZV.js → Badge-D-WU4kUV.js} +1 -1
- package/dist/renderer/assets/{Chat-BHxpB0Vz.js → Chat-BGkC5tAe.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-Cnhe-XvR.js → ChatMarkdown-CfbC1jRd.js} +1 -1
- package/dist/renderer/assets/{Config-CreR5RZX.js → Config-qQb4bX5D.js} +1 -1
- package/dist/renderer/assets/{CronList-OHg6JWsr.js → CronList-CKhggzYk.js} +1 -1
- package/dist/renderer/assets/{Dashboard-C083Q4zC.js → Dashboard-xn6cUy_n.js} +2 -2
- package/dist/renderer/assets/{EmptyState-Br-H8kqG.js → EmptyState-BdKBBUpr.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-DKhVUY4_.js → KnowledgeDetail-Duk8IQP3.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-BM3TMxhj.js → KnowledgeHome-BfjX_4Sv.js} +1 -1
- package/dist/renderer/assets/{Logs-debYolKz.js → Logs-Bfq61QMn.js} +1 -1
- package/dist/renderer/assets/{Modal-BIetew5T.js → Modal-B3vlBmBr.js} +1 -1
- package/dist/renderer/assets/{Page-183Y5-ef.js → Page-CPsLOP3z.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-CB6-zRBY.js → ProjectDetail-D-g46wyF.js} +1 -1
- package/dist/renderer/assets/{ProjectList-BEUflZUB.js → ProjectList-BrQEKDQ8.js} +1 -1
- package/dist/renderer/assets/{Select-CNIQBVMx.js → Select-C4CFMQmF.js} +1 -1
- package/dist/renderer/assets/{SessionChat-CLpfIftJ.js → SessionChat-06Jny5_G.js} +1 -1
- package/dist/renderer/assets/{SessionList-BUwfQcGl.js → SessionList-DPWIUbOg.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-B9EcRcki.js → ThreadChat-BjtU-j9q.js} +1 -1
- package/dist/renderer/assets/{Workspace-D82il8Vz.js → Workspace-BcNmHuJx.js} +1 -1
- package/dist/renderer/assets/{arrow-left-T9D9J6Ln.js → arrow-left-DPzSmidS.js} +1 -1
- package/dist/renderer/assets/{book-open-Bfdr9s70.js → book-open-B0xhoRqZ.js} +1 -1
- package/dist/renderer/assets/{check-CPJsC_C4.js → check-BTJTyd0C.js} +1 -1
- package/dist/renderer/assets/{chevron-down-BDoIQdna.js → chevron-down-B6jJXlhi.js} +1 -1
- package/dist/renderer/assets/{circle-CRGTgPlR.js → circle-qRhJR9qR.js} +1 -1
- package/dist/renderer/assets/{desktop-DJ_OjjxL.js → desktop-DXmXjjUR.js} +1 -1
- package/dist/renderer/assets/{funnel-BISvzz6U.js → funnel-CbYKGTn8.js} +1 -1
- package/dist/renderer/assets/{heart-EllaLuEV.js → heart-DjwWJfkz.js} +1 -1
- package/dist/renderer/assets/{index-YUnN4zTb.js → index-BWmHJRhe.js} +2 -2
- package/dist/renderer/assets/{index-Db87Dhd_.js → index-CyZs_2NJ.js} +1 -1
- package/dist/renderer/assets/{pencil-w4F5pFB6.js → pencil-1dfAivmj.js} +1 -1
- package/dist/renderer/assets/{play-CNY6DepD.js → play-CB3w1G6S.js} +1 -1
- package/dist/renderer/assets/{plug-DnMzECJy.js → plug-Bvep5o3r.js} +1 -1
- package/dist/renderer/assets/{plus-D33HMCG5.js → plus-DJVg9m09.js} +1 -1
- package/dist/renderer/assets/projects-xnW0fTtq.js +1 -0
- package/dist/renderer/assets/{save-B0PXgtN_.js → save-xjJn3qf2.js} +1 -1
- package/dist/renderer/assets/{search-zNtpxpxG.js → search-BK3UrQxM.js} +1 -1
- package/dist/renderer/assets/{server-DIsU-ww0.js → server-Dyd2eXJu.js} +1 -1
- package/dist/renderer/assets/{session-utils-CxQ3KCei.js → session-utils-eXC0020s.js} +1 -1
- package/dist/renderer/assets/{sessions-DFDVZGz-.js → sessions-WLYpkyUt.js} +1 -1
- package/dist/renderer/assets/{shield-check-5hrgmi9Y.js → shield-check-C0N6aWzF.js} +1 -1
- package/dist/renderer/assets/{trash-2-DWftp5eL.js → trash-2-BGMTNJA6.js} +1 -1
- package/dist/renderer/assets/{user-CKjiW1bV.js → user-BcGMLyhu.js} +1 -1
- package/dist/renderer/assets/{wifi-off-DyBtTbdO.js → wifi-off-FhcQx0HK.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +27 -39
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +25 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +13 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +7 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +31 -8
- package/dist-electron/services/local-ai-core/src/acp/local-core-slash-commands.js +28 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +21 -11
- package/dist-electron/services/local-ai-core/src/channel/lark/post.js +7 -16
- package/dist-electron/services/local-ai-core/src/channel/lark/rendering/markdown.js +94 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/rendering/messages.js +51 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +14 -0
- package/dist-electron/services/local-ai-core/src/router/workspace-router.js +40 -4
- package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +134 -0
- package/dist-electron/tests/electron/plugin-kernel.test.js +26 -0
- package/dist-electron/tests/integration/lark-rendering.test.js +123 -0
- package/dist-electron/tests/integration/local-core-refactor.test.js +335 -4
- package/package.json +1 -1
- 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 =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
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
|
+
});
|