@hasna/todos 0.10.21 → 0.11.1
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/dist/cli/index.js +613 -47
- package/dist/db/budgets.d.ts +27 -0
- package/dist/db/budgets.d.ts.map +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/snapshots.d.ts +37 -0
- package/dist/db/snapshots.d.ts.map +1 -0
- package/dist/db/tasks.d.ts +26 -0
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/db/traces.d.ts +38 -0
- package/dist/db/traces.d.ts.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +332 -17
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +568 -35
- package/dist/server/index.js +157 -22
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -4,37 +4,27 @@ var __defProp = Object.defineProperty;
|
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
|
|
8
|
-
return this[key];
|
|
9
|
-
}
|
|
7
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
10
8
|
var __toCommonJS = (from) => {
|
|
11
|
-
var entry =
|
|
9
|
+
var entry = __moduleCache.get(from), desc;
|
|
12
10
|
if (entry)
|
|
13
11
|
return entry;
|
|
14
12
|
entry = __defProp({}, "__esModule", { value: true });
|
|
15
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
-
});
|
|
22
|
-
}
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
14
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
15
|
+
get: () => from[key],
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
}));
|
|
23
18
|
__moduleCache.set(from, entry);
|
|
24
19
|
return entry;
|
|
25
20
|
};
|
|
26
|
-
var __moduleCache;
|
|
27
|
-
var __returnValue = (v) => v;
|
|
28
|
-
function __exportSetter(name, newValue) {
|
|
29
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
30
|
-
}
|
|
31
21
|
var __export = (target, all) => {
|
|
32
22
|
for (var name in all)
|
|
33
23
|
__defProp(target, name, {
|
|
34
24
|
get: all[name],
|
|
35
25
|
enumerable: true,
|
|
36
26
|
configurable: true,
|
|
37
|
-
set:
|
|
27
|
+
set: (newValue) => all[name] = () => newValue
|
|
38
28
|
});
|
|
39
29
|
};
|
|
40
30
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -392,6 +382,15 @@ function ensureSchema(db) {
|
|
|
392
382
|
ensureColumn("tasks", "assigned_by", "TEXT");
|
|
393
383
|
ensureColumn("tasks", "assigned_from_project", "TEXT");
|
|
394
384
|
ensureColumn("tasks", "started_at", "TEXT");
|
|
385
|
+
ensureColumn("tasks", "task_type", "TEXT");
|
|
386
|
+
ensureColumn("tasks", "cost_tokens", "INTEGER DEFAULT 0");
|
|
387
|
+
ensureColumn("tasks", "cost_usd", "REAL DEFAULT 0");
|
|
388
|
+
ensureColumn("tasks", "delegated_from", "TEXT");
|
|
389
|
+
ensureColumn("tasks", "delegation_depth", "INTEGER DEFAULT 0");
|
|
390
|
+
ensureColumn("tasks", "retry_count", "INTEGER DEFAULT 0");
|
|
391
|
+
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
392
|
+
ensureColumn("tasks", "retry_after", "TEXT");
|
|
393
|
+
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
395
394
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
396
395
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
397
396
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -1005,6 +1004,58 @@ var init_database = __esm(() => {
|
|
|
1005
1004
|
`
|
|
1006
1005
|
ALTER TABLE tasks ADD COLUMN task_type TEXT;
|
|
1007
1006
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type);
|
|
1007
|
+
ALTER TABLE tasks ADD COLUMN cost_tokens INTEGER DEFAULT 0;
|
|
1008
|
+
ALTER TABLE tasks ADD COLUMN cost_usd REAL DEFAULT 0;
|
|
1009
|
+
ALTER TABLE tasks ADD COLUMN delegated_from TEXT;
|
|
1010
|
+
ALTER TABLE tasks ADD COLUMN delegation_depth INTEGER DEFAULT 0;
|
|
1011
|
+
ALTER TABLE tasks ADD COLUMN retry_count INTEGER DEFAULT 0;
|
|
1012
|
+
ALTER TABLE tasks ADD COLUMN max_retries INTEGER DEFAULT 3;
|
|
1013
|
+
ALTER TABLE tasks ADD COLUMN retry_after TEXT;
|
|
1014
|
+
ALTER TABLE tasks ADD COLUMN sla_minutes INTEGER;
|
|
1015
|
+
|
|
1016
|
+
CREATE TABLE IF NOT EXISTS task_traces (
|
|
1017
|
+
id TEXT PRIMARY KEY,
|
|
1018
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1019
|
+
agent_id TEXT,
|
|
1020
|
+
trace_type TEXT NOT NULL CHECK(trace_type IN ('tool_call','llm_call','error','handoff','custom')),
|
|
1021
|
+
name TEXT,
|
|
1022
|
+
input_summary TEXT,
|
|
1023
|
+
output_summary TEXT,
|
|
1024
|
+
duration_ms INTEGER,
|
|
1025
|
+
tokens INTEGER,
|
|
1026
|
+
cost_usd REAL,
|
|
1027
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1028
|
+
);
|
|
1029
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_task ON task_traces(task_id);
|
|
1030
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_agent ON task_traces(agent_id);
|
|
1031
|
+
|
|
1032
|
+
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
1033
|
+
id TEXT PRIMARY KEY,
|
|
1034
|
+
agent_id TEXT,
|
|
1035
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1036
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1037
|
+
snapshot_type TEXT NOT NULL CHECK(snapshot_type IN ('interrupt','complete','handoff','checkpoint')),
|
|
1038
|
+
plan_summary TEXT,
|
|
1039
|
+
files_open TEXT DEFAULT '[]',
|
|
1040
|
+
attempts TEXT DEFAULT '[]',
|
|
1041
|
+
blockers TEXT DEFAULT '[]',
|
|
1042
|
+
next_steps TEXT,
|
|
1043
|
+
metadata TEXT DEFAULT '{}',
|
|
1044
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1045
|
+
);
|
|
1046
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_agent ON context_snapshots(agent_id);
|
|
1047
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_task ON context_snapshots(task_id);
|
|
1048
|
+
|
|
1049
|
+
CREATE TABLE IF NOT EXISTS agent_budgets (
|
|
1050
|
+
agent_id TEXT PRIMARY KEY,
|
|
1051
|
+
max_concurrent INTEGER DEFAULT 5,
|
|
1052
|
+
max_cost_usd REAL,
|
|
1053
|
+
max_task_minutes INTEGER,
|
|
1054
|
+
period_hours INTEGER DEFAULT 24,
|
|
1055
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1056
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1008
1059
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
1009
1060
|
`
|
|
1010
1061
|
];
|
|
@@ -1601,12 +1652,14 @@ var exports_tasks = {};
|
|
|
1601
1652
|
__export(exports_tasks, {
|
|
1602
1653
|
updateTask: () => updateTask,
|
|
1603
1654
|
unlockTask: () => unlockTask,
|
|
1655
|
+
stealTask: () => stealTask,
|
|
1604
1656
|
startTask: () => startTask,
|
|
1605
1657
|
setTaskStatus: () => setTaskStatus,
|
|
1606
1658
|
setTaskPriority: () => setTaskPriority,
|
|
1607
1659
|
removeDependency: () => removeDependency,
|
|
1608
1660
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
1609
1661
|
moveTask: () => moveTask,
|
|
1662
|
+
logCost: () => logCost,
|
|
1610
1663
|
lockTask: () => lockTask,
|
|
1611
1664
|
listTasks: () => listTasks,
|
|
1612
1665
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
@@ -1629,6 +1682,7 @@ __export(exports_tasks, {
|
|
|
1629
1682
|
countTasks: () => countTasks,
|
|
1630
1683
|
completeTask: () => completeTask,
|
|
1631
1684
|
cloneTask: () => cloneTask,
|
|
1685
|
+
claimOrSteal: () => claimOrSteal,
|
|
1632
1686
|
claimNextTask: () => claimNextTask,
|
|
1633
1687
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
1634
1688
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
@@ -2110,6 +2164,20 @@ function completeTask(id, agentId, db, options) {
|
|
|
2110
2164
|
if (spawnedFromTemplate) {
|
|
2111
2165
|
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
2112
2166
|
}
|
|
2167
|
+
const unblockedDeps = d.query(`SELECT DISTINCT t.id, t.short_id, t.title FROM tasks t
|
|
2168
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
2169
|
+
WHERE td.depends_on = ? AND t.status = 'pending'
|
|
2170
|
+
AND NOT EXISTS (
|
|
2171
|
+
SELECT 1 FROM task_dependencies td2
|
|
2172
|
+
JOIN tasks dep2 ON dep2.id = td2.depends_on
|
|
2173
|
+
WHERE td2.task_id = t.id AND dep2.status NOT IN ('completed', 'cancelled') AND dep2.id != ?
|
|
2174
|
+
)`).all(id, id);
|
|
2175
|
+
if (unblockedDeps.length > 0) {
|
|
2176
|
+
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
2177
|
+
for (const dep of unblockedDeps) {
|
|
2178
|
+
dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2113
2181
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
2114
2182
|
}
|
|
2115
2183
|
function lockTask(id, agentId, db) {
|
|
@@ -2406,24 +2474,36 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
2406
2474
|
};
|
|
2407
2475
|
let retryTask;
|
|
2408
2476
|
if (options?.retry) {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2477
|
+
const retryCount = (task.retry_count || 0) + 1;
|
|
2478
|
+
const maxRetries = task.max_retries || 3;
|
|
2479
|
+
if (retryCount > maxRetries) {
|
|
2480
|
+
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
2481
|
+
JSON.stringify({ ...meta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }),
|
|
2482
|
+
id
|
|
2483
|
+
]);
|
|
2484
|
+
} else {
|
|
2485
|
+
const backoffMinutes = Math.pow(5, retryCount - 1);
|
|
2486
|
+
const retryAfter = options.retry_after || new Date(Date.now() + backoffMinutes * 60 * 1000).toISOString();
|
|
2487
|
+
let title = task.title;
|
|
2488
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
2489
|
+
title = title.slice(task.short_id.length + 2);
|
|
2490
|
+
}
|
|
2491
|
+
retryTask = createTask({
|
|
2492
|
+
title,
|
|
2493
|
+
description: task.description ?? undefined,
|
|
2494
|
+
priority: task.priority,
|
|
2495
|
+
project_id: task.project_id ?? undefined,
|
|
2496
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
2497
|
+
plan_id: task.plan_id ?? undefined,
|
|
2498
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
2499
|
+
tags: task.tags,
|
|
2500
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: reason } },
|
|
2501
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
2502
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
2503
|
+
due_at: retryAfter
|
|
2504
|
+
}, d);
|
|
2505
|
+
d.run("UPDATE tasks SET retry_count = ?, max_retries = ?, retry_after = ? WHERE id = ?", [retryCount, maxRetries, retryAfter, retryTask.id]);
|
|
2412
2506
|
}
|
|
2413
|
-
retryTask = createTask({
|
|
2414
|
-
title,
|
|
2415
|
-
description: task.description ?? undefined,
|
|
2416
|
-
priority: task.priority,
|
|
2417
|
-
project_id: task.project_id ?? undefined,
|
|
2418
|
-
task_list_id: task.task_list_id ?? undefined,
|
|
2419
|
-
plan_id: task.plan_id ?? undefined,
|
|
2420
|
-
assigned_to: task.assigned_to ?? undefined,
|
|
2421
|
-
tags: task.tags,
|
|
2422
|
-
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
2423
|
-
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
2424
|
-
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
2425
|
-
due_at: options.retry_after || task.due_at || undefined
|
|
2426
|
-
}, d);
|
|
2427
2507
|
}
|
|
2428
2508
|
return { task: failedTask, retryTask };
|
|
2429
2509
|
}
|
|
@@ -2447,6 +2527,40 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
2447
2527
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
2448
2528
|
return rows.map(rowToTask);
|
|
2449
2529
|
}
|
|
2530
|
+
function logCost(taskId, tokens, usd, db) {
|
|
2531
|
+
const d = db || getDatabase();
|
|
2532
|
+
d.run("UPDATE tasks SET cost_tokens = cost_tokens + ?, cost_usd = cost_usd + ?, updated_at = ? WHERE id = ?", [tokens, usd, now(), taskId]);
|
|
2533
|
+
}
|
|
2534
|
+
function stealTask(agentId, opts, db) {
|
|
2535
|
+
const d = db || getDatabase();
|
|
2536
|
+
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
2537
|
+
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
2538
|
+
if (staleTasks.length === 0)
|
|
2539
|
+
return null;
|
|
2540
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
2541
|
+
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
2542
|
+
const target = staleTasks[0];
|
|
2543
|
+
const timestamp = now();
|
|
2544
|
+
d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ?`, [agentId, agentId, timestamp, timestamp, target.id]);
|
|
2545
|
+
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
2546
|
+
dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
|
|
2547
|
+
return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
2548
|
+
}
|
|
2549
|
+
function claimOrSteal(agentId, filters, db) {
|
|
2550
|
+
const d = db || getDatabase();
|
|
2551
|
+
const tx = d.transaction(() => {
|
|
2552
|
+
const next = getNextTask(agentId, filters, d);
|
|
2553
|
+
if (next) {
|
|
2554
|
+
const started = startTask(next.id, agentId, d);
|
|
2555
|
+
return { task: started, stolen: false };
|
|
2556
|
+
}
|
|
2557
|
+
const stolen = stealTask(agentId, { stale_minutes: filters?.stale_minutes, project_id: filters?.project_id, task_list_id: filters?.task_list_id }, d);
|
|
2558
|
+
if (stolen)
|
|
2559
|
+
return { task: stolen, stolen: true };
|
|
2560
|
+
return null;
|
|
2561
|
+
});
|
|
2562
|
+
return tx();
|
|
2563
|
+
}
|
|
2450
2564
|
function getStatus(filters, agentId, options, db) {
|
|
2451
2565
|
const d = db || getDatabase();
|
|
2452
2566
|
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
@@ -3042,6 +3156,219 @@ var init_task_commits = __esm(() => {
|
|
|
3042
3156
|
init_database();
|
|
3043
3157
|
});
|
|
3044
3158
|
|
|
3159
|
+
// src/db/traces.ts
|
|
3160
|
+
var exports_traces = {};
|
|
3161
|
+
__export(exports_traces, {
|
|
3162
|
+
logTrace: () => logTrace,
|
|
3163
|
+
getTraceStats: () => getTraceStats,
|
|
3164
|
+
getTaskTraces: () => getTaskTraces
|
|
3165
|
+
});
|
|
3166
|
+
function logTrace(input, db) {
|
|
3167
|
+
const d = db || getDatabase();
|
|
3168
|
+
const id = uuid();
|
|
3169
|
+
const timestamp = now();
|
|
3170
|
+
d.run(`INSERT INTO task_traces (id, task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd, created_at)
|
|
3171
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3172
|
+
id,
|
|
3173
|
+
input.task_id,
|
|
3174
|
+
input.agent_id || null,
|
|
3175
|
+
input.trace_type,
|
|
3176
|
+
input.name || null,
|
|
3177
|
+
input.input_summary || null,
|
|
3178
|
+
input.output_summary || null,
|
|
3179
|
+
input.duration_ms ?? null,
|
|
3180
|
+
input.tokens ?? null,
|
|
3181
|
+
input.cost_usd ?? null,
|
|
3182
|
+
timestamp
|
|
3183
|
+
]);
|
|
3184
|
+
return {
|
|
3185
|
+
id,
|
|
3186
|
+
task_id: input.task_id,
|
|
3187
|
+
agent_id: input.agent_id || null,
|
|
3188
|
+
trace_type: input.trace_type,
|
|
3189
|
+
name: input.name || null,
|
|
3190
|
+
input_summary: input.input_summary || null,
|
|
3191
|
+
output_summary: input.output_summary || null,
|
|
3192
|
+
duration_ms: input.duration_ms ?? null,
|
|
3193
|
+
tokens: input.tokens ?? null,
|
|
3194
|
+
cost_usd: input.cost_usd ?? null,
|
|
3195
|
+
created_at: timestamp
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
function getTaskTraces(taskId, db) {
|
|
3199
|
+
const d = db || getDatabase();
|
|
3200
|
+
return d.query("SELECT * FROM task_traces WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
|
|
3201
|
+
}
|
|
3202
|
+
function getTraceStats(taskId, db) {
|
|
3203
|
+
const d = db || getDatabase();
|
|
3204
|
+
const row = d.query(`SELECT COUNT(*) as total,
|
|
3205
|
+
SUM(CASE WHEN trace_type = 'tool_call' THEN 1 ELSE 0 END) as tool_calls,
|
|
3206
|
+
SUM(CASE WHEN trace_type = 'llm_call' THEN 1 ELSE 0 END) as llm_calls,
|
|
3207
|
+
SUM(CASE WHEN trace_type = 'error' THEN 1 ELSE 0 END) as errors,
|
|
3208
|
+
COALESCE(SUM(tokens), 0) as total_tokens,
|
|
3209
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
3210
|
+
COALESCE(SUM(duration_ms), 0) as total_duration_ms
|
|
3211
|
+
FROM task_traces WHERE task_id = ?`).get(taskId);
|
|
3212
|
+
return row;
|
|
3213
|
+
}
|
|
3214
|
+
var init_traces = __esm(() => {
|
|
3215
|
+
init_database();
|
|
3216
|
+
});
|
|
3217
|
+
|
|
3218
|
+
// src/db/snapshots.ts
|
|
3219
|
+
var exports_snapshots = {};
|
|
3220
|
+
__export(exports_snapshots, {
|
|
3221
|
+
saveSnapshot: () => saveSnapshot,
|
|
3222
|
+
listSnapshots: () => listSnapshots,
|
|
3223
|
+
getLatestSnapshot: () => getLatestSnapshot
|
|
3224
|
+
});
|
|
3225
|
+
function rowToSnapshot(row) {
|
|
3226
|
+
return {
|
|
3227
|
+
...row,
|
|
3228
|
+
snapshot_type: row.snapshot_type,
|
|
3229
|
+
files_open: JSON.parse(row.files_open || "[]"),
|
|
3230
|
+
attempts: JSON.parse(row.attempts || "[]"),
|
|
3231
|
+
blockers: JSON.parse(row.blockers || "[]"),
|
|
3232
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
function saveSnapshot(input, db) {
|
|
3236
|
+
const d = db || getDatabase();
|
|
3237
|
+
const id = uuid();
|
|
3238
|
+
const timestamp = now();
|
|
3239
|
+
d.run(`INSERT INTO context_snapshots (id, agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps, metadata, created_at)
|
|
3240
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3241
|
+
id,
|
|
3242
|
+
input.agent_id || null,
|
|
3243
|
+
input.task_id || null,
|
|
3244
|
+
input.project_id || null,
|
|
3245
|
+
input.snapshot_type,
|
|
3246
|
+
input.plan_summary || null,
|
|
3247
|
+
JSON.stringify(input.files_open || []),
|
|
3248
|
+
JSON.stringify(input.attempts || []),
|
|
3249
|
+
JSON.stringify(input.blockers || []),
|
|
3250
|
+
input.next_steps || null,
|
|
3251
|
+
JSON.stringify(input.metadata || {}),
|
|
3252
|
+
timestamp
|
|
3253
|
+
]);
|
|
3254
|
+
return {
|
|
3255
|
+
id,
|
|
3256
|
+
agent_id: input.agent_id || null,
|
|
3257
|
+
task_id: input.task_id || null,
|
|
3258
|
+
project_id: input.project_id || null,
|
|
3259
|
+
snapshot_type: input.snapshot_type,
|
|
3260
|
+
plan_summary: input.plan_summary || null,
|
|
3261
|
+
files_open: input.files_open || [],
|
|
3262
|
+
attempts: input.attempts || [],
|
|
3263
|
+
blockers: input.blockers || [],
|
|
3264
|
+
next_steps: input.next_steps || null,
|
|
3265
|
+
metadata: input.metadata || {},
|
|
3266
|
+
created_at: timestamp
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
function getLatestSnapshot(agentId, taskId, db) {
|
|
3270
|
+
const d = db || getDatabase();
|
|
3271
|
+
const conditions = [];
|
|
3272
|
+
const params = [];
|
|
3273
|
+
if (agentId) {
|
|
3274
|
+
conditions.push("agent_id = ?");
|
|
3275
|
+
params.push(agentId);
|
|
3276
|
+
}
|
|
3277
|
+
if (taskId) {
|
|
3278
|
+
conditions.push("task_id = ?");
|
|
3279
|
+
params.push(taskId);
|
|
3280
|
+
}
|
|
3281
|
+
if (conditions.length === 0)
|
|
3282
|
+
return null;
|
|
3283
|
+
const where = conditions.join(" AND ");
|
|
3284
|
+
const row = d.query(`SELECT * FROM context_snapshots WHERE ${where} ORDER BY created_at DESC LIMIT 1`).get(...params);
|
|
3285
|
+
return row ? rowToSnapshot(row) : null;
|
|
3286
|
+
}
|
|
3287
|
+
function listSnapshots(opts, db) {
|
|
3288
|
+
const d = db || getDatabase();
|
|
3289
|
+
const conditions = [];
|
|
3290
|
+
const params = [];
|
|
3291
|
+
if (opts.agent_id) {
|
|
3292
|
+
conditions.push("agent_id = ?");
|
|
3293
|
+
params.push(opts.agent_id);
|
|
3294
|
+
}
|
|
3295
|
+
if (opts.task_id) {
|
|
3296
|
+
conditions.push("task_id = ?");
|
|
3297
|
+
params.push(opts.task_id);
|
|
3298
|
+
}
|
|
3299
|
+
if (opts.project_id) {
|
|
3300
|
+
conditions.push("project_id = ?");
|
|
3301
|
+
params.push(opts.project_id);
|
|
3302
|
+
}
|
|
3303
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3304
|
+
const limit = opts.limit || 20;
|
|
3305
|
+
params.push(limit);
|
|
3306
|
+
return d.query(`SELECT * FROM context_snapshots ${where} ORDER BY created_at DESC LIMIT ?`).all(...params).map(rowToSnapshot);
|
|
3307
|
+
}
|
|
3308
|
+
var init_snapshots = __esm(() => {
|
|
3309
|
+
init_database();
|
|
3310
|
+
});
|
|
3311
|
+
|
|
3312
|
+
// src/db/budgets.ts
|
|
3313
|
+
var exports_budgets = {};
|
|
3314
|
+
__export(exports_budgets, {
|
|
3315
|
+
setBudget: () => setBudget,
|
|
3316
|
+
getBudget: () => getBudget,
|
|
3317
|
+
checkBudget: () => checkBudget
|
|
3318
|
+
});
|
|
3319
|
+
function setBudget(agentId, opts, db) {
|
|
3320
|
+
const d = db || getDatabase();
|
|
3321
|
+
const timestamp = now();
|
|
3322
|
+
d.run(`INSERT INTO agent_budgets (agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours, created_at, updated_at)
|
|
3323
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3324
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
3325
|
+
max_concurrent = COALESCE(?, max_concurrent),
|
|
3326
|
+
max_cost_usd = COALESCE(?, max_cost_usd),
|
|
3327
|
+
max_task_minutes = COALESCE(?, max_task_minutes),
|
|
3328
|
+
period_hours = COALESCE(?, period_hours),
|
|
3329
|
+
updated_at = ?`, [
|
|
3330
|
+
agentId,
|
|
3331
|
+
opts.max_concurrent ?? 5,
|
|
3332
|
+
opts.max_cost_usd ?? null,
|
|
3333
|
+
opts.max_task_minutes ?? null,
|
|
3334
|
+
opts.period_hours ?? 24,
|
|
3335
|
+
timestamp,
|
|
3336
|
+
timestamp,
|
|
3337
|
+
opts.max_concurrent ?? null,
|
|
3338
|
+
opts.max_cost_usd ?? null,
|
|
3339
|
+
opts.max_task_minutes ?? null,
|
|
3340
|
+
opts.period_hours ?? null,
|
|
3341
|
+
timestamp
|
|
3342
|
+
]);
|
|
3343
|
+
return getBudget(agentId, d);
|
|
3344
|
+
}
|
|
3345
|
+
function getBudget(agentId, db) {
|
|
3346
|
+
const d = db || getDatabase();
|
|
3347
|
+
return d.query("SELECT * FROM agent_budgets WHERE agent_id = ?").get(agentId);
|
|
3348
|
+
}
|
|
3349
|
+
function checkBudget(agentId, db) {
|
|
3350
|
+
const d = db || getDatabase();
|
|
3351
|
+
const budget = getBudget(agentId, d);
|
|
3352
|
+
if (!budget)
|
|
3353
|
+
return { allowed: true, current_concurrent: 0, max_concurrent: 999 };
|
|
3354
|
+
const concurrent = countTasks({ status: "in_progress", assigned_to: agentId }, d);
|
|
3355
|
+
if (concurrent >= budget.max_concurrent) {
|
|
3356
|
+
return { allowed: false, reason: `Concurrent limit reached (${concurrent}/${budget.max_concurrent})`, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
3357
|
+
}
|
|
3358
|
+
if (budget.max_cost_usd != null) {
|
|
3359
|
+
const periodStart = new Date(Date.now() - budget.period_hours * 60 * 60 * 1000).toISOString();
|
|
3360
|
+
const costRow = d.query("SELECT COALESCE(SUM(cost_usd), 0) as total FROM tasks WHERE (assigned_to = ? OR agent_id = ?) AND updated_at > ?").get(agentId, agentId, periodStart);
|
|
3361
|
+
if (costRow.total >= budget.max_cost_usd) {
|
|
3362
|
+
return { allowed: false, reason: `Cost limit reached ($${costRow.total.toFixed(2)}/$${budget.max_cost_usd.toFixed(2)} in ${budget.period_hours}h)`, current_concurrent: concurrent, max_concurrent: budget.max_concurrent, current_cost_usd: costRow.total, max_cost_usd: budget.max_cost_usd };
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
return { allowed: true, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
3366
|
+
}
|
|
3367
|
+
var init_budgets = __esm(() => {
|
|
3368
|
+
init_database();
|
|
3369
|
+
init_tasks();
|
|
3370
|
+
});
|
|
3371
|
+
|
|
3045
3372
|
// src/lib/github.ts
|
|
3046
3373
|
var exports_github = {};
|
|
3047
3374
|
__export(exports_github, {
|
|
@@ -10464,6 +10791,33 @@ ID: ${updated.id}`
|
|
|
10464
10791
|
}
|
|
10465
10792
|
});
|
|
10466
10793
|
}
|
|
10794
|
+
if (shouldRegisterTool("update_agent")) {
|
|
10795
|
+
server.tool("update_agent", "Update an agent's description, role, title, or other metadata. Resolve by id or name.", {
|
|
10796
|
+
id: exports_external.string().optional(),
|
|
10797
|
+
name: exports_external.string().optional(),
|
|
10798
|
+
description: exports_external.string().optional(),
|
|
10799
|
+
role: exports_external.string().optional(),
|
|
10800
|
+
title: exports_external.string().optional(),
|
|
10801
|
+
level: exports_external.string().optional(),
|
|
10802
|
+
capabilities: exports_external.array(exports_external.string()).optional(),
|
|
10803
|
+
permissions: exports_external.array(exports_external.string()).optional(),
|
|
10804
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
10805
|
+
}, async ({ id, name, ...updates }) => {
|
|
10806
|
+
try {
|
|
10807
|
+
if (!id && !name) {
|
|
10808
|
+
return { content: [{ type: "text", text: "Provide either id or name." }], isError: true };
|
|
10809
|
+
}
|
|
10810
|
+
const agent = id ? getAgent(id) : getAgentByName(name);
|
|
10811
|
+
if (!agent) {
|
|
10812
|
+
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
10813
|
+
}
|
|
10814
|
+
const updated = updateAgent(agent.id, updates);
|
|
10815
|
+
return { content: [{ type: "text", text: `Agent updated: ${updated.name} (${updated.id.slice(0, 8)})` }] };
|
|
10816
|
+
} catch (e) {
|
|
10817
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10818
|
+
}
|
|
10819
|
+
});
|
|
10820
|
+
}
|
|
10467
10821
|
if (shouldRegisterTool("delete_agent")) {
|
|
10468
10822
|
server.tool("delete_agent", "Archive an agent (soft delete). The agent is hidden from list_agents but preserved for task history. Use unarchive_agent to restore. Resolve by id or name.", {
|
|
10469
10823
|
id: exports_external.string().optional(),
|
|
@@ -10822,6 +11176,135 @@ Blocked:`);
|
|
|
10822
11176
|
}
|
|
10823
11177
|
});
|
|
10824
11178
|
}
|
|
11179
|
+
if (shouldRegisterTool("log_cost")) {
|
|
11180
|
+
server.tool("log_cost", "Log token usage and cost to a task. Accumulates \u2014 call after each LLM invocation.", { task_id: exports_external.string(), tokens: exports_external.number().describe("Token count"), usd: exports_external.number().describe("Cost in USD") }, async ({ task_id, tokens, usd }) => {
|
|
11181
|
+
try {
|
|
11182
|
+
const { logCost: logCost2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
11183
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
11184
|
+
logCost2(resolvedId, tokens, usd);
|
|
11185
|
+
return { content: [{ type: "text", text: `Logged ${tokens} tokens ($${usd.toFixed(4)}) to ${task_id}` }] };
|
|
11186
|
+
} catch (e) {
|
|
11187
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11188
|
+
}
|
|
11189
|
+
});
|
|
11190
|
+
}
|
|
11191
|
+
if (shouldRegisterTool("log_trace")) {
|
|
11192
|
+
server.tool("log_trace", "Log a trace entry (tool call, LLM call, error, handoff) to a task for observability.", {
|
|
11193
|
+
task_id: exports_external.string(),
|
|
11194
|
+
agent_id: exports_external.string().optional(),
|
|
11195
|
+
trace_type: exports_external.enum(["tool_call", "llm_call", "error", "handoff", "custom"]),
|
|
11196
|
+
name: exports_external.string().optional(),
|
|
11197
|
+
input_summary: exports_external.string().optional(),
|
|
11198
|
+
output_summary: exports_external.string().optional(),
|
|
11199
|
+
duration_ms: exports_external.number().optional(),
|
|
11200
|
+
tokens: exports_external.number().optional(),
|
|
11201
|
+
cost_usd: exports_external.number().optional()
|
|
11202
|
+
}, async ({ task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd }) => {
|
|
11203
|
+
try {
|
|
11204
|
+
const { logTrace: logTrace2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
11205
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
11206
|
+
const trace = logTrace2({ task_id: resolvedId, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd });
|
|
11207
|
+
return { content: [{ type: "text", text: `Trace logged: ${trace.id} [${trace_type}] ${name || ""}` }] };
|
|
11208
|
+
} catch (e) {
|
|
11209
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11210
|
+
}
|
|
11211
|
+
});
|
|
11212
|
+
}
|
|
11213
|
+
if (shouldRegisterTool("get_traces")) {
|
|
11214
|
+
server.tool("get_traces", "Get execution traces for a task \u2014 tool calls, LLM invocations, errors, handoffs.", { task_id: exports_external.string() }, async ({ task_id }) => {
|
|
11215
|
+
try {
|
|
11216
|
+
const { getTaskTraces: getTaskTraces2, getTraceStats: getTraceStats2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
11217
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
11218
|
+
const traces = getTaskTraces2(resolvedId);
|
|
11219
|
+
const stats = getTraceStats2(resolvedId);
|
|
11220
|
+
const lines = [`Traces for ${task_id}: ${stats.total} total (${stats.tool_calls} tools, ${stats.llm_calls} LLM, ${stats.errors} errors) | ${stats.total_tokens} tokens | $${stats.total_cost_usd.toFixed(4)} | ${stats.total_duration_ms}ms`];
|
|
11221
|
+
for (const t of traces.slice(0, 20)) {
|
|
11222
|
+
lines.push(` ${t.created_at} [${t.trace_type}] ${t.name || ""} ${t.tokens ? t.tokens + "tok" : ""} ${t.duration_ms ? t.duration_ms + "ms" : ""}`);
|
|
11223
|
+
}
|
|
11224
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
11225
|
+
`) }] };
|
|
11226
|
+
} catch (e) {
|
|
11227
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11228
|
+
}
|
|
11229
|
+
});
|
|
11230
|
+
}
|
|
11231
|
+
if (shouldRegisterTool("save_snapshot")) {
|
|
11232
|
+
server.tool("save_snapshot", "Save a structured context snapshot \u2014 what you were working on, what files are open, what was tried, blockers, next steps. Call on session end or before handoff.", {
|
|
11233
|
+
agent_id: exports_external.string().optional(),
|
|
11234
|
+
task_id: exports_external.string().optional(),
|
|
11235
|
+
project_id: exports_external.string().optional(),
|
|
11236
|
+
snapshot_type: exports_external.enum(["interrupt", "complete", "handoff", "checkpoint"]),
|
|
11237
|
+
plan_summary: exports_external.string().optional(),
|
|
11238
|
+
files_open: exports_external.array(exports_external.string()).optional(),
|
|
11239
|
+
attempts: exports_external.array(exports_external.string()).optional(),
|
|
11240
|
+
blockers: exports_external.array(exports_external.string()).optional(),
|
|
11241
|
+
next_steps: exports_external.string().optional()
|
|
11242
|
+
}, async ({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps }) => {
|
|
11243
|
+
try {
|
|
11244
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
11245
|
+
const snap = saveSnapshot2({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps });
|
|
11246
|
+
return { content: [{ type: "text", text: `Snapshot saved: ${snap.id} [${snapshot_type}]${plan_summary ? " \u2014 " + plan_summary.slice(0, 80) : ""}` }] };
|
|
11247
|
+
} catch (e) {
|
|
11248
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11249
|
+
}
|
|
11250
|
+
});
|
|
11251
|
+
}
|
|
11252
|
+
if (shouldRegisterTool("get_snapshot")) {
|
|
11253
|
+
server.tool("get_snapshot", "Get the latest context snapshot for an agent or task \u2014 use to resume work after interruption.", { agent_id: exports_external.string().optional(), task_id: exports_external.string().optional() }, async ({ agent_id, task_id }) => {
|
|
11254
|
+
try {
|
|
11255
|
+
const { getLatestSnapshot: getLatestSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
11256
|
+
const snap = getLatestSnapshot2(agent_id, task_id);
|
|
11257
|
+
if (!snap)
|
|
11258
|
+
return { content: [{ type: "text", text: "No snapshot found." }] };
|
|
11259
|
+
const lines = [`Snapshot [${snap.snapshot_type}] from ${snap.created_at}`];
|
|
11260
|
+
if (snap.plan_summary)
|
|
11261
|
+
lines.push(`Plan: ${snap.plan_summary}`);
|
|
11262
|
+
if (snap.files_open.length > 0)
|
|
11263
|
+
lines.push(`Files: ${snap.files_open.join(", ")}`);
|
|
11264
|
+
if (snap.blockers.length > 0)
|
|
11265
|
+
lines.push(`Blockers: ${snap.blockers.join(", ")}`);
|
|
11266
|
+
if (snap.next_steps)
|
|
11267
|
+
lines.push(`Next: ${snap.next_steps}`);
|
|
11268
|
+
if (snap.attempts.length > 0)
|
|
11269
|
+
lines.push(`Attempts: ${snap.attempts.join("; ")}`);
|
|
11270
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
11271
|
+
`) }] };
|
|
11272
|
+
} catch (e) {
|
|
11273
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11274
|
+
}
|
|
11275
|
+
});
|
|
11276
|
+
}
|
|
11277
|
+
if (shouldRegisterTool("set_budget")) {
|
|
11278
|
+
server.tool("set_budget", "Set execution budget for an agent \u2014 max concurrent tasks, cost limit, time limit per period.", {
|
|
11279
|
+
agent_id: exports_external.string(),
|
|
11280
|
+
max_concurrent: exports_external.number().optional(),
|
|
11281
|
+
max_cost_usd: exports_external.number().optional(),
|
|
11282
|
+
max_task_minutes: exports_external.number().optional(),
|
|
11283
|
+
period_hours: exports_external.number().optional()
|
|
11284
|
+
}, async ({ agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours }) => {
|
|
11285
|
+
try {
|
|
11286
|
+
const { setBudget: setBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
11287
|
+
const budget = setBudget2(agent_id, { max_concurrent, max_cost_usd, max_task_minutes, period_hours });
|
|
11288
|
+
return { content: [{ type: "text", text: `Budget set for ${agent_id}: max ${budget.max_concurrent} concurrent, $${budget.max_cost_usd ?? "\u221E"} cost limit, ${budget.period_hours}h period` }] };
|
|
11289
|
+
} catch (e) {
|
|
11290
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11291
|
+
}
|
|
11292
|
+
});
|
|
11293
|
+
}
|
|
11294
|
+
if (shouldRegisterTool("check_budget")) {
|
|
11295
|
+
server.tool("check_budget", "Check if an agent is within their execution budget (concurrent tasks, cost, time).", { agent_id: exports_external.string() }, async ({ agent_id }) => {
|
|
11296
|
+
try {
|
|
11297
|
+
const { checkBudget: checkBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
11298
|
+
const result = checkBudget2(agent_id);
|
|
11299
|
+
if (result.allowed) {
|
|
11300
|
+
return { content: [{ type: "text", text: `Budget OK: ${result.current_concurrent}/${result.max_concurrent} concurrent tasks` }] };
|
|
11301
|
+
}
|
|
11302
|
+
return { content: [{ type: "text", text: `BUDGET EXCEEDED: ${result.reason}` }], isError: true };
|
|
11303
|
+
} catch (e) {
|
|
11304
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11305
|
+
}
|
|
11306
|
+
});
|
|
11307
|
+
}
|
|
10825
11308
|
if (shouldRegisterTool("import_github_issue")) {
|
|
10826
11309
|
server.tool("import_github_issue", "Import a GitHub issue as a task. Requires gh CLI installed and authenticated.", {
|
|
10827
11310
|
url: exports_external.string().describe("GitHub issue URL (e.g. https://github.com/owner/repo/issues/42)"),
|
|
@@ -11532,6 +12015,56 @@ ${formatTaskDetail(task, 300)}` }] };
|
|
|
11532
12015
|
}
|
|
11533
12016
|
});
|
|
11534
12017
|
}
|
|
12018
|
+
if (shouldRegisterTool("steal_task")) {
|
|
12019
|
+
server.tool("steal_task", "Work-stealing: take the highest-priority stale in_progress task from another agent and reassign it to you.", {
|
|
12020
|
+
agent_id: exports_external.string().describe("Your agent ID"),
|
|
12021
|
+
stale_minutes: exports_external.number().optional().describe("How long a task must be stale before stealing (default: 30)"),
|
|
12022
|
+
project_id: exports_external.string().optional(),
|
|
12023
|
+
task_list_id: exports_external.string().optional()
|
|
12024
|
+
}, async ({ agent_id, stale_minutes, project_id, task_list_id }) => {
|
|
12025
|
+
try {
|
|
12026
|
+
const task = stealTask(agent_id, { stale_minutes, project_id, task_list_id });
|
|
12027
|
+
if (!task)
|
|
12028
|
+
return { content: [{ type: "text", text: "No stale tasks available to steal." }] };
|
|
12029
|
+
return { content: [{ type: "text", text: `Stolen: ${formatTask(task)}
|
|
12030
|
+
Previous owner: ${task.metadata?._stolen_from || "unknown"}` }] };
|
|
12031
|
+
} catch (e) {
|
|
12032
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12033
|
+
}
|
|
12034
|
+
});
|
|
12035
|
+
}
|
|
12036
|
+
if (shouldRegisterTool("claim_or_steal")) {
|
|
12037
|
+
server.tool("claim_or_steal", "Try to claim a pending task first; if none available, steal from a stale agent. Best single call for getting work.", {
|
|
12038
|
+
agent_id: exports_external.string(),
|
|
12039
|
+
project_id: exports_external.string().optional(),
|
|
12040
|
+
task_list_id: exports_external.string().optional(),
|
|
12041
|
+
plan_id: exports_external.string().optional(),
|
|
12042
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
12043
|
+
stale_minutes: exports_external.number().optional().describe("Stale threshold for work-stealing fallback (default: 30)")
|
|
12044
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags, stale_minutes }) => {
|
|
12045
|
+
try {
|
|
12046
|
+
const filters = {};
|
|
12047
|
+
if (project_id)
|
|
12048
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
12049
|
+
if (task_list_id)
|
|
12050
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
12051
|
+
if (plan_id)
|
|
12052
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
12053
|
+
if (tags)
|
|
12054
|
+
filters.tags = tags;
|
|
12055
|
+
if (stale_minutes)
|
|
12056
|
+
filters.stale_minutes = stale_minutes;
|
|
12057
|
+
const result = claimOrSteal(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
12058
|
+
if (!result)
|
|
12059
|
+
return { content: [{ type: "text", text: "No tasks available to claim or steal." }] };
|
|
12060
|
+
const prefix = result.stolen ? "Stolen" : "Claimed";
|
|
12061
|
+
return { content: [{ type: "text", text: `${prefix}: ${formatTask(result.task)}
|
|
12062
|
+
${formatTaskDetail(result.task, 300)}` }] };
|
|
12063
|
+
} catch (e) {
|
|
12064
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12065
|
+
}
|
|
12066
|
+
});
|
|
12067
|
+
}
|
|
11535
12068
|
if (shouldRegisterTool("get_stale_tasks")) {
|
|
11536
12069
|
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
11537
12070
|
stale_minutes: exports_external.number().optional(),
|