@hasna/todos 0.9.73 → 0.9.75
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 +988 -18
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/handoffs.d.ts +25 -0
- package/dist/db/handoffs.d.ts.map +1 -0
- package/dist/db/projects.d.ts +5 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +10 -0
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -9
- package/dist/mcp/index.js +385 -13
- package/dist/server/index.js +151 -25
- package/dist/types/index.d.ts +39 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = __moduleCache.get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
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
|
+
}));
|
|
18
|
+
__moduleCache.set(from, entry);
|
|
19
|
+
return entry;
|
|
20
|
+
};
|
|
4
21
|
var __export = (target, all) => {
|
|
5
22
|
for (var name in all)
|
|
6
23
|
__defProp(target, name, {
|
|
@@ -181,6 +198,18 @@ function ensureSchema(db) {
|
|
|
181
198
|
metadata TEXT DEFAULT '{}',
|
|
182
199
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
183
200
|
)`);
|
|
201
|
+
ensureTable("project_sources", `
|
|
202
|
+
CREATE TABLE project_sources (
|
|
203
|
+
id TEXT PRIMARY KEY,
|
|
204
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
205
|
+
type TEXT NOT NULL,
|
|
206
|
+
name TEXT NOT NULL,
|
|
207
|
+
uri TEXT NOT NULL,
|
|
208
|
+
description TEXT,
|
|
209
|
+
metadata TEXT DEFAULT '{}',
|
|
210
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
211
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
212
|
+
)`);
|
|
184
213
|
ensureColumn("projects", "task_list_id", "TEXT");
|
|
185
214
|
ensureColumn("projects", "task_prefix", "TEXT");
|
|
186
215
|
ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
|
|
@@ -194,6 +223,9 @@ function ensureSchema(db) {
|
|
|
194
223
|
ensureColumn("tasks", "approved_at", "TEXT");
|
|
195
224
|
ensureColumn("tasks", "recurrence_rule", "TEXT");
|
|
196
225
|
ensureColumn("tasks", "recurrence_parent_id", "TEXT REFERENCES tasks(id) ON DELETE SET NULL");
|
|
226
|
+
ensureColumn("tasks", "confidence", "REAL");
|
|
227
|
+
ensureColumn("tasks", "reason", "TEXT");
|
|
228
|
+
ensureColumn("tasks", "spawned_from_session", "TEXT");
|
|
197
229
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
198
230
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
199
231
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -222,6 +254,8 @@ function ensureSchema(db) {
|
|
|
222
254
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
|
|
223
255
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id)");
|
|
224
256
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL");
|
|
257
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id)");
|
|
258
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type)");
|
|
225
259
|
}
|
|
226
260
|
function backfillTaskTags(db) {
|
|
227
261
|
try {
|
|
@@ -590,6 +624,45 @@ var init_database = __esm(() => {
|
|
|
590
624
|
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
591
625
|
ALTER TABLE agents ADD COLUMN working_dir TEXT;
|
|
592
626
|
INSERT OR IGNORE INTO _migrations (id) VALUES (17);
|
|
627
|
+
`,
|
|
628
|
+
`
|
|
629
|
+
ALTER TABLE tasks ADD COLUMN confidence REAL;
|
|
630
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (18);
|
|
631
|
+
`,
|
|
632
|
+
`
|
|
633
|
+
ALTER TABLE tasks ADD COLUMN reason TEXT;
|
|
634
|
+
ALTER TABLE tasks ADD COLUMN spawned_from_session TEXT;
|
|
635
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (19);
|
|
636
|
+
`,
|
|
637
|
+
`
|
|
638
|
+
CREATE TABLE IF NOT EXISTS handoffs (
|
|
639
|
+
id TEXT PRIMARY KEY,
|
|
640
|
+
agent_id TEXT,
|
|
641
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
642
|
+
summary TEXT NOT NULL,
|
|
643
|
+
completed TEXT,
|
|
644
|
+
in_progress TEXT,
|
|
645
|
+
blockers TEXT,
|
|
646
|
+
next_steps TEXT,
|
|
647
|
+
created_at TEXT NOT NULL
|
|
648
|
+
);
|
|
649
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (20);
|
|
650
|
+
`,
|
|
651
|
+
`
|
|
652
|
+
CREATE TABLE IF NOT EXISTS project_sources (
|
|
653
|
+
id TEXT PRIMARY KEY,
|
|
654
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
655
|
+
type TEXT NOT NULL,
|
|
656
|
+
name TEXT NOT NULL,
|
|
657
|
+
uri TEXT NOT NULL,
|
|
658
|
+
description TEXT,
|
|
659
|
+
metadata TEXT DEFAULT '{}',
|
|
660
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
661
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
662
|
+
);
|
|
663
|
+
CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id);
|
|
664
|
+
CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type);
|
|
665
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (21);
|
|
593
666
|
`
|
|
594
667
|
];
|
|
595
668
|
});
|
|
@@ -921,6 +994,77 @@ var init_agents = __esm(() => {
|
|
|
921
994
|
AGENT_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
|
|
922
995
|
});
|
|
923
996
|
|
|
997
|
+
// src/db/handoffs.ts
|
|
998
|
+
var exports_handoffs = {};
|
|
999
|
+
__export(exports_handoffs, {
|
|
1000
|
+
listHandoffs: () => listHandoffs,
|
|
1001
|
+
getLatestHandoff: () => getLatestHandoff,
|
|
1002
|
+
createHandoff: () => createHandoff
|
|
1003
|
+
});
|
|
1004
|
+
function createHandoff(input, db) {
|
|
1005
|
+
const d = db || getDatabase();
|
|
1006
|
+
const id = uuid();
|
|
1007
|
+
const timestamp = now();
|
|
1008
|
+
d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
|
|
1009
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1010
|
+
id,
|
|
1011
|
+
input.agent_id || null,
|
|
1012
|
+
input.project_id || null,
|
|
1013
|
+
input.summary,
|
|
1014
|
+
input.completed ? JSON.stringify(input.completed) : null,
|
|
1015
|
+
input.in_progress ? JSON.stringify(input.in_progress) : null,
|
|
1016
|
+
input.blockers ? JSON.stringify(input.blockers) : null,
|
|
1017
|
+
input.next_steps ? JSON.stringify(input.next_steps) : null,
|
|
1018
|
+
timestamp
|
|
1019
|
+
]);
|
|
1020
|
+
return {
|
|
1021
|
+
id,
|
|
1022
|
+
agent_id: input.agent_id || null,
|
|
1023
|
+
project_id: input.project_id || null,
|
|
1024
|
+
summary: input.summary,
|
|
1025
|
+
completed: input.completed || null,
|
|
1026
|
+
in_progress: input.in_progress || null,
|
|
1027
|
+
blockers: input.blockers || null,
|
|
1028
|
+
next_steps: input.next_steps || null,
|
|
1029
|
+
created_at: timestamp
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function rowToHandoff(row) {
|
|
1033
|
+
return {
|
|
1034
|
+
...row,
|
|
1035
|
+
completed: row.completed ? JSON.parse(row.completed) : null,
|
|
1036
|
+
in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
|
|
1037
|
+
blockers: row.blockers ? JSON.parse(row.blockers) : null,
|
|
1038
|
+
next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function listHandoffs(projectId, limit = 10, db) {
|
|
1042
|
+
const d = db || getDatabase();
|
|
1043
|
+
if (projectId) {
|
|
1044
|
+
return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
|
|
1045
|
+
}
|
|
1046
|
+
return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
|
|
1047
|
+
}
|
|
1048
|
+
function getLatestHandoff(agentId, projectId, db) {
|
|
1049
|
+
const d = db || getDatabase();
|
|
1050
|
+
let query = "SELECT * FROM handoffs WHERE 1=1";
|
|
1051
|
+
const params = [];
|
|
1052
|
+
if (agentId) {
|
|
1053
|
+
query += " AND agent_id = ?";
|
|
1054
|
+
params.push(agentId);
|
|
1055
|
+
}
|
|
1056
|
+
if (projectId) {
|
|
1057
|
+
query += " AND project_id = ?";
|
|
1058
|
+
params.push(projectId);
|
|
1059
|
+
}
|
|
1060
|
+
query += " ORDER BY rowid DESC LIMIT 1";
|
|
1061
|
+
const row = d.query(query).get(...params);
|
|
1062
|
+
return row ? rowToHandoff(row) : null;
|
|
1063
|
+
}
|
|
1064
|
+
var init_handoffs = __esm(() => {
|
|
1065
|
+
init_database();
|
|
1066
|
+
});
|
|
1067
|
+
|
|
924
1068
|
// src/mcp/index.ts
|
|
925
1069
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
926
1070
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -5055,6 +5199,40 @@ function listProjects(db) {
|
|
|
5055
5199
|
const d = db || getDatabase();
|
|
5056
5200
|
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
5057
5201
|
}
|
|
5202
|
+
function rowToSource(row) {
|
|
5203
|
+
return {
|
|
5204
|
+
...row,
|
|
5205
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {}
|
|
5206
|
+
};
|
|
5207
|
+
}
|
|
5208
|
+
function addProjectSource(input, db) {
|
|
5209
|
+
const d = db || getDatabase();
|
|
5210
|
+
const id = uuid();
|
|
5211
|
+
const timestamp = now();
|
|
5212
|
+
d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
|
|
5213
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5214
|
+
id,
|
|
5215
|
+
input.project_id,
|
|
5216
|
+
input.type,
|
|
5217
|
+
input.name,
|
|
5218
|
+
input.uri,
|
|
5219
|
+
input.description || null,
|
|
5220
|
+
JSON.stringify(input.metadata || {}),
|
|
5221
|
+
timestamp,
|
|
5222
|
+
timestamp
|
|
5223
|
+
]);
|
|
5224
|
+
return rowToSource(d.query("SELECT * FROM project_sources WHERE id = ?").get(id));
|
|
5225
|
+
}
|
|
5226
|
+
function removeProjectSource(id, db) {
|
|
5227
|
+
const d = db || getDatabase();
|
|
5228
|
+
const result = d.run("DELETE FROM project_sources WHERE id = ?", [id]);
|
|
5229
|
+
return result.changes > 0;
|
|
5230
|
+
}
|
|
5231
|
+
function listProjectSources(projectId, db) {
|
|
5232
|
+
const d = db || getDatabase();
|
|
5233
|
+
const rows = d.query("SELECT * FROM project_sources WHERE project_id = ? ORDER BY name").all(projectId);
|
|
5234
|
+
return rows.map(rowToSource);
|
|
5235
|
+
}
|
|
5058
5236
|
function nextTaskShortId(projectId, db) {
|
|
5059
5237
|
const d = db || getDatabase();
|
|
5060
5238
|
const project = getProject(projectId, d);
|
|
@@ -5349,8 +5527,8 @@ function createTask(input, db) {
|
|
|
5349
5527
|
const tags = input.tags || [];
|
|
5350
5528
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
5351
5529
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
5352
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id)
|
|
5353
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5530
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session)
|
|
5531
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5354
5532
|
id,
|
|
5355
5533
|
shortId,
|
|
5356
5534
|
input.project_id || null,
|
|
@@ -5376,7 +5554,9 @@ function createTask(input, db) {
|
|
|
5376
5554
|
null,
|
|
5377
5555
|
input.recurrence_rule || null,
|
|
5378
5556
|
input.recurrence_parent_id || null,
|
|
5379
|
-
input.spawns_template_id || null
|
|
5557
|
+
input.spawns_template_id || null,
|
|
5558
|
+
input.reason || null,
|
|
5559
|
+
input.spawned_from_session || null
|
|
5380
5560
|
]);
|
|
5381
5561
|
if (tags.length > 0) {
|
|
5382
5562
|
insertTaskTags(id, tags, d);
|
|
@@ -5733,13 +5913,21 @@ function completeTask(id, agentId, db, options) {
|
|
|
5733
5913
|
checkCompletionGuard(task, agentId || null, d);
|
|
5734
5914
|
const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes, attachment_ids: options.attachment_ids } : undefined;
|
|
5735
5915
|
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes || evidence.attachment_ids);
|
|
5736
|
-
|
|
5737
|
-
|
|
5916
|
+
const completionMeta = {};
|
|
5917
|
+
if (hasEvidence)
|
|
5918
|
+
completionMeta._evidence = evidence;
|
|
5919
|
+
if (options?.confidence !== undefined) {
|
|
5920
|
+
completionMeta._completion = { confidence: options.confidence };
|
|
5921
|
+
}
|
|
5922
|
+
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
5923
|
+
if (hasMeta) {
|
|
5924
|
+
const meta2 = { ...task.metadata, ...completionMeta };
|
|
5738
5925
|
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
|
|
5739
5926
|
}
|
|
5740
5927
|
const timestamp = now();
|
|
5741
|
-
|
|
5742
|
-
|
|
5928
|
+
const confidence = options?.confidence !== undefined ? options.confidence : null;
|
|
5929
|
+
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, confidence = ?, version = version + 1, updated_at = ?
|
|
5930
|
+
WHERE id = ?`, [timestamp, confidence, timestamp, id]);
|
|
5743
5931
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
5744
5932
|
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
5745
5933
|
let spawnedTask = null;
|
|
@@ -5758,14 +5946,14 @@ function completeTask(id, agentId, db, options) {
|
|
|
5758
5946
|
spawnedFromTemplate = createTask(input, d);
|
|
5759
5947
|
} catch {}
|
|
5760
5948
|
}
|
|
5761
|
-
const meta =
|
|
5949
|
+
const meta = hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata;
|
|
5762
5950
|
if (spawnedTask) {
|
|
5763
5951
|
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
5764
5952
|
}
|
|
5765
5953
|
if (spawnedFromTemplate) {
|
|
5766
5954
|
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
5767
5955
|
}
|
|
5768
|
-
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
5956
|
+
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
5769
5957
|
}
|
|
5770
5958
|
function lockTask(id, agentId, db) {
|
|
5771
5959
|
const d = db || getDatabase();
|
|
@@ -6212,6 +6400,20 @@ function setTaskPriority(id, priority, _agentId, db) {
|
|
|
6212
6400
|
}
|
|
6213
6401
|
throw new Error(`Failed to set priority after 3 attempts`);
|
|
6214
6402
|
}
|
|
6403
|
+
function redistributeStaleTasks(agentId, options, db) {
|
|
6404
|
+
const d = db || getDatabase();
|
|
6405
|
+
const maxAge = options?.max_age_minutes ?? 60;
|
|
6406
|
+
const stale = getStaleTasks(maxAge, options?.project_id ? { project_id: options.project_id } : undefined, d);
|
|
6407
|
+
const limited = options?.limit ? stale.slice(0, options.limit) : stale;
|
|
6408
|
+
const timestamp = now();
|
|
6409
|
+
const released = [];
|
|
6410
|
+
for (const t of limited) {
|
|
6411
|
+
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, status = 'pending', version = version + 1, updated_at = ? WHERE id = ?`, [timestamp, t.id]);
|
|
6412
|
+
released.push({ ...t, locked_by: null, locked_at: null, status: "pending" });
|
|
6413
|
+
}
|
|
6414
|
+
const claimed = released.length > 0 ? claimNextTask(agentId, options?.project_id ? { project_id: options.project_id } : undefined, d) : null;
|
|
6415
|
+
return { released, claimed };
|
|
6416
|
+
}
|
|
6215
6417
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
6216
6418
|
const visited = new Set;
|
|
6217
6419
|
const queue = [dependsOn];
|
|
@@ -7268,7 +7470,9 @@ if (shouldRegisterTool("create_task")) {
|
|
|
7268
7470
|
estimated_minutes: exports_external.number().optional(),
|
|
7269
7471
|
requires_approval: exports_external.boolean().optional(),
|
|
7270
7472
|
recurrence_rule: exports_external.string().optional(),
|
|
7271
|
-
spawns_template_id: exports_external.string().optional().describe("Template ID to auto-create as next task when this task is completed (pipeline/handoff chains)")
|
|
7473
|
+
spawns_template_id: exports_external.string().optional().describe("Template ID to auto-create as next task when this task is completed (pipeline/handoff chains)"),
|
|
7474
|
+
reason: exports_external.string().optional().describe("Why this task exists \u2014 context for agents picking it up"),
|
|
7475
|
+
spawned_from_session: exports_external.string().optional().describe("Session ID that created this task (for tracing task lineage)")
|
|
7272
7476
|
}, async (params) => {
|
|
7273
7477
|
try {
|
|
7274
7478
|
const resolved = { ...params };
|
|
@@ -7483,12 +7687,13 @@ if (shouldRegisterTool("complete_task")) {
|
|
|
7483
7687
|
test_results: exports_external.string().optional().describe("Summary of test results"),
|
|
7484
7688
|
commit_hash: exports_external.string().optional().describe("Git commit hash associated with this completion"),
|
|
7485
7689
|
notes: exports_external.string().optional().describe("Notes about the completion"),
|
|
7486
|
-
attachment_ids: exports_external.array(exports_external.string()).optional().describe("IDs of attachments uploaded via @hasna/attachments to link as evidence")
|
|
7487
|
-
|
|
7690
|
+
attachment_ids: exports_external.array(exports_external.string()).optional().describe("IDs of attachments uploaded via @hasna/attachments to link as evidence"),
|
|
7691
|
+
confidence: exports_external.number().min(0).max(1).optional().describe("Agent's confidence 0.0-1.0 that the task is fully complete. Default: 1.0. Low confidence (<0.7) is flagged as a signal for review.")
|
|
7692
|
+
}, async ({ id, agent_id, skip_recurrence, files_changed, test_results, commit_hash, notes, attachment_ids, confidence }) => {
|
|
7488
7693
|
try {
|
|
7489
7694
|
const resolvedId = resolveId(id);
|
|
7490
7695
|
const evidence = files_changed || test_results || commit_hash || notes || attachment_ids ? { files_changed, test_results, commit_hash, notes, attachment_ids } : undefined;
|
|
7491
|
-
const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence, ...evidence });
|
|
7696
|
+
const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence, confidence, ...evidence });
|
|
7492
7697
|
let text = `completed: ${formatTask(task)}`;
|
|
7493
7698
|
if (task.metadata._next_recurrence) {
|
|
7494
7699
|
const next = task.metadata._next_recurrence;
|
|
@@ -7640,6 +7845,65 @@ if (shouldRegisterTool("create_project")) {
|
|
|
7640
7845
|
}
|
|
7641
7846
|
});
|
|
7642
7847
|
}
|
|
7848
|
+
if (shouldRegisterTool("add_project_source")) {
|
|
7849
|
+
server.tool("add_project_source", "Add a data source to a project (S3 bucket, Google Drive folder, local path, GitHub repo, Notion page, etc.). Sources are revealed to agents when they load the project.", {
|
|
7850
|
+
project_id: exports_external.string().describe("Project ID"),
|
|
7851
|
+
type: exports_external.string().describe("Source type: 's3', 'gdrive', 'local', 'github', 'notion', 'http', or any custom label"),
|
|
7852
|
+
name: exports_external.string().describe("Human-readable label for this source"),
|
|
7853
|
+
uri: exports_external.string().describe("The source URI (bucket path, folder URL, local path, repo URL, etc.)"),
|
|
7854
|
+
description: exports_external.string().optional().describe("What this source contains or how agents should use it"),
|
|
7855
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Extra config (e.g. region, access role, subfolder)")
|
|
7856
|
+
}, async (params) => {
|
|
7857
|
+
try {
|
|
7858
|
+
const resolvedProjectId = resolveId(params.project_id, "projects");
|
|
7859
|
+
const source = addProjectSource({ ...params, project_id: resolvedProjectId });
|
|
7860
|
+
return {
|
|
7861
|
+
content: [{
|
|
7862
|
+
type: "text",
|
|
7863
|
+
text: `Source added: ${source.id.slice(0, 8)} | [${source.type}] ${source.name} \u2192 ${source.uri}`
|
|
7864
|
+
}]
|
|
7865
|
+
};
|
|
7866
|
+
} catch (e) {
|
|
7867
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7868
|
+
}
|
|
7869
|
+
});
|
|
7870
|
+
}
|
|
7871
|
+
if (shouldRegisterTool("remove_project_source")) {
|
|
7872
|
+
server.tool("remove_project_source", "Remove a data source from a project by source ID.", {
|
|
7873
|
+
source_id: exports_external.string().describe("Source ID to remove")
|
|
7874
|
+
}, async ({ source_id }) => {
|
|
7875
|
+
try {
|
|
7876
|
+
const db = getDatabase();
|
|
7877
|
+
const row = db.query("SELECT * FROM project_sources WHERE id LIKE ?").get(`${source_id}%`);
|
|
7878
|
+
if (!row)
|
|
7879
|
+
return { content: [{ type: "text", text: `Source not found: ${source_id}` }], isError: true };
|
|
7880
|
+
removeProjectSource(row.id);
|
|
7881
|
+
return { content: [{ type: "text", text: `Source removed: ${row.name}` }] };
|
|
7882
|
+
} catch (e) {
|
|
7883
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7884
|
+
}
|
|
7885
|
+
});
|
|
7886
|
+
}
|
|
7887
|
+
if (shouldRegisterTool("list_project_sources")) {
|
|
7888
|
+
server.tool("list_project_sources", "List all data sources attached to a project.", {
|
|
7889
|
+
project_id: exports_external.string().describe("Project ID")
|
|
7890
|
+
}, async ({ project_id }) => {
|
|
7891
|
+
try {
|
|
7892
|
+
const resolvedId = resolveId(project_id, "projects");
|
|
7893
|
+
const sources = listProjectSources(resolvedId);
|
|
7894
|
+
if (sources.length === 0) {
|
|
7895
|
+
return { content: [{ type: "text", text: "No sources configured for this project." }] };
|
|
7896
|
+
}
|
|
7897
|
+
const lines = sources.map((s) => `${s.id.slice(0, 8)} | [${s.type}] ${s.name} \u2192 ${s.uri}${s.description ? `
|
|
7898
|
+
${s.description}` : ""}`);
|
|
7899
|
+
return { content: [{ type: "text", text: `${sources.length} source(s):
|
|
7900
|
+
${lines.join(`
|
|
7901
|
+
`)}` }] };
|
|
7902
|
+
} catch (e) {
|
|
7903
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7904
|
+
}
|
|
7905
|
+
});
|
|
7906
|
+
}
|
|
7643
7907
|
if (shouldRegisterTool("create_plan")) {
|
|
7644
7908
|
server.tool("create_plan", "Create a plan to group related tasks.", {
|
|
7645
7909
|
name: exports_external.string(),
|
|
@@ -8956,6 +9220,42 @@ if (shouldRegisterTool("bootstrap")) {
|
|
|
8956
9220
|
lines.push(`Other agents active: ${others.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
8957
9221
|
}
|
|
8958
9222
|
}
|
|
9223
|
+
if (project_id) {
|
|
9224
|
+
const resolvedId = resolveId(project_id, "projects");
|
|
9225
|
+
const sources = listProjectSources(resolvedId);
|
|
9226
|
+
if (sources.length > 0) {
|
|
9227
|
+
lines.push("");
|
|
9228
|
+
lines.push(`## Data Sources`);
|
|
9229
|
+
for (const s of sources) {
|
|
9230
|
+
lines.push(`[${s.type}] ${s.name}: ${s.uri}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
9231
|
+
}
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
lines.push(`
|
|
9235
|
+
as_of: ${new Date().toISOString()}`);
|
|
9236
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
9237
|
+
`) }] };
|
|
9238
|
+
} catch (e) {
|
|
9239
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9240
|
+
}
|
|
9241
|
+
});
|
|
9242
|
+
}
|
|
9243
|
+
if (shouldRegisterTool("redistribute_stale_tasks")) {
|
|
9244
|
+
server.tool("redistribute_stale_tasks", "Release stale in-progress tasks and optionally claim the best one. Work-stealing for multi-agent.", {
|
|
9245
|
+
agent_id: exports_external.string().describe("Agent ID claiming the next task after releasing stale ones"),
|
|
9246
|
+
max_age_minutes: exports_external.number().optional().describe("Tasks idle longer than this (default: 60) are released"),
|
|
9247
|
+
project_id: exports_external.string().optional().describe("Limit to a specific project"),
|
|
9248
|
+
limit: exports_external.number().optional().describe("Max number of stale tasks to release")
|
|
9249
|
+
}, async ({ agent_id, max_age_minutes, project_id, limit }) => {
|
|
9250
|
+
try {
|
|
9251
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
9252
|
+
const result = redistributeStaleTasks(agent_id, { max_age_minutes, project_id: resolvedProjectId, limit });
|
|
9253
|
+
const lines = [`Released ${result.released.length} stale task(s).`];
|
|
9254
|
+
for (const t of result.released)
|
|
9255
|
+
lines.push(` ${formatTask(t)}`);
|
|
9256
|
+
if (result.claimed)
|
|
9257
|
+
lines.push(`
|
|
9258
|
+
Claimed: ${formatTask(result.claimed)}`);
|
|
8959
9259
|
return { content: [{ type: "text", text: lines.join(`
|
|
8960
9260
|
`) }] };
|
|
8961
9261
|
} catch (e) {
|
|
@@ -8983,6 +9283,9 @@ if (shouldRegisterTool("search_tools")) {
|
|
|
8983
9283
|
"log_progress",
|
|
8984
9284
|
"create_project",
|
|
8985
9285
|
"list_projects",
|
|
9286
|
+
"add_project_source",
|
|
9287
|
+
"remove_project_source",
|
|
9288
|
+
"list_project_sources",
|
|
8986
9289
|
"create_plan",
|
|
8987
9290
|
"list_plans",
|
|
8988
9291
|
"get_plan",
|
|
@@ -9030,6 +9333,7 @@ if (shouldRegisterTool("search_tools")) {
|
|
|
9030
9333
|
"decompose_task",
|
|
9031
9334
|
"set_task_status",
|
|
9032
9335
|
"set_task_priority",
|
|
9336
|
+
"redistribute_stale_tasks",
|
|
9033
9337
|
"search_tools",
|
|
9034
9338
|
"describe_tools"
|
|
9035
9339
|
].filter((name) => shouldRegisterTool(name));
|
|
@@ -9090,6 +9394,15 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
9090
9394
|
Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
|
|
9091
9395
|
Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
|
|
9092
9396
|
list_projects: "List all registered projects. No params.",
|
|
9397
|
+
add_project_source: `Add a data source to a project (S3, GDrive, local path, GitHub, Notion, HTTP, etc.).
|
|
9398
|
+
Params: project_id(string, req), type(string, req \u2014 e.g. 's3','gdrive','local','github','notion','http'), name(string, req), uri(string, req), description(string), metadata(object)
|
|
9399
|
+
Example: {project_id: 'a1b2c3d4', type: 's3', name: 'Assets bucket', uri: 's3://my-bucket/assets/', description: 'Project media files'}`,
|
|
9400
|
+
remove_project_source: `Remove a data source from a project.
|
|
9401
|
+
Params: source_id(string, req \u2014 source ID or prefix)
|
|
9402
|
+
Example: {source_id: 'abc12345'}`,
|
|
9403
|
+
list_project_sources: `List all data sources for a project.
|
|
9404
|
+
Params: project_id(string, req)
|
|
9405
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
9093
9406
|
create_plan: `Create a plan to group related tasks.
|
|
9094
9407
|
Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
|
|
9095
9408
|
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
@@ -9217,6 +9530,9 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
9217
9530
|
set_task_priority: `Set task priority without needing version. Auto-retries on conflict (up to 3 attempts). Use instead of update_task when you only need to change priority.
|
|
9218
9531
|
Params: id(string, req), priority(low|medium|high|critical, req)
|
|
9219
9532
|
Example: {id: 'a1b2c3d4', priority: 'high'}`,
|
|
9533
|
+
redistribute_stale_tasks: `Release stale in-progress tasks and optionally claim the best one. Multi-agent work-stealing.
|
|
9534
|
+
Params: agent_id(string, req), max_age_minutes(number, default:60), project_id(string, optional), limit(number, optional)
|
|
9535
|
+
Example: {agent_id: 'a1b2c3d4', max_age_minutes: 30}`,
|
|
9220
9536
|
search_tools: `List all tool names or filter by substring.
|
|
9221
9537
|
Params: query(string, optional)
|
|
9222
9538
|
Example: {query: 'task'}`,
|
|
@@ -9247,6 +9563,62 @@ server.resource("agents", "todos://agents", { description: "All registered agent
|
|
|
9247
9563
|
const agents = listAgents();
|
|
9248
9564
|
return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
|
|
9249
9565
|
});
|
|
9566
|
+
if (shouldRegisterTool("create_handoff")) {
|
|
9567
|
+
server.tool("create_handoff", "Create a session handoff note for agent coordination.", {
|
|
9568
|
+
agent_id: exports_external.string().optional().describe("Agent creating the handoff"),
|
|
9569
|
+
project_id: exports_external.string().optional().describe("Project ID"),
|
|
9570
|
+
summary: exports_external.string().describe("What was accomplished this session"),
|
|
9571
|
+
completed: exports_external.array(exports_external.string()).optional().describe("Items completed"),
|
|
9572
|
+
in_progress: exports_external.array(exports_external.string()).optional().describe("Items still in progress"),
|
|
9573
|
+
blockers: exports_external.array(exports_external.string()).optional().describe("Blocking issues"),
|
|
9574
|
+
next_steps: exports_external.array(exports_external.string()).optional().describe("Recommended next actions")
|
|
9575
|
+
}, async ({ agent_id, project_id, summary, completed, in_progress, blockers, next_steps }) => {
|
|
9576
|
+
try {
|
|
9577
|
+
const { createHandoff: createHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
9578
|
+
const handoff = createHandoff2({
|
|
9579
|
+
agent_id,
|
|
9580
|
+
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
9581
|
+
summary,
|
|
9582
|
+
completed,
|
|
9583
|
+
in_progress,
|
|
9584
|
+
blockers,
|
|
9585
|
+
next_steps
|
|
9586
|
+
});
|
|
9587
|
+
return { content: [{ type: "text", text: `Handoff created: ${handoff.id.slice(0, 8)} by ${handoff.agent_id || "unknown"}` }] };
|
|
9588
|
+
} catch (e) {
|
|
9589
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9590
|
+
}
|
|
9591
|
+
});
|
|
9592
|
+
}
|
|
9593
|
+
if (shouldRegisterTool("get_latest_handoff")) {
|
|
9594
|
+
server.tool("get_latest_handoff", "Get the most recent handoff for an agent or project.", {
|
|
9595
|
+
agent_id: exports_external.string().optional().describe("Filter by agent"),
|
|
9596
|
+
project_id: exports_external.string().optional().describe("Filter by project")
|
|
9597
|
+
}, async ({ agent_id, project_id }) => {
|
|
9598
|
+
try {
|
|
9599
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
9600
|
+
const handoff = getLatestHandoff2(agent_id, project_id ? resolveId(project_id, "projects") : undefined);
|
|
9601
|
+
if (!handoff)
|
|
9602
|
+
return { content: [{ type: "text", text: "No handoffs found." }] };
|
|
9603
|
+
const lines = [
|
|
9604
|
+
`${handoff.created_at.slice(0, 16)} ${handoff.agent_id || "unknown"}`,
|
|
9605
|
+
handoff.summary
|
|
9606
|
+
];
|
|
9607
|
+
if (handoff.completed?.length)
|
|
9608
|
+
lines.push(`Done: ${handoff.completed.join(", ")}`);
|
|
9609
|
+
if (handoff.in_progress?.length)
|
|
9610
|
+
lines.push(`In progress: ${handoff.in_progress.join(", ")}`);
|
|
9611
|
+
if (handoff.blockers?.length)
|
|
9612
|
+
lines.push(`Blocked: ${handoff.blockers.join(", ")}`);
|
|
9613
|
+
if (handoff.next_steps?.length)
|
|
9614
|
+
lines.push(`Next: ${handoff.next_steps.join(", ")}`);
|
|
9615
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
9616
|
+
`) }] };
|
|
9617
|
+
} catch (e) {
|
|
9618
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9619
|
+
}
|
|
9620
|
+
});
|
|
9621
|
+
}
|
|
9250
9622
|
server.resource("task-lists", "todos://task-lists", { description: "All task lists", mimeType: "application/json" }, async () => {
|
|
9251
9623
|
const lists = listTaskLists();
|
|
9252
9624
|
return { contents: [{ uri: "todos://task-lists", text: JSON.stringify(lists, null, 2), mimeType: "application/json" }] };
|