@sleep2agi/commhub-server 0.5.0-preview.24 → 0.5.0-preview.26
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 +20 -3
- package/package.json +7 -6
- package/src/auth.ts +126 -28
- package/src/db-adapter.ts +198 -32
- package/src/db.ts +58 -11
- package/src/index.ts +105 -39
- package/src/tools.ts +80 -130
package/src/tools.ts
CHANGED
|
@@ -45,9 +45,8 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
45
45
|
console.log(`[${ts()}] ${alias} (${resume_id.slice(0, 8)}) → report_status: ${status}${task ? " | " + task.slice(0, 60) : ""}${effectiveNetId ? " [net]" : ""}`);
|
|
46
46
|
const trimmedOutput = output?.slice(0, 4000);
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Only delete same-alias sessions within the same network (prevent cross-network alias conflict)
|
|
48
|
+
db.transaction(() => {
|
|
49
|
+
// Only delete same-alias sessions within the same network
|
|
51
50
|
if (effectiveNetId) {
|
|
52
51
|
db.run("DELETE FROM sessions WHERE alias = ?1 AND resume_id != ?2 AND network_id = ?3", [alias, resume_id, effectiveNetId]);
|
|
53
52
|
} else {
|
|
@@ -57,33 +56,19 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
57
56
|
`INSERT INTO sessions (resume_id, alias, tmux_name, server, ip, hostname, agent, project_dir, version, status, task, output, progress, score, node_id, session_id, config_path, channels, network_id, last_seen_at, updated_at)
|
|
58
57
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, datetime('now'), datetime('now'))
|
|
59
58
|
ON CONFLICT(resume_id) DO UPDATE SET
|
|
60
|
-
alias = COALESCE(?2, sessions.alias),
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
output = COALESCE(?12, sessions.output),
|
|
71
|
-
progress = COALESCE(?13, sessions.progress),
|
|
72
|
-
score = COALESCE(?14, sessions.score),
|
|
73
|
-
node_id = COALESCE(?15, sessions.node_id),
|
|
74
|
-
session_id = COALESCE(?16, sessions.session_id),
|
|
75
|
-
config_path = COALESCE(?17, sessions.config_path),
|
|
76
|
-
channels = COALESCE(?18, sessions.channels),
|
|
77
|
-
network_id = COALESCE(?19, sessions.network_id),
|
|
78
|
-
last_seen_at = datetime('now'),
|
|
79
|
-
updated_at = datetime('now')`,
|
|
59
|
+
alias = COALESCE(?2, sessions.alias), tmux_name = COALESCE(?3, sessions.tmux_name),
|
|
60
|
+
server = COALESCE(?4, sessions.server), ip = COALESCE(?5, sessions.ip),
|
|
61
|
+
hostname = COALESCE(?6, sessions.hostname), agent = COALESCE(?7, sessions.agent),
|
|
62
|
+
project_dir = COALESCE(?8, sessions.project_dir), version = COALESCE(?9, sessions.version),
|
|
63
|
+
status = ?10, task = COALESCE(?11, sessions.task),
|
|
64
|
+
output = COALESCE(?12, sessions.output), progress = COALESCE(?13, sessions.progress),
|
|
65
|
+
score = COALESCE(?14, sessions.score), node_id = COALESCE(?15, sessions.node_id),
|
|
66
|
+
session_id = COALESCE(?16, sessions.session_id), config_path = COALESCE(?17, sessions.config_path),
|
|
67
|
+
channels = COALESCE(?18, sessions.channels), network_id = COALESCE(?19, sessions.network_id),
|
|
68
|
+
last_seen_at = datetime('now'), updated_at = datetime('now')`,
|
|
80
69
|
[resume_id, alias, tmux ?? null, srv ?? null, clientIP ?? null, hn ?? null, ag ?? null, pd ?? null, ver ?? null, status, task ?? null, trimmedOutput ?? null, progress ?? null, score ?? null, node_id ?? null, session_id ?? null, config_path ?? null, channels ?? null, netId ?? null]
|
|
81
70
|
);
|
|
82
|
-
|
|
83
|
-
} catch (e) {
|
|
84
|
-
try { db.run("ROLLBACK"); } catch {}
|
|
85
|
-
throw e;
|
|
86
|
-
}
|
|
71
|
+
});
|
|
87
72
|
|
|
88
73
|
// V2: sync tasks table — report_status(working) → tasks.running
|
|
89
74
|
if (status === "working" && task) {
|
|
@@ -95,9 +80,9 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
95
80
|
);
|
|
96
81
|
if (runResult.changes > 0) {
|
|
97
82
|
// Find task_id for logging
|
|
98
|
-
const t = db.
|
|
99
|
-
"SELECT task_id FROM tasks WHERE to_name = ?1 AND content = ?2 AND status = 'running' ORDER BY started_at DESC LIMIT 1"
|
|
100
|
-
|
|
83
|
+
const t = db.get<{ task_id: string }>(
|
|
84
|
+
"SELECT task_id FROM tasks WHERE to_name = ?1 AND content = ?2 AND status = 'running' ORDER BY started_at DESC LIMIT 1",
|
|
85
|
+
alias, task);
|
|
101
86
|
if (t) logTaskEvent(t.task_id, null, "running", alias);
|
|
102
87
|
}
|
|
103
88
|
} catch {}
|
|
@@ -127,9 +112,9 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
127
112
|
}
|
|
128
113
|
|
|
129
114
|
// inbox uses alias for routing
|
|
130
|
-
const row = db.
|
|
131
|
-
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0"
|
|
132
|
-
|
|
115
|
+
const row = db.get<{ cnt: number }>(
|
|
116
|
+
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0",
|
|
117
|
+
alias);
|
|
133
118
|
|
|
134
119
|
return {
|
|
135
120
|
content: [
|
|
@@ -161,51 +146,40 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
161
146
|
async ({ alias, task, result, artifacts, score, duration_minutes }) => {
|
|
162
147
|
console.log(`[${ts()}] ${alias} → report_completion: ${task.slice(0, 60)}`);
|
|
163
148
|
const id = uuidv4();
|
|
164
|
-
|
|
165
|
-
db.run("BEGIN IMMEDIATE");
|
|
149
|
+
const taskUpdateChanges = db.transaction(() => {
|
|
166
150
|
db.run(
|
|
167
151
|
`INSERT INTO completions (id, session_name, task, result, artifacts, score, duration_minutes)
|
|
168
152
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)`,
|
|
169
153
|
[id, alias, task, result, artifacts ? JSON.stringify(artifacts) : null, score ?? null, duration_minutes ?? null]
|
|
170
154
|
);
|
|
171
|
-
|
|
172
155
|
db.run(
|
|
173
156
|
`UPDATE sessions SET status = 'idle', task = NULL, progress = 0, updated_at = datetime('now')
|
|
174
157
|
WHERE alias = ?1`,
|
|
175
158
|
[alias]
|
|
176
159
|
);
|
|
177
|
-
|
|
178
160
|
// V2: sync tasks table — try by task_id first, then by content
|
|
179
|
-
const
|
|
161
|
+
const tu = db.run(
|
|
180
162
|
`UPDATE tasks SET status = 'replied', result = ?1, completed_at = datetime('now')
|
|
181
163
|
WHERE task_id = ?2 AND status IN ('delivered', 'acked', 'running')`,
|
|
182
164
|
[result.slice(0, 4000), task]
|
|
183
165
|
);
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
const match = db.query<{ task_id: string }, [string, string]>(
|
|
166
|
+
if (tu.changes === 0) {
|
|
167
|
+
const match = db.get<{ task_id: string }>(
|
|
187
168
|
`SELECT task_id FROM tasks WHERE to_name = ?1 AND content = ?2
|
|
188
|
-
AND status IN ('delivered', 'acked', 'running') ORDER BY created_at DESC LIMIT 1
|
|
189
|
-
|
|
169
|
+
AND status IN ('delivered', 'acked', 'running') ORDER BY created_at DESC LIMIT 1`,
|
|
170
|
+
alias, task);
|
|
190
171
|
if (match) {
|
|
191
|
-
db.run(
|
|
192
|
-
|
|
193
|
-
WHERE task_id = ?2`,
|
|
194
|
-
[result.slice(0, 4000), match.task_id]
|
|
195
|
-
);
|
|
172
|
+
db.run(`UPDATE tasks SET status = 'replied', result = ?1, completed_at = datetime('now') WHERE task_id = ?2`,
|
|
173
|
+
[result.slice(0, 4000), match.task_id]);
|
|
196
174
|
}
|
|
197
175
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
} catch (e) {
|
|
206
|
-
try { db.run("ROLLBACK"); } catch {}
|
|
207
|
-
throw e;
|
|
208
|
-
}
|
|
176
|
+
return tu.changes;
|
|
177
|
+
});
|
|
178
|
+
// Log event after transaction
|
|
179
|
+
const updatedTaskId = taskUpdateChanges > 0 ? task : (db.get<{ task_id: string }>(
|
|
180
|
+
"SELECT task_id FROM tasks WHERE to_name = ?1 AND status = 'replied' ORDER BY completed_at DESC LIMIT 1",
|
|
181
|
+
alias)?.task_id);
|
|
182
|
+
if (updatedTaskId) logTaskEvent(updatedTaskId, null, "replied", alias, "report_completion");
|
|
209
183
|
|
|
210
184
|
return {
|
|
211
185
|
content: [{ type: "text" as const, text: JSON.stringify({ ok: true, completion_id: id }) }],
|
|
@@ -221,16 +195,16 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
221
195
|
limit: z.number().min(1).max(100).optional().default(10),
|
|
222
196
|
},
|
|
223
197
|
async ({ alias, limit }) => {
|
|
224
|
-
const rows0 = db.
|
|
225
|
-
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0"
|
|
226
|
-
|
|
198
|
+
const rows0 = db.get<{ cnt: number }>(
|
|
199
|
+
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0",
|
|
200
|
+
alias);
|
|
227
201
|
console.log(`[${ts()}] ${alias} → get_inbox: ${rows0?.cnt ?? 0} pending messages`);
|
|
228
|
-
const rows = db.
|
|
202
|
+
const rows = db.all(
|
|
229
203
|
`SELECT id, type, priority, content, context, from_session, created_at
|
|
230
204
|
FROM inbox WHERE session_name = ?1 AND acked = 0
|
|
231
205
|
ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 ELSE 2 END, created_at
|
|
232
|
-
LIMIT ?2
|
|
233
|
-
|
|
206
|
+
LIMIT ?2`,
|
|
207
|
+
alias, limit);
|
|
234
208
|
|
|
235
209
|
return {
|
|
236
210
|
content: [{ type: "text" as const, text: JSON.stringify({ ok: true, messages: rows }) }],
|
|
@@ -294,12 +268,11 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
294
268
|
if (filter_status) { sql += " AND status = ?"; params.push(filter_status); }
|
|
295
269
|
if (filter_server) { sql += " AND server = ?"; params.push(filter_server); }
|
|
296
270
|
sql += " ORDER BY updated_at DESC";
|
|
297
|
-
return db.
|
|
298
|
-
})
|
|
271
|
+
return db.all(sql, ...params);
|
|
272
|
+
});
|
|
299
273
|
|
|
300
|
-
const summary = db.
|
|
301
|
-
"SELECT status, COUNT(*) as count FROM sessions GROUP BY status"
|
|
302
|
-
).all();
|
|
274
|
+
const summary = db.all(
|
|
275
|
+
"SELECT status, COUNT(*) as count FROM sessions GROUP BY status");
|
|
303
276
|
|
|
304
277
|
return {
|
|
305
278
|
content: [
|
|
@@ -318,13 +291,13 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
318
291
|
{ alias: z.string().min(1).max(200).describe("Session alias") },
|
|
319
292
|
async ({ alias }) => {
|
|
320
293
|
console.log(`[${ts()}] hub → get_session_status: ${alias}`);
|
|
321
|
-
const session = db.
|
|
322
|
-
const pending = db.
|
|
323
|
-
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0"
|
|
324
|
-
|
|
325
|
-
const recent = db.
|
|
326
|
-
"SELECT * FROM completions WHERE session_name = ?1 ORDER BY completed_at DESC LIMIT 5"
|
|
327
|
-
|
|
294
|
+
const session = db.get("SELECT * FROM sessions WHERE alias = ?1", alias);
|
|
295
|
+
const pending = db.get<{ cnt: number }>(
|
|
296
|
+
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0",
|
|
297
|
+
alias);
|
|
298
|
+
const recent = db.all(
|
|
299
|
+
"SELECT * FROM completions WHERE session_name = ?1 ORDER BY completed_at DESC LIMIT 5",
|
|
300
|
+
alias);
|
|
328
301
|
|
|
329
302
|
return {
|
|
330
303
|
content: [
|
|
@@ -353,7 +326,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
353
326
|
const effectiveNetId = getNetworkId(netId);
|
|
354
327
|
|
|
355
328
|
// License check
|
|
356
|
-
const license = db.
|
|
329
|
+
const license = db.get<any>("SELECT type, expires_at FROM licenses ORDER BY created_at LIMIT 1");
|
|
357
330
|
if (license?.expires_at) {
|
|
358
331
|
const now = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
359
332
|
if (license.expires_at < now) {
|
|
@@ -367,8 +340,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
367
340
|
console.log(`[${ts()}] ${from_session} → send_task → ${alias}: ${task.slice(0, 60)}${priority === "high" ? " [HIGH]" : ""}`);
|
|
368
341
|
const id = uuidv4();
|
|
369
342
|
// 事务:inbox + tasks 双写
|
|
370
|
-
|
|
371
|
-
db.run("BEGIN IMMEDIATE");
|
|
343
|
+
db.transaction(() => {
|
|
372
344
|
db.run(
|
|
373
345
|
`INSERT INTO inbox (id, session_name, type, priority, content, context, from_session, requires_response, network_id)
|
|
374
346
|
VALUES (?1, ?2, 'task', ?3, ?4, ?5, ?6, 'reply', ?7)`,
|
|
@@ -379,19 +351,15 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
379
351
|
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7)`,
|
|
380
352
|
[id, from_session, alias, priority, task, `+${ttl_seconds || 3600} seconds`, effectiveNetId]
|
|
381
353
|
);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
} catch (e) {
|
|
385
|
-
try { db.run("ROLLBACK"); } catch {}
|
|
386
|
-
throw e;
|
|
387
|
-
}
|
|
354
|
+
});
|
|
355
|
+
logTaskEvent(id, null, "delivered", from_session, `→ ${alias}`);
|
|
388
356
|
|
|
389
|
-
const session = db.
|
|
357
|
+
const session = db.get<any>("SELECT status FROM sessions WHERE alias = ?1", alias);
|
|
390
358
|
|
|
391
359
|
// SSE push by alias
|
|
392
|
-
const pending = db.
|
|
393
|
-
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0"
|
|
394
|
-
|
|
360
|
+
const pending = db.get<{ cnt: number }>(
|
|
361
|
+
"SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0",
|
|
362
|
+
alias);
|
|
395
363
|
pushEvent(alias, { type: "new_task", inbox_count: pending?.cnt ?? 1, priority, from: from_session });
|
|
396
364
|
|
|
397
365
|
return {
|
|
@@ -426,7 +394,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
426
394
|
[id, alias, message, from_session]
|
|
427
395
|
);
|
|
428
396
|
|
|
429
|
-
const session = db.
|
|
397
|
+
const session = db.get<any>("SELECT status FROM sessions WHERE alias = ?1", alias);
|
|
430
398
|
|
|
431
399
|
pushEvent(alias, { type: "new_message", message, from: from_session, message_id: id });
|
|
432
400
|
|
|
@@ -459,9 +427,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
459
427
|
async ({ alias, text, in_reply_to, status: replyStatus, from_session }) => {
|
|
460
428
|
console.log(`[${ts()}] ${from_session} → send_reply (${replyStatus}) → ${alias}: ${text.slice(0, 60)}`);
|
|
461
429
|
const id = uuidv4();
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
db.run("BEGIN IMMEDIATE");
|
|
430
|
+
const replyLogged = db.transaction(() => {
|
|
465
431
|
db.run(
|
|
466
432
|
`INSERT INTO inbox (id, session_name, type, priority, content, from_session, in_reply_to, requires_response)
|
|
467
433
|
VALUES (?1, ?2, 'reply', 'normal', ?3, ?4, ?5, 'none')`,
|
|
@@ -477,20 +443,17 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
477
443
|
);
|
|
478
444
|
if (result.changes === 0) {
|
|
479
445
|
console.log(`[${ts()}] ⚠ send_reply: task ${in_reply_to?.slice(0, 8)} not found or already terminal`);
|
|
480
|
-
|
|
481
|
-
replyLogged = true;
|
|
446
|
+
return false;
|
|
482
447
|
}
|
|
448
|
+
return true;
|
|
483
449
|
}
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
try { db.run("ROLLBACK"); } catch {}
|
|
487
|
-
throw e;
|
|
488
|
-
}
|
|
450
|
+
return false;
|
|
451
|
+
});
|
|
489
452
|
|
|
490
453
|
// Log event after commit (outside transaction)
|
|
491
454
|
if (replyLogged && in_reply_to) logTaskEvent(in_reply_to, null, replyStatus, from_session, text.slice(0, 200));
|
|
492
455
|
|
|
493
|
-
const session = db.
|
|
456
|
+
const session = db.get<any>("SELECT status FROM sessions WHERE alias = ?1", alias);
|
|
494
457
|
pushEvent(alias, { type: "new_reply", from: from_session, message_id: id, in_reply_to, status: replyStatus });
|
|
495
458
|
|
|
496
459
|
return {
|
|
@@ -537,17 +500,14 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
537
500
|
async ({ task_id, from_session }) => {
|
|
538
501
|
console.log(`[${ts()}] ${from_session} → retry_task → ${task_id.slice(0, 8)}`);
|
|
539
502
|
// Find the original task
|
|
540
|
-
const task = db.
|
|
541
|
-
"SELECT * FROM tasks WHERE task_id = ?1"
|
|
542
|
-
).get(task_id);
|
|
503
|
+
const task = db.get<any>("SELECT * FROM tasks WHERE task_id = ?1", task_id);
|
|
543
504
|
if (!task) {
|
|
544
505
|
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: "task not found" }) }] };
|
|
545
506
|
}
|
|
546
507
|
if (!["failed", "expired", "cancelled"].includes(task.status)) {
|
|
547
508
|
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: `task status is ${task.status}, not retryable` }) }] };
|
|
548
509
|
}
|
|
549
|
-
|
|
550
|
-
db.run("BEGIN IMMEDIATE");
|
|
510
|
+
db.transaction(() => {
|
|
551
511
|
// Reset task status
|
|
552
512
|
db.run(
|
|
553
513
|
`UPDATE tasks SET status = 'delivered', result = NULL, completed_at = NULL, started_at = NULL, delivered_at = datetime('now'), expires_at = datetime('now', '+1 hour')
|
|
@@ -561,12 +521,8 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
561
521
|
VALUES (?1, ?2, 'task', ?3, ?4, ?5, 'reply')`,
|
|
562
522
|
[retryInboxId, task.to_name, task.priority, task.content, from_session]
|
|
563
523
|
);
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
} catch (e) {
|
|
567
|
-
try { db.run("ROLLBACK"); } catch {}
|
|
568
|
-
throw e;
|
|
569
|
-
}
|
|
524
|
+
});
|
|
525
|
+
logTaskEvent(task_id, task.status, "delivered", from_session, "retry");
|
|
570
526
|
// SSE push
|
|
571
527
|
pushEvent(task.to_name, { type: "new_task", inbox_count: 1, priority: task.priority, from: from_session });
|
|
572
528
|
return {
|
|
@@ -583,7 +539,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
583
539
|
task_id: z.string().min(1).max(200).describe("Task ID to query"),
|
|
584
540
|
},
|
|
585
541
|
async ({ task_id }) => {
|
|
586
|
-
const task = db.
|
|
542
|
+
const task = db.get<any>("SELECT * FROM tasks WHERE task_id = ?1", task_id);
|
|
587
543
|
return {
|
|
588
544
|
content: [{
|
|
589
545
|
type: "text" as const,
|
|
@@ -614,12 +570,11 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
614
570
|
if (from_name) { sql += ` AND from_name = ?${params.length + 1}`; params.push(from_name); }
|
|
615
571
|
sql += ` ORDER BY created_at DESC LIMIT ?${params.length + 1}`;
|
|
616
572
|
params.push(limit);
|
|
617
|
-
const tasks = db.
|
|
573
|
+
const tasks = db.all(sql, ...params);
|
|
618
574
|
|
|
619
575
|
// Stats
|
|
620
|
-
const stats = db.
|
|
621
|
-
"SELECT status, COUNT(*) as count FROM tasks GROUP BY status"
|
|
622
|
-
).all();
|
|
576
|
+
const stats = db.all(
|
|
577
|
+
"SELECT status, COUNT(*) as count FROM tasks GROUP BY status");
|
|
623
578
|
|
|
624
579
|
return {
|
|
625
580
|
content: [{
|
|
@@ -668,26 +623,21 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
668
623
|
},
|
|
669
624
|
async ({ task_id, new_alias, from_session }) => {
|
|
670
625
|
console.log(`[${ts()}] ${from_session} → reassign_task → ${task_id.slice(0, 8)} → ${new_alias}`);
|
|
671
|
-
const task = db.
|
|
626
|
+
const task = db.get<any>("SELECT * FROM tasks WHERE task_id = ?1", task_id);
|
|
672
627
|
if (!task) return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: "task not found" }) }] };
|
|
673
628
|
if (["replied", "failed", "cancelled", "expired"].includes(task.status)) {
|
|
674
629
|
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: `task is terminal (${task.status})` }) }] };
|
|
675
630
|
}
|
|
676
631
|
const oldAlias = task.to_name;
|
|
677
|
-
|
|
678
|
-
db.run("BEGIN IMMEDIATE");
|
|
632
|
+
db.transaction(() => {
|
|
679
633
|
// Ack old inbox to prevent original agent from picking it up
|
|
680
634
|
db.run("UPDATE inbox SET acked = 1 WHERE id = ?1 AND acked = 0", [task_id]);
|
|
681
635
|
db.run("UPDATE tasks SET to_name = ?1, status = 'delivered', started_at = NULL, delivered_at = datetime('now') WHERE task_id = ?2", [new_alias, task_id]);
|
|
682
636
|
const newInboxId = uuidv4();
|
|
683
637
|
db.run("INSERT INTO inbox (id, session_name, type, priority, content, from_session, requires_response) VALUES (?1, ?2, 'task', ?3, ?4, ?5, 'reply')",
|
|
684
638
|
[newInboxId, new_alias, task.priority, task.content, from_session]);
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
} catch (e) {
|
|
688
|
-
try { db.run("ROLLBACK"); } catch {}
|
|
689
|
-
throw e;
|
|
690
|
-
}
|
|
639
|
+
});
|
|
640
|
+
logTaskEvent(task_id, task.status, "delivered", from_session, `reassign: ${oldAlias} → ${new_alias}`);
|
|
691
641
|
pushEvent(new_alias, { type: "new_task", inbox_count: 1, priority: task.priority, from: from_session });
|
|
692
642
|
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: true, task_id, reassigned_from: oldAlias, reassigned_to: new_alias }) }] };
|
|
693
643
|
}
|
|
@@ -710,7 +660,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
710
660
|
if (filter_server) { sql += " AND server = ?"; params.push(filter_server); }
|
|
711
661
|
if (filter_status) { sql += " AND status = ?"; params.push(filter_status); }
|
|
712
662
|
|
|
713
|
-
const targets = db.
|
|
663
|
+
const targets = db.all<{ alias: string }>(sql, ...params);
|
|
714
664
|
const ids: string[] = [];
|
|
715
665
|
|
|
716
666
|
for (const t of targets) {
|
|
@@ -759,7 +709,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
759
709
|
sql += ` ORDER BY completed_at DESC LIMIT ?${paramIdx}`;
|
|
760
710
|
params.push(limit);
|
|
761
711
|
|
|
762
|
-
const rows = db.
|
|
712
|
+
const rows = db.all(sql, ...params);
|
|
763
713
|
return {
|
|
764
714
|
content: [{ type: "text" as const, text: JSON.stringify({ ok: true, completions: rows }) }],
|
|
765
715
|
};
|