@kafca/agentdock 0.1.42 → 0.1.43
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 +4 -0
- package/dist/renderer/assets/{Badge-D-WU4kUV.js → Badge-CbG6yRSm.js} +1 -1
- package/dist/renderer/assets/{Chat-BGkC5tAe.js → Chat-BvNNWGGX.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-CfbC1jRd.js → ChatMarkdown-Ct-PvJB8.js} +1 -1
- package/dist/renderer/assets/{Config-qQb4bX5D.js → Config-lQX2qVVV.js} +2 -2
- package/dist/renderer/assets/CronList-Dj_3muVG.js +1 -0
- package/dist/renderer/assets/{Dashboard-xn6cUy_n.js → Dashboard-eh_RhGYb.js} +2 -2
- package/dist/renderer/assets/{EmptyState-BdKBBUpr.js → EmptyState-Bn2Qh14U.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-Duk8IQP3.js → KnowledgeDetail-Bsz6Ou41.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-BfjX_4Sv.js → KnowledgeHome-Cgj4XFXn.js} +2 -2
- package/dist/renderer/assets/{Logs-Bfq61QMn.js → Logs-Dp-NF6GN.js} +1 -1
- package/dist/renderer/assets/{Modal-B3vlBmBr.js → Modal-B6YPPBN6.js} +1 -1
- package/dist/renderer/assets/MonitorList-xEZWg460.js +1 -0
- package/dist/renderer/assets/{Page-CPsLOP3z.js → Page-Bn7RjGOS.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-D-g46wyF.js → ProjectDetail-Cv_jIE61.js} +2 -2
- package/dist/renderer/assets/{ProjectList-BrQEKDQ8.js → ProjectList-BDk5_gqY.js} +1 -1
- package/dist/renderer/assets/{Select-C4CFMQmF.js → Select-xshfMIEQ.js} +1 -1
- package/dist/renderer/assets/SessionChat-BkHy90z3.js +1 -0
- package/dist/renderer/assets/{SessionList-DPWIUbOg.js → SessionList-BZyXT92L.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-BjtU-j9q.js → ThreadChat-DUOIyXrS.js} +7 -7
- package/dist/renderer/assets/{Workspace-BcNmHuJx.js → Workspace-Dl80fNEC.js} +1 -1
- package/dist/renderer/assets/{arrow-left-DPzSmidS.js → arrow-left-C54ZI-AS.js} +1 -1
- package/dist/renderer/assets/{book-open-B0xhoRqZ.js → book-open-Dl7GO0zn.js} +1 -1
- package/dist/renderer/assets/{check-BTJTyd0C.js → check-CiqgtrOL.js} +1 -1
- package/dist/renderer/assets/{chevron-down-B6jJXlhi.js → chevron-down-DTL2OuVh.js} +1 -1
- package/dist/renderer/assets/{circle-qRhJR9qR.js → circle-DaVKfa3P.js} +1 -1
- package/dist/renderer/assets/desktop-C6q6wBae.js +5 -0
- package/dist/renderer/assets/desktop-jaAiayNB.js +1 -0
- package/dist/renderer/assets/en-D2SkCrhG.js +1 -0
- package/dist/renderer/assets/{funnel-CbYKGTn8.js → funnel-CrmnLMJI.js} +1 -1
- package/dist/renderer/assets/{heart-DjwWJfkz.js → heart-BMfQI7y_.js} +1 -1
- package/dist/renderer/assets/{index-BWmHJRhe.js → index-HPNb1zfx.js} +39 -34
- package/dist/renderer/assets/{index-CyZs_2NJ.js → index-nreiKY1f.js} +3 -3
- package/dist/renderer/assets/{pencil-1dfAivmj.js → pencil-wTQ4l465.js} +1 -1
- package/dist/renderer/assets/{play-CB3w1G6S.js → play-BK--MXmw.js} +1 -1
- package/dist/renderer/assets/{plug-Bvep5o3r.js → plug-BIx9fjip.js} +1 -1
- package/dist/renderer/assets/{plus-DJVg9m09.js → plus-Dn7JFWQ8.js} +1 -1
- package/dist/renderer/assets/projects-brJZgwEJ.js +1 -0
- package/dist/renderer/assets/{save-xjJn3qf2.js → save-CC3zOSWb.js} +1 -1
- package/dist/renderer/assets/{search-BK3UrQxM.js → search-CDTy9Czu.js} +1 -1
- package/dist/renderer/assets/{server-Dyd2eXJu.js → server-DtQk3t2a.js} +1 -1
- package/dist/renderer/assets/{session-utils-eXC0020s.js → session-utils-4ep5D66T.js} +1 -1
- package/dist/renderer/assets/{sessions-WLYpkyUt.js → sessions-Dor1LteH.js} +1 -1
- package/dist/renderer/assets/{shield-check-C0N6aWzF.js → shield-check-Cz2LpU6r.js} +1 -1
- package/dist/renderer/assets/{trash-2-BGMTNJA6.js → trash-2-C2EmWFZV.js} +1 -1
- package/dist/renderer/assets/{user-BcGMLyhu.js → user-I8B-cs5A.js} +1 -1
- package/dist/renderer/assets/{wifi-off-FhcQx0HK.js → wifi-off-CPevTQJn.js} +1 -1
- package/dist/renderer/assets/zh-DIcdHycM.js +1 -0
- package/dist/renderer/index.html +1 -1
- package/dist-electron/packages/contracts/src/local-core.js +28 -0
- package/dist-electron/packages/core-sdk/src/index.js +32 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +51 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +4 -1364
- package/dist-electron/services/local-ai-core/src/acp/store/agent-task-store.js +166 -0
- package/dist-electron/services/local-ai-core/src/acp/store/automation-monitor-store.js +231 -0
- package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +263 -0
- package/dist-electron/services/local-ai-core/src/acp/store/platform-store.js +186 -0
- package/dist-electron/services/local-ai-core/src/acp/store/scheduler-store.js +201 -0
- package/dist-electron/services/local-ai-core/src/acp/store/schema.js +275 -0
- package/dist-electron/services/local-ai-core/src/acp/store/security-store.js +266 -0
- package/dist-electron/services/local-ai-core/src/acp/store/thread-store.js +232 -0
- package/dist-electron/services/local-ai-core/src/acp/store/utils.js +47 -0
- package/dist-electron/services/local-ai-core/src/acp/store/workspace-registry-store.js +109 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-conversation-executor.js +136 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-monitor-repository.js +56 -0
- package/dist-electron/services/local-ai-core/src/automation/automation-monitor-service.js +359 -0
- package/dist-electron/services/local-ai-core/src/automation/condition-evaluator.js +77 -0
- package/dist-electron/services/local-ai-core/src/automation/mock-stock-provider.js +40 -0
- package/dist-electron/services/local-ai-core/src/automation/monitor-id.js +10 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +55 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +101 -29
- package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +53 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +51 -33
- package/dist-electron/services/local-ai-core/src/cli/lac.js +196 -0
- package/dist-electron/services/local-ai-core/src/cli/monitor-cli-parsers.js +37 -0
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +43 -0
- package/dist-electron/services/local-ai-core/src/kernel/capability-registry.js +12 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/monitor-stock-plugin.js +30 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +41 -0
- package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +31 -0
- package/dist-electron/services/local-ai-core/src/runtime/server.js +36 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-bridge-session.js +25 -11
- package/dist-electron/services/local-ai-core/src/thread/session-command-service.js +275 -0
- package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +27 -0
- package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +15 -15
- package/dist-electron/shared/desktop.js +21 -1
- package/dist-electron/tests/contracts/local-core-contracts.test.js +9 -0
- package/dist-electron/tests/electron/automation-monitor.test.js +38 -0
- package/dist-electron/tests/electron/lac-cli.test.js +146 -0
- package/dist-electron/tests/integration/local-core-refactor.test.js +194 -0
- package/package.json +1 -1
- package/dist/renderer/assets/CronList-CKhggzYk.js +0 -1
- package/dist/renderer/assets/SessionChat-06Jny5_G.js +0 -1
- package/dist/renderer/assets/desktop-DXmXjjUR.js +0 -1
- package/dist/renderer/assets/desktop-O8_6ZCVZ.js +0 -4
- package/dist/renderer/assets/en-CKhCF6St.js +0 -1
- package/dist/renderer/assets/projects-xnW0fTtq.js +0 -1
- package/dist/renderer/assets/zh-Dz5FLTBV.js +0 -1
|
@@ -5,19 +5,20 @@ class ScheduledBridgeSession {
|
|
|
5
5
|
static async open(input) {
|
|
6
6
|
const sessionKey = input.workspaceRouter.getThreadSessionKey(input.threadId);
|
|
7
7
|
const channelRuntime = input.getChannelRuntime();
|
|
8
|
+
const target = input.target || targetFromScheduledJob(input.job);
|
|
8
9
|
const unregister = await channelRuntime.registerScheduledThreadBridge?.({
|
|
9
|
-
workspaceId:
|
|
10
|
-
platform:
|
|
11
|
-
route:
|
|
10
|
+
workspaceId: target.workspaceId,
|
|
11
|
+
platform: target.platform,
|
|
12
|
+
route: target.route,
|
|
12
13
|
threadId: input.threadId,
|
|
13
14
|
sessionKey,
|
|
14
15
|
});
|
|
15
|
-
await sendScheduledStartNotice(channelRuntime, input.
|
|
16
|
+
await sendScheduledStartNotice(channelRuntime, target, sessionKey, input.noticeIcon, input.noticeTitle);
|
|
16
17
|
return {
|
|
17
18
|
mode: 'bridge-stream',
|
|
18
|
-
workspaceId:
|
|
19
|
-
platform:
|
|
20
|
-
route:
|
|
19
|
+
workspaceId: target.workspaceId,
|
|
20
|
+
platform: target.platform,
|
|
21
|
+
route: target.route,
|
|
21
22
|
threadId: input.threadId,
|
|
22
23
|
sessionKey,
|
|
23
24
|
close: async () => {
|
|
@@ -27,7 +28,20 @@ class ScheduledBridgeSession {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
exports.ScheduledBridgeSession = ScheduledBridgeSession;
|
|
30
|
-
|
|
31
|
+
function targetFromScheduledJob(job) {
|
|
32
|
+
if (!job) {
|
|
33
|
+
throw new Error('Scheduled bridge session requires a job or automation target.');
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: job.id,
|
|
37
|
+
workspaceId: job.workspaceId,
|
|
38
|
+
platform: job.platform,
|
|
39
|
+
route: job.route,
|
|
40
|
+
title: job.description || job.promptTemplate || job.id,
|
|
41
|
+
promptTemplate: job.promptTemplate,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function sendScheduledStartNotice(channelRuntime, target, sessionKey, noticeIcon, noticeTitle) {
|
|
31
45
|
if (!channelRuntime.onBridgeEvent) {
|
|
32
46
|
return;
|
|
33
47
|
}
|
|
@@ -36,15 +50,15 @@ async function sendScheduledStartNotice(channelRuntime, job, sessionKey) {
|
|
|
36
50
|
type: 'status',
|
|
37
51
|
sessionKey,
|
|
38
52
|
bridgeKind: 'status',
|
|
39
|
-
content:
|
|
53
|
+
content: `${noticeIcon || '⏰'} ${noticeTitle || automationNoticeTitle(target)}`,
|
|
40
54
|
});
|
|
41
55
|
}
|
|
42
56
|
catch {
|
|
43
57
|
// The scheduled run should continue even if the proactive start notice fails.
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
|
-
function
|
|
47
|
-
const title = normalizeNoticeTitle(
|
|
60
|
+
function automationNoticeTitle(target) {
|
|
61
|
+
const title = normalizeNoticeTitle(target.title) || normalizeNoticeTitle(target.promptTemplate) || target.id;
|
|
48
62
|
return truncateNoticeTitle(title, 80);
|
|
49
63
|
}
|
|
50
64
|
function normalizeNoticeTitle(value) {
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionCommandService = void 0;
|
|
4
|
+
exports.matchThread = matchThread;
|
|
5
|
+
const slash_command_registry_js_1 = require("./slash-command-registry.js");
|
|
6
|
+
const PAGE_SIZE = 8;
|
|
7
|
+
const DEFAULT_HISTORY_LIMIT = 10;
|
|
8
|
+
const MAX_HISTORY_LIMIT = 50;
|
|
9
|
+
class SessionCommandService {
|
|
10
|
+
operations;
|
|
11
|
+
registry = new slash_command_registry_js_1.SlashCommandRegistry();
|
|
12
|
+
constructor(operations) {
|
|
13
|
+
this.operations = operations;
|
|
14
|
+
this.registry.register({ names: ['new'], execute: (command, context) => this.newSession(command.args, context) });
|
|
15
|
+
this.registry.register({ names: ['list', 'sessions'], execute: (command, context) => this.listSessions(command.args, context) });
|
|
16
|
+
this.registry.register({ names: ['switch', 'sw'], execute: (command, context) => this.switchSession(command.args, context) });
|
|
17
|
+
this.registry.register({ names: ['history', 'his'], execute: (command, context) => this.history(command.args, context) });
|
|
18
|
+
this.registry.register({ names: ['name'], execute: (command, context) => this.name(command.args, context) });
|
|
19
|
+
this.registry.register({ names: ['search'], execute: (command, context) => this.search(command.args, context) });
|
|
20
|
+
this.registry.register({ names: ['del', 'delete', 'rm'], execute: (command, context) => this.delete(command.args, context) });
|
|
21
|
+
}
|
|
22
|
+
async execute(text, context) {
|
|
23
|
+
return await this.registry.execute(text, context) || { handled: false, displayText: '' };
|
|
24
|
+
}
|
|
25
|
+
async newSession(args, context) {
|
|
26
|
+
const title = args.join(' ').trim() || context.defaultTitle || `New thread ${new Date().toLocaleTimeString()}`;
|
|
27
|
+
const created = await this.operations.createThread(context.workspaceId, title);
|
|
28
|
+
return {
|
|
29
|
+
handled: true,
|
|
30
|
+
displayText: `已开始新会话:${created.title}`,
|
|
31
|
+
effects: [
|
|
32
|
+
{ type: 'created_thread', threadId: created.id },
|
|
33
|
+
{ type: 'activate_thread', threadId: created.id, reason: 'created' },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async listSessions(args, context) {
|
|
38
|
+
const all = await this.sortedThreads(context.workspaceId);
|
|
39
|
+
const page = parsePage(args[0]);
|
|
40
|
+
const pageInfo = paginate(all, page);
|
|
41
|
+
const text = this.renderList(pageInfo.items, context.currentThreadId, {
|
|
42
|
+
total: all.length,
|
|
43
|
+
page: pageInfo.page,
|
|
44
|
+
totalPages: pageInfo.totalPages,
|
|
45
|
+
empty: '当前工作区还没有会话。',
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
handled: true,
|
|
49
|
+
displayText: text,
|
|
50
|
+
card: {
|
|
51
|
+
title: '会话列表',
|
|
52
|
+
actions: this.listActions(pageInfo.items, pageInfo.page, pageInfo.totalPages),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async switchSession(args, context) {
|
|
57
|
+
const query = args.join(' ').trim();
|
|
58
|
+
if (!query) {
|
|
59
|
+
return { handled: true, displayText: '用法:`/switch <编号 | id前缀 | 标题>`' };
|
|
60
|
+
}
|
|
61
|
+
const matched = await this.resolveThread(context.workspaceId, query);
|
|
62
|
+
if (!matched) {
|
|
63
|
+
return { handled: true, displayText: `没有找到匹配的会话:${query}` };
|
|
64
|
+
}
|
|
65
|
+
if (matched.id === context.currentThreadId) {
|
|
66
|
+
return { handled: true, displayText: `当前已经在会话:${this.displayName(matched)}` };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
handled: true,
|
|
70
|
+
displayText: `已切换到会话:${this.displayName(matched)}\n后续消息将发送到该会话。`,
|
|
71
|
+
effects: [
|
|
72
|
+
{ type: 'activate_thread', threadId: matched.id, reason: 'switched' },
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async history(args, context) {
|
|
77
|
+
const limit = Math.max(1, Math.min(parseInt(String(args[0] || DEFAULT_HISTORY_LIMIT), 10) || DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT));
|
|
78
|
+
const thread = await this.operations.getThread(context.currentThreadId);
|
|
79
|
+
const messages = thread.messages.slice(-limit);
|
|
80
|
+
if (!messages.length) {
|
|
81
|
+
return { handled: true, displayText: '当前会话还没有消息。' };
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
handled: true,
|
|
85
|
+
displayText: [
|
|
86
|
+
`最近 ${messages.length} 条消息:`,
|
|
87
|
+
...messages.map((message) => this.renderHistoryMessage(message)),
|
|
88
|
+
].join('\n\n'),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async name(args, context) {
|
|
92
|
+
const raw = args.join(' ').trim();
|
|
93
|
+
if (!raw) {
|
|
94
|
+
return { handled: true, displayText: '用法:`/name <新名称>` 或 `/name <编号 | id前缀> <新名称>`' };
|
|
95
|
+
}
|
|
96
|
+
const [first = '', ...rest] = args;
|
|
97
|
+
let targetId = context.currentThreadId;
|
|
98
|
+
let nextName = raw;
|
|
99
|
+
if (rest.length > 0) {
|
|
100
|
+
const matched = await this.resolveThread(context.workspaceId, first);
|
|
101
|
+
if (matched) {
|
|
102
|
+
targetId = matched.id;
|
|
103
|
+
nextName = rest.join(' ').trim();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!nextName) {
|
|
107
|
+
return { handled: true, displayText: '会话名称不能为空。' };
|
|
108
|
+
}
|
|
109
|
+
const renamed = await this.operations.renameThread(targetId, nextName);
|
|
110
|
+
return {
|
|
111
|
+
handled: true,
|
|
112
|
+
displayText: `已重命名会话:${renamed.title}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async search(args, context) {
|
|
116
|
+
const query = args.join(' ').trim().toLowerCase();
|
|
117
|
+
if (!query) {
|
|
118
|
+
return { handled: true, displayText: '用法:`/search <关键词>`' };
|
|
119
|
+
}
|
|
120
|
+
const all = await this.sortedThreads(context.workspaceId);
|
|
121
|
+
const matched = all.filter((thread) => thread.title.toLowerCase().includes(query) ||
|
|
122
|
+
thread.id.toLowerCase().includes(query) ||
|
|
123
|
+
String(thread.excerpt || '').toLowerCase().includes(query));
|
|
124
|
+
const pageInfo = paginate(matched, 1);
|
|
125
|
+
const text = this.renderList(pageInfo.items, context.currentThreadId, {
|
|
126
|
+
total: matched.length,
|
|
127
|
+
page: 1,
|
|
128
|
+
totalPages: pageInfo.totalPages,
|
|
129
|
+
empty: `没有找到匹配的会话:${args.join(' ')}`,
|
|
130
|
+
title: `搜索结果:${args.join(' ')}`,
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
handled: true,
|
|
134
|
+
displayText: text,
|
|
135
|
+
card: {
|
|
136
|
+
title: '搜索会话',
|
|
137
|
+
actions: this.listActions(pageInfo.items, 1, pageInfo.totalPages),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async delete(args, context) {
|
|
142
|
+
const confirm = args.some((arg) => arg === '--confirm' || arg.toLowerCase() === 'confirm');
|
|
143
|
+
const query = args.filter((arg) => arg !== '--confirm' && arg.toLowerCase() !== 'confirm').join(' ').trim();
|
|
144
|
+
if (!query) {
|
|
145
|
+
return { handled: true, displayText: '用法:`/del <编号 | id前缀 | 标题>`,确认删除:`/del <目标> --confirm`' };
|
|
146
|
+
}
|
|
147
|
+
const matched = await this.resolveThread(context.workspaceId, query);
|
|
148
|
+
if (!matched) {
|
|
149
|
+
return { handled: true, displayText: `没有找到匹配的会话:${query}` };
|
|
150
|
+
}
|
|
151
|
+
if (matched.id === context.currentThreadId) {
|
|
152
|
+
return { handled: true, displayText: '不能删除当前正在使用的会话。请先 `/switch` 到其他会话后再删除。' };
|
|
153
|
+
}
|
|
154
|
+
if (!confirm) {
|
|
155
|
+
return {
|
|
156
|
+
handled: true,
|
|
157
|
+
displayText: [
|
|
158
|
+
`确认删除会话:${this.displayName(matched)}?`,
|
|
159
|
+
'删除后会清空该会话历史。',
|
|
160
|
+
`确认命令:\`/del ${shortThreadId(matched.id)} --confirm\``,
|
|
161
|
+
].join('\n'),
|
|
162
|
+
card: {
|
|
163
|
+
title: '确认删除会话',
|
|
164
|
+
actions: [[
|
|
165
|
+
{ label: '确认删除', command: `/del ${shortThreadId(matched.id)} --confirm`, type: 'danger' },
|
|
166
|
+
{ label: '取消', command: '/list', type: 'default' },
|
|
167
|
+
]],
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
await this.operations.deleteThread(matched.id);
|
|
172
|
+
return {
|
|
173
|
+
handled: true,
|
|
174
|
+
displayText: `已删除会话:${this.displayName(matched)}`,
|
|
175
|
+
effects: [
|
|
176
|
+
{ type: 'deleted_thread', threadId: matched.id },
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async sortedThreads(workspaceId) {
|
|
181
|
+
const threads = await this.operations.listThreads(workspaceId);
|
|
182
|
+
return [...threads].sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
183
|
+
}
|
|
184
|
+
async resolveThread(workspaceId, query) {
|
|
185
|
+
const threads = await this.sortedThreads(workspaceId);
|
|
186
|
+
return matchThread(threads, query);
|
|
187
|
+
}
|
|
188
|
+
renderList(threads, currentThreadId, input) {
|
|
189
|
+
if (input.total === 0) {
|
|
190
|
+
return input.empty;
|
|
191
|
+
}
|
|
192
|
+
const lines = [
|
|
193
|
+
input.title || `会话列表(${input.total} 个)`,
|
|
194
|
+
...threads.map((thread, index) => {
|
|
195
|
+
const globalIndex = (input.page - 1) * PAGE_SIZE + index + 1;
|
|
196
|
+
const marker = thread.id === currentThreadId ? '▶' : '◻';
|
|
197
|
+
return `${marker} **${globalIndex}.** ${this.displayName(thread)} · ${thread.historyCount} msgs · ${formatDate(thread.updatedAt)}`;
|
|
198
|
+
}),
|
|
199
|
+
];
|
|
200
|
+
if (input.totalPages > 1) {
|
|
201
|
+
lines.push(`第 ${input.page}/${input.totalPages} 页,使用 \`/list ${input.page + 1 > input.totalPages ? input.totalPages : input.page + 1}\` 查看更多。`);
|
|
202
|
+
}
|
|
203
|
+
lines.push('使用 `/switch <编号>` 切换,`/history` 查看历史,`/new 名称` 新建会话。');
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
listActions(threads, page, totalPages) {
|
|
207
|
+
const rows = threads.map((thread, index) => {
|
|
208
|
+
const globalIndex = (page - 1) * PAGE_SIZE + index + 1;
|
|
209
|
+
return [
|
|
210
|
+
{ label: `${globalIndex}. ${truncate(this.displayName(thread), 18)}`, command: `/switch ${globalIndex}`, type: 'primary' },
|
|
211
|
+
{ label: '删除', command: `/del ${globalIndex}`, type: 'danger' },
|
|
212
|
+
];
|
|
213
|
+
});
|
|
214
|
+
const nav = [];
|
|
215
|
+
if (page > 1)
|
|
216
|
+
nav.push({ label: '上一页', command: `/list ${page - 1}` });
|
|
217
|
+
if (page < totalPages)
|
|
218
|
+
nav.push({ label: '下一页', command: `/list ${page + 1}` });
|
|
219
|
+
if (nav.length)
|
|
220
|
+
rows.push(nav);
|
|
221
|
+
return rows;
|
|
222
|
+
}
|
|
223
|
+
renderHistoryMessage(message) {
|
|
224
|
+
const role = message.role === 'assistant' ? '助手' : message.role === 'user' ? '用户' : '系统';
|
|
225
|
+
return `**${role}** · ${formatDate(message.timestamp)}\n${truncate(String(message.content || '').trim(), 600) || '(empty)'}`;
|
|
226
|
+
}
|
|
227
|
+
displayName(thread) {
|
|
228
|
+
const title = String(thread.title || '').trim();
|
|
229
|
+
if (title)
|
|
230
|
+
return title;
|
|
231
|
+
const excerpt = String(thread.excerpt || '').replace(/\s+/g, ' ').trim();
|
|
232
|
+
return excerpt ? truncate(excerpt, 40) : shortThreadId(thread.id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.SessionCommandService = SessionCommandService;
|
|
236
|
+
function matchThread(threads, query) {
|
|
237
|
+
const normalized = String(query || '').trim();
|
|
238
|
+
if (!normalized)
|
|
239
|
+
return undefined;
|
|
240
|
+
if (/^\d+$/.test(normalized)) {
|
|
241
|
+
const index = Number(normalized);
|
|
242
|
+
if (index >= 1 && index <= threads.length) {
|
|
243
|
+
return threads[index - 1];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const lower = normalized.toLowerCase();
|
|
247
|
+
return threads.find((thread) => thread.id.startsWith(normalized))
|
|
248
|
+
|| threads.find((thread) => thread.title.toLowerCase() === lower)
|
|
249
|
+
|| threads.find((thread) => thread.title.toLowerCase().startsWith(lower))
|
|
250
|
+
|| threads.find((thread) => String(thread.excerpt || '').toLowerCase().includes(lower));
|
|
251
|
+
}
|
|
252
|
+
function paginate(items, requestedPage) {
|
|
253
|
+
const totalPages = Math.max(1, Math.ceil(items.length / PAGE_SIZE));
|
|
254
|
+
const page = Math.max(1, Math.min(requestedPage, totalPages));
|
|
255
|
+
const start = (page - 1) * PAGE_SIZE;
|
|
256
|
+
return { items: items.slice(start, start + PAGE_SIZE), page, totalPages };
|
|
257
|
+
}
|
|
258
|
+
function parsePage(value) {
|
|
259
|
+
const parsed = Number(value || 1);
|
|
260
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 1;
|
|
261
|
+
}
|
|
262
|
+
function shortThreadId(threadId) {
|
|
263
|
+
const normalized = threadId.includes(':') ? threadId.split(':').at(-1) || threadId : threadId;
|
|
264
|
+
return normalized.length > 12 ? normalized.slice(0, 12) : normalized;
|
|
265
|
+
}
|
|
266
|
+
function formatDate(value) {
|
|
267
|
+
const date = new Date(value);
|
|
268
|
+
if (Number.isNaN(date.getTime()))
|
|
269
|
+
return value;
|
|
270
|
+
return `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
|
271
|
+
}
|
|
272
|
+
function truncate(value, maxLength) {
|
|
273
|
+
const chars = Array.from(value);
|
|
274
|
+
return chars.length > maxLength ? `${chars.slice(0, maxLength).join('')}...` : value;
|
|
275
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SlashCommandRegistry = void 0;
|
|
4
|
+
const local_core_slash_commands_js_1 = require("../acp/local-core-slash-commands.js");
|
|
5
|
+
class SlashCommandRegistry {
|
|
6
|
+
handlers = new Map();
|
|
7
|
+
register(handler) {
|
|
8
|
+
for (const name of handler.names) {
|
|
9
|
+
const normalized = String(name || '').trim().toLowerCase();
|
|
10
|
+
if (normalized) {
|
|
11
|
+
this.handlers.set(normalized, handler);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async execute(text, context) {
|
|
16
|
+
const command = (0, local_core_slash_commands_js_1.parseSlashCommand)(text);
|
|
17
|
+
if (!command) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const handler = this.handlers.get(command.name);
|
|
21
|
+
if (!handler) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return handler.execute(command, context);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.SlashCommandRegistry = SlashCommandRegistry;
|
|
@@ -3,29 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ThreadCommandService = void 0;
|
|
4
4
|
const local_core_slash_commands_js_1 = require("../acp/local-core-slash-commands.js");
|
|
5
5
|
const index_js_1 = require("../agents/index.js");
|
|
6
|
+
const slash_command_registry_js_1 = require("./slash-command-registry.js");
|
|
6
7
|
class ThreadCommandService {
|
|
7
8
|
options;
|
|
9
|
+
registry = new slash_command_registry_js_1.SlashCommandRegistry();
|
|
8
10
|
constructor(options) {
|
|
9
11
|
this.options = options;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!command) {
|
|
14
|
-
return { handled: false, displayText: '' };
|
|
15
|
-
}
|
|
16
|
-
if (command.name === 'mode') {
|
|
17
|
-
return {
|
|
12
|
+
this.registry.register({
|
|
13
|
+
names: ['mode'],
|
|
14
|
+
execute: async (command, input) => ({
|
|
18
15
|
handled: true,
|
|
19
16
|
displayText: await this.executeModeCommand(input.threadId, input.workspaceId, command.args),
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
this.registry.register({
|
|
20
|
+
names: ['agent'],
|
|
21
|
+
execute: (command, input) => ({
|
|
24
22
|
handled: true,
|
|
25
23
|
displayText: this.executeAgentCommand(input.threadId, input.workspaceId, command.args, input.defaultAgentType),
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async execute(input) {
|
|
28
|
+
return await this.registry.execute(input.content, input) || { handled: false, displayText: '' };
|
|
29
29
|
}
|
|
30
30
|
async executeModeCommand(threadId, workspaceId, args) {
|
|
31
31
|
const row = this.options.getThreadRow(threadId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CHANNEL_PROTOCOL_INSTRUCTION = exports.SCHEDULER_PROTOCOL_INSTRUCTION = exports.LOCALCORE_ACP_AGENT_TYPE = exports.DESKTOP_INTERACTIVE_PERMISSION_AGENT_TYPES = exports.DESKTOP_PROVIDER_THINKING_OPTIONS = exports.DESKTOP_PROVIDER_PRESET_OPTIONS = exports.DESKTOP_PLATFORM_TYPE_OPTIONS = exports.DESKTOP_AGENT_TYPE_OPTIONS = exports.DESKTOP_LARK_SDK_PACKAGE = exports.DESKTOP_CLAUDECODE_ACP_PACKAGE = exports.DESKTOP_CODEX_ACP_PACKAGE = exports.DESKTOP_PI_CODING_AGENT_PACKAGE = exports.DESKTOP_PI_ACP_PACKAGE = exports.DEFAULT_DESKTOP_CLAUDECODE_MODEL = exports.DEFAULT_DESKTOP_OPENCODE_MODEL = exports.DEFAULT_DESKTOP_AGENT_TYPE = void 0;
|
|
3
|
+
exports.CHANNEL_PROTOCOL_INSTRUCTION = exports.MONITOR_PROTOCOL_INSTRUCTION = exports.SCHEDULER_PROTOCOL_INSTRUCTION = exports.LOCALCORE_ACP_AGENT_TYPE = exports.DESKTOP_INTERACTIVE_PERMISSION_AGENT_TYPES = exports.DESKTOP_PROVIDER_THINKING_OPTIONS = exports.DESKTOP_PROVIDER_PRESET_OPTIONS = exports.DESKTOP_PLATFORM_TYPE_OPTIONS = exports.DESKTOP_AGENT_TYPE_OPTIONS = exports.DESKTOP_LARK_SDK_PACKAGE = exports.DESKTOP_CLAUDECODE_ACP_PACKAGE = exports.DESKTOP_CODEX_ACP_PACKAGE = exports.DESKTOP_PI_CODING_AGENT_PACKAGE = exports.DESKTOP_PI_ACP_PACKAGE = exports.DEFAULT_DESKTOP_CLAUDECODE_MODEL = exports.DEFAULT_DESKTOP_OPENCODE_MODEL = exports.DEFAULT_DESKTOP_AGENT_TYPE = void 0;
|
|
4
4
|
exports.deriveDesktopRuntimePhase = deriveDesktopRuntimePhase;
|
|
5
5
|
exports.deriveDesktopRuntimeRoles = deriveDesktopRuntimeRoles;
|
|
6
6
|
exports.normalizeDesktopPlatformType = normalizeDesktopPlatformType;
|
|
@@ -78,6 +78,24 @@ exports.SCHEDULER_PROTOCOL_INSTRUCTION = [
|
|
|
78
78
|
'Only use the scheduler CLI when the user explicitly asks for scheduled automation.',
|
|
79
79
|
'[/Scheduler Tools]',
|
|
80
80
|
].join('\n');
|
|
81
|
+
exports.MONITOR_PROTOCOL_INSTRUCTION = [
|
|
82
|
+
'[Monitor Tools]',
|
|
83
|
+
'If the user asks to create, view, edit, delete, or manually run an event monitor for this conversation, use the Bash tool to run the local monitor CLI.',
|
|
84
|
+
'Use these commands:',
|
|
85
|
+
'lac monitor add --title "<short title>" --source stock.quote --symbol "<ticker>" --condition "<metric operator value>" --message "<exact analysis prompt>" [--cooldown 15m] [--execution-mode same-thread|side-thread]',
|
|
86
|
+
'lac monitor list',
|
|
87
|
+
'lac monitor list --thread',
|
|
88
|
+
'lac monitor info <short-monitor-id>',
|
|
89
|
+
'lac monitor edit <short-monitor-id> [--title "<title>"] [--condition "<expr>"] [--message "<exact prompt>"] [--enabled true|false] [--cooldown 15m] [--execution-mode same-thread|side-thread]',
|
|
90
|
+
'lac monitor del <short-monitor-id>',
|
|
91
|
+
'lac monitor run <short-monitor-id>',
|
|
92
|
+
'Supported stock metrics include latestPrice, change_percent, changePercent, and abs_change_percent. Example condition: "abs_change_percent >= 3".',
|
|
93
|
+
'Environment variables LOCAL_AI_WORKSPACE_ID, LOCAL_AI_THREAD_ID, LOCAL_AI_PLATFORM, LOCAL_AI_CHAT_ID, and LOCAL_AI_PLATFORM_USER_ID are already set when available.',
|
|
94
|
+
'Prefer relying on those variables instead of inventing your own route.',
|
|
95
|
+
'Use --execution-mode side-thread by default so monitor analysis does not interrupt the current conversation.',
|
|
96
|
+
'Only use the monitor CLI when the user explicitly asks for event monitoring automation.',
|
|
97
|
+
'[/Monitor Tools]',
|
|
98
|
+
].join('\n');
|
|
81
99
|
exports.CHANNEL_PROTOCOL_INSTRUCTION = [
|
|
82
100
|
'[Channel Tools]',
|
|
83
101
|
'If the user asks you to send a local file back through the current channel conversation, use the Bash tool to run the local channel CLI.',
|
|
@@ -138,6 +156,8 @@ function wrapUserMessageWithSchedulerProtocol(content, extraBlocks = []) {
|
|
|
138
156
|
return [
|
|
139
157
|
exports.SCHEDULER_PROTOCOL_INSTRUCTION,
|
|
140
158
|
'',
|
|
159
|
+
exports.MONITOR_PROTOCOL_INSTRUCTION,
|
|
160
|
+
'',
|
|
141
161
|
exports.CHANNEL_PROTOCOL_INSTRUCTION,
|
|
142
162
|
...extraBlocks.flatMap((block) => (block ? ['', block] : [])),
|
|
143
163
|
'',
|
|
@@ -53,6 +53,15 @@ const index_js_1 = require("../../packages/contracts/src/index.js");
|
|
|
53
53
|
strict_1.default.equal((0, index_js_1.normalizeScheduledJobRunStatus)('complete'), 'succeeded');
|
|
54
54
|
strict_1.default.equal((0, index_js_1.normalizeScheduledJobRunStatus)('cancelled'), 'skipped');
|
|
55
55
|
});
|
|
56
|
+
(0, node_test_1.default)('automation monitor status normalization accepts canonical aliases', () => {
|
|
57
|
+
strict_1.default.equal((0, index_js_1.normalizeAutomationMonitorStatus)(undefined), 'queued');
|
|
58
|
+
strict_1.default.equal((0, index_js_1.normalizeAutomationMonitorStatus)('completed'), 'succeeded');
|
|
59
|
+
strict_1.default.equal((0, index_js_1.normalizeAutomationMonitorStatus)('SKIPPED'), 'skipped');
|
|
60
|
+
});
|
|
61
|
+
(0, node_test_1.default)('automation monitor condition operator normalization rejects unsupported values', () => {
|
|
62
|
+
strict_1.default.equal((0, index_js_1.normalizeAutomationMonitorConditionOperator)('>='), '>=');
|
|
63
|
+
strict_1.default.throws(() => (0, index_js_1.normalizeAutomationMonitorConditionOperator)('contains'), /condition operator must be/);
|
|
64
|
+
});
|
|
56
65
|
(0, node_test_1.default)('channel content part type normalization accepts canonical aliases', () => {
|
|
57
66
|
strict_1.default.equal((0, index_js_1.normalizeChannelContentPartType)(' TEXT '), 'text');
|
|
58
67
|
strict_1.default.equal((0, index_js_1.normalizeChannelContentPartType)('permission-card'), 'permission_card');
|
|
@@ -0,0 +1,38 @@
|
|
|
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 condition_evaluator_js_1 = require("../../services/local-ai-core/src/automation/condition-evaluator.js");
|
|
9
|
+
const event = {
|
|
10
|
+
id: 'event-1',
|
|
11
|
+
sourceType: 'stock.quote',
|
|
12
|
+
occurredAt: '2026-05-11T00:00:00.000Z',
|
|
13
|
+
subject: 'AAPL',
|
|
14
|
+
payload: {
|
|
15
|
+
latestPrice: 188,
|
|
16
|
+
change_percent: 4.2,
|
|
17
|
+
volumeRatio: 2.1,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
(0, node_test_1.default)('monitor condition evaluator supports simple comparisons and safe boolean expressions', () => {
|
|
21
|
+
strict_1.default.equal((0, condition_evaluator_js_1.evaluateMonitorCondition)({
|
|
22
|
+
metric: 'abs_change_percent',
|
|
23
|
+
operator: '>=',
|
|
24
|
+
value: 3,
|
|
25
|
+
}, event), true);
|
|
26
|
+
strict_1.default.equal((0, condition_evaluator_js_1.evaluateMonitorCondition)({
|
|
27
|
+
metric: 'expression',
|
|
28
|
+
operator: '==',
|
|
29
|
+
value: true,
|
|
30
|
+
expression: 'abs_change_percent >= 3 && latestPrice > 100',
|
|
31
|
+
}, event), true);
|
|
32
|
+
strict_1.default.equal((0, condition_evaluator_js_1.evaluateMonitorCondition)({
|
|
33
|
+
metric: 'expression',
|
|
34
|
+
operator: '==',
|
|
35
|
+
value: true,
|
|
36
|
+
expression: 'latestPrice < 100 || volumeRatio >= 2',
|
|
37
|
+
}, event), true);
|
|
38
|
+
});
|