@kafca/agentdock 0.1.7
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 +70 -0
- package/bin/agentdock.mjs +275 -0
- package/build/icon.png +0 -0
- package/dist/renderer/assets/index-BaNpzC6d.js +482 -0
- package/dist/renderer/assets/index-CLWJyO0E.css +11 -0
- package/dist/renderer/favicon.svg +19 -0
- package/dist/renderer/index.html +14 -0
- package/dist/renderer/logo-options.svg +153 -0
- package/dist-electron/electron/knowledge-skill-script.test.js +125 -0
- package/dist-electron/electron/lac-cli.test.js +221 -0
- package/dist-electron/electron/local-core-refactor.test.js +1075 -0
- package/dist-electron/electron/main.js +163 -0
- package/dist-electron/electron/plugin-kernel.test.js +534 -0
- package/dist-electron/electron/preload.js +2 -0
- package/dist-electron/package.json +3 -0
- package/dist-electron/packages/contracts/src/index.js +18 -0
- package/dist-electron/packages/contracts/src/local-core.js +2 -0
- package/dist-electron/packages/core-sdk/src/index.js +406 -0
- package/dist-electron/packages/knowledge-api/src/ai-vector-provider.js +388 -0
- package/dist-electron/packages/knowledge-api/src/index.js +146 -0
- package/dist-electron/packages/knowledge-api/src/sqlite-store.js +467 -0
- package/dist-electron/packages/knowledge-api/src/thread-knowledge-store.js +21 -0
- package/dist-electron/packages/knowledge-api/test/ai-vector-provider.test.js +412 -0
- package/dist-electron/packages/plugin-sdk/src/index.js +2 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +369 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +110 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +171 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +635 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +190 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +244 -0
- package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +52 -0
- package/dist-electron/services/local-ai-core/src/cli/lac.js +290 -0
- package/dist-electron/services/local-ai-core/src/gateway/local-core-lark-gateway.js +976 -0
- package/dist-electron/services/local-ai-core/src/gateway/local-core-weixin-gateway.js +1143 -0
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +291 -0
- package/dist-electron/services/local-ai-core/src/kernel/capability-registry.js +67 -0
- package/dist-electron/services/local-ai-core/src/kernel/diagnostics.js +27 -0
- package/dist-electron/services/local-ai-core/src/kernel/event-bus.js +27 -0
- package/dist-electron/services/local-ai-core/src/kernel/lifecycle-manager.js +85 -0
- package/dist-electron/services/local-ai-core/src/kernel/plugin-registry.js +60 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +114 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +59 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +56 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-ai-vector-plugin.js +7 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-noop-plugin.js +7 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/runtime-capabilities-plugin.js +62 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-cron-plugin.js +49 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-lark-plugin.js +38 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-weixin-plugin.js +38 -0
- package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +230 -0
- package/dist-electron/services/local-ai-core/src/router/workspace-router-types.js +2 -0
- package/dist-electron/services/local-ai-core/src/router/workspace-router.js +389 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +315 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +282 -0
- package/dist-electron/services/local-ai-core/src/runtime/server.js +465 -0
- package/dist-electron/services/local-ai-core/src/runtime/standalone.js +38 -0
- package/dist-electron/services/local-ai-core/src/scheduler/adapters.js +2 -0
- package/dist-electron/services/local-ai-core/src/scheduler/cron-command-detector.js +70 -0
- package/dist-electron/services/local-ai-core/src/scheduler/cron.js +53 -0
- package/dist-electron/services/local-ai-core/src/scheduler/execution-policy.js +2 -0
- package/dist-electron/services/local-ai-core/src/scheduler/lark-execution-policies.js +66 -0
- package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +77 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +46 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js +64 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +133 -0
- package/dist-electron/services/local-ai-core/src/scheduler/weixin-execution-policies.js +66 -0
- package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +78 -0
- package/dist-electron/services/local-ai-core/src/thread/workspace-thread-id.js +17 -0
- package/dist-electron/services/local-ai-core/src/thread/workspace-thread-mappers.js +43 -0
- package/dist-electron/shared/desktop.js +195 -0
- package/dist-electron/src/api/client.js +73 -0
- package/dist-electron/src/api/sessions.js +21 -0
- package/dist-electron/src/lib/session-utils.js +51 -0
- package/dist-electron/src/pages/Threads/thread-chat-model.js +246 -0
- package/dist-electron/src/pages/Threads/thread-chat-model.test.js +76 -0
- package/dist-electron/src/pages/Threads/thread-chat-permission.js +26 -0
- package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +102 -0
- package/dist-electron/src/pages/Threads/thread-chat-task-state.js +65 -0
- package/package.json +146 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalAiCoreServer = void 0;
|
|
4
|
+
const node_http_1 = require("node:http");
|
|
5
|
+
function json(res, statusCode, data, ok = true, error) {
|
|
6
|
+
res.statusCode = statusCode;
|
|
7
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
8
|
+
res.end(JSON.stringify(ok ? { ok: true, data } : { ok: false, error }));
|
|
9
|
+
}
|
|
10
|
+
async function readJsonBody(req) {
|
|
11
|
+
const body = await readRawBody(req);
|
|
12
|
+
if (!body.length) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
return JSON.parse(Buffer.from(body).toString('utf8'));
|
|
16
|
+
}
|
|
17
|
+
async function readRawBody(req) {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for await (const chunk of req) {
|
|
20
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
21
|
+
}
|
|
22
|
+
return Buffer.concat(chunks);
|
|
23
|
+
}
|
|
24
|
+
function setCorsHeaders(req, res) {
|
|
25
|
+
const origin = String(req.headers.origin || '');
|
|
26
|
+
if (origin === 'null' || origin.startsWith('http://127.0.0.1:') || origin.startsWith('http://localhost:')) {
|
|
27
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
28
|
+
res.setHeader('Vary', 'Origin');
|
|
29
|
+
}
|
|
30
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PATCH,DELETE,OPTIONS');
|
|
31
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
32
|
+
}
|
|
33
|
+
function createSseEvent(name, payload) {
|
|
34
|
+
return `event: ${name}\ndata: ${JSON.stringify(payload)}\n\n`;
|
|
35
|
+
}
|
|
36
|
+
class LocalAiCoreServer {
|
|
37
|
+
bindings;
|
|
38
|
+
host;
|
|
39
|
+
port;
|
|
40
|
+
sseClients = new Set();
|
|
41
|
+
heartbeatTimers = new Map();
|
|
42
|
+
server = (0, node_http_1.createServer)((req, res) => {
|
|
43
|
+
void this.handleRequest(req, res);
|
|
44
|
+
});
|
|
45
|
+
constructor(bindings, options = {}) {
|
|
46
|
+
this.bindings = bindings;
|
|
47
|
+
this.host = options.host || '127.0.0.1';
|
|
48
|
+
this.port = options.port || 9831;
|
|
49
|
+
this.bindings.on('runtime', (runtime) => {
|
|
50
|
+
this.broadcast({ type: 'runtime.updated', runtime });
|
|
51
|
+
});
|
|
52
|
+
this.bindings.on('bridge', (bridge) => {
|
|
53
|
+
this.broadcast({ type: 'stream.updated', stream: bridge });
|
|
54
|
+
if (bridge.sessionKey) {
|
|
55
|
+
const threadId = this.findThreadIdFromSessionKey(bridge.sessionKey);
|
|
56
|
+
this.broadcast({
|
|
57
|
+
type: 'presence.updated',
|
|
58
|
+
threadId,
|
|
59
|
+
live: bridge.type !== 'typing_stop',
|
|
60
|
+
stream: bridge,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
this.bindings.on('scheduler-job', (job) => {
|
|
65
|
+
this.broadcast({ type: 'scheduler.job.updated', job });
|
|
66
|
+
});
|
|
67
|
+
this.bindings.on('scheduler-run', (run) => {
|
|
68
|
+
this.broadcast({ type: 'scheduler.run.updated', run });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async start() {
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
this.server.once('error', reject);
|
|
74
|
+
this.server.listen(this.port, this.host, () => {
|
|
75
|
+
this.server.removeListener('error', reject);
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async stop() {
|
|
81
|
+
for (const client of this.sseClients) {
|
|
82
|
+
client.end();
|
|
83
|
+
}
|
|
84
|
+
this.sseClients.clear();
|
|
85
|
+
for (const timer of this.heartbeatTimers.values()) {
|
|
86
|
+
clearInterval(timer);
|
|
87
|
+
}
|
|
88
|
+
this.heartbeatTimers.clear();
|
|
89
|
+
await new Promise((resolve, reject) => {
|
|
90
|
+
this.server.close((error) => {
|
|
91
|
+
if (error) {
|
|
92
|
+
reject(error);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async handleRequest(req, res) {
|
|
100
|
+
setCorsHeaders(req, res);
|
|
101
|
+
if (req.method === 'OPTIONS') {
|
|
102
|
+
res.statusCode = 204;
|
|
103
|
+
res.end();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const url = new URL(req.url || '/', `http://${this.host}:${this.port}`);
|
|
107
|
+
const path = url.pathname;
|
|
108
|
+
try {
|
|
109
|
+
if (req.method === 'GET' && path === '/api/local/v1/health') {
|
|
110
|
+
json(res, 200, { name: 'local-ai-core', version: '0.1.0' });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (req.method === 'GET' && path === '/api/local/v1/runtime') {
|
|
114
|
+
json(res, 200, await this.bindings.getRuntimeStatus());
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (req.method === 'POST' && path === '/api/local/v1/runtime/service/start') {
|
|
118
|
+
json(res, 200, await this.bindings.startService());
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (req.method === 'POST' && path === '/api/local/v1/runtime/service/stop') {
|
|
122
|
+
json(res, 200, await this.bindings.stopService());
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (req.method === 'POST' && path === '/api/local/v1/runtime/service/restart') {
|
|
126
|
+
json(res, 200, await this.bindings.restartService());
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (req.method === 'GET' && path === '/api/local/v1/runtime/logs') {
|
|
130
|
+
const limit = Number(url.searchParams.get('limit') || '200');
|
|
131
|
+
json(res, 200, this.bindings.getLogs(limit));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (req.method === 'GET' && path === '/api/local/v1/runtime/config') {
|
|
135
|
+
json(res, 200, await this.bindings.readConfigFile());
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (req.method === 'POST' && path === '/api/local/v1/runtime/config/raw') {
|
|
139
|
+
const body = await readJsonBody(req);
|
|
140
|
+
json(res, 200, await this.bindings.saveRawConfigFile(String(body.raw || '')));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (req.method === 'POST' && path === '/api/local/v1/runtime/config/structured') {
|
|
144
|
+
const body = await readJsonBody(req);
|
|
145
|
+
json(res, 200, await this.bindings.saveStructuredConfigFile((body.config || {})));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (req.method === 'POST' && path === '/api/local/v1/runtime/settings') {
|
|
149
|
+
const body = await readJsonBody(req);
|
|
150
|
+
json(res, 200, await this.bindings.saveSettings(body));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (req.method === 'GET' && path.startsWith('/api/local/v1/platforms/')) {
|
|
154
|
+
const suffix = path.slice('/api/local/v1/platforms/'.length);
|
|
155
|
+
const segments = suffix.split('/').filter(Boolean).map((segment) => decodeURIComponent(segment));
|
|
156
|
+
const [platform = '', workspaceOrCollection = '', action = ''] = segments;
|
|
157
|
+
if (!platform) {
|
|
158
|
+
json(res, 404, null, false, 'Platform not found');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (segments.length === 1) {
|
|
162
|
+
json(res, 200, { gateways: await this.bindings.listChannelGatewayStatuses(platform) });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (workspaceOrCollection === 'pairings' && segments.length === 2) {
|
|
166
|
+
const workspaceId = String(url.searchParams.get('workspace_id') || '');
|
|
167
|
+
json(res, 200, { pairings: await this.bindings.listChannelPendingPairings(platform, workspaceId || undefined) });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (workspaceOrCollection === 'users' && segments.length === 2) {
|
|
171
|
+
const workspaceId = String(url.searchParams.get('workspace_id') || '');
|
|
172
|
+
json(res, 200, { users: await this.bindings.listChannelAuthorizedUsers(platform, workspaceId || undefined) });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (segments.length === 2) {
|
|
176
|
+
json(res, 200, await this.bindings.getChannelGatewayStatus(platform, workspaceOrCollection));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (platform === 'weixin' && segments.length === 4 && action === 'qrcode' && segments[3] === 'status') {
|
|
180
|
+
const ticket = String(url.searchParams.get('ticket') || '');
|
|
181
|
+
if (!ticket) {
|
|
182
|
+
json(res, 400, null, false, 'Missing ticket parameter');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
json(res, 200, await this.bindings.checkWeixinQrCodeStatus(workspaceOrCollection, ticket));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/platforms/')) {
|
|
190
|
+
const suffix = path.slice('/api/local/v1/platforms/'.length);
|
|
191
|
+
const segments = suffix.split('/').filter(Boolean).map((segment) => decodeURIComponent(segment));
|
|
192
|
+
const [platform = '', workspaceOrCollection = '', action = ''] = segments;
|
|
193
|
+
if (!platform) {
|
|
194
|
+
json(res, 404, null, false, 'Platform not found');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (workspaceOrCollection === 'pairings' && action === 'approve') {
|
|
198
|
+
const body = await readJsonBody(req);
|
|
199
|
+
json(res, 200, await this.bindings.approveChannelPairing(platform, String(body.code || '')));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (workspaceOrCollection === 'pairings' && action === 'reject') {
|
|
203
|
+
const body = await readJsonBody(req);
|
|
204
|
+
json(res, 200, await this.bindings.rejectChannelPairing(platform, String(body.code || '')));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (action === 'test') {
|
|
208
|
+
json(res, 200, await this.bindings.testChannelConnection(platform, workspaceOrCollection));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (action === 'enable') {
|
|
212
|
+
json(res, 200, await this.bindings.enableChannelGateway(platform, workspaceOrCollection));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (action === 'disable') {
|
|
216
|
+
json(res, 200, await this.bindings.disableChannelGateway(platform, workspaceOrCollection));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (platform === 'weixin' && segments.length === 3 && workspaceOrCollection !== 'pairings' && action === 'qrcode') {
|
|
220
|
+
json(res, 200, await this.bindings.getWeixinQrCode(workspaceOrCollection));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (req.method === 'GET' && path === '/api/local/v1/scheduler/jobs') {
|
|
225
|
+
const workspaceId = String(url.searchParams.get('workspace_id') || '');
|
|
226
|
+
json(res, 200, { jobs: await this.bindings.listScheduledJobs(workspaceId || undefined) });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (req.method === 'POST' && path === '/api/local/v1/scheduler/jobs') {
|
|
230
|
+
const body = await readJsonBody(req);
|
|
231
|
+
json(res, 200, await this.bindings.createScheduledJob(body));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (req.method === 'GET'
|
|
235
|
+
&& path.startsWith('/api/local/v1/scheduler/jobs/')
|
|
236
|
+
&& !path.endsWith('/runs')
|
|
237
|
+
&& !path.endsWith('/run')) {
|
|
238
|
+
const jobId = decodeURIComponent(path.slice('/api/local/v1/scheduler/jobs/'.length));
|
|
239
|
+
json(res, 200, await this.bindings.getScheduledJob(jobId));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (req.method === 'GET' && path.startsWith('/api/local/v1/scheduler/jobs/') && path.endsWith('/runs')) {
|
|
243
|
+
const jobId = decodeURIComponent(path.slice('/api/local/v1/scheduler/jobs/'.length, -'/runs'.length));
|
|
244
|
+
json(res, 200, { runs: await this.bindings.listScheduledJobRuns(jobId) });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/scheduler/jobs/') && path.endsWith('/run')) {
|
|
248
|
+
const jobId = decodeURIComponent(path.slice('/api/local/v1/scheduler/jobs/'.length, -'/run'.length));
|
|
249
|
+
json(res, 200, await this.bindings.runScheduledJob(jobId));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (req.method === 'PATCH' && path.startsWith('/api/local/v1/scheduler/jobs/')) {
|
|
253
|
+
const jobId = decodeURIComponent(path.slice('/api/local/v1/scheduler/jobs/'.length));
|
|
254
|
+
const body = await readJsonBody(req);
|
|
255
|
+
json(res, 200, await this.bindings.updateScheduledJob(jobId, body));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (req.method === 'DELETE' && path.startsWith('/api/local/v1/scheduler/jobs/')) {
|
|
259
|
+
const jobId = decodeURIComponent(path.slice('/api/local/v1/scheduler/jobs/'.length));
|
|
260
|
+
json(res, 200, await this.bindings.deleteScheduledJob(jobId));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (req.method === 'GET' && path === '/api/local/v1/workspaces') {
|
|
264
|
+
json(res, 200, { workspaces: await this.bindings.listWorkspaces() });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (req.method === 'GET' && path === '/api/local/v1/threads') {
|
|
268
|
+
const workspaceId = String(url.searchParams.get('workspace_id') || '');
|
|
269
|
+
json(res, 200, { threads: workspaceId ? await this.bindings.listThreads(workspaceId) : [] });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (req.method === 'POST' && path === '/api/local/v1/threads') {
|
|
273
|
+
const body = await readJsonBody(req);
|
|
274
|
+
json(res, 200, await this.bindings.createThread(String(body.workspaceId || ''), String(body.title || '') || undefined));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (req.method === 'GET' && path.startsWith('/api/local/v1/threads/')) {
|
|
278
|
+
const threadId = decodeURIComponent(path.slice('/api/local/v1/threads/'.length));
|
|
279
|
+
json(res, 200, await this.bindings.getThread(threadId));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (req.method === 'PATCH' && path.startsWith('/api/local/v1/threads/') && path.endsWith('/knowledge-bases')) {
|
|
283
|
+
const threadId = decodeURIComponent(path.slice('/api/local/v1/threads/'.length, -'/knowledge-bases'.length));
|
|
284
|
+
const body = await readJsonBody(req);
|
|
285
|
+
const knowledgeBaseIds = Array.isArray(body.knowledgeBaseIds)
|
|
286
|
+
? body.knowledgeBaseIds.map((value) => String(value || ''))
|
|
287
|
+
: [];
|
|
288
|
+
json(res, 200, await this.bindings.updateThreadKnowledgeBases(threadId, knowledgeBaseIds));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (req.method === 'PATCH' && path.startsWith('/api/local/v1/threads/')) {
|
|
292
|
+
const threadId = decodeURIComponent(path.slice('/api/local/v1/threads/'.length));
|
|
293
|
+
const body = await readJsonBody(req);
|
|
294
|
+
json(res, 200, await this.bindings.renameThread(threadId, String(body.title || '')));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (req.method === 'DELETE' && path.startsWith('/api/local/v1/threads/')) {
|
|
298
|
+
const threadId = decodeURIComponent(path.slice('/api/local/v1/threads/'.length));
|
|
299
|
+
json(res, 200, await this.bindings.deleteThread(threadId));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/threads/') && path.endsWith('/messages')) {
|
|
303
|
+
const threadId = decodeURIComponent(path.slice('/api/local/v1/threads/'.length, -'/messages'.length));
|
|
304
|
+
const body = await readJsonBody(req);
|
|
305
|
+
json(res, 200, await this.bindings.sendThreadMessage(threadId, String(body.content || '')));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/threads/') && path.endsWith('/actions')) {
|
|
309
|
+
const threadId = decodeURIComponent(path.slice('/api/local/v1/threads/'.length, -'/actions'.length));
|
|
310
|
+
const body = await readJsonBody(req);
|
|
311
|
+
json(res, 200, await this.bindings.sendThreadAction(threadId, String(body.content || '')));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/runs/') && path.endsWith('/interrupt')) {
|
|
315
|
+
const runId = decodeURIComponent(path.slice('/api/local/v1/runs/'.length, -'/interrupt'.length));
|
|
316
|
+
json(res, 200, await this.bindings.interruptRun(runId));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (req.method === 'GET' && path === '/api/local/v1/knowledge/sources') {
|
|
320
|
+
json(res, 200, { sources: await this.bindings.listKnowledgeSources() });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (req.method === 'GET' && path === '/api/local/v1/knowledge/config') {
|
|
324
|
+
json(res, 200, await this.bindings.getKnowledgeConfig());
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (req.method === 'POST' && path === '/api/local/v1/knowledge/config') {
|
|
328
|
+
const body = await readJsonBody(req);
|
|
329
|
+
json(res, 200, await this.bindings.updateKnowledgeConfig(body));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (req.method === 'GET' && path === '/api/local/v1/knowledge/folders') {
|
|
333
|
+
json(res, 200, { folders: await this.bindings.listKnowledgeFolders() });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (req.method === 'POST' && path === '/api/local/v1/knowledge/folders') {
|
|
337
|
+
const body = await readJsonBody(req);
|
|
338
|
+
json(res, 200, await this.bindings.createKnowledgeFolder(body));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (req.method === 'PATCH' && path.startsWith('/api/local/v1/knowledge/folders/')) {
|
|
342
|
+
const folderId = decodeURIComponent(path.slice('/api/local/v1/knowledge/folders/'.length));
|
|
343
|
+
const body = await readJsonBody(req);
|
|
344
|
+
json(res, 200, await this.bindings.updateKnowledgeFolder(folderId, body));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (req.method === 'DELETE' && path.startsWith('/api/local/v1/knowledge/folders/')) {
|
|
348
|
+
const folderId = decodeURIComponent(path.slice('/api/local/v1/knowledge/folders/'.length));
|
|
349
|
+
json(res, 200, await this.bindings.deleteKnowledgeFolder(folderId));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (req.method === 'GET' && path === '/api/local/v1/knowledge/bases') {
|
|
353
|
+
json(res, 200, { bases: await this.bindings.listKnowledgeBases() });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (req.method === 'POST' && path === '/api/local/v1/knowledge/bases') {
|
|
357
|
+
const body = await readJsonBody(req);
|
|
358
|
+
json(res, 200, await this.bindings.createKnowledgeBase(body));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (req.method === 'GET' && path.startsWith('/api/local/v1/knowledge/bases/') && !path.endsWith('/files') && !path.endsWith('/search')) {
|
|
362
|
+
const knowledgeBaseId = decodeURIComponent(path.slice('/api/local/v1/knowledge/bases/'.length));
|
|
363
|
+
json(res, 200, await this.bindings.getKnowledgeBase(knowledgeBaseId));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (req.method === 'PATCH' && path.startsWith('/api/local/v1/knowledge/bases/')) {
|
|
367
|
+
const knowledgeBaseId = decodeURIComponent(path.slice('/api/local/v1/knowledge/bases/'.length));
|
|
368
|
+
const body = await readJsonBody(req);
|
|
369
|
+
json(res, 200, await this.bindings.updateKnowledgeBase(knowledgeBaseId, body));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (req.method === 'DELETE' && path.startsWith('/api/local/v1/knowledge/bases/') && !path.includes('/files/')) {
|
|
373
|
+
const knowledgeBaseId = decodeURIComponent(path.slice('/api/local/v1/knowledge/bases/'.length));
|
|
374
|
+
json(res, 200, await this.bindings.deleteKnowledgeBase(knowledgeBaseId));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (req.method === 'GET' && path.startsWith('/api/local/v1/knowledge/bases/') && path.endsWith('/files')) {
|
|
378
|
+
const knowledgeBaseId = decodeURIComponent(path.slice('/api/local/v1/knowledge/bases/'.length, -'/files'.length));
|
|
379
|
+
json(res, 200, { files: await this.bindings.listKnowledgeBaseFiles(knowledgeBaseId) });
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/knowledge/bases/') && path.endsWith('/files')) {
|
|
383
|
+
const knowledgeBaseId = decodeURIComponent(path.slice('/api/local/v1/knowledge/bases/'.length, -'/files'.length));
|
|
384
|
+
const contentType = String(req.headers['content-type'] || '').trim();
|
|
385
|
+
if (!contentType) {
|
|
386
|
+
throw new Error('Upload content type is required.');
|
|
387
|
+
}
|
|
388
|
+
const body = await readRawBody(req);
|
|
389
|
+
json(res, 200, { results: await this.bindings.uploadKnowledgeBaseFiles(knowledgeBaseId, { contentType, body }) });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (req.method === 'DELETE' && path.includes('/api/local/v1/knowledge/bases/') && path.includes('/files/')) {
|
|
393
|
+
const prefix = '/api/local/v1/knowledge/bases/';
|
|
394
|
+
const fileMarker = '/files/';
|
|
395
|
+
const knowledgeBaseId = decodeURIComponent(path.slice(prefix.length, path.indexOf(fileMarker)));
|
|
396
|
+
const fileId = decodeURIComponent(path.slice(path.indexOf(fileMarker) + fileMarker.length));
|
|
397
|
+
json(res, 200, await this.bindings.deleteKnowledgeBaseFile(knowledgeBaseId, fileId));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/knowledge/bases/') && path.endsWith('/search')) {
|
|
401
|
+
const knowledgeBaseId = decodeURIComponent(path.slice('/api/local/v1/knowledge/bases/'.length, -'/search'.length));
|
|
402
|
+
const body = await readJsonBody(req);
|
|
403
|
+
json(res, 200, { results: await this.bindings.searchKnowledgeBase(knowledgeBaseId, body) });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (req.method === 'GET' && path === '/api/local/v1/capabilities') {
|
|
407
|
+
json(res, 200, await this.bindings.getCapabilities());
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (req.method === 'GET' && path === '/api/local/v1/capabilities/snapshot') {
|
|
411
|
+
json(res, 200, await this.bindings.getCapabilitySnapshot());
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (req.method === 'GET' && path === '/api/local/v1/plugins/diagnostics') {
|
|
415
|
+
json(res, 200, await this.bindings.getPluginDiagnostics());
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (req.method === 'POST' && path.startsWith('/api/local/v1/workspaces/') && path.endsWith('/streaming-probe')) {
|
|
419
|
+
const workspaceId = decodeURIComponent(path.slice('/api/local/v1/workspaces/'.length, -'/streaming-probe'.length));
|
|
420
|
+
json(res, 200, await this.bindings.probeWorkspaceStreaming(workspaceId));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (req.method === 'GET' && path === '/api/local/v1/events') {
|
|
424
|
+
this.attachSseClient(res);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
json(res, 404, null, false, `Unknown route: ${path}`);
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
json(res, 500, null, false, error instanceof Error ? error.message : String(error));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
attachSseClient(res) {
|
|
434
|
+
res.writeHead(200, {
|
|
435
|
+
'Content-Type': 'text/event-stream',
|
|
436
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
437
|
+
Connection: 'keep-alive',
|
|
438
|
+
});
|
|
439
|
+
res.write(': connected\n\n');
|
|
440
|
+
this.sseClients.add(res);
|
|
441
|
+
const heartbeat = setInterval(() => {
|
|
442
|
+
res.write(': heartbeat\n\n');
|
|
443
|
+
}, 15000);
|
|
444
|
+
this.heartbeatTimers.set(res, heartbeat);
|
|
445
|
+
res.on('close', () => {
|
|
446
|
+
clearInterval(heartbeat);
|
|
447
|
+
this.heartbeatTimers.delete(res);
|
|
448
|
+
this.sseClients.delete(res);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
broadcast(event) {
|
|
452
|
+
const payload = createSseEvent(event.type, event);
|
|
453
|
+
for (const client of this.sseClients) {
|
|
454
|
+
client.write(payload);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
findThreadIdFromSessionKey(sessionKey) {
|
|
458
|
+
const parts = sessionKey.split(':');
|
|
459
|
+
if (parts.length < 3) {
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
return `${encodeURIComponent(parts[1] || '')}::${encodeURIComponent(parts[2] || '')}`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
exports.LocalAiCoreServer = LocalAiCoreServer;
|
|
@@ -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_process_1 = __importDefault(require("node:process"));
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const local_core_controller_js_1 = require("./local-core-controller.js");
|
|
10
|
+
const server_js_1 = require("./server.js");
|
|
11
|
+
async function main() {
|
|
12
|
+
const userDataPath = node_process_1.default.env.AI_WORKSTATION_USER_DATA_DIR?.trim() || (0, node_path_1.join)(node_process_1.default.cwd(), '.agentdock-core');
|
|
13
|
+
(0, node_fs_1.mkdirSync)(userDataPath, { recursive: true });
|
|
14
|
+
const controller = new local_core_controller_js_1.LocalCoreController(userDataPath);
|
|
15
|
+
controller.on('logs', (line) => {
|
|
16
|
+
if (!line) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
node_process_1.default.stdout.write(`[local-ai-core] ${line}\n`);
|
|
20
|
+
});
|
|
21
|
+
controller.on('bridge', (event) => {
|
|
22
|
+
node_process_1.default.stdout.write(`[local-ai-core bridge] ${JSON.stringify(event)}\n`);
|
|
23
|
+
});
|
|
24
|
+
await controller.init();
|
|
25
|
+
const server = new server_js_1.LocalAiCoreServer(controller);
|
|
26
|
+
await server.start();
|
|
27
|
+
node_process_1.default.on('SIGINT', async () => {
|
|
28
|
+
await server.stop();
|
|
29
|
+
await controller.close();
|
|
30
|
+
node_process_1.default.exit(0);
|
|
31
|
+
});
|
|
32
|
+
node_process_1.default.on('SIGTERM', async () => {
|
|
33
|
+
await server.stop();
|
|
34
|
+
await controller.close();
|
|
35
|
+
node_process_1.default.exit(0);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
void main();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectCronCommands = detectCronCommands;
|
|
4
|
+
exports.stripCronCommands = stripCronCommands;
|
|
5
|
+
function stripCodeBlocks(content) {
|
|
6
|
+
return content.replace(/```[\s\S]*?```/g, '');
|
|
7
|
+
}
|
|
8
|
+
function detectCronCommands(content) {
|
|
9
|
+
if (!content || typeof content !== 'string') {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const cleanContent = stripCodeBlocks(content);
|
|
13
|
+
const commands = [];
|
|
14
|
+
const createMatches = cleanContent.matchAll(/\[CRON_CREATE\]\s*\n?([\s\S]*?)\[\/CRON_CREATE\]/gi);
|
|
15
|
+
for (const match of createMatches) {
|
|
16
|
+
const parsed = parseCronCreateBody(match[1] || '');
|
|
17
|
+
if (parsed) {
|
|
18
|
+
commands.push({ kind: 'create', ...parsed });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!commands.some((command) => command.kind === 'create')) {
|
|
22
|
+
const fallbackMatch = cleanContent.match(/\[CRON_CREATE\]\s*\n?([\s\S]*?)(?=\[CRON_(?:LIST|DELETE)|$)/i);
|
|
23
|
+
if (fallbackMatch) {
|
|
24
|
+
const parsed = parseCronCreateBody(fallbackMatch[1] || '');
|
|
25
|
+
if (parsed) {
|
|
26
|
+
commands.push({ kind: 'create', ...parsed });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (/\[CRON_LIST\]/i.test(cleanContent)) {
|
|
31
|
+
commands.push({ kind: 'list' });
|
|
32
|
+
}
|
|
33
|
+
const deleteMatches = cleanContent.matchAll(/\[CRON_DELETE:\s*([^\]]+)\]/gi);
|
|
34
|
+
for (const match of deleteMatches) {
|
|
35
|
+
const jobId = String(match[1] || '').trim();
|
|
36
|
+
if (jobId) {
|
|
37
|
+
commands.push({ kind: 'delete', jobId });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return commands;
|
|
41
|
+
}
|
|
42
|
+
function stripCronCommands(content) {
|
|
43
|
+
if (!content || typeof content !== 'string') {
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
46
|
+
return content
|
|
47
|
+
.replace(/\[CRON_CREATE\][\s\S]*?\[\/CRON_CREATE\]/gi, '')
|
|
48
|
+
.replace(/\[CRON_LIST\]/gi, '')
|
|
49
|
+
.replace(/\[CRON_DELETE:[^\]]+\]/gi, '')
|
|
50
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
51
|
+
.trim();
|
|
52
|
+
}
|
|
53
|
+
function parseCronCreateBody(body) {
|
|
54
|
+
if (!body) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const name = body.match(/^name:\s*(.+)$/im)?.[1]?.trim();
|
|
58
|
+
const schedule = body.match(/^schedule:\s*(.+)$/im)?.[1]?.trim();
|
|
59
|
+
const scheduleDescription = body.match(/^schedule_description:\s*(.+)$/im)?.[1]?.trim();
|
|
60
|
+
const message = body.match(/message:\s*([\s\S]*?)(?=\n(?:name|schedule|schedule_description):|$)/i)?.[1]?.trim();
|
|
61
|
+
if (!name || !schedule || !scheduleDescription || !message) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
schedule,
|
|
67
|
+
scheduleDescription,
|
|
68
|
+
message: message.replace(/\[\/CRON_CREATE\]/gi, '').trim(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cronMatchesDate = cronMatchesDate;
|
|
4
|
+
exports.floorToMinute = floorToMinute;
|
|
5
|
+
function expandSegment(segment, min, max) {
|
|
6
|
+
const values = new Set();
|
|
7
|
+
const normalized = segment.trim();
|
|
8
|
+
if (!normalized || normalized === '*') {
|
|
9
|
+
for (let value = min; value <= max; value += 1) {
|
|
10
|
+
values.add(value);
|
|
11
|
+
}
|
|
12
|
+
return values;
|
|
13
|
+
}
|
|
14
|
+
for (const part of normalized.split(',')) {
|
|
15
|
+
const token = part.trim();
|
|
16
|
+
if (!token) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const [rangePart, stepPart] = token.split('/');
|
|
20
|
+
const step = Math.max(1, Number(stepPart || '1'));
|
|
21
|
+
let rangeStart = min;
|
|
22
|
+
let rangeEnd = max;
|
|
23
|
+
if (rangePart && rangePart !== '*') {
|
|
24
|
+
const [startPart, endPart] = rangePart.split('-');
|
|
25
|
+
rangeStart = Math.max(min, Number(startPart));
|
|
26
|
+
rangeEnd = Math.min(max, Number(endPart || startPart));
|
|
27
|
+
}
|
|
28
|
+
for (let value = rangeStart; value <= rangeEnd; value += step) {
|
|
29
|
+
if (value >= min && value <= max) {
|
|
30
|
+
values.add(value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
36
|
+
function cronMatchesDate(cronExpr, date) {
|
|
37
|
+
const fields = String(cronExpr || '').trim().split(/\s+/);
|
|
38
|
+
if (fields.length !== 5) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
|
|
42
|
+
const checks = [
|
|
43
|
+
[minute, 0, 59, date.getMinutes()],
|
|
44
|
+
[hour, 0, 23, date.getHours()],
|
|
45
|
+
[dayOfMonth, 1, 31, date.getDate()],
|
|
46
|
+
[month, 1, 12, date.getMonth() + 1],
|
|
47
|
+
[dayOfWeek, 0, 6, date.getDay()],
|
|
48
|
+
];
|
|
49
|
+
return checks.every(([segment, min, max, current]) => expandSegment(segment, min, max).has(current));
|
|
50
|
+
}
|
|
51
|
+
function floorToMinute(date) {
|
|
52
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), 0, 0);
|
|
53
|
+
}
|