@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.
Files changed (79) hide show
  1. package/README.md +70 -0
  2. package/bin/agentdock.mjs +275 -0
  3. package/build/icon.png +0 -0
  4. package/dist/renderer/assets/index-BaNpzC6d.js +482 -0
  5. package/dist/renderer/assets/index-CLWJyO0E.css +11 -0
  6. package/dist/renderer/favicon.svg +19 -0
  7. package/dist/renderer/index.html +14 -0
  8. package/dist/renderer/logo-options.svg +153 -0
  9. package/dist-electron/electron/knowledge-skill-script.test.js +125 -0
  10. package/dist-electron/electron/lac-cli.test.js +221 -0
  11. package/dist-electron/electron/local-core-refactor.test.js +1075 -0
  12. package/dist-electron/electron/main.js +163 -0
  13. package/dist-electron/electron/plugin-kernel.test.js +534 -0
  14. package/dist-electron/electron/preload.js +2 -0
  15. package/dist-electron/package.json +3 -0
  16. package/dist-electron/packages/contracts/src/index.js +18 -0
  17. package/dist-electron/packages/contracts/src/local-core.js +2 -0
  18. package/dist-electron/packages/core-sdk/src/index.js +406 -0
  19. package/dist-electron/packages/knowledge-api/src/ai-vector-provider.js +388 -0
  20. package/dist-electron/packages/knowledge-api/src/index.js +146 -0
  21. package/dist-electron/packages/knowledge-api/src/sqlite-store.js +467 -0
  22. package/dist-electron/packages/knowledge-api/src/thread-knowledge-store.js +21 -0
  23. package/dist-electron/packages/knowledge-api/test/ai-vector-provider.test.js +412 -0
  24. package/dist-electron/packages/plugin-sdk/src/index.js +2 -0
  25. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +369 -0
  26. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +110 -0
  27. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +171 -0
  28. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +635 -0
  29. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +190 -0
  30. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +244 -0
  31. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +52 -0
  32. package/dist-electron/services/local-ai-core/src/cli/lac.js +290 -0
  33. package/dist-electron/services/local-ai-core/src/gateway/local-core-lark-gateway.js +976 -0
  34. package/dist-electron/services/local-ai-core/src/gateway/local-core-weixin-gateway.js +1143 -0
  35. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +291 -0
  36. package/dist-electron/services/local-ai-core/src/kernel/capability-registry.js +67 -0
  37. package/dist-electron/services/local-ai-core/src/kernel/diagnostics.js +27 -0
  38. package/dist-electron/services/local-ai-core/src/kernel/event-bus.js +27 -0
  39. package/dist-electron/services/local-ai-core/src/kernel/lifecycle-manager.js +85 -0
  40. package/dist-electron/services/local-ai-core/src/kernel/plugin-registry.js +60 -0
  41. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +114 -0
  42. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +59 -0
  43. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +56 -0
  44. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-ai-vector-plugin.js +7 -0
  45. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-noop-plugin.js +7 -0
  46. package/dist-electron/services/local-ai-core/src/plugins/builtin/runtime-capabilities-plugin.js +62 -0
  47. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-cron-plugin.js +49 -0
  48. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-lark-plugin.js +38 -0
  49. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-weixin-plugin.js +38 -0
  50. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +230 -0
  51. package/dist-electron/services/local-ai-core/src/router/workspace-router-types.js +2 -0
  52. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +389 -0
  53. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +315 -0
  54. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +282 -0
  55. package/dist-electron/services/local-ai-core/src/runtime/server.js +465 -0
  56. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +38 -0
  57. package/dist-electron/services/local-ai-core/src/scheduler/adapters.js +2 -0
  58. package/dist-electron/services/local-ai-core/src/scheduler/cron-command-detector.js +70 -0
  59. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +53 -0
  60. package/dist-electron/services/local-ai-core/src/scheduler/execution-policy.js +2 -0
  61. package/dist-electron/services/local-ai-core/src/scheduler/lark-execution-policies.js +66 -0
  62. package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +77 -0
  63. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +46 -0
  64. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js +64 -0
  65. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +133 -0
  66. package/dist-electron/services/local-ai-core/src/scheduler/weixin-execution-policies.js +66 -0
  67. package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +78 -0
  68. package/dist-electron/services/local-ai-core/src/thread/workspace-thread-id.js +17 -0
  69. package/dist-electron/services/local-ai-core/src/thread/workspace-thread-mappers.js +43 -0
  70. package/dist-electron/shared/desktop.js +195 -0
  71. package/dist-electron/src/api/client.js +73 -0
  72. package/dist-electron/src/api/sessions.js +21 -0
  73. package/dist-electron/src/lib/session-utils.js +51 -0
  74. package/dist-electron/src/pages/Threads/thread-chat-model.js +246 -0
  75. package/dist-electron/src/pages/Threads/thread-chat-model.test.js +76 -0
  76. package/dist-electron/src/pages/Threads/thread-chat-permission.js +26 -0
  77. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +102 -0
  78. package/dist-electron/src/pages/Threads/thread-chat-task-state.js +65 -0
  79. package/package.json +146 -0
