@hasna/todos 0.10.22 → 0.11.2
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/brains.d.ts +3 -0
- package/dist/cli/brains.d.ts.map +1 -0
- package/dist/cli/index.js +1402 -552
- 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 +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +481 -44
- package/dist/lib/gatherer.d.ts +16 -0
- package/dist/lib/gatherer.d.ts.map +1 -0
- package/dist/lib/model-config.d.ts +14 -0
- package/dist/lib/model-config.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +550 -18
- package/dist/server/index.js +141 -17
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/mcp/index.js
CHANGED
|
@@ -392,6 +392,15 @@ function ensureSchema(db) {
|
|
|
392
392
|
ensureColumn("tasks", "assigned_by", "TEXT");
|
|
393
393
|
ensureColumn("tasks", "assigned_from_project", "TEXT");
|
|
394
394
|
ensureColumn("tasks", "started_at", "TEXT");
|
|
395
|
+
ensureColumn("tasks", "task_type", "TEXT");
|
|
396
|
+
ensureColumn("tasks", "cost_tokens", "INTEGER DEFAULT 0");
|
|
397
|
+
ensureColumn("tasks", "cost_usd", "REAL DEFAULT 0");
|
|
398
|
+
ensureColumn("tasks", "delegated_from", "TEXT");
|
|
399
|
+
ensureColumn("tasks", "delegation_depth", "INTEGER DEFAULT 0");
|
|
400
|
+
ensureColumn("tasks", "retry_count", "INTEGER DEFAULT 0");
|
|
401
|
+
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
402
|
+
ensureColumn("tasks", "retry_after", "TEXT");
|
|
403
|
+
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
395
404
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
396
405
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
397
406
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -1005,6 +1014,58 @@ var init_database = __esm(() => {
|
|
|
1005
1014
|
`
|
|
1006
1015
|
ALTER TABLE tasks ADD COLUMN task_type TEXT;
|
|
1007
1016
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type);
|
|
1017
|
+
ALTER TABLE tasks ADD COLUMN cost_tokens INTEGER DEFAULT 0;
|
|
1018
|
+
ALTER TABLE tasks ADD COLUMN cost_usd REAL DEFAULT 0;
|
|
1019
|
+
ALTER TABLE tasks ADD COLUMN delegated_from TEXT;
|
|
1020
|
+
ALTER TABLE tasks ADD COLUMN delegation_depth INTEGER DEFAULT 0;
|
|
1021
|
+
ALTER TABLE tasks ADD COLUMN retry_count INTEGER DEFAULT 0;
|
|
1022
|
+
ALTER TABLE tasks ADD COLUMN max_retries INTEGER DEFAULT 3;
|
|
1023
|
+
ALTER TABLE tasks ADD COLUMN retry_after TEXT;
|
|
1024
|
+
ALTER TABLE tasks ADD COLUMN sla_minutes INTEGER;
|
|
1025
|
+
|
|
1026
|
+
CREATE TABLE IF NOT EXISTS task_traces (
|
|
1027
|
+
id TEXT PRIMARY KEY,
|
|
1028
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1029
|
+
agent_id TEXT,
|
|
1030
|
+
trace_type TEXT NOT NULL CHECK(trace_type IN ('tool_call','llm_call','error','handoff','custom')),
|
|
1031
|
+
name TEXT,
|
|
1032
|
+
input_summary TEXT,
|
|
1033
|
+
output_summary TEXT,
|
|
1034
|
+
duration_ms INTEGER,
|
|
1035
|
+
tokens INTEGER,
|
|
1036
|
+
cost_usd REAL,
|
|
1037
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1038
|
+
);
|
|
1039
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_task ON task_traces(task_id);
|
|
1040
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_agent ON task_traces(agent_id);
|
|
1041
|
+
|
|
1042
|
+
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
1043
|
+
id TEXT PRIMARY KEY,
|
|
1044
|
+
agent_id TEXT,
|
|
1045
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1046
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1047
|
+
snapshot_type TEXT NOT NULL CHECK(snapshot_type IN ('interrupt','complete','handoff','checkpoint')),
|
|
1048
|
+
plan_summary TEXT,
|
|
1049
|
+
files_open TEXT DEFAULT '[]',
|
|
1050
|
+
attempts TEXT DEFAULT '[]',
|
|
1051
|
+
blockers TEXT DEFAULT '[]',
|
|
1052
|
+
next_steps TEXT,
|
|
1053
|
+
metadata TEXT DEFAULT '{}',
|
|
1054
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1055
|
+
);
|
|
1056
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_agent ON context_snapshots(agent_id);
|
|
1057
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_task ON context_snapshots(task_id);
|
|
1058
|
+
|
|
1059
|
+
CREATE TABLE IF NOT EXISTS agent_budgets (
|
|
1060
|
+
agent_id TEXT PRIMARY KEY,
|
|
1061
|
+
max_concurrent INTEGER DEFAULT 5,
|
|
1062
|
+
max_cost_usd REAL,
|
|
1063
|
+
max_task_minutes INTEGER,
|
|
1064
|
+
period_hours INTEGER DEFAULT 24,
|
|
1065
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1066
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1008
1069
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
1009
1070
|
`
|
|
1010
1071
|
];
|
|
@@ -1601,12 +1662,14 @@ var exports_tasks = {};
|
|
|
1601
1662
|
__export(exports_tasks, {
|
|
1602
1663
|
updateTask: () => updateTask,
|
|
1603
1664
|
unlockTask: () => unlockTask,
|
|
1665
|
+
stealTask: () => stealTask,
|
|
1604
1666
|
startTask: () => startTask,
|
|
1605
1667
|
setTaskStatus: () => setTaskStatus,
|
|
1606
1668
|
setTaskPriority: () => setTaskPriority,
|
|
1607
1669
|
removeDependency: () => removeDependency,
|
|
1608
1670
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
1609
1671
|
moveTask: () => moveTask,
|
|
1672
|
+
logCost: () => logCost,
|
|
1610
1673
|
lockTask: () => lockTask,
|
|
1611
1674
|
listTasks: () => listTasks,
|
|
1612
1675
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
@@ -1629,6 +1692,7 @@ __export(exports_tasks, {
|
|
|
1629
1692
|
countTasks: () => countTasks,
|
|
1630
1693
|
completeTask: () => completeTask,
|
|
1631
1694
|
cloneTask: () => cloneTask,
|
|
1695
|
+
claimOrSteal: () => claimOrSteal,
|
|
1632
1696
|
claimNextTask: () => claimNextTask,
|
|
1633
1697
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
1634
1698
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
@@ -2110,6 +2174,20 @@ function completeTask(id, agentId, db, options) {
|
|
|
2110
2174
|
if (spawnedFromTemplate) {
|
|
2111
2175
|
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
2112
2176
|
}
|
|
2177
|
+
const unblockedDeps = d.query(`SELECT DISTINCT t.id, t.short_id, t.title FROM tasks t
|
|
2178
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
2179
|
+
WHERE td.depends_on = ? AND t.status = 'pending'
|
|
2180
|
+
AND NOT EXISTS (
|
|
2181
|
+
SELECT 1 FROM task_dependencies td2
|
|
2182
|
+
JOIN tasks dep2 ON dep2.id = td2.depends_on
|
|
2183
|
+
WHERE td2.task_id = t.id AND dep2.status NOT IN ('completed', 'cancelled') AND dep2.id != ?
|
|
2184
|
+
)`).all(id, id);
|
|
2185
|
+
if (unblockedDeps.length > 0) {
|
|
2186
|
+
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
2187
|
+
for (const dep of unblockedDeps) {
|
|
2188
|
+
dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2113
2191
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
2114
2192
|
}
|
|
2115
2193
|
function lockTask(id, agentId, db) {
|
|
@@ -2406,24 +2484,36 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
2406
2484
|
};
|
|
2407
2485
|
let retryTask;
|
|
2408
2486
|
if (options?.retry) {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2487
|
+
const retryCount = (task.retry_count || 0) + 1;
|
|
2488
|
+
const maxRetries = task.max_retries || 3;
|
|
2489
|
+
if (retryCount > maxRetries) {
|
|
2490
|
+
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
2491
|
+
JSON.stringify({ ...meta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }),
|
|
2492
|
+
id
|
|
2493
|
+
]);
|
|
2494
|
+
} else {
|
|
2495
|
+
const backoffMinutes = Math.pow(5, retryCount - 1);
|
|
2496
|
+
const retryAfter = options.retry_after || new Date(Date.now() + backoffMinutes * 60 * 1000).toISOString();
|
|
2497
|
+
let title = task.title;
|
|
2498
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
2499
|
+
title = title.slice(task.short_id.length + 2);
|
|
2500
|
+
}
|
|
2501
|
+
retryTask = createTask({
|
|
2502
|
+
title,
|
|
2503
|
+
description: task.description ?? undefined,
|
|
2504
|
+
priority: task.priority,
|
|
2505
|
+
project_id: task.project_id ?? undefined,
|
|
2506
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
2507
|
+
plan_id: task.plan_id ?? undefined,
|
|
2508
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
2509
|
+
tags: task.tags,
|
|
2510
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: reason } },
|
|
2511
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
2512
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
2513
|
+
due_at: retryAfter
|
|
2514
|
+
}, d);
|
|
2515
|
+
d.run("UPDATE tasks SET retry_count = ?, max_retries = ?, retry_after = ? WHERE id = ?", [retryCount, maxRetries, retryAfter, retryTask.id]);
|
|
2412
2516
|
}
|
|
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
2517
|
}
|
|
2428
2518
|
return { task: failedTask, retryTask };
|
|
2429
2519
|
}
|
|
@@ -2447,6 +2537,40 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
2447
2537
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
2448
2538
|
return rows.map(rowToTask);
|
|
2449
2539
|
}
|
|
2540
|
+
function logCost(taskId, tokens, usd, db) {
|
|
2541
|
+
const d = db || getDatabase();
|
|
2542
|
+
d.run("UPDATE tasks SET cost_tokens = cost_tokens + ?, cost_usd = cost_usd + ?, updated_at = ? WHERE id = ?", [tokens, usd, now(), taskId]);
|
|
2543
|
+
}
|
|
2544
|
+
function stealTask(agentId, opts, db) {
|
|
2545
|
+
const d = db || getDatabase();
|
|
2546
|
+
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
2547
|
+
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
2548
|
+
if (staleTasks.length === 0)
|
|
2549
|
+
return null;
|
|
2550
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
2551
|
+
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
2552
|
+
const target = staleTasks[0];
|
|
2553
|
+
const timestamp = now();
|
|
2554
|
+
d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ?`, [agentId, agentId, timestamp, timestamp, target.id]);
|
|
2555
|
+
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
2556
|
+
dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
|
|
2557
|
+
return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
2558
|
+
}
|
|
2559
|
+
function claimOrSteal(agentId, filters, db) {
|
|
2560
|
+
const d = db || getDatabase();
|
|
2561
|
+
const tx = d.transaction(() => {
|
|
2562
|
+
const next = getNextTask(agentId, filters, d);
|
|
2563
|
+
if (next) {
|
|
2564
|
+
const started = startTask(next.id, agentId, d);
|
|
2565
|
+
return { task: started, stolen: false };
|
|
2566
|
+
}
|
|
2567
|
+
const stolen = stealTask(agentId, { stale_minutes: filters?.stale_minutes, project_id: filters?.project_id, task_list_id: filters?.task_list_id }, d);
|
|
2568
|
+
if (stolen)
|
|
2569
|
+
return { task: stolen, stolen: true };
|
|
2570
|
+
return null;
|
|
2571
|
+
});
|
|
2572
|
+
return tx();
|
|
2573
|
+
}
|
|
2450
2574
|
function getStatus(filters, agentId, options, db) {
|
|
2451
2575
|
const d = db || getDatabase();
|
|
2452
2576
|
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
@@ -3042,6 +3166,219 @@ var init_task_commits = __esm(() => {
|
|
|
3042
3166
|
init_database();
|
|
3043
3167
|
});
|
|
3044
3168
|
|
|
3169
|
+
// src/db/traces.ts
|
|
3170
|
+
var exports_traces = {};
|
|
3171
|
+
__export(exports_traces, {
|
|
3172
|
+
logTrace: () => logTrace,
|
|
3173
|
+
getTraceStats: () => getTraceStats,
|
|
3174
|
+
getTaskTraces: () => getTaskTraces
|
|
3175
|
+
});
|
|
3176
|
+
function logTrace(input, db) {
|
|
3177
|
+
const d = db || getDatabase();
|
|
3178
|
+
const id = uuid();
|
|
3179
|
+
const timestamp = now();
|
|
3180
|
+
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)
|
|
3181
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3182
|
+
id,
|
|
3183
|
+
input.task_id,
|
|
3184
|
+
input.agent_id || null,
|
|
3185
|
+
input.trace_type,
|
|
3186
|
+
input.name || null,
|
|
3187
|
+
input.input_summary || null,
|
|
3188
|
+
input.output_summary || null,
|
|
3189
|
+
input.duration_ms ?? null,
|
|
3190
|
+
input.tokens ?? null,
|
|
3191
|
+
input.cost_usd ?? null,
|
|
3192
|
+
timestamp
|
|
3193
|
+
]);
|
|
3194
|
+
return {
|
|
3195
|
+
id,
|
|
3196
|
+
task_id: input.task_id,
|
|
3197
|
+
agent_id: input.agent_id || null,
|
|
3198
|
+
trace_type: input.trace_type,
|
|
3199
|
+
name: input.name || null,
|
|
3200
|
+
input_summary: input.input_summary || null,
|
|
3201
|
+
output_summary: input.output_summary || null,
|
|
3202
|
+
duration_ms: input.duration_ms ?? null,
|
|
3203
|
+
tokens: input.tokens ?? null,
|
|
3204
|
+
cost_usd: input.cost_usd ?? null,
|
|
3205
|
+
created_at: timestamp
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
function getTaskTraces(taskId, db) {
|
|
3209
|
+
const d = db || getDatabase();
|
|
3210
|
+
return d.query("SELECT * FROM task_traces WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
|
|
3211
|
+
}
|
|
3212
|
+
function getTraceStats(taskId, db) {
|
|
3213
|
+
const d = db || getDatabase();
|
|
3214
|
+
const row = d.query(`SELECT COUNT(*) as total,
|
|
3215
|
+
SUM(CASE WHEN trace_type = 'tool_call' THEN 1 ELSE 0 END) as tool_calls,
|
|
3216
|
+
SUM(CASE WHEN trace_type = 'llm_call' THEN 1 ELSE 0 END) as llm_calls,
|
|
3217
|
+
SUM(CASE WHEN trace_type = 'error' THEN 1 ELSE 0 END) as errors,
|
|
3218
|
+
COALESCE(SUM(tokens), 0) as total_tokens,
|
|
3219
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
3220
|
+
COALESCE(SUM(duration_ms), 0) as total_duration_ms
|
|
3221
|
+
FROM task_traces WHERE task_id = ?`).get(taskId);
|
|
3222
|
+
return row;
|
|
3223
|
+
}
|
|
3224
|
+
var init_traces = __esm(() => {
|
|
3225
|
+
init_database();
|
|
3226
|
+
});
|
|
3227
|
+
|
|
3228
|
+
// src/db/snapshots.ts
|
|
3229
|
+
var exports_snapshots = {};
|
|
3230
|
+
__export(exports_snapshots, {
|
|
3231
|
+
saveSnapshot: () => saveSnapshot,
|
|
3232
|
+
listSnapshots: () => listSnapshots,
|
|
3233
|
+
getLatestSnapshot: () => getLatestSnapshot
|
|
3234
|
+
});
|
|
3235
|
+
function rowToSnapshot(row) {
|
|
3236
|
+
return {
|
|
3237
|
+
...row,
|
|
3238
|
+
snapshot_type: row.snapshot_type,
|
|
3239
|
+
files_open: JSON.parse(row.files_open || "[]"),
|
|
3240
|
+
attempts: JSON.parse(row.attempts || "[]"),
|
|
3241
|
+
blockers: JSON.parse(row.blockers || "[]"),
|
|
3242
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
function saveSnapshot(input, db) {
|
|
3246
|
+
const d = db || getDatabase();
|
|
3247
|
+
const id = uuid();
|
|
3248
|
+
const timestamp = now();
|
|
3249
|
+
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)
|
|
3250
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3251
|
+
id,
|
|
3252
|
+
input.agent_id || null,
|
|
3253
|
+
input.task_id || null,
|
|
3254
|
+
input.project_id || null,
|
|
3255
|
+
input.snapshot_type,
|
|
3256
|
+
input.plan_summary || null,
|
|
3257
|
+
JSON.stringify(input.files_open || []),
|
|
3258
|
+
JSON.stringify(input.attempts || []),
|
|
3259
|
+
JSON.stringify(input.blockers || []),
|
|
3260
|
+
input.next_steps || null,
|
|
3261
|
+
JSON.stringify(input.metadata || {}),
|
|
3262
|
+
timestamp
|
|
3263
|
+
]);
|
|
3264
|
+
return {
|
|
3265
|
+
id,
|
|
3266
|
+
agent_id: input.agent_id || null,
|
|
3267
|
+
task_id: input.task_id || null,
|
|
3268
|
+
project_id: input.project_id || null,
|
|
3269
|
+
snapshot_type: input.snapshot_type,
|
|
3270
|
+
plan_summary: input.plan_summary || null,
|
|
3271
|
+
files_open: input.files_open || [],
|
|
3272
|
+
attempts: input.attempts || [],
|
|
3273
|
+
blockers: input.blockers || [],
|
|
3274
|
+
next_steps: input.next_steps || null,
|
|
3275
|
+
metadata: input.metadata || {},
|
|
3276
|
+
created_at: timestamp
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function getLatestSnapshot(agentId, taskId, db) {
|
|
3280
|
+
const d = db || getDatabase();
|
|
3281
|
+
const conditions = [];
|
|
3282
|
+
const params = [];
|
|
3283
|
+
if (agentId) {
|
|
3284
|
+
conditions.push("agent_id = ?");
|
|
3285
|
+
params.push(agentId);
|
|
3286
|
+
}
|
|
3287
|
+
if (taskId) {
|
|
3288
|
+
conditions.push("task_id = ?");
|
|
3289
|
+
params.push(taskId);
|
|
3290
|
+
}
|
|
3291
|
+
if (conditions.length === 0)
|
|
3292
|
+
return null;
|
|
3293
|
+
const where = conditions.join(" AND ");
|
|
3294
|
+
const row = d.query(`SELECT * FROM context_snapshots WHERE ${where} ORDER BY created_at DESC LIMIT 1`).get(...params);
|
|
3295
|
+
return row ? rowToSnapshot(row) : null;
|
|
3296
|
+
}
|
|
3297
|
+
function listSnapshots(opts, db) {
|
|
3298
|
+
const d = db || getDatabase();
|
|
3299
|
+
const conditions = [];
|
|
3300
|
+
const params = [];
|
|
3301
|
+
if (opts.agent_id) {
|
|
3302
|
+
conditions.push("agent_id = ?");
|
|
3303
|
+
params.push(opts.agent_id);
|
|
3304
|
+
}
|
|
3305
|
+
if (opts.task_id) {
|
|
3306
|
+
conditions.push("task_id = ?");
|
|
3307
|
+
params.push(opts.task_id);
|
|
3308
|
+
}
|
|
3309
|
+
if (opts.project_id) {
|
|
3310
|
+
conditions.push("project_id = ?");
|
|
3311
|
+
params.push(opts.project_id);
|
|
3312
|
+
}
|
|
3313
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3314
|
+
const limit = opts.limit || 20;
|
|
3315
|
+
params.push(limit);
|
|
3316
|
+
return d.query(`SELECT * FROM context_snapshots ${where} ORDER BY created_at DESC LIMIT ?`).all(...params).map(rowToSnapshot);
|
|
3317
|
+
}
|
|
3318
|
+
var init_snapshots = __esm(() => {
|
|
3319
|
+
init_database();
|
|
3320
|
+
});
|
|
3321
|
+
|
|
3322
|
+
// src/db/budgets.ts
|
|
3323
|
+
var exports_budgets = {};
|
|
3324
|
+
__export(exports_budgets, {
|
|
3325
|
+
setBudget: () => setBudget,
|
|
3326
|
+
getBudget: () => getBudget,
|
|
3327
|
+
checkBudget: () => checkBudget
|
|
3328
|
+
});
|
|
3329
|
+
function setBudget(agentId, opts, db) {
|
|
3330
|
+
const d = db || getDatabase();
|
|
3331
|
+
const timestamp = now();
|
|
3332
|
+
d.run(`INSERT INTO agent_budgets (agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours, created_at, updated_at)
|
|
3333
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3334
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
3335
|
+
max_concurrent = COALESCE(?, max_concurrent),
|
|
3336
|
+
max_cost_usd = COALESCE(?, max_cost_usd),
|
|
3337
|
+
max_task_minutes = COALESCE(?, max_task_minutes),
|
|
3338
|
+
period_hours = COALESCE(?, period_hours),
|
|
3339
|
+
updated_at = ?`, [
|
|
3340
|
+
agentId,
|
|
3341
|
+
opts.max_concurrent ?? 5,
|
|
3342
|
+
opts.max_cost_usd ?? null,
|
|
3343
|
+
opts.max_task_minutes ?? null,
|
|
3344
|
+
opts.period_hours ?? 24,
|
|
3345
|
+
timestamp,
|
|
3346
|
+
timestamp,
|
|
3347
|
+
opts.max_concurrent ?? null,
|
|
3348
|
+
opts.max_cost_usd ?? null,
|
|
3349
|
+
opts.max_task_minutes ?? null,
|
|
3350
|
+
opts.period_hours ?? null,
|
|
3351
|
+
timestamp
|
|
3352
|
+
]);
|
|
3353
|
+
return getBudget(agentId, d);
|
|
3354
|
+
}
|
|
3355
|
+
function getBudget(agentId, db) {
|
|
3356
|
+
const d = db || getDatabase();
|
|
3357
|
+
return d.query("SELECT * FROM agent_budgets WHERE agent_id = ?").get(agentId);
|
|
3358
|
+
}
|
|
3359
|
+
function checkBudget(agentId, db) {
|
|
3360
|
+
const d = db || getDatabase();
|
|
3361
|
+
const budget = getBudget(agentId, d);
|
|
3362
|
+
if (!budget)
|
|
3363
|
+
return { allowed: true, current_concurrent: 0, max_concurrent: 999 };
|
|
3364
|
+
const concurrent = countTasks({ status: "in_progress", assigned_to: agentId }, d);
|
|
3365
|
+
if (concurrent >= budget.max_concurrent) {
|
|
3366
|
+
return { allowed: false, reason: `Concurrent limit reached (${concurrent}/${budget.max_concurrent})`, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
3367
|
+
}
|
|
3368
|
+
if (budget.max_cost_usd != null) {
|
|
3369
|
+
const periodStart = new Date(Date.now() - budget.period_hours * 60 * 60 * 1000).toISOString();
|
|
3370
|
+
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);
|
|
3371
|
+
if (costRow.total >= budget.max_cost_usd) {
|
|
3372
|
+
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 };
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
return { allowed: true, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
3376
|
+
}
|
|
3377
|
+
var init_budgets = __esm(() => {
|
|
3378
|
+
init_database();
|
|
3379
|
+
init_tasks();
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3045
3382
|
// src/lib/github.ts
|
|
3046
3383
|
var exports_github = {};
|
|
3047
3384
|
__export(exports_github, {
|
|
@@ -9398,7 +9735,23 @@ function formatError(error) {
|
|
|
9398
9735
|
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
9399
9736
|
}
|
|
9400
9737
|
if (error instanceof Error) {
|
|
9401
|
-
|
|
9738
|
+
const msg = error.message;
|
|
9739
|
+
if (msg.includes("UNIQUE constraint failed: projects.path")) {
|
|
9740
|
+
const db = getDatabase();
|
|
9741
|
+
const existing = db.prepare("SELECT id, name FROM projects WHERE path = ?").get(msg.match(/'([^']+)'$/)?.[1] ?? "");
|
|
9742
|
+
return JSON.stringify({ code: "DUPLICATE_PROJECT", message: `Project already exists at this path${existing ? ` (id: ${existing.id}, name: ${existing.name})` : ""}. Use list_projects to find it.`, suggestion: "Use list_projects or get_project to retrieve the existing project." });
|
|
9743
|
+
}
|
|
9744
|
+
if (msg.includes("UNIQUE constraint failed: projects.name")) {
|
|
9745
|
+
return JSON.stringify({ code: "DUPLICATE_PROJECT", message: "A project with this name already exists. Use a different name or list_projects to find the existing one.", suggestion: "Use list_projects to see existing projects." });
|
|
9746
|
+
}
|
|
9747
|
+
if (msg.includes("UNIQUE constraint failed")) {
|
|
9748
|
+
const table = msg.match(/UNIQUE constraint failed: (\w+)\./)?.[1] ?? "unknown";
|
|
9749
|
+
return JSON.stringify({ code: "DUPLICATE_ENTRY", message: `Duplicate entry in ${table}. The record already exists.`, suggestion: `Use the list or get endpoint for ${table} to find the existing record.` });
|
|
9750
|
+
}
|
|
9751
|
+
if (msg.includes("FOREIGN KEY constraint failed")) {
|
|
9752
|
+
return JSON.stringify({ code: "REFERENCE_ERROR", message: "Referenced record does not exist. Check that the ID is correct.", suggestion: "Verify the referenced ID exists before creating this record." });
|
|
9753
|
+
}
|
|
9754
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: msg });
|
|
9402
9755
|
}
|
|
9403
9756
|
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
9404
9757
|
}
|
|
@@ -10849,6 +11202,135 @@ Blocked:`);
|
|
|
10849
11202
|
}
|
|
10850
11203
|
});
|
|
10851
11204
|
}
|
|
11205
|
+
if (shouldRegisterTool("log_cost")) {
|
|
11206
|
+
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 }) => {
|
|
11207
|
+
try {
|
|
11208
|
+
const { logCost: logCost2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
11209
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
11210
|
+
logCost2(resolvedId, tokens, usd);
|
|
11211
|
+
return { content: [{ type: "text", text: `Logged ${tokens} tokens ($${usd.toFixed(4)}) to ${task_id}` }] };
|
|
11212
|
+
} catch (e) {
|
|
11213
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11214
|
+
}
|
|
11215
|
+
});
|
|
11216
|
+
}
|
|
11217
|
+
if (shouldRegisterTool("log_trace")) {
|
|
11218
|
+
server.tool("log_trace", "Log a trace entry (tool call, LLM call, error, handoff) to a task for observability.", {
|
|
11219
|
+
task_id: exports_external.string(),
|
|
11220
|
+
agent_id: exports_external.string().optional(),
|
|
11221
|
+
trace_type: exports_external.enum(["tool_call", "llm_call", "error", "handoff", "custom"]),
|
|
11222
|
+
name: exports_external.string().optional(),
|
|
11223
|
+
input_summary: exports_external.string().optional(),
|
|
11224
|
+
output_summary: exports_external.string().optional(),
|
|
11225
|
+
duration_ms: exports_external.number().optional(),
|
|
11226
|
+
tokens: exports_external.number().optional(),
|
|
11227
|
+
cost_usd: exports_external.number().optional()
|
|
11228
|
+
}, async ({ task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd }) => {
|
|
11229
|
+
try {
|
|
11230
|
+
const { logTrace: logTrace2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
11231
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
11232
|
+
const trace = logTrace2({ task_id: resolvedId, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd });
|
|
11233
|
+
return { content: [{ type: "text", text: `Trace logged: ${trace.id} [${trace_type}] ${name || ""}` }] };
|
|
11234
|
+
} catch (e) {
|
|
11235
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11236
|
+
}
|
|
11237
|
+
});
|
|
11238
|
+
}
|
|
11239
|
+
if (shouldRegisterTool("get_traces")) {
|
|
11240
|
+
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 }) => {
|
|
11241
|
+
try {
|
|
11242
|
+
const { getTaskTraces: getTaskTraces2, getTraceStats: getTraceStats2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
11243
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
11244
|
+
const traces = getTaskTraces2(resolvedId);
|
|
11245
|
+
const stats = getTraceStats2(resolvedId);
|
|
11246
|
+
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`];
|
|
11247
|
+
for (const t of traces.slice(0, 20)) {
|
|
11248
|
+
lines.push(` ${t.created_at} [${t.trace_type}] ${t.name || ""} ${t.tokens ? t.tokens + "tok" : ""} ${t.duration_ms ? t.duration_ms + "ms" : ""}`);
|
|
11249
|
+
}
|
|
11250
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
11251
|
+
`) }] };
|
|
11252
|
+
} catch (e) {
|
|
11253
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11254
|
+
}
|
|
11255
|
+
});
|
|
11256
|
+
}
|
|
11257
|
+
if (shouldRegisterTool("save_snapshot")) {
|
|
11258
|
+
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.", {
|
|
11259
|
+
agent_id: exports_external.string().optional(),
|
|
11260
|
+
task_id: exports_external.string().optional(),
|
|
11261
|
+
project_id: exports_external.string().optional(),
|
|
11262
|
+
snapshot_type: exports_external.enum(["interrupt", "complete", "handoff", "checkpoint"]),
|
|
11263
|
+
plan_summary: exports_external.string().optional(),
|
|
11264
|
+
files_open: exports_external.array(exports_external.string()).optional(),
|
|
11265
|
+
attempts: exports_external.array(exports_external.string()).optional(),
|
|
11266
|
+
blockers: exports_external.array(exports_external.string()).optional(),
|
|
11267
|
+
next_steps: exports_external.string().optional()
|
|
11268
|
+
}, async ({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps }) => {
|
|
11269
|
+
try {
|
|
11270
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
11271
|
+
const snap = saveSnapshot2({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps });
|
|
11272
|
+
return { content: [{ type: "text", text: `Snapshot saved: ${snap.id} [${snapshot_type}]${plan_summary ? " \u2014 " + plan_summary.slice(0, 80) : ""}` }] };
|
|
11273
|
+
} catch (e) {
|
|
11274
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11275
|
+
}
|
|
11276
|
+
});
|
|
11277
|
+
}
|
|
11278
|
+
if (shouldRegisterTool("get_snapshot")) {
|
|
11279
|
+
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 }) => {
|
|
11280
|
+
try {
|
|
11281
|
+
const { getLatestSnapshot: getLatestSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
11282
|
+
const snap = getLatestSnapshot2(agent_id, task_id);
|
|
11283
|
+
if (!snap)
|
|
11284
|
+
return { content: [{ type: "text", text: "No snapshot found." }] };
|
|
11285
|
+
const lines = [`Snapshot [${snap.snapshot_type}] from ${snap.created_at}`];
|
|
11286
|
+
if (snap.plan_summary)
|
|
11287
|
+
lines.push(`Plan: ${snap.plan_summary}`);
|
|
11288
|
+
if (snap.files_open.length > 0)
|
|
11289
|
+
lines.push(`Files: ${snap.files_open.join(", ")}`);
|
|
11290
|
+
if (snap.blockers.length > 0)
|
|
11291
|
+
lines.push(`Blockers: ${snap.blockers.join(", ")}`);
|
|
11292
|
+
if (snap.next_steps)
|
|
11293
|
+
lines.push(`Next: ${snap.next_steps}`);
|
|
11294
|
+
if (snap.attempts.length > 0)
|
|
11295
|
+
lines.push(`Attempts: ${snap.attempts.join("; ")}`);
|
|
11296
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
11297
|
+
`) }] };
|
|
11298
|
+
} catch (e) {
|
|
11299
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11300
|
+
}
|
|
11301
|
+
});
|
|
11302
|
+
}
|
|
11303
|
+
if (shouldRegisterTool("set_budget")) {
|
|
11304
|
+
server.tool("set_budget", "Set execution budget for an agent \u2014 max concurrent tasks, cost limit, time limit per period.", {
|
|
11305
|
+
agent_id: exports_external.string(),
|
|
11306
|
+
max_concurrent: exports_external.number().optional(),
|
|
11307
|
+
max_cost_usd: exports_external.number().optional(),
|
|
11308
|
+
max_task_minutes: exports_external.number().optional(),
|
|
11309
|
+
period_hours: exports_external.number().optional()
|
|
11310
|
+
}, async ({ agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours }) => {
|
|
11311
|
+
try {
|
|
11312
|
+
const { setBudget: setBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
11313
|
+
const budget = setBudget2(agent_id, { max_concurrent, max_cost_usd, max_task_minutes, period_hours });
|
|
11314
|
+
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` }] };
|
|
11315
|
+
} catch (e) {
|
|
11316
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11317
|
+
}
|
|
11318
|
+
});
|
|
11319
|
+
}
|
|
11320
|
+
if (shouldRegisterTool("check_budget")) {
|
|
11321
|
+
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 }) => {
|
|
11322
|
+
try {
|
|
11323
|
+
const { checkBudget: checkBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
11324
|
+
const result = checkBudget2(agent_id);
|
|
11325
|
+
if (result.allowed) {
|
|
11326
|
+
return { content: [{ type: "text", text: `Budget OK: ${result.current_concurrent}/${result.max_concurrent} concurrent tasks` }] };
|
|
11327
|
+
}
|
|
11328
|
+
return { content: [{ type: "text", text: `BUDGET EXCEEDED: ${result.reason}` }], isError: true };
|
|
11329
|
+
} catch (e) {
|
|
11330
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11331
|
+
}
|
|
11332
|
+
});
|
|
11333
|
+
}
|
|
10852
11334
|
if (shouldRegisterTool("import_github_issue")) {
|
|
10853
11335
|
server.tool("import_github_issue", "Import a GitHub issue as a task. Requires gh CLI installed and authenticated.", {
|
|
10854
11336
|
url: exports_external.string().describe("GitHub issue URL (e.g. https://github.com/owner/repo/issues/42)"),
|
|
@@ -11559,6 +12041,56 @@ ${formatTaskDetail(task, 300)}` }] };
|
|
|
11559
12041
|
}
|
|
11560
12042
|
});
|
|
11561
12043
|
}
|
|
12044
|
+
if (shouldRegisterTool("steal_task")) {
|
|
12045
|
+
server.tool("steal_task", "Work-stealing: take the highest-priority stale in_progress task from another agent and reassign it to you.", {
|
|
12046
|
+
agent_id: exports_external.string().describe("Your agent ID"),
|
|
12047
|
+
stale_minutes: exports_external.number().optional().describe("How long a task must be stale before stealing (default: 30)"),
|
|
12048
|
+
project_id: exports_external.string().optional(),
|
|
12049
|
+
task_list_id: exports_external.string().optional()
|
|
12050
|
+
}, async ({ agent_id, stale_minutes, project_id, task_list_id }) => {
|
|
12051
|
+
try {
|
|
12052
|
+
const task = stealTask(agent_id, { stale_minutes, project_id, task_list_id });
|
|
12053
|
+
if (!task)
|
|
12054
|
+
return { content: [{ type: "text", text: "No stale tasks available to steal." }] };
|
|
12055
|
+
return { content: [{ type: "text", text: `Stolen: ${formatTask(task)}
|
|
12056
|
+
Previous owner: ${task.metadata?._stolen_from || "unknown"}` }] };
|
|
12057
|
+
} catch (e) {
|
|
12058
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12059
|
+
}
|
|
12060
|
+
});
|
|
12061
|
+
}
|
|
12062
|
+
if (shouldRegisterTool("claim_or_steal")) {
|
|
12063
|
+
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.", {
|
|
12064
|
+
agent_id: exports_external.string(),
|
|
12065
|
+
project_id: exports_external.string().optional(),
|
|
12066
|
+
task_list_id: exports_external.string().optional(),
|
|
12067
|
+
plan_id: exports_external.string().optional(),
|
|
12068
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
12069
|
+
stale_minutes: exports_external.number().optional().describe("Stale threshold for work-stealing fallback (default: 30)")
|
|
12070
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags, stale_minutes }) => {
|
|
12071
|
+
try {
|
|
12072
|
+
const filters = {};
|
|
12073
|
+
if (project_id)
|
|
12074
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
12075
|
+
if (task_list_id)
|
|
12076
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
12077
|
+
if (plan_id)
|
|
12078
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
12079
|
+
if (tags)
|
|
12080
|
+
filters.tags = tags;
|
|
12081
|
+
if (stale_minutes)
|
|
12082
|
+
filters.stale_minutes = stale_minutes;
|
|
12083
|
+
const result = claimOrSteal(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
12084
|
+
if (!result)
|
|
12085
|
+
return { content: [{ type: "text", text: "No tasks available to claim or steal." }] };
|
|
12086
|
+
const prefix = result.stolen ? "Stolen" : "Claimed";
|
|
12087
|
+
return { content: [{ type: "text", text: `${prefix}: ${formatTask(result.task)}
|
|
12088
|
+
${formatTaskDetail(result.task, 300)}` }] };
|
|
12089
|
+
} catch (e) {
|
|
12090
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12091
|
+
}
|
|
12092
|
+
});
|
|
12093
|
+
}
|
|
11562
12094
|
if (shouldRegisterTool("get_stale_tasks")) {
|
|
11563
12095
|
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
11564
12096
|
stale_minutes: exports_external.number().optional(),
|