@hasna/todos 0.10.22 → 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 +556 -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 +541 -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/cli/index.js
CHANGED
|
@@ -6,60 +6,39 @@ var __defProp = Object.defineProperty;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
function __accessProp(key) {
|
|
10
|
-
return this[key];
|
|
11
|
-
}
|
|
12
|
-
var __toESMCache_node;
|
|
13
|
-
var __toESMCache_esm;
|
|
14
9
|
var __toESM = (mod, isNodeMode, target) => {
|
|
15
|
-
var canCache = mod != null && typeof mod === "object";
|
|
16
|
-
if (canCache) {
|
|
17
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
18
|
-
var cached = cache.get(mod);
|
|
19
|
-
if (cached)
|
|
20
|
-
return cached;
|
|
21
|
-
}
|
|
22
10
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
23
11
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
24
12
|
for (let key of __getOwnPropNames(mod))
|
|
25
13
|
if (!__hasOwnProp.call(to, key))
|
|
26
14
|
__defProp(to, key, {
|
|
27
|
-
get:
|
|
15
|
+
get: () => mod[key],
|
|
28
16
|
enumerable: true
|
|
29
17
|
});
|
|
30
|
-
if (canCache)
|
|
31
|
-
cache.set(mod, to);
|
|
32
18
|
return to;
|
|
33
19
|
};
|
|
20
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
34
21
|
var __toCommonJS = (from) => {
|
|
35
|
-
var entry =
|
|
22
|
+
var entry = __moduleCache.get(from), desc;
|
|
36
23
|
if (entry)
|
|
37
24
|
return entry;
|
|
38
25
|
entry = __defProp({}, "__esModule", { value: true });
|
|
39
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
-
});
|
|
46
|
-
}
|
|
26
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
27
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
28
|
+
get: () => from[key],
|
|
29
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
30
|
+
}));
|
|
47
31
|
__moduleCache.set(from, entry);
|
|
48
32
|
return entry;
|
|
49
33
|
};
|
|
50
|
-
var __moduleCache;
|
|
51
34
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
52
|
-
var __returnValue = (v) => v;
|
|
53
|
-
function __exportSetter(name, newValue) {
|
|
54
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
55
|
-
}
|
|
56
35
|
var __export = (target, all) => {
|
|
57
36
|
for (var name in all)
|
|
58
37
|
__defProp(target, name, {
|
|
59
38
|
get: all[name],
|
|
60
39
|
enumerable: true,
|
|
61
40
|
configurable: true,
|
|
62
|
-
set:
|
|
41
|
+
set: (newValue) => all[name] = () => newValue
|
|
63
42
|
});
|
|
64
43
|
};
|
|
65
44
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -2347,6 +2326,15 @@ function ensureSchema(db) {
|
|
|
2347
2326
|
ensureColumn("tasks", "assigned_by", "TEXT");
|
|
2348
2327
|
ensureColumn("tasks", "assigned_from_project", "TEXT");
|
|
2349
2328
|
ensureColumn("tasks", "started_at", "TEXT");
|
|
2329
|
+
ensureColumn("tasks", "task_type", "TEXT");
|
|
2330
|
+
ensureColumn("tasks", "cost_tokens", "INTEGER DEFAULT 0");
|
|
2331
|
+
ensureColumn("tasks", "cost_usd", "REAL DEFAULT 0");
|
|
2332
|
+
ensureColumn("tasks", "delegated_from", "TEXT");
|
|
2333
|
+
ensureColumn("tasks", "delegation_depth", "INTEGER DEFAULT 0");
|
|
2334
|
+
ensureColumn("tasks", "retry_count", "INTEGER DEFAULT 0");
|
|
2335
|
+
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
2336
|
+
ensureColumn("tasks", "retry_after", "TEXT");
|
|
2337
|
+
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
2350
2338
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
2351
2339
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
2352
2340
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -2960,6 +2948,58 @@ var init_database = __esm(() => {
|
|
|
2960
2948
|
`
|
|
2961
2949
|
ALTER TABLE tasks ADD COLUMN task_type TEXT;
|
|
2962
2950
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type);
|
|
2951
|
+
ALTER TABLE tasks ADD COLUMN cost_tokens INTEGER DEFAULT 0;
|
|
2952
|
+
ALTER TABLE tasks ADD COLUMN cost_usd REAL DEFAULT 0;
|
|
2953
|
+
ALTER TABLE tasks ADD COLUMN delegated_from TEXT;
|
|
2954
|
+
ALTER TABLE tasks ADD COLUMN delegation_depth INTEGER DEFAULT 0;
|
|
2955
|
+
ALTER TABLE tasks ADD COLUMN retry_count INTEGER DEFAULT 0;
|
|
2956
|
+
ALTER TABLE tasks ADD COLUMN max_retries INTEGER DEFAULT 3;
|
|
2957
|
+
ALTER TABLE tasks ADD COLUMN retry_after TEXT;
|
|
2958
|
+
ALTER TABLE tasks ADD COLUMN sla_minutes INTEGER;
|
|
2959
|
+
|
|
2960
|
+
CREATE TABLE IF NOT EXISTS task_traces (
|
|
2961
|
+
id TEXT PRIMARY KEY,
|
|
2962
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
2963
|
+
agent_id TEXT,
|
|
2964
|
+
trace_type TEXT NOT NULL CHECK(trace_type IN ('tool_call','llm_call','error','handoff','custom')),
|
|
2965
|
+
name TEXT,
|
|
2966
|
+
input_summary TEXT,
|
|
2967
|
+
output_summary TEXT,
|
|
2968
|
+
duration_ms INTEGER,
|
|
2969
|
+
tokens INTEGER,
|
|
2970
|
+
cost_usd REAL,
|
|
2971
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2972
|
+
);
|
|
2973
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_task ON task_traces(task_id);
|
|
2974
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_agent ON task_traces(agent_id);
|
|
2975
|
+
|
|
2976
|
+
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
2977
|
+
id TEXT PRIMARY KEY,
|
|
2978
|
+
agent_id TEXT,
|
|
2979
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
2980
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
2981
|
+
snapshot_type TEXT NOT NULL CHECK(snapshot_type IN ('interrupt','complete','handoff','checkpoint')),
|
|
2982
|
+
plan_summary TEXT,
|
|
2983
|
+
files_open TEXT DEFAULT '[]',
|
|
2984
|
+
attempts TEXT DEFAULT '[]',
|
|
2985
|
+
blockers TEXT DEFAULT '[]',
|
|
2986
|
+
next_steps TEXT,
|
|
2987
|
+
metadata TEXT DEFAULT '{}',
|
|
2988
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2989
|
+
);
|
|
2990
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_agent ON context_snapshots(agent_id);
|
|
2991
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_task ON context_snapshots(task_id);
|
|
2992
|
+
|
|
2993
|
+
CREATE TABLE IF NOT EXISTS agent_budgets (
|
|
2994
|
+
agent_id TEXT PRIMARY KEY,
|
|
2995
|
+
max_concurrent INTEGER DEFAULT 5,
|
|
2996
|
+
max_cost_usd REAL,
|
|
2997
|
+
max_task_minutes INTEGER,
|
|
2998
|
+
period_hours INTEGER DEFAULT 24,
|
|
2999
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3000
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3001
|
+
);
|
|
3002
|
+
|
|
2963
3003
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
2964
3004
|
`
|
|
2965
3005
|
];
|
|
@@ -3755,12 +3795,14 @@ var exports_tasks = {};
|
|
|
3755
3795
|
__export(exports_tasks, {
|
|
3756
3796
|
updateTask: () => updateTask,
|
|
3757
3797
|
unlockTask: () => unlockTask,
|
|
3798
|
+
stealTask: () => stealTask,
|
|
3758
3799
|
startTask: () => startTask,
|
|
3759
3800
|
setTaskStatus: () => setTaskStatus,
|
|
3760
3801
|
setTaskPriority: () => setTaskPriority,
|
|
3761
3802
|
removeDependency: () => removeDependency,
|
|
3762
3803
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
3763
3804
|
moveTask: () => moveTask,
|
|
3805
|
+
logCost: () => logCost,
|
|
3764
3806
|
lockTask: () => lockTask,
|
|
3765
3807
|
listTasks: () => listTasks,
|
|
3766
3808
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
@@ -3783,6 +3825,7 @@ __export(exports_tasks, {
|
|
|
3783
3825
|
countTasks: () => countTasks,
|
|
3784
3826
|
completeTask: () => completeTask,
|
|
3785
3827
|
cloneTask: () => cloneTask,
|
|
3828
|
+
claimOrSteal: () => claimOrSteal,
|
|
3786
3829
|
claimNextTask: () => claimNextTask,
|
|
3787
3830
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
3788
3831
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
@@ -4264,6 +4307,20 @@ function completeTask(id, agentId, db, options) {
|
|
|
4264
4307
|
if (spawnedFromTemplate) {
|
|
4265
4308
|
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
4266
4309
|
}
|
|
4310
|
+
const unblockedDeps = d.query(`SELECT DISTINCT t.id, t.short_id, t.title FROM tasks t
|
|
4311
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
4312
|
+
WHERE td.depends_on = ? AND t.status = 'pending'
|
|
4313
|
+
AND NOT EXISTS (
|
|
4314
|
+
SELECT 1 FROM task_dependencies td2
|
|
4315
|
+
JOIN tasks dep2 ON dep2.id = td2.depends_on
|
|
4316
|
+
WHERE td2.task_id = t.id AND dep2.status NOT IN ('completed', 'cancelled') AND dep2.id != ?
|
|
4317
|
+
)`).all(id, id);
|
|
4318
|
+
if (unblockedDeps.length > 0) {
|
|
4319
|
+
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
4320
|
+
for (const dep of unblockedDeps) {
|
|
4321
|
+
dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4267
4324
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
4268
4325
|
}
|
|
4269
4326
|
function lockTask(id, agentId, db) {
|
|
@@ -4560,24 +4617,36 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
4560
4617
|
};
|
|
4561
4618
|
let retryTask;
|
|
4562
4619
|
if (options?.retry) {
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4620
|
+
const retryCount = (task.retry_count || 0) + 1;
|
|
4621
|
+
const maxRetries = task.max_retries || 3;
|
|
4622
|
+
if (retryCount > maxRetries) {
|
|
4623
|
+
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
4624
|
+
JSON.stringify({ ...meta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }),
|
|
4625
|
+
id
|
|
4626
|
+
]);
|
|
4627
|
+
} else {
|
|
4628
|
+
const backoffMinutes = Math.pow(5, retryCount - 1);
|
|
4629
|
+
const retryAfter = options.retry_after || new Date(Date.now() + backoffMinutes * 60 * 1000).toISOString();
|
|
4630
|
+
let title = task.title;
|
|
4631
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
4632
|
+
title = title.slice(task.short_id.length + 2);
|
|
4633
|
+
}
|
|
4634
|
+
retryTask = createTask({
|
|
4635
|
+
title,
|
|
4636
|
+
description: task.description ?? undefined,
|
|
4637
|
+
priority: task.priority,
|
|
4638
|
+
project_id: task.project_id ?? undefined,
|
|
4639
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
4640
|
+
plan_id: task.plan_id ?? undefined,
|
|
4641
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
4642
|
+
tags: task.tags,
|
|
4643
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: reason } },
|
|
4644
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
4645
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
4646
|
+
due_at: retryAfter
|
|
4647
|
+
}, d);
|
|
4648
|
+
d.run("UPDATE tasks SET retry_count = ?, max_retries = ?, retry_after = ? WHERE id = ?", [retryCount, maxRetries, retryAfter, retryTask.id]);
|
|
4566
4649
|
}
|
|
4567
|
-
retryTask = createTask({
|
|
4568
|
-
title,
|
|
4569
|
-
description: task.description ?? undefined,
|
|
4570
|
-
priority: task.priority,
|
|
4571
|
-
project_id: task.project_id ?? undefined,
|
|
4572
|
-
task_list_id: task.task_list_id ?? undefined,
|
|
4573
|
-
plan_id: task.plan_id ?? undefined,
|
|
4574
|
-
assigned_to: task.assigned_to ?? undefined,
|
|
4575
|
-
tags: task.tags,
|
|
4576
|
-
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
4577
|
-
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
4578
|
-
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
4579
|
-
due_at: options.retry_after || task.due_at || undefined
|
|
4580
|
-
}, d);
|
|
4581
4650
|
}
|
|
4582
4651
|
return { task: failedTask, retryTask };
|
|
4583
4652
|
}
|
|
@@ -4601,6 +4670,40 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
4601
4670
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
4602
4671
|
return rows.map(rowToTask);
|
|
4603
4672
|
}
|
|
4673
|
+
function logCost(taskId, tokens, usd, db) {
|
|
4674
|
+
const d = db || getDatabase();
|
|
4675
|
+
d.run("UPDATE tasks SET cost_tokens = cost_tokens + ?, cost_usd = cost_usd + ?, updated_at = ? WHERE id = ?", [tokens, usd, now(), taskId]);
|
|
4676
|
+
}
|
|
4677
|
+
function stealTask(agentId, opts, db) {
|
|
4678
|
+
const d = db || getDatabase();
|
|
4679
|
+
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
4680
|
+
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
4681
|
+
if (staleTasks.length === 0)
|
|
4682
|
+
return null;
|
|
4683
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
4684
|
+
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
4685
|
+
const target = staleTasks[0];
|
|
4686
|
+
const timestamp = now();
|
|
4687
|
+
d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ?`, [agentId, agentId, timestamp, timestamp, target.id]);
|
|
4688
|
+
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
4689
|
+
dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
|
|
4690
|
+
return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
4691
|
+
}
|
|
4692
|
+
function claimOrSteal(agentId, filters, db) {
|
|
4693
|
+
const d = db || getDatabase();
|
|
4694
|
+
const tx = d.transaction(() => {
|
|
4695
|
+
const next = getNextTask(agentId, filters, d);
|
|
4696
|
+
if (next) {
|
|
4697
|
+
const started = startTask(next.id, agentId, d);
|
|
4698
|
+
return { task: started, stolen: false };
|
|
4699
|
+
}
|
|
4700
|
+
const stolen = stealTask(agentId, { stale_minutes: filters?.stale_minutes, project_id: filters?.project_id, task_list_id: filters?.task_list_id }, d);
|
|
4701
|
+
if (stolen)
|
|
4702
|
+
return { task: stolen, stolen: true };
|
|
4703
|
+
return null;
|
|
4704
|
+
});
|
|
4705
|
+
return tx();
|
|
4706
|
+
}
|
|
4604
4707
|
function getStatus(filters, agentId, options, db) {
|
|
4605
4708
|
const d = db || getDatabase();
|
|
4606
4709
|
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
@@ -10363,6 +10466,219 @@ var init_zod = __esm(() => {
|
|
|
10363
10466
|
init_external();
|
|
10364
10467
|
});
|
|
10365
10468
|
|
|
10469
|
+
// src/db/traces.ts
|
|
10470
|
+
var exports_traces = {};
|
|
10471
|
+
__export(exports_traces, {
|
|
10472
|
+
logTrace: () => logTrace,
|
|
10473
|
+
getTraceStats: () => getTraceStats,
|
|
10474
|
+
getTaskTraces: () => getTaskTraces
|
|
10475
|
+
});
|
|
10476
|
+
function logTrace(input, db) {
|
|
10477
|
+
const d = db || getDatabase();
|
|
10478
|
+
const id = uuid();
|
|
10479
|
+
const timestamp = now();
|
|
10480
|
+
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)
|
|
10481
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10482
|
+
id,
|
|
10483
|
+
input.task_id,
|
|
10484
|
+
input.agent_id || null,
|
|
10485
|
+
input.trace_type,
|
|
10486
|
+
input.name || null,
|
|
10487
|
+
input.input_summary || null,
|
|
10488
|
+
input.output_summary || null,
|
|
10489
|
+
input.duration_ms ?? null,
|
|
10490
|
+
input.tokens ?? null,
|
|
10491
|
+
input.cost_usd ?? null,
|
|
10492
|
+
timestamp
|
|
10493
|
+
]);
|
|
10494
|
+
return {
|
|
10495
|
+
id,
|
|
10496
|
+
task_id: input.task_id,
|
|
10497
|
+
agent_id: input.agent_id || null,
|
|
10498
|
+
trace_type: input.trace_type,
|
|
10499
|
+
name: input.name || null,
|
|
10500
|
+
input_summary: input.input_summary || null,
|
|
10501
|
+
output_summary: input.output_summary || null,
|
|
10502
|
+
duration_ms: input.duration_ms ?? null,
|
|
10503
|
+
tokens: input.tokens ?? null,
|
|
10504
|
+
cost_usd: input.cost_usd ?? null,
|
|
10505
|
+
created_at: timestamp
|
|
10506
|
+
};
|
|
10507
|
+
}
|
|
10508
|
+
function getTaskTraces(taskId, db) {
|
|
10509
|
+
const d = db || getDatabase();
|
|
10510
|
+
return d.query("SELECT * FROM task_traces WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
|
|
10511
|
+
}
|
|
10512
|
+
function getTraceStats(taskId, db) {
|
|
10513
|
+
const d = db || getDatabase();
|
|
10514
|
+
const row = d.query(`SELECT COUNT(*) as total,
|
|
10515
|
+
SUM(CASE WHEN trace_type = 'tool_call' THEN 1 ELSE 0 END) as tool_calls,
|
|
10516
|
+
SUM(CASE WHEN trace_type = 'llm_call' THEN 1 ELSE 0 END) as llm_calls,
|
|
10517
|
+
SUM(CASE WHEN trace_type = 'error' THEN 1 ELSE 0 END) as errors,
|
|
10518
|
+
COALESCE(SUM(tokens), 0) as total_tokens,
|
|
10519
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
10520
|
+
COALESCE(SUM(duration_ms), 0) as total_duration_ms
|
|
10521
|
+
FROM task_traces WHERE task_id = ?`).get(taskId);
|
|
10522
|
+
return row;
|
|
10523
|
+
}
|
|
10524
|
+
var init_traces = __esm(() => {
|
|
10525
|
+
init_database();
|
|
10526
|
+
});
|
|
10527
|
+
|
|
10528
|
+
// src/db/snapshots.ts
|
|
10529
|
+
var exports_snapshots = {};
|
|
10530
|
+
__export(exports_snapshots, {
|
|
10531
|
+
saveSnapshot: () => saveSnapshot,
|
|
10532
|
+
listSnapshots: () => listSnapshots,
|
|
10533
|
+
getLatestSnapshot: () => getLatestSnapshot
|
|
10534
|
+
});
|
|
10535
|
+
function rowToSnapshot(row) {
|
|
10536
|
+
return {
|
|
10537
|
+
...row,
|
|
10538
|
+
snapshot_type: row.snapshot_type,
|
|
10539
|
+
files_open: JSON.parse(row.files_open || "[]"),
|
|
10540
|
+
attempts: JSON.parse(row.attempts || "[]"),
|
|
10541
|
+
blockers: JSON.parse(row.blockers || "[]"),
|
|
10542
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
10543
|
+
};
|
|
10544
|
+
}
|
|
10545
|
+
function saveSnapshot(input, db) {
|
|
10546
|
+
const d = db || getDatabase();
|
|
10547
|
+
const id = uuid();
|
|
10548
|
+
const timestamp = now();
|
|
10549
|
+
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)
|
|
10550
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10551
|
+
id,
|
|
10552
|
+
input.agent_id || null,
|
|
10553
|
+
input.task_id || null,
|
|
10554
|
+
input.project_id || null,
|
|
10555
|
+
input.snapshot_type,
|
|
10556
|
+
input.plan_summary || null,
|
|
10557
|
+
JSON.stringify(input.files_open || []),
|
|
10558
|
+
JSON.stringify(input.attempts || []),
|
|
10559
|
+
JSON.stringify(input.blockers || []),
|
|
10560
|
+
input.next_steps || null,
|
|
10561
|
+
JSON.stringify(input.metadata || {}),
|
|
10562
|
+
timestamp
|
|
10563
|
+
]);
|
|
10564
|
+
return {
|
|
10565
|
+
id,
|
|
10566
|
+
agent_id: input.agent_id || null,
|
|
10567
|
+
task_id: input.task_id || null,
|
|
10568
|
+
project_id: input.project_id || null,
|
|
10569
|
+
snapshot_type: input.snapshot_type,
|
|
10570
|
+
plan_summary: input.plan_summary || null,
|
|
10571
|
+
files_open: input.files_open || [],
|
|
10572
|
+
attempts: input.attempts || [],
|
|
10573
|
+
blockers: input.blockers || [],
|
|
10574
|
+
next_steps: input.next_steps || null,
|
|
10575
|
+
metadata: input.metadata || {},
|
|
10576
|
+
created_at: timestamp
|
|
10577
|
+
};
|
|
10578
|
+
}
|
|
10579
|
+
function getLatestSnapshot(agentId, taskId, db) {
|
|
10580
|
+
const d = db || getDatabase();
|
|
10581
|
+
const conditions = [];
|
|
10582
|
+
const params = [];
|
|
10583
|
+
if (agentId) {
|
|
10584
|
+
conditions.push("agent_id = ?");
|
|
10585
|
+
params.push(agentId);
|
|
10586
|
+
}
|
|
10587
|
+
if (taskId) {
|
|
10588
|
+
conditions.push("task_id = ?");
|
|
10589
|
+
params.push(taskId);
|
|
10590
|
+
}
|
|
10591
|
+
if (conditions.length === 0)
|
|
10592
|
+
return null;
|
|
10593
|
+
const where = conditions.join(" AND ");
|
|
10594
|
+
const row = d.query(`SELECT * FROM context_snapshots WHERE ${where} ORDER BY created_at DESC LIMIT 1`).get(...params);
|
|
10595
|
+
return row ? rowToSnapshot(row) : null;
|
|
10596
|
+
}
|
|
10597
|
+
function listSnapshots(opts, db) {
|
|
10598
|
+
const d = db || getDatabase();
|
|
10599
|
+
const conditions = [];
|
|
10600
|
+
const params = [];
|
|
10601
|
+
if (opts.agent_id) {
|
|
10602
|
+
conditions.push("agent_id = ?");
|
|
10603
|
+
params.push(opts.agent_id);
|
|
10604
|
+
}
|
|
10605
|
+
if (opts.task_id) {
|
|
10606
|
+
conditions.push("task_id = ?");
|
|
10607
|
+
params.push(opts.task_id);
|
|
10608
|
+
}
|
|
10609
|
+
if (opts.project_id) {
|
|
10610
|
+
conditions.push("project_id = ?");
|
|
10611
|
+
params.push(opts.project_id);
|
|
10612
|
+
}
|
|
10613
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
10614
|
+
const limit = opts.limit || 20;
|
|
10615
|
+
params.push(limit);
|
|
10616
|
+
return d.query(`SELECT * FROM context_snapshots ${where} ORDER BY created_at DESC LIMIT ?`).all(...params).map(rowToSnapshot);
|
|
10617
|
+
}
|
|
10618
|
+
var init_snapshots = __esm(() => {
|
|
10619
|
+
init_database();
|
|
10620
|
+
});
|
|
10621
|
+
|
|
10622
|
+
// src/db/budgets.ts
|
|
10623
|
+
var exports_budgets = {};
|
|
10624
|
+
__export(exports_budgets, {
|
|
10625
|
+
setBudget: () => setBudget,
|
|
10626
|
+
getBudget: () => getBudget,
|
|
10627
|
+
checkBudget: () => checkBudget
|
|
10628
|
+
});
|
|
10629
|
+
function setBudget(agentId, opts, db) {
|
|
10630
|
+
const d = db || getDatabase();
|
|
10631
|
+
const timestamp = now();
|
|
10632
|
+
d.run(`INSERT INTO agent_budgets (agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours, created_at, updated_at)
|
|
10633
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
10634
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
10635
|
+
max_concurrent = COALESCE(?, max_concurrent),
|
|
10636
|
+
max_cost_usd = COALESCE(?, max_cost_usd),
|
|
10637
|
+
max_task_minutes = COALESCE(?, max_task_minutes),
|
|
10638
|
+
period_hours = COALESCE(?, period_hours),
|
|
10639
|
+
updated_at = ?`, [
|
|
10640
|
+
agentId,
|
|
10641
|
+
opts.max_concurrent ?? 5,
|
|
10642
|
+
opts.max_cost_usd ?? null,
|
|
10643
|
+
opts.max_task_minutes ?? null,
|
|
10644
|
+
opts.period_hours ?? 24,
|
|
10645
|
+
timestamp,
|
|
10646
|
+
timestamp,
|
|
10647
|
+
opts.max_concurrent ?? null,
|
|
10648
|
+
opts.max_cost_usd ?? null,
|
|
10649
|
+
opts.max_task_minutes ?? null,
|
|
10650
|
+
opts.period_hours ?? null,
|
|
10651
|
+
timestamp
|
|
10652
|
+
]);
|
|
10653
|
+
return getBudget(agentId, d);
|
|
10654
|
+
}
|
|
10655
|
+
function getBudget(agentId, db) {
|
|
10656
|
+
const d = db || getDatabase();
|
|
10657
|
+
return d.query("SELECT * FROM agent_budgets WHERE agent_id = ?").get(agentId);
|
|
10658
|
+
}
|
|
10659
|
+
function checkBudget(agentId, db) {
|
|
10660
|
+
const d = db || getDatabase();
|
|
10661
|
+
const budget = getBudget(agentId, d);
|
|
10662
|
+
if (!budget)
|
|
10663
|
+
return { allowed: true, current_concurrent: 0, max_concurrent: 999 };
|
|
10664
|
+
const concurrent = countTasks({ status: "in_progress", assigned_to: agentId }, d);
|
|
10665
|
+
if (concurrent >= budget.max_concurrent) {
|
|
10666
|
+
return { allowed: false, reason: `Concurrent limit reached (${concurrent}/${budget.max_concurrent})`, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
10667
|
+
}
|
|
10668
|
+
if (budget.max_cost_usd != null) {
|
|
10669
|
+
const periodStart = new Date(Date.now() - budget.period_hours * 60 * 60 * 1000).toISOString();
|
|
10670
|
+
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);
|
|
10671
|
+
if (costRow.total >= budget.max_cost_usd) {
|
|
10672
|
+
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 };
|
|
10673
|
+
}
|
|
10674
|
+
}
|
|
10675
|
+
return { allowed: true, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
10676
|
+
}
|
|
10677
|
+
var init_budgets = __esm(() => {
|
|
10678
|
+
init_database();
|
|
10679
|
+
init_tasks();
|
|
10680
|
+
});
|
|
10681
|
+
|
|
10366
10682
|
// src/lib/github.ts
|
|
10367
10683
|
var exports_github = {};
|
|
10368
10684
|
__export(exports_github, {
|
|
@@ -13037,6 +13353,135 @@ Blocked:`);
|
|
|
13037
13353
|
}
|
|
13038
13354
|
});
|
|
13039
13355
|
}
|
|
13356
|
+
if (shouldRegisterTool("log_cost")) {
|
|
13357
|
+
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 }) => {
|
|
13358
|
+
try {
|
|
13359
|
+
const { logCost: logCost2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
13360
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
13361
|
+
logCost2(resolvedId, tokens, usd);
|
|
13362
|
+
return { content: [{ type: "text", text: `Logged ${tokens} tokens ($${usd.toFixed(4)}) to ${task_id}` }] };
|
|
13363
|
+
} catch (e) {
|
|
13364
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13365
|
+
}
|
|
13366
|
+
});
|
|
13367
|
+
}
|
|
13368
|
+
if (shouldRegisterTool("log_trace")) {
|
|
13369
|
+
server.tool("log_trace", "Log a trace entry (tool call, LLM call, error, handoff) to a task for observability.", {
|
|
13370
|
+
task_id: exports_external.string(),
|
|
13371
|
+
agent_id: exports_external.string().optional(),
|
|
13372
|
+
trace_type: exports_external.enum(["tool_call", "llm_call", "error", "handoff", "custom"]),
|
|
13373
|
+
name: exports_external.string().optional(),
|
|
13374
|
+
input_summary: exports_external.string().optional(),
|
|
13375
|
+
output_summary: exports_external.string().optional(),
|
|
13376
|
+
duration_ms: exports_external.number().optional(),
|
|
13377
|
+
tokens: exports_external.number().optional(),
|
|
13378
|
+
cost_usd: exports_external.number().optional()
|
|
13379
|
+
}, async ({ task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd }) => {
|
|
13380
|
+
try {
|
|
13381
|
+
const { logTrace: logTrace2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
13382
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
13383
|
+
const trace = logTrace2({ task_id: resolvedId, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd });
|
|
13384
|
+
return { content: [{ type: "text", text: `Trace logged: ${trace.id} [${trace_type}] ${name || ""}` }] };
|
|
13385
|
+
} catch (e) {
|
|
13386
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13387
|
+
}
|
|
13388
|
+
});
|
|
13389
|
+
}
|
|
13390
|
+
if (shouldRegisterTool("get_traces")) {
|
|
13391
|
+
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 }) => {
|
|
13392
|
+
try {
|
|
13393
|
+
const { getTaskTraces: getTaskTraces2, getTraceStats: getTraceStats2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
13394
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
13395
|
+
const traces = getTaskTraces2(resolvedId);
|
|
13396
|
+
const stats = getTraceStats2(resolvedId);
|
|
13397
|
+
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`];
|
|
13398
|
+
for (const t of traces.slice(0, 20)) {
|
|
13399
|
+
lines.push(` ${t.created_at} [${t.trace_type}] ${t.name || ""} ${t.tokens ? t.tokens + "tok" : ""} ${t.duration_ms ? t.duration_ms + "ms" : ""}`);
|
|
13400
|
+
}
|
|
13401
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
13402
|
+
`) }] };
|
|
13403
|
+
} catch (e) {
|
|
13404
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13405
|
+
}
|
|
13406
|
+
});
|
|
13407
|
+
}
|
|
13408
|
+
if (shouldRegisterTool("save_snapshot")) {
|
|
13409
|
+
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.", {
|
|
13410
|
+
agent_id: exports_external.string().optional(),
|
|
13411
|
+
task_id: exports_external.string().optional(),
|
|
13412
|
+
project_id: exports_external.string().optional(),
|
|
13413
|
+
snapshot_type: exports_external.enum(["interrupt", "complete", "handoff", "checkpoint"]),
|
|
13414
|
+
plan_summary: exports_external.string().optional(),
|
|
13415
|
+
files_open: exports_external.array(exports_external.string()).optional(),
|
|
13416
|
+
attempts: exports_external.array(exports_external.string()).optional(),
|
|
13417
|
+
blockers: exports_external.array(exports_external.string()).optional(),
|
|
13418
|
+
next_steps: exports_external.string().optional()
|
|
13419
|
+
}, async ({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps }) => {
|
|
13420
|
+
try {
|
|
13421
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
13422
|
+
const snap = saveSnapshot2({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps });
|
|
13423
|
+
return { content: [{ type: "text", text: `Snapshot saved: ${snap.id} [${snapshot_type}]${plan_summary ? " \u2014 " + plan_summary.slice(0, 80) : ""}` }] };
|
|
13424
|
+
} catch (e) {
|
|
13425
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13426
|
+
}
|
|
13427
|
+
});
|
|
13428
|
+
}
|
|
13429
|
+
if (shouldRegisterTool("get_snapshot")) {
|
|
13430
|
+
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 }) => {
|
|
13431
|
+
try {
|
|
13432
|
+
const { getLatestSnapshot: getLatestSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
13433
|
+
const snap = getLatestSnapshot2(agent_id, task_id);
|
|
13434
|
+
if (!snap)
|
|
13435
|
+
return { content: [{ type: "text", text: "No snapshot found." }] };
|
|
13436
|
+
const lines = [`Snapshot [${snap.snapshot_type}] from ${snap.created_at}`];
|
|
13437
|
+
if (snap.plan_summary)
|
|
13438
|
+
lines.push(`Plan: ${snap.plan_summary}`);
|
|
13439
|
+
if (snap.files_open.length > 0)
|
|
13440
|
+
lines.push(`Files: ${snap.files_open.join(", ")}`);
|
|
13441
|
+
if (snap.blockers.length > 0)
|
|
13442
|
+
lines.push(`Blockers: ${snap.blockers.join(", ")}`);
|
|
13443
|
+
if (snap.next_steps)
|
|
13444
|
+
lines.push(`Next: ${snap.next_steps}`);
|
|
13445
|
+
if (snap.attempts.length > 0)
|
|
13446
|
+
lines.push(`Attempts: ${snap.attempts.join("; ")}`);
|
|
13447
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
13448
|
+
`) }] };
|
|
13449
|
+
} catch (e) {
|
|
13450
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13451
|
+
}
|
|
13452
|
+
});
|
|
13453
|
+
}
|
|
13454
|
+
if (shouldRegisterTool("set_budget")) {
|
|
13455
|
+
server.tool("set_budget", "Set execution budget for an agent \u2014 max concurrent tasks, cost limit, time limit per period.", {
|
|
13456
|
+
agent_id: exports_external.string(),
|
|
13457
|
+
max_concurrent: exports_external.number().optional(),
|
|
13458
|
+
max_cost_usd: exports_external.number().optional(),
|
|
13459
|
+
max_task_minutes: exports_external.number().optional(),
|
|
13460
|
+
period_hours: exports_external.number().optional()
|
|
13461
|
+
}, async ({ agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours }) => {
|
|
13462
|
+
try {
|
|
13463
|
+
const { setBudget: setBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
13464
|
+
const budget = setBudget2(agent_id, { max_concurrent, max_cost_usd, max_task_minutes, period_hours });
|
|
13465
|
+
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` }] };
|
|
13466
|
+
} catch (e) {
|
|
13467
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13468
|
+
}
|
|
13469
|
+
});
|
|
13470
|
+
}
|
|
13471
|
+
if (shouldRegisterTool("check_budget")) {
|
|
13472
|
+
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 }) => {
|
|
13473
|
+
try {
|
|
13474
|
+
const { checkBudget: checkBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
13475
|
+
const result = checkBudget2(agent_id);
|
|
13476
|
+
if (result.allowed) {
|
|
13477
|
+
return { content: [{ type: "text", text: `Budget OK: ${result.current_concurrent}/${result.max_concurrent} concurrent tasks` }] };
|
|
13478
|
+
}
|
|
13479
|
+
return { content: [{ type: "text", text: `BUDGET EXCEEDED: ${result.reason}` }], isError: true };
|
|
13480
|
+
} catch (e) {
|
|
13481
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13482
|
+
}
|
|
13483
|
+
});
|
|
13484
|
+
}
|
|
13040
13485
|
if (shouldRegisterTool("import_github_issue")) {
|
|
13041
13486
|
server.tool("import_github_issue", "Import a GitHub issue as a task. Requires gh CLI installed and authenticated.", {
|
|
13042
13487
|
url: exports_external.string().describe("GitHub issue URL (e.g. https://github.com/owner/repo/issues/42)"),
|
|
@@ -13747,6 +14192,56 @@ ${formatTaskDetail(task, 300)}` }] };
|
|
|
13747
14192
|
}
|
|
13748
14193
|
});
|
|
13749
14194
|
}
|
|
14195
|
+
if (shouldRegisterTool("steal_task")) {
|
|
14196
|
+
server.tool("steal_task", "Work-stealing: take the highest-priority stale in_progress task from another agent and reassign it to you.", {
|
|
14197
|
+
agent_id: exports_external.string().describe("Your agent ID"),
|
|
14198
|
+
stale_minutes: exports_external.number().optional().describe("How long a task must be stale before stealing (default: 30)"),
|
|
14199
|
+
project_id: exports_external.string().optional(),
|
|
14200
|
+
task_list_id: exports_external.string().optional()
|
|
14201
|
+
}, async ({ agent_id, stale_minutes, project_id, task_list_id }) => {
|
|
14202
|
+
try {
|
|
14203
|
+
const task = stealTask(agent_id, { stale_minutes, project_id, task_list_id });
|
|
14204
|
+
if (!task)
|
|
14205
|
+
return { content: [{ type: "text", text: "No stale tasks available to steal." }] };
|
|
14206
|
+
return { content: [{ type: "text", text: `Stolen: ${formatTask(task)}
|
|
14207
|
+
Previous owner: ${task.metadata?._stolen_from || "unknown"}` }] };
|
|
14208
|
+
} catch (e) {
|
|
14209
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14210
|
+
}
|
|
14211
|
+
});
|
|
14212
|
+
}
|
|
14213
|
+
if (shouldRegisterTool("claim_or_steal")) {
|
|
14214
|
+
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.", {
|
|
14215
|
+
agent_id: exports_external.string(),
|
|
14216
|
+
project_id: exports_external.string().optional(),
|
|
14217
|
+
task_list_id: exports_external.string().optional(),
|
|
14218
|
+
plan_id: exports_external.string().optional(),
|
|
14219
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
14220
|
+
stale_minutes: exports_external.number().optional().describe("Stale threshold for work-stealing fallback (default: 30)")
|
|
14221
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags, stale_minutes }) => {
|
|
14222
|
+
try {
|
|
14223
|
+
const filters = {};
|
|
14224
|
+
if (project_id)
|
|
14225
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
14226
|
+
if (task_list_id)
|
|
14227
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
14228
|
+
if (plan_id)
|
|
14229
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
14230
|
+
if (tags)
|
|
14231
|
+
filters.tags = tags;
|
|
14232
|
+
if (stale_minutes)
|
|
14233
|
+
filters.stale_minutes = stale_minutes;
|
|
14234
|
+
const result = claimOrSteal(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
14235
|
+
if (!result)
|
|
14236
|
+
return { content: [{ type: "text", text: "No tasks available to claim or steal." }] };
|
|
14237
|
+
const prefix = result.stolen ? "Stolen" : "Claimed";
|
|
14238
|
+
return { content: [{ type: "text", text: `${prefix}: ${formatTask(result.task)}
|
|
14239
|
+
${formatTaskDetail(result.task, 300)}` }] };
|
|
14240
|
+
} catch (e) {
|
|
14241
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14242
|
+
}
|
|
14243
|
+
});
|
|
14244
|
+
}
|
|
13750
14245
|
if (shouldRegisterTool("get_stale_tasks")) {
|
|
13751
14246
|
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
13752
14247
|
stale_minutes: exports_external.number().optional(),
|
|
@@ -19758,6 +20253,20 @@ program2.command("claim <agent>").description("Atomically claim the best pending
|
|
|
19758
20253
|
}
|
|
19759
20254
|
console.log(chalk.green(`Claimed: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
19760
20255
|
});
|
|
20256
|
+
program2.command("steal <agent>").description("Work-stealing: take the highest-priority stale task from another agent").option("--stale-minutes <n>", "How long a task must be stale (default: 30)", "30").option("--project <id>", "Filter to project").action((agent, opts) => {
|
|
20257
|
+
const globalOpts = program2.opts();
|
|
20258
|
+
const { stealTask: stealTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
20259
|
+
const task = stealTask2(agent, { stale_minutes: parseInt(opts.staleMinutes, 10), project_id: opts.project });
|
|
20260
|
+
if (!task) {
|
|
20261
|
+
console.log(chalk.dim("No stale tasks available to steal."));
|
|
20262
|
+
return;
|
|
20263
|
+
}
|
|
20264
|
+
if (globalOpts.json) {
|
|
20265
|
+
output(task, true);
|
|
20266
|
+
return;
|
|
20267
|
+
}
|
|
20268
|
+
console.log(chalk.green(`Stolen: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
20269
|
+
});
|
|
19761
20270
|
program2.command("status").description("Show full project health snapshot").option("--agent <id>", "Include next task for this agent").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
19762
20271
|
const db = getDatabase();
|
|
19763
20272
|
const filters = {};
|