@@ -0,0 +1,635 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalCoreAcpStore = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_sqlite_1 = require("node:sqlite");
7
+ const node_path_1 = require("node:path");
8
+ const desktop_js_1 = require("../../../../shared/desktop.js");
9
+ const workspace_thread_mappers_js_1 = require("../thread/workspace-thread-mappers.js");
10
+ const workspace_thread_id_js_1 = require("../thread/workspace-thread-id.js");
11
+ class LocalCoreAcpStore {
12
+ db;
13
+ constructor(userDataPath) {
14
+ const dbPath = (0, node_path_1.join)(userDataPath, 'runtime', 'local-core.db');
15
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(dbPath), { recursive: true });
16
+ this.db = new node_sqlite_1.DatabaseSync(dbPath);
17
+ this.db.exec(`
18
+ PRAGMA foreign_keys = ON;
19
+ CREATE TABLE IF NOT EXISTS threads (
20
+ id TEXT PRIMARY KEY,
21
+ workspace_id TEXT NOT NULL,
22
+ session_id TEXT NOT NULL,
23
+ bridge_session_key TEXT NOT NULL,
24
+ title TEXT NOT NULL,
25
+ agent_type TEXT NOT NULL,
26
+ created_at TEXT NOT NULL,
27
+ updated_at TEXT NOT NULL,
28
+ history_count INTEGER NOT NULL DEFAULT 0,
29
+ excerpt TEXT NOT NULL DEFAULT '',
30
+ acp_session_id TEXT,
31
+ acp_supports_load INTEGER NOT NULL DEFAULT 0
32
+ );
33
+ CREATE INDEX IF NOT EXISTS idx_threads_workspace_updated ON threads (workspace_id, updated_at DESC);
34
+ CREATE TABLE IF NOT EXISTS messages (
35
+ id TEXT PRIMARY KEY,
36
+ thread_id TEXT NOT NULL,
37
+ role TEXT NOT NULL,
38
+ content TEXT NOT NULL,
39
+ timestamp TEXT NOT NULL,
40
+ kind TEXT NOT NULL,
41
+ seq INTEGER NOT NULL,
42
+ FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE
43
+ );
44
+ CREATE INDEX IF NOT EXISTS idx_messages_thread_seq ON messages (thread_id, seq ASC);
45
+ CREATE TABLE IF NOT EXISTS runs (
46
+ id TEXT PRIMARY KEY,
47
+ thread_id TEXT NOT NULL,
48
+ status TEXT NOT NULL,
49
+ started_at TEXT NOT NULL,
50
+ updated_at TEXT NOT NULL,
51
+ FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE
52
+ );
53
+ CREATE INDEX IF NOT EXISTS idx_runs_thread_updated ON runs (thread_id, updated_at DESC);
54
+ CREATE TABLE IF NOT EXISTS platform_pairings (
55
+ code TEXT PRIMARY KEY,
56
+ workspace_id TEXT NOT NULL,
57
+ platform TEXT NOT NULL,
58
+ platform_user_id TEXT NOT NULL,
59
+ chat_id TEXT NOT NULL,
60
+ display_name TEXT NOT NULL,
61
+ requested_at TEXT NOT NULL,
62
+ expires_at TEXT NOT NULL,
63
+ status TEXT NOT NULL
64
+ );
65
+ CREATE INDEX IF NOT EXISTS idx_platform_pairings_workspace_status ON platform_pairings (workspace_id, status, expires_at DESC);
66
+ CREATE TABLE IF NOT EXISTS platform_users (
67
+ id TEXT PRIMARY KEY,
68
+ workspace_id TEXT NOT NULL,
69
+ platform TEXT NOT NULL,
70
+ platform_user_id TEXT NOT NULL,
71
+ chat_id TEXT NOT NULL,
72
+ display_name TEXT NOT NULL,
73
+ thread_id TEXT,
74
+ authorized_at TEXT NOT NULL,
75
+ UNIQUE(workspace_id, platform, platform_user_id)
76
+ );
77
+ CREATE INDEX IF NOT EXISTS idx_platform_users_workspace_platform ON platform_users (workspace_id, platform);
78
+ CREATE TABLE IF NOT EXISTS platform_thread_bindings (
79
+ workspace_id TEXT NOT NULL,
80
+ platform TEXT NOT NULL,
81
+ chat_id TEXT NOT NULL,
82
+ platform_user_id TEXT NOT NULL,
83
+ thread_id TEXT NOT NULL,
84
+ last_platform_message_id TEXT,
85
+ created_at TEXT NOT NULL,
86
+ updated_at TEXT NOT NULL,
87
+ PRIMARY KEY (workspace_id, platform, chat_id, platform_user_id)
88
+ );
89
+ CREATE INDEX IF NOT EXISTS idx_platform_thread_bindings_thread ON platform_thread_bindings (thread_id);
90
+ CREATE TABLE IF NOT EXISTS scheduled_jobs (
91
+ id TEXT PRIMARY KEY,
92
+ workspace_id TEXT NOT NULL,
93
+ platform TEXT NOT NULL,
94
+ route_type TEXT NOT NULL,
95
+ route_config TEXT NOT NULL,
96
+ execution_mode TEXT NOT NULL DEFAULT 'same-thread',
97
+ trigger_type TEXT NOT NULL,
98
+ cron_expr TEXT,
99
+ run_at TEXT,
100
+ prompt_template TEXT NOT NULL,
101
+ description TEXT NOT NULL DEFAULT '',
102
+ enabled INTEGER NOT NULL DEFAULT 1,
103
+ concurrency_policy TEXT NOT NULL DEFAULT 'skip_if_running',
104
+ created_at TEXT NOT NULL,
105
+ updated_at TEXT NOT NULL,
106
+ last_run_at TEXT,
107
+ last_status TEXT,
108
+ last_error TEXT
109
+ );
110
+ CREATE INDEX IF NOT EXISTS idx_scheduled_jobs_workspace_updated ON scheduled_jobs (workspace_id, updated_at DESC);
111
+ CREATE INDEX IF NOT EXISTS idx_scheduled_jobs_enabled ON scheduled_jobs (enabled, trigger_type, run_at);
112
+ CREATE TABLE IF NOT EXISTS scheduled_job_runs (
113
+ id TEXT PRIMARY KEY,
114
+ job_id TEXT NOT NULL,
115
+ status TEXT NOT NULL,
116
+ triggered_at TEXT NOT NULL,
117
+ started_at TEXT,
118
+ finished_at TEXT,
119
+ error TEXT,
120
+ thread_id TEXT,
121
+ run_id TEXT,
122
+ platform_message_id TEXT,
123
+ FOREIGN KEY (job_id) REFERENCES scheduled_jobs(id) ON DELETE CASCADE
124
+ );
125
+ CREATE INDEX IF NOT EXISTS idx_scheduled_job_runs_job_triggered ON scheduled_job_runs (job_id, triggered_at DESC);
126
+ `);
127
+ this.ensureColumn('scheduled_jobs', 'execution_mode', "TEXT NOT NULL DEFAULT 'same-thread'");
128
+ }
129
+ close() {
130
+ this.db.close();
131
+ }
132
+ listThreadSummaries(workspaceId) {
133
+ const rows = this.db.prepare(`
134
+ SELECT id, workspace_id, session_id, bridge_session_key, title, agent_type, created_at, updated_at, history_count, excerpt
135
+ FROM threads
136
+ WHERE workspace_id = ?
137
+ ORDER BY updated_at DESC
138
+ `).all(workspaceId);
139
+ return rows.map((row) => ({
140
+ id: row.id,
141
+ workspaceId: row.workspace_id,
142
+ title: row.title,
143
+ live: false,
144
+ updatedAt: row.updated_at,
145
+ createdAt: row.created_at,
146
+ historyCount: row.history_count,
147
+ excerpt: row.excerpt,
148
+ runId: undefined,
149
+ bridgeSessionKey: row.bridge_session_key,
150
+ agentType: row.agent_type,
151
+ }));
152
+ }
153
+ countThreads(workspaceId) {
154
+ const row = this.db.prepare('SELECT COUNT(*) AS total FROM threads WHERE workspace_id = ?').get(workspaceId);
155
+ return Number(row?.total || 0);
156
+ }
157
+ createThread(workspaceId, title, agentType = desktop_js_1.LOCALCORE_ACP_AGENT_TYPE) {
158
+ const sessionId = (0, node_crypto_1.randomUUID)();
159
+ const threadId = (0, workspace_thread_id_js_1.encodeThreadId)(workspaceId, sessionId);
160
+ const now = new Date().toISOString();
161
+ const bridgeSessionKey = `${desktop_js_1.LOCALCORE_ACP_AGENT_TYPE}:${workspaceId}:${sessionId}`;
162
+ this.db.prepare(`
163
+ INSERT INTO threads (id, workspace_id, session_id, bridge_session_key, title, agent_type, created_at, updated_at, history_count, excerpt)
164
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, '')
165
+ `).run(threadId, workspaceId, sessionId, bridgeSessionKey, title, agentType, now, now);
166
+ return {
167
+ id: threadId,
168
+ workspaceId,
169
+ title,
170
+ live: false,
171
+ updatedAt: now,
172
+ createdAt: now,
173
+ historyCount: 0,
174
+ excerpt: '',
175
+ bridgeSessionKey,
176
+ agentType,
177
+ messages: [],
178
+ selectedKnowledgeBaseIds: [],
179
+ pendingPermissionRequest: null,
180
+ };
181
+ }
182
+ getThread(threadId, selectedKnowledgeBaseIds) {
183
+ const row = this.getThreadRow(threadId);
184
+ if (!row) {
185
+ throw new Error(`Thread not found: ${threadId}`);
186
+ }
187
+ const messages = this.db.prepare(`
188
+ SELECT id, thread_id, role, content, timestamp, kind, seq
189
+ FROM messages
190
+ WHERE thread_id = ?
191
+ ORDER BY seq ASC
192
+ `).all(threadId);
193
+ return {
194
+ id: row.id,
195
+ workspaceId: row.workspace_id,
196
+ title: row.title,
197
+ live: false,
198
+ updatedAt: row.updated_at,
199
+ createdAt: row.created_at,
200
+ historyCount: row.history_count,
201
+ excerpt: row.excerpt,
202
+ bridgeSessionKey: row.bridge_session_key,
203
+ agentType: row.agent_type,
204
+ messages: messages.map((message) => ({
205
+ id: message.id,
206
+ role: message.role,
207
+ content: message.content,
208
+ timestamp: message.timestamp,
209
+ kind: message.kind,
210
+ })),
211
+ selectedKnowledgeBaseIds,
212
+ pendingPermissionRequest: null,
213
+ };
214
+ }
215
+ renameThread(threadId, title) {
216
+ const now = new Date().toISOString();
217
+ this.db.prepare('UPDATE threads SET title = ?, updated_at = ? WHERE id = ?').run(title, now, threadId);
218
+ }
219
+ deleteThread(threadId) {
220
+ this.db.prepare('DELETE FROM threads WHERE id = ?').run(threadId);
221
+ }
222
+ appendMessage(threadId, role, content, kind) {
223
+ const timestamp = new Date().toISOString();
224
+ const nextSequenceRow = this.db.prepare('SELECT COALESCE(MAX(seq), -1) + 1 AS next_seq FROM messages WHERE thread_id = ?').get(threadId);
225
+ const nextSeq = Number(nextSequenceRow?.next_seq || 0);
226
+ const id = `${timestamp}-${role}-${nextSeq}`;
227
+ const excerpt = (0, workspace_thread_mappers_js_1.normalizeMessageContent)(content);
228
+ this.db.exec('BEGIN IMMEDIATE');
229
+ try {
230
+ this.db.prepare(`
231
+ INSERT INTO messages (id, thread_id, role, content, timestamp, kind, seq)
232
+ VALUES (?, ?, ?, ?, ?, ?, ?)
233
+ `).run(id, threadId, role, content, timestamp, kind, nextSeq);
234
+ this.db.prepare(`
235
+ UPDATE threads
236
+ SET updated_at = ?, history_count = history_count + 1, excerpt = ?
237
+ WHERE id = ?
238
+ `).run(timestamp, excerpt, threadId);
239
+ this.db.exec('COMMIT');
240
+ }
241
+ catch (error) {
242
+ this.db.exec('ROLLBACK');
243
+ throw error;
244
+ }
245
+ return { id, timestamp };
246
+ }
247
+ updateRun(runId, threadId, status) {
248
+ const now = new Date().toISOString();
249
+ const existing = this.db.prepare('SELECT id FROM runs WHERE id = ?').get(runId);
250
+ if (existing) {
251
+ this.db.prepare('UPDATE runs SET status = ?, updated_at = ? WHERE id = ?').run(status, now, runId);
252
+ return;
253
+ }
254
+ this.db.prepare(`
255
+ INSERT INTO runs (id, thread_id, status, started_at, updated_at)
256
+ VALUES (?, ?, ?, ?, ?)
257
+ `).run(runId, threadId, status, now, now);
258
+ }
259
+ getLatestRunForThread(threadId) {
260
+ return this.db.prepare(`
261
+ SELECT id, thread_id, status, started_at, updated_at
262
+ FROM runs
263
+ WHERE thread_id = ?
264
+ ORDER BY updated_at DESC
265
+ LIMIT 1
266
+ `).get(threadId);
267
+ }
268
+ getRun(runId) {
269
+ return this.db.prepare(`
270
+ SELECT id, thread_id, status, started_at, updated_at
271
+ FROM runs
272
+ WHERE id = ?
273
+ `).get(runId);
274
+ }
275
+ listScheduledJobs(workspaceId) {
276
+ const query = workspaceId
277
+ ? `
278
+ SELECT id, workspace_id, platform, route_type, route_config, trigger_type, cron_expr, run_at, prompt_template, description,
279
+ enabled, concurrency_policy, created_at, updated_at, last_run_at, last_status, last_error, execution_mode
280
+ FROM scheduled_jobs
281
+ WHERE workspace_id = ?
282
+ ORDER BY updated_at DESC
283
+ `
284
+ : `
285
+ SELECT id, workspace_id, platform, route_type, route_config, trigger_type, cron_expr, run_at, prompt_template, description,
286
+ enabled, concurrency_policy, created_at, updated_at, last_run_at, last_status, last_error, execution_mode
287
+ FROM scheduled_jobs
288
+ ORDER BY updated_at DESC
289
+ `;
290
+ const rows = this.db.prepare(query).all(...(workspaceId ? [workspaceId] : []));
291
+ return rows.map((row) => this.toScheduledJob(row));
292
+ }
293
+ getScheduledJob(jobId) {
294
+ const row = this.db.prepare(`
295
+ SELECT id, workspace_id, platform, route_type, route_config, trigger_type, cron_expr, run_at, prompt_template, description,
296
+ enabled, concurrency_policy, created_at, updated_at, last_run_at, last_status, last_error, execution_mode
297
+ FROM scheduled_jobs
298
+ WHERE id = ?
299
+ `).get(jobId);
300
+ return row ? this.toScheduledJob(row) : undefined;
301
+ }
302
+ createScheduledJob(input) {
303
+ const id = `job:${(0, node_crypto_1.randomUUID)()}`;
304
+ const now = new Date().toISOString();
305
+ this.db.prepare(`
306
+ INSERT INTO scheduled_jobs (
307
+ id, workspace_id, platform, route_type, route_config, execution_mode, trigger_type, cron_expr, run_at,
308
+ prompt_template, description, enabled, concurrency_policy, created_at, updated_at
309
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'skip_if_running', ?, ?)
310
+ `).run(id, input.workspaceId, input.platform, input.route.type, JSON.stringify(input.route), input.executionMode || 'same-thread', input.triggerType, input.cronExpr || null, input.runAt || null, input.promptTemplate, input.description || '', input.enabled === false ? 0 : 1, now, now);
311
+ return this.getScheduledJob(id);
312
+ }
313
+ updateScheduledJob(jobId, input) {
314
+ const existing = this.getScheduledJob(jobId);
315
+ if (!existing) {
316
+ throw new Error(`Scheduled job not found: ${jobId}`);
317
+ }
318
+ const next = {
319
+ ...existing,
320
+ ...(input.route ? { route: input.route } : {}),
321
+ ...(input.executionMode ? { executionMode: input.executionMode } : {}),
322
+ ...(input.triggerType ? { triggerType: input.triggerType } : {}),
323
+ ...(Object.prototype.hasOwnProperty.call(input, 'cronExpr') ? { cronExpr: input.cronExpr || undefined } : {}),
324
+ ...(Object.prototype.hasOwnProperty.call(input, 'runAt') ? { runAt: input.runAt || undefined } : {}),
325
+ ...(typeof input.promptTemplate === 'string' ? { promptTemplate: input.promptTemplate } : {}),
326
+ ...(typeof input.description === 'string' ? { description: input.description } : {}),
327
+ ...(typeof input.enabled === 'boolean' ? { enabled: input.enabled } : {}),
328
+ updatedAt: new Date().toISOString(),
329
+ };
330
+ this.db.prepare(`
331
+ UPDATE scheduled_jobs
332
+ SET route_type = ?, route_config = ?, execution_mode = ?, trigger_type = ?, cron_expr = ?, run_at = ?, prompt_template = ?,
333
+ description = ?, enabled = ?, updated_at = ?
334
+ WHERE id = ?
335
+ `).run(next.route.type, JSON.stringify(next.route), next.executionMode, next.triggerType, next.cronExpr || null, next.runAt || null, next.promptTemplate, next.description, next.enabled ? 1 : 0, next.updatedAt, jobId);
336
+ return this.getScheduledJob(jobId);
337
+ }
338
+ deleteScheduledJob(jobId) {
339
+ this.db.prepare('DELETE FROM scheduled_jobs WHERE id = ?').run(jobId);
340
+ return { deleted: true };
341
+ }
342
+ listScheduledJobRuns(jobId) {
343
+ const rows = this.db.prepare(`
344
+ SELECT id, job_id, status, triggered_at, started_at, finished_at, error, thread_id, run_id, platform_message_id
345
+ FROM scheduled_job_runs
346
+ WHERE job_id = ?
347
+ ORDER BY triggered_at DESC
348
+ `).all(jobId);
349
+ return rows.map((row) => this.toScheduledJobRun(row));
350
+ }
351
+ createScheduledJobRun(jobId, status, input = {}) {
352
+ const id = `jobrun:${(0, node_crypto_1.randomUUID)()}`;
353
+ const triggeredAt = input.triggeredAt || new Date().toISOString();
354
+ this.db.prepare(`
355
+ INSERT INTO scheduled_job_runs (id, job_id, status, triggered_at, started_at, finished_at, error, thread_id, run_id, platform_message_id)
356
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
357
+ `).run(id, jobId, status, triggeredAt, input.startedAt || null, input.finishedAt || null, input.error || null, input.threadId || null, input.runId || null, input.platformMessageId || null);
358
+ this.updateScheduledJobStatus(jobId, {
359
+ lastRunAt: triggeredAt,
360
+ lastStatus: status,
361
+ lastError: input.error || '',
362
+ });
363
+ return this.getScheduledJobRun(id);
364
+ }
365
+ getScheduledJobRun(runId) {
366
+ const row = this.db.prepare(`
367
+ SELECT id, job_id, status, triggered_at, started_at, finished_at, error, thread_id, run_id, platform_message_id
368
+ FROM scheduled_job_runs
369
+ WHERE id = ?
370
+ `).get(runId);
371
+ return row ? this.toScheduledJobRun(row) : undefined;
372
+ }
373
+ updateScheduledJobRun(runId, input) {
374
+ const existing = this.getScheduledJobRun(runId);
375
+ if (!existing) {
376
+ throw new Error(`Scheduled job run not found: ${runId}`);
377
+ }
378
+ const next = {
379
+ ...existing,
380
+ ...input,
381
+ };
382
+ this.db.prepare(`
383
+ UPDATE scheduled_job_runs
384
+ SET status = ?, triggered_at = ?, started_at = ?, finished_at = ?, error = ?, thread_id = ?, run_id = ?, platform_message_id = ?
385
+ WHERE id = ?
386
+ `).run(next.status, next.triggeredAt, next.startedAt || null, next.finishedAt || null, next.error || null, next.threadId || null, next.runId || null, next.platformMessageId || null, runId);
387
+ if (input.status || Object.prototype.hasOwnProperty.call(input, 'error') || input.finishedAt || input.triggeredAt) {
388
+ this.updateScheduledJobStatus(existing.jobId, {
389
+ lastRunAt: next.triggeredAt,
390
+ lastStatus: next.status,
391
+ lastError: next.error || '',
392
+ });
393
+ }
394
+ return this.getScheduledJobRun(runId);
395
+ }
396
+ updateScheduledJobStatus(jobId, input) {
397
+ this.db.prepare(`
398
+ UPDATE scheduled_jobs
399
+ SET last_run_at = COALESCE(?, last_run_at),
400
+ last_status = COALESCE(?, last_status),
401
+ last_error = CASE WHEN ? IS NULL THEN last_error ELSE ? END,
402
+ enabled = CASE WHEN ? IS NULL THEN enabled ELSE ? END,
403
+ updated_at = ?
404
+ WHERE id = ?
405
+ `).run(input.lastRunAt || null, input.lastStatus || null, input.lastError == null ? null : input.lastError, input.lastError == null ? null : input.lastError, typeof input.enabled === 'boolean' ? (input.enabled ? 1 : 0) : null, typeof input.enabled === 'boolean' ? (input.enabled ? 1 : 0) : null, new Date().toISOString(), jobId);
406
+ }
407
+ getThreadRow(threadId) {
408
+ return this.db.prepare(`
409
+ SELECT id, workspace_id, session_id, bridge_session_key, title, agent_type, created_at, updated_at, history_count, excerpt, acp_session_id, acp_supports_load
410
+ FROM threads
411
+ WHERE id = ?
412
+ `).get(threadId);
413
+ }
414
+ updateThreadSession(threadId, sessionId, supportsLoad) {
415
+ this.db.prepare(`
416
+ UPDATE threads
417
+ SET acp_session_id = ?, acp_supports_load = ?, updated_at = COALESCE(updated_at, ?)
418
+ WHERE id = ?
419
+ `).run(sessionId, supportsLoad ? 1 : 0, new Date().toISOString(), threadId);
420
+ }
421
+ createPairingRequest(input) {
422
+ this.db.prepare(`
423
+ INSERT INTO platform_pairings (code, workspace_id, platform, platform_user_id, chat_id, display_name, requested_at, expires_at, status)
424
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
425
+ `).run(input.code, input.workspace_id, input.platform || 'lark', input.platform_user_id, input.chat_id, input.display_name, input.requested_at, input.expires_at, input.status);
426
+ }
427
+ listPendingPairings(workspaceId) {
428
+ const query = workspaceId
429
+ ? `
430
+ SELECT code, workspace_id, platform, platform_user_id, chat_id, display_name, requested_at, expires_at, status
431
+ FROM platform_pairings
432
+ WHERE workspace_id = ? AND status = 'pending'
433
+ ORDER BY requested_at DESC
434
+ `
435
+ : `
436
+ SELECT code, workspace_id, platform, platform_user_id, chat_id, display_name, requested_at, expires_at, status
437
+ FROM platform_pairings
438
+ WHERE status = 'pending'
439
+ ORDER BY requested_at DESC
440
+ `;
441
+ return this.db.prepare(query).all(...(workspaceId ? [workspaceId] : []));
442
+ }
443
+ getPairingRequest(code) {
444
+ return this.db.prepare(`
445
+ SELECT code, workspace_id, platform, platform_user_id, chat_id, display_name, requested_at, expires_at, status
446
+ FROM platform_pairings
447
+ WHERE code = ?
448
+ `).get(code);
449
+ }
450
+ updatePairingStatus(code, status) {
451
+ this.db.prepare('UPDATE platform_pairings SET status = ? WHERE code = ?').run(status, code);
452
+ }
453
+ expirePendingPairings(nowIso = new Date().toISOString()) {
454
+ this.db.prepare(`
455
+ UPDATE platform_pairings
456
+ SET status = 'expired'
457
+ WHERE status = 'pending' AND expires_at < ?
458
+ `).run(nowIso);
459
+ }
460
+ getAuthorizedUser(workspaceId, platformUserId, platform = 'lark') {
461
+ return this.db.prepare(`
462
+ SELECT id, workspace_id, platform, platform_user_id, chat_id, display_name, thread_id, authorized_at
463
+ FROM platform_users
464
+ WHERE workspace_id = ? AND platform = ? AND platform_user_id = ?
465
+ `).get(workspaceId, platform, platformUserId);
466
+ }
467
+ listAuthorizedUsers(workspaceId, platform) {
468
+ const params = [];
469
+ const predicates = [];
470
+ if (workspaceId) {
471
+ predicates.push('workspace_id = ?');
472
+ params.push(workspaceId);
473
+ }
474
+ if (platform) {
475
+ predicates.push('platform = ?');
476
+ params.push(platform);
477
+ }
478
+ const where = predicates.length > 0 ? `WHERE ${predicates.join(' AND ')}` : '';
479
+ const query = `
480
+ SELECT id, workspace_id, platform, platform_user_id, chat_id, display_name, thread_id, authorized_at
481
+ FROM platform_users
482
+ ${where}
483
+ ORDER BY authorized_at DESC
484
+ `;
485
+ const rows = this.db.prepare(query).all(...params);
486
+ return rows.map((row) => ({
487
+ id: row.id,
488
+ workspaceId: row.workspace_id,
489
+ platform: row.platform,
490
+ participantId: row.platform_user_id,
491
+ channelId: row.chat_id,
492
+ platformUserId: row.platform_user_id,
493
+ chatId: row.chat_id,
494
+ displayName: row.display_name,
495
+ threadId: row.thread_id || undefined,
496
+ authorizedAt: row.authorized_at,
497
+ }));
498
+ }
499
+ createAuthorizedUser(input) {
500
+ this.db.prepare(`
501
+ INSERT INTO platform_users (id, workspace_id, platform, platform_user_id, chat_id, display_name, thread_id, authorized_at)
502
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
503
+ ON CONFLICT(workspace_id, platform, platform_user_id) DO UPDATE SET
504
+ chat_id = excluded.chat_id,
505
+ display_name = excluded.display_name,
506
+ thread_id = COALESCE(excluded.thread_id, platform_users.thread_id),
507
+ authorized_at = excluded.authorized_at
508
+ `).run(input.id, input.workspace_id, input.platform || 'lark', input.platform_user_id, input.chat_id, input.display_name, input.thread_id, input.authorized_at);
509
+ }
510
+ updateAuthorizedUserThread(workspaceId, platformUserId, threadId, platform = 'lark') {
511
+ this.db.prepare(`
512
+ UPDATE platform_users
513
+ SET thread_id = ?
514
+ WHERE workspace_id = ? AND platform = ? AND platform_user_id = ?
515
+ `).run(threadId, workspaceId, platform, platformUserId);
516
+ }
517
+ getPlatformThreadBinding(workspaceId, chatId, platformUserId, platform = 'lark') {
518
+ return this.db.prepare(`
519
+ SELECT workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, created_at, updated_at
520
+ FROM platform_thread_bindings
521
+ WHERE workspace_id = ? AND platform = ? AND chat_id = ? AND platform_user_id = ?
522
+ `).get(workspaceId, platform, chatId, platformUserId);
523
+ }
524
+ getPlatformThreadBindingByThreadId(threadId) {
525
+ return this.db.prepare(`
526
+ SELECT workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, created_at, updated_at
527
+ FROM platform_thread_bindings
528
+ WHERE thread_id = ?
529
+ ORDER BY updated_at DESC
530
+ LIMIT 1
531
+ `).get(threadId);
532
+ }
533
+ upsertPlatformThreadBinding(input) {
534
+ this.db.prepare(`
535
+ INSERT INTO platform_thread_bindings
536
+ (workspace_id, platform, chat_id, platform_user_id, thread_id, last_platform_message_id, created_at, updated_at)
537
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
538
+ ON CONFLICT(workspace_id, platform, chat_id, platform_user_id) DO UPDATE SET
539
+ thread_id = excluded.thread_id,
540
+ last_platform_message_id = COALESCE(excluded.last_platform_message_id, platform_thread_bindings.last_platform_message_id),
541
+ updated_at = excluded.updated_at
542
+ `).run(input.workspace_id, input.platform || 'lark', input.chat_id, input.platform_user_id, input.thread_id, input.last_platform_message_id, input.created_at, input.updated_at);
543
+ }
544
+ updatePlatformThreadMessageId(workspaceId, chatId, platformUserId, messageId, platform = 'lark') {
545
+ this.db.prepare(`
546
+ UPDATE platform_thread_bindings
547
+ SET last_platform_message_id = ?, updated_at = ?
548
+ WHERE workspace_id = ? AND platform = ? AND chat_id = ? AND platform_user_id = ?
549
+ `).run(messageId, new Date().toISOString(), workspaceId, platform, chatId, platformUserId);
550
+ }
551
+ clearPlatformThreadMessageId(workspaceId, chatId, platformUserId, platform = 'lark') {
552
+ this.db.prepare(`
553
+ UPDATE platform_thread_bindings
554
+ SET last_platform_message_id = NULL, updated_at = ?
555
+ WHERE workspace_id = ? AND platform = ? AND chat_id = ? AND platform_user_id = ?
556
+ `).run(new Date().toISOString(), workspaceId, platform, chatId, platformUserId);
557
+ }
558
+ listPairingRequests(workspaceId, platform) {
559
+ const params = [];
560
+ const predicates = [];
561
+ if (workspaceId) {
562
+ predicates.push('workspace_id = ?');
563
+ params.push(workspaceId);
564
+ }
565
+ if (platform) {
566
+ predicates.push('platform = ?');
567
+ params.push(platform);
568
+ }
569
+ const where = predicates.length > 0 ? `WHERE ${predicates.join(' AND ')}` : '';
570
+ const query = `
571
+ SELECT code, workspace_id, platform, platform_user_id, chat_id, display_name, requested_at, expires_at, status
572
+ FROM platform_pairings
573
+ ${where}
574
+ ORDER BY requested_at DESC
575
+ `;
576
+ const rows = this.db.prepare(query).all(...params);
577
+ return rows.map((row) => ({
578
+ code: row.code,
579
+ workspaceId: row.workspace_id,
580
+ platform: row.platform,
581
+ participantId: row.platform_user_id,
582
+ channelId: row.chat_id,
583
+ platformUserId: row.platform_user_id,
584
+ chatId: row.chat_id,
585
+ displayName: row.display_name,
586
+ requestedAt: row.requested_at,
587
+ expiresAt: row.expires_at,
588
+ status: row.status,
589
+ }));
590
+ }
591
+ toScheduledJob(row) {
592
+ const route = JSON.parse(row.route_config);
593
+ return {
594
+ id: row.id,
595
+ workspaceId: row.workspace_id,
596
+ platform: row.platform,
597
+ route,
598
+ executionMode: row.execution_mode || 'same-thread',
599
+ triggerType: row.trigger_type,
600
+ cronExpr: row.cron_expr || undefined,
601
+ runAt: row.run_at || undefined,
602
+ promptTemplate: row.prompt_template,
603
+ description: row.description,
604
+ enabled: Boolean(row.enabled),
605
+ concurrencyPolicy: row.concurrency_policy,
606
+ createdAt: row.created_at,
607
+ updatedAt: row.updated_at,
608
+ lastRunAt: row.last_run_at || undefined,
609
+ lastStatus: row.last_status || undefined,
610
+ lastError: row.last_error || undefined,
611
+ };
612
+ }
613
+ toScheduledJobRun(row) {
614
+ return {
615
+ id: row.id,
616
+ jobId: row.job_id,
617
+ status: row.status,
618
+ triggeredAt: row.triggered_at,
619
+ startedAt: row.started_at || undefined,
620
+ finishedAt: row.finished_at || undefined,
621
+ error: row.error || undefined,
622
+ threadId: row.thread_id || undefined,
623
+ runId: row.run_id || undefined,
624
+ platformMessageId: row.platform_message_id || undefined,
625
+ };
626
+ }
627
+ ensureColumn(table, column, definition) {
628
+ const rows = this.db.prepare(`PRAGMA table_info(${table})`).all();
629
+ if (rows.some((row) => row.name === column)) {
630
+ return;
631
+ }
632
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
633
+ }
634
+ }
635
+ exports.LocalCoreAcpStore = LocalCoreAcpStore;