@hasna/todos 0.11.27 → 0.11.29
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 +424 -91
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +26 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/db/webhooks.d.ts +5 -0
- package/dist/db/webhooks.d.ts.map +1 -1
- package/dist/index.js +144 -13
- package/dist/mcp/index.js +362 -34
- package/dist/mcp/tools/webhooks.d.ts.map +1 -1
- package/dist/server/index.js +181 -8
- package/dist/types/index.d.ts +28 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -4,37 +4,27 @@ var __defProp = Object.defineProperty;
|
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
|
|
8
|
-
return this[key];
|
|
9
|
-
}
|
|
7
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
10
8
|
var __toCommonJS = (from) => {
|
|
11
|
-
var entry =
|
|
9
|
+
var entry = __moduleCache.get(from), desc;
|
|
12
10
|
if (entry)
|
|
13
11
|
return entry;
|
|
14
12
|
entry = __defProp({}, "__esModule", { value: true });
|
|
15
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
-
});
|
|
22
|
-
}
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
14
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
15
|
+
get: () => from[key],
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
}));
|
|
23
18
|
__moduleCache.set(from, entry);
|
|
24
19
|
return entry;
|
|
25
20
|
};
|
|
26
|
-
var __moduleCache;
|
|
27
|
-
var __returnValue = (v) => v;
|
|
28
|
-
function __exportSetter(name, newValue) {
|
|
29
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
30
|
-
}
|
|
31
21
|
var __export = (target, all) => {
|
|
32
22
|
for (var name in all)
|
|
33
23
|
__defProp(target, name, {
|
|
34
24
|
get: all[name],
|
|
35
25
|
enumerable: true,
|
|
36
26
|
configurable: true,
|
|
37
|
-
set:
|
|
27
|
+
set: (newValue) => all[name] = () => newValue
|
|
38
28
|
});
|
|
39
29
|
};
|
|
40
30
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -384,6 +374,32 @@ function ensureSchema(db) {
|
|
|
384
374
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_synced ON tasks(synced_at)");
|
|
385
375
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_projects_machine ON projects(machine_id)");
|
|
386
376
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_machine ON agents(machine_id)");
|
|
377
|
+
ensureTable("task_time_logs", `
|
|
378
|
+
CREATE TABLE task_time_logs (
|
|
379
|
+
id TEXT PRIMARY KEY,
|
|
380
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
381
|
+
agent_id TEXT,
|
|
382
|
+
started_at TEXT,
|
|
383
|
+
ended_at TEXT,
|
|
384
|
+
minutes INTEGER NOT NULL,
|
|
385
|
+
notes TEXT,
|
|
386
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
387
|
+
)`);
|
|
388
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_time_logs_task ON task_time_logs(task_id)");
|
|
389
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_time_logs_agent ON task_time_logs(agent_id)");
|
|
390
|
+
ensureColumn("tasks", "actual_minutes", "INTEGER");
|
|
391
|
+
ensureTable("task_watchers", `
|
|
392
|
+
CREATE TABLE task_watchers (
|
|
393
|
+
id TEXT PRIMARY KEY,
|
|
394
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
395
|
+
agent_id TEXT NOT NULL,
|
|
396
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
397
|
+
UNIQUE(task_id, agent_id)
|
|
398
|
+
)`);
|
|
399
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_watchers_task ON task_watchers(task_id)");
|
|
400
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_watchers_agent ON task_watchers(agent_id)");
|
|
401
|
+
ensureColumn("task_dependencies", "external_project_id", "TEXT");
|
|
402
|
+
ensureColumn("task_dependencies", "external_task_id", "TEXT");
|
|
387
403
|
}
|
|
388
404
|
function backfillTaskTags(db) {
|
|
389
405
|
try {
|
|
@@ -1111,6 +1127,39 @@ var init_schema = __esm(() => {
|
|
|
1111
1127
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id, machine_id) WHERE short_id IS NOT NULL AND machine_id IS NOT NULL;
|
|
1112
1128
|
CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
|
|
1113
1129
|
INSERT OR IGNORE INTO _migrations (id) VALUES (44);
|
|
1130
|
+
`,
|
|
1131
|
+
`
|
|
1132
|
+
CREATE TABLE IF NOT EXISTS task_time_logs (
|
|
1133
|
+
id TEXT PRIMARY KEY,
|
|
1134
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1135
|
+
agent_id TEXT,
|
|
1136
|
+
started_at TEXT,
|
|
1137
|
+
ended_at TEXT,
|
|
1138
|
+
minutes INTEGER NOT NULL,
|
|
1139
|
+
notes TEXT,
|
|
1140
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1141
|
+
);
|
|
1142
|
+
CREATE INDEX IF NOT EXISTS idx_task_time_logs_task ON task_time_logs(task_id);
|
|
1143
|
+
CREATE INDEX IF NOT EXISTS idx_task_time_logs_agent ON task_time_logs(agent_id);
|
|
1144
|
+
ALTER TABLE tasks ADD COLUMN actual_minutes INTEGER;
|
|
1145
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (45);
|
|
1146
|
+
`,
|
|
1147
|
+
`
|
|
1148
|
+
CREATE TABLE IF NOT EXISTS task_watchers (
|
|
1149
|
+
id TEXT PRIMARY KEY,
|
|
1150
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1151
|
+
agent_id TEXT NOT NULL,
|
|
1152
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1153
|
+
UNIQUE(task_id, agent_id)
|
|
1154
|
+
);
|
|
1155
|
+
CREATE INDEX IF NOT EXISTS idx_task_watchers_task ON task_watchers(task_id);
|
|
1156
|
+
CREATE INDEX IF NOT EXISTS idx_task_watchers_agent ON task_watchers(agent_id);
|
|
1157
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (46);
|
|
1158
|
+
`,
|
|
1159
|
+
`
|
|
1160
|
+
ALTER TABLE task_dependencies ADD COLUMN external_project_id TEXT;
|
|
1161
|
+
ALTER TABLE task_dependencies ADD COLUMN external_task_id TEXT;
|
|
1162
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (47);
|
|
1114
1163
|
`
|
|
1115
1164
|
];
|
|
1116
1165
|
});
|
|
@@ -1341,7 +1390,7 @@ var init_database = __esm(() => {
|
|
|
1341
1390
|
init_machines();
|
|
1342
1391
|
});
|
|
1343
1392
|
|
|
1344
|
-
// node_modules/@hasna/cloud/dist/index.js
|
|
1393
|
+
// ../../../../node_modules/@hasna/cloud/dist/index.js
|
|
1345
1394
|
var exports_dist = {};
|
|
1346
1395
|
__export(exports_dist, {
|
|
1347
1396
|
translateSql: () => translateSql,
|
|
@@ -1436,11 +1485,11 @@ import { join as join5 } from "path";
|
|
|
1436
1485
|
import { join as join6, dirname as dirname2 } from "path";
|
|
1437
1486
|
import { existsSync as existsSync6, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
1438
1487
|
import { homedir as homedir5, platform } from "os";
|
|
1439
|
-
function
|
|
1488
|
+
function __accessProp(key) {
|
|
1440
1489
|
return this[key];
|
|
1441
1490
|
}
|
|
1442
|
-
function
|
|
1443
|
-
this[name] =
|
|
1491
|
+
function __exportSetter(name, newValue) {
|
|
1492
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
1444
1493
|
}
|
|
1445
1494
|
function translateSql(sql, dialect) {
|
|
1446
1495
|
if (dialect === "sqlite")
|
|
@@ -3897,7 +3946,7 @@ async function ensureAllPgDatabases() {
|
|
|
3897
3946
|
}
|
|
3898
3947
|
return results;
|
|
3899
3948
|
}
|
|
3900
|
-
function registerCloudTools(server, serviceName) {
|
|
3949
|
+
function registerCloudTools(server, serviceName, opts = {}) {
|
|
3901
3950
|
server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
|
|
3902
3951
|
const config = getCloudConfig();
|
|
3903
3952
|
const lines = [
|
|
@@ -3930,8 +3979,13 @@ function registerCloudTools(server, serviceName) {
|
|
|
3930
3979
|
isError: true
|
|
3931
3980
|
};
|
|
3932
3981
|
}
|
|
3933
|
-
const local = new SqliteAdapter(getDbPath2(serviceName));
|
|
3982
|
+
const local = new SqliteAdapter(opts.dbPath ?? getDbPath2(serviceName));
|
|
3934
3983
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
3984
|
+
if (opts.migrations?.length) {
|
|
3985
|
+
for (const sql of opts.migrations) {
|
|
3986
|
+
await cloud.run(sql);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3935
3989
|
const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
|
|
3936
3990
|
const results = await syncPush(local, cloud, { tables: tableList });
|
|
3937
3991
|
local.close();
|
|
@@ -3953,7 +4007,7 @@ function registerCloudTools(server, serviceName) {
|
|
|
3953
4007
|
isError: true
|
|
3954
4008
|
};
|
|
3955
4009
|
}
|
|
3956
|
-
const local = new SqliteAdapter(getDbPath2(serviceName));
|
|
4010
|
+
const local = new SqliteAdapter(opts.dbPath ?? getDbPath2(serviceName));
|
|
3957
4011
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
3958
4012
|
let tableList;
|
|
3959
4013
|
if (tablesStr) {
|
|
@@ -4089,19 +4143,19 @@ var __create, __getProtoOf, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toE
|
|
|
4089
4143
|
for (let key of __getOwnPropNames2(mod))
|
|
4090
4144
|
if (!__hasOwnProp2.call(to, key))
|
|
4091
4145
|
__defProp2(to, key, {
|
|
4092
|
-
get:
|
|
4146
|
+
get: __accessProp.bind(mod, key),
|
|
4093
4147
|
enumerable: true
|
|
4094
4148
|
});
|
|
4095
4149
|
if (canCache)
|
|
4096
4150
|
cache.set(mod, to);
|
|
4097
4151
|
return to;
|
|
4098
|
-
}, __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports),
|
|
4152
|
+
}, __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue = (v) => v, __export2 = (target, all) => {
|
|
4099
4153
|
for (var name in all)
|
|
4100
4154
|
__defProp2(target, name, {
|
|
4101
4155
|
get: all[name],
|
|
4102
4156
|
enumerable: true,
|
|
4103
4157
|
configurable: true,
|
|
4104
|
-
set:
|
|
4158
|
+
set: __exportSetter.bind(all, name)
|
|
4105
4159
|
});
|
|
4106
4160
|
}, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), __require, require_postgres_array, require_arrayParser, require_postgres_date, require_mutable, require_postgres_interval, require_postgres_bytea, require_textParsers, require_pg_int8, require_binaryParsers, require_builtins, require_pg_types, require_defaults, require_utils, require_utils_legacy, require_utils_webcrypto, require_utils2, require_cert_signatures, require_sasl, require_type_overrides, require_pg_connection_string, require_connection_parameters, require_result, require_query, require_messages, require_buffer_writer, require_serializer, require_buffer_reader, require_parser, require_dist, require_empty, require_stream, require_connection, require_split2, require_helper, require_lib, require_client, require_pg_pool, require_query2, require_client2, require_lib2, import_lib, Client, Pool, Connection, types2, Query, DatabaseError, escapeIdentifier, escapeLiteral, Result, TypeOverrides, defaults, esm_default, init_esm, init_adapter, util3, objectUtil2, ZodParsedType2, getParsedType2 = (data) => {
|
|
4107
4161
|
const t = typeof data;
|
|
@@ -13018,6 +13072,7 @@ var init_recurrence = __esm(() => {
|
|
|
13018
13072
|
// src/db/webhooks.ts
|
|
13019
13073
|
var exports_webhooks = {};
|
|
13020
13074
|
__export(exports_webhooks, {
|
|
13075
|
+
validateWebhookUrl: () => validateWebhookUrl,
|
|
13021
13076
|
listWebhooks: () => listWebhooks,
|
|
13022
13077
|
listDeliveries: () => listDeliveries,
|
|
13023
13078
|
getWebhook: () => getWebhook,
|
|
@@ -13025,6 +13080,40 @@ __export(exports_webhooks, {
|
|
|
13025
13080
|
deleteWebhook: () => deleteWebhook,
|
|
13026
13081
|
createWebhook: () => createWebhook
|
|
13027
13082
|
});
|
|
13083
|
+
function validateWebhookUrl(urlString) {
|
|
13084
|
+
try {
|
|
13085
|
+
const url = new URL(urlString);
|
|
13086
|
+
if (url.protocol !== "https:") {
|
|
13087
|
+
throw new Error("Webhook URLs must use HTTPS");
|
|
13088
|
+
}
|
|
13089
|
+
const hostname2 = url.hostname.toLowerCase();
|
|
13090
|
+
if (hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2 === "0.0.0.0") {
|
|
13091
|
+
throw new Error("Webhook URLs cannot target localhost");
|
|
13092
|
+
}
|
|
13093
|
+
if (hostname2 === "169.254.169.254" || hostname2.startsWith("169.254.")) {
|
|
13094
|
+
throw new Error("Webhook URLs cannot target cloud metadata endpoints");
|
|
13095
|
+
}
|
|
13096
|
+
const privateRanges = [
|
|
13097
|
+
/^10\./,
|
|
13098
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
13099
|
+
/^192\.168\./,
|
|
13100
|
+
/^127\./,
|
|
13101
|
+
/^169\.254\./,
|
|
13102
|
+
/^fc00:/i,
|
|
13103
|
+
/^fe80:/i
|
|
13104
|
+
];
|
|
13105
|
+
for (const range of privateRanges) {
|
|
13106
|
+
if (range.test(hostname2)) {
|
|
13107
|
+
throw new Error("Webhook URLs cannot target private IP ranges");
|
|
13108
|
+
}
|
|
13109
|
+
}
|
|
13110
|
+
} catch (e) {
|
|
13111
|
+
if (e instanceof Error && e.message.startsWith("Webhook URLs")) {
|
|
13112
|
+
throw e;
|
|
13113
|
+
}
|
|
13114
|
+
throw new Error(`Invalid webhook URL: ${urlString}`);
|
|
13115
|
+
}
|
|
13116
|
+
}
|
|
13028
13117
|
function rowToWebhook(row) {
|
|
13029
13118
|
return {
|
|
13030
13119
|
...row,
|
|
@@ -13038,6 +13127,7 @@ function rowToWebhook(row) {
|
|
|
13038
13127
|
}
|
|
13039
13128
|
function createWebhook(input, db) {
|
|
13040
13129
|
const d = db || getDatabase();
|
|
13130
|
+
validateWebhookUrl(input.url);
|
|
13041
13131
|
const id = uuid();
|
|
13042
13132
|
d.run(`INSERT INTO webhooks (id, url, events, secret, project_id, task_list_id, agent_id, task_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
13043
13133
|
id,
|
|
@@ -13636,7 +13726,9 @@ var init_checklists = __esm(() => {
|
|
|
13636
13726
|
// src/db/tasks.ts
|
|
13637
13727
|
var exports_tasks = {};
|
|
13638
13728
|
__export(exports_tasks, {
|
|
13729
|
+
watchTask: () => watchTask,
|
|
13639
13730
|
updateTask: () => updateTask,
|
|
13731
|
+
unwatchTask: () => unwatchTask,
|
|
13640
13732
|
unlockTask: () => unlockTask,
|
|
13641
13733
|
unarchiveTask: () => unarchiveTask,
|
|
13642
13734
|
stealTask: () => stealTask,
|
|
@@ -13645,12 +13737,17 @@ __export(exports_tasks, {
|
|
|
13645
13737
|
setTaskPriority: () => setTaskPriority,
|
|
13646
13738
|
removeDependency: () => removeDependency,
|
|
13647
13739
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
13740
|
+
notifyWatchers: () => notifyWatchers,
|
|
13648
13741
|
moveTask: () => moveTask,
|
|
13742
|
+
logTime: () => logTime,
|
|
13649
13743
|
logCost: () => logCost,
|
|
13650
13744
|
lockTask: () => lockTask,
|
|
13651
13745
|
listTasks: () => listTasks,
|
|
13746
|
+
getTimeReport: () => getTimeReport,
|
|
13747
|
+
getTimeLogs: () => getTimeLogs,
|
|
13652
13748
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
13653
13749
|
getTaskWithRelations: () => getTaskWithRelations,
|
|
13750
|
+
getTaskWatchers: () => getTaskWatchers,
|
|
13654
13751
|
getTaskStats: () => getTaskStats,
|
|
13655
13752
|
getTaskGraph: () => getTaskGraph,
|
|
13656
13753
|
getTaskDependents: () => getTaskDependents,
|
|
@@ -14833,6 +14930,58 @@ function getOverdueTasks(projectId, db) {
|
|
|
14833
14930
|
const rows = d.query(query).all(...params);
|
|
14834
14931
|
return rows.map(rowToTask);
|
|
14835
14932
|
}
|
|
14933
|
+
function logTime(input, db) {
|
|
14934
|
+
const d = db || getDatabase();
|
|
14935
|
+
const id = uuid();
|
|
14936
|
+
const ts = now();
|
|
14937
|
+
d.run(`INSERT INTO task_time_logs (id, task_id, agent_id, minutes, started_at, ended_at, notes, created_at)
|
|
14938
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.task_id, input.agent_id || null, input.minutes, input.started_at || null, input.ended_at || null, input.notes || null, ts]);
|
|
14939
|
+
return { id, task_id: input.task_id, agent_id: input.agent_id || null, minutes: input.minutes, started_at: input.started_at || null, ended_at: input.ended_at || null, notes: input.notes || null, created_at: ts };
|
|
14940
|
+
}
|
|
14941
|
+
function getTimeLogs(taskId, db) {
|
|
14942
|
+
const d = db || getDatabase();
|
|
14943
|
+
return d.query(`SELECT * FROM task_time_logs WHERE task_id = ? ORDER BY created_at DESC`).all(taskId);
|
|
14944
|
+
}
|
|
14945
|
+
function getTimeReport(opts, db) {
|
|
14946
|
+
const d = db || getDatabase();
|
|
14947
|
+
let query = `SELECT t.id as task_id, t.title, t.estimated_minutes, t.actual_minutes FROM tasks t WHERE t.status = 'completed'`;
|
|
14948
|
+
const params = [];
|
|
14949
|
+
if (opts?.project_id) {
|
|
14950
|
+
query += ` AND t.project_id = ?`;
|
|
14951
|
+
params.push(opts.project_id);
|
|
14952
|
+
}
|
|
14953
|
+
if (opts?.agent_id) {
|
|
14954
|
+
query += ` AND t.assigned_to = ?`;
|
|
14955
|
+
params.push(opts.agent_id);
|
|
14956
|
+
}
|
|
14957
|
+
if (opts?.since) {
|
|
14958
|
+
query += ` AND t.completed_at >= ?`;
|
|
14959
|
+
params.push(opts.since);
|
|
14960
|
+
}
|
|
14961
|
+
const rows = d.query(query).all(...params);
|
|
14962
|
+
return rows.map((row) => ({ ...row, time_logs: getTimeLogs(row.task_id, d) }));
|
|
14963
|
+
}
|
|
14964
|
+
function watchTask(taskId, agentId, db) {
|
|
14965
|
+
const d = db || getDatabase();
|
|
14966
|
+
const id = uuid();
|
|
14967
|
+
const ts = now();
|
|
14968
|
+
d.run(`INSERT OR IGNORE INTO task_watchers (id, task_id, agent_id, created_at) VALUES (?, ?, ?, ?)`, [id, taskId, agentId, ts]);
|
|
14969
|
+
const existing = d.query(`SELECT * FROM task_watchers WHERE task_id = ? AND agent_id = ?`).get(taskId, agentId);
|
|
14970
|
+
return existing;
|
|
14971
|
+
}
|
|
14972
|
+
function unwatchTask(taskId, agentId, db) {
|
|
14973
|
+
const d = db || getDatabase();
|
|
14974
|
+
const result = d.run(`DELETE FROM task_watchers WHERE task_id = ? AND agent_id = ?`, [taskId, agentId]);
|
|
14975
|
+
return result.changes > 0;
|
|
14976
|
+
}
|
|
14977
|
+
function getTaskWatchers(taskId, db) {
|
|
14978
|
+
const d = db || getDatabase();
|
|
14979
|
+
return d.query(`SELECT * FROM task_watchers WHERE task_id = ?`).all(taskId);
|
|
14980
|
+
}
|
|
14981
|
+
function notifyWatchers(taskId, event, data, db) {
|
|
14982
|
+
const watchers = getTaskWatchers(taskId, db);
|
|
14983
|
+
dispatchWebhook(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
|
|
14984
|
+
}
|
|
14836
14985
|
var init_tasks = __esm(() => {
|
|
14837
14986
|
init_types2();
|
|
14838
14987
|
init_database();
|
|
@@ -14919,6 +15068,9 @@ function registerAgent(input, db) {
|
|
|
14919
15068
|
if (callerHasNoSession && existingHasActiveSession) {
|
|
14920
15069
|
return buildConflictError(existing, lastSeenMs, input.pool, d);
|
|
14921
15070
|
}
|
|
15071
|
+
if (input.project_id && existing.session_id && isActive && existing.active_project_id && existing.active_project_id !== input.project_id) {
|
|
15072
|
+
return buildConflictError(existing, lastSeenMs, input.pool, d);
|
|
15073
|
+
}
|
|
14922
15074
|
}
|
|
14923
15075
|
const updates = ["last_seen_at = ?", "status = 'active'"];
|
|
14924
15076
|
const params = [now()];
|
|
@@ -14934,14 +15086,18 @@ function registerAgent(input, db) {
|
|
|
14934
15086
|
updates.push("description = ?");
|
|
14935
15087
|
params.push(input.description);
|
|
14936
15088
|
}
|
|
15089
|
+
if (input.project_id && input.session_id) {
|
|
15090
|
+
updates.push("active_project_id = ?");
|
|
15091
|
+
params.push(input.project_id);
|
|
15092
|
+
}
|
|
14937
15093
|
params.push(existing.id);
|
|
14938
15094
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
14939
15095
|
return getAgent(existing.id, d);
|
|
14940
15096
|
}
|
|
14941
15097
|
const id = shortUuid();
|
|
14942
15098
|
const timestamp = now();
|
|
14943
|
-
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir)
|
|
14944
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
15099
|
+
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir, active_project_id)
|
|
15100
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
14945
15101
|
id,
|
|
14946
15102
|
normalizedName,
|
|
14947
15103
|
input.description || null,
|
|
@@ -14956,7 +15112,8 @@ function registerAgent(input, db) {
|
|
|
14956
15112
|
timestamp,
|
|
14957
15113
|
timestamp,
|
|
14958
15114
|
input.session_id || null,
|
|
14959
|
-
input.working_dir || null
|
|
15115
|
+
input.working_dir || null,
|
|
15116
|
+
input.project_id && input.session_id ? input.project_id : null
|
|
14960
15117
|
]);
|
|
14961
15118
|
return getAgent(id, d);
|
|
14962
15119
|
}
|
|
@@ -17825,6 +17982,39 @@ var init_pg_migrations = __esm(() => {
|
|
|
17825
17982
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id, machine_id) WHERE short_id IS NOT NULL AND machine_id IS NOT NULL;
|
|
17826
17983
|
CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
|
|
17827
17984
|
INSERT INTO _migrations (id) VALUES (40) ON CONFLICT DO NOTHING;
|
|
17985
|
+
`,
|
|
17986
|
+
`
|
|
17987
|
+
CREATE TABLE IF NOT EXISTS task_time_logs (
|
|
17988
|
+
id TEXT PRIMARY KEY,
|
|
17989
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
17990
|
+
agent_id TEXT,
|
|
17991
|
+
started_at TIMESTAMPTZ,
|
|
17992
|
+
ended_at TIMESTAMPTZ,
|
|
17993
|
+
minutes INTEGER NOT NULL,
|
|
17994
|
+
notes TEXT,
|
|
17995
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
17996
|
+
);
|
|
17997
|
+
CREATE INDEX IF NOT EXISTS idx_task_time_logs_task ON task_time_logs(task_id);
|
|
17998
|
+
CREATE INDEX IF NOT EXISTS idx_task_time_logs_agent ON task_time_logs(agent_id);
|
|
17999
|
+
ALTER TABLE tasks ADD COLUMN actual_minutes INTEGER;
|
|
18000
|
+
INSERT INTO _migrations (id) VALUES (41) ON CONFLICT DO NOTHING;
|
|
18001
|
+
`,
|
|
18002
|
+
`
|
|
18003
|
+
CREATE TABLE IF NOT EXISTS task_watchers (
|
|
18004
|
+
id TEXT PRIMARY KEY,
|
|
18005
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
18006
|
+
agent_id TEXT NOT NULL,
|
|
18007
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18008
|
+
UNIQUE(task_id, agent_id)
|
|
18009
|
+
);
|
|
18010
|
+
CREATE INDEX IF NOT EXISTS idx_task_watchers_task ON task_watchers(task_id);
|
|
18011
|
+
CREATE INDEX IF NOT EXISTS idx_task_watchers_agent ON task_watchers(agent_id);
|
|
18012
|
+
INSERT INTO _migrations (id) VALUES (42) ON CONFLICT DO NOTHING;
|
|
18013
|
+
`,
|
|
18014
|
+
`
|
|
18015
|
+
ALTER TABLE task_dependencies ADD COLUMN external_project_id TEXT;
|
|
18016
|
+
ALTER TABLE task_dependencies ADD COLUMN external_task_id TEXT;
|
|
18017
|
+
INSERT INTO _migrations (id) VALUES (43) ON CONFLICT DO NOTHING;
|
|
17828
18018
|
`
|
|
17829
18019
|
];
|
|
17830
18020
|
});
|
|
@@ -23404,7 +23594,8 @@ function registerWebhookTools(server, { shouldRegisterTool, formatError }) {
|
|
|
23404
23594
|
task_id: exports_external.string().optional().describe("Only fire for events on this specific task")
|
|
23405
23595
|
}, async (params) => {
|
|
23406
23596
|
try {
|
|
23407
|
-
const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
23597
|
+
const { createWebhook: createWebhook2, validateWebhookUrl: validateWebhookUrl2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
23598
|
+
validateWebhookUrl2(params.url);
|
|
23408
23599
|
const wh = createWebhook2(params);
|
|
23409
23600
|
const scope = [wh.project_id && `project:${wh.project_id}`, wh.task_list_id && `list:${wh.task_list_id}`, wh.agent_id && `agent:${wh.agent_id}`, wh.task_id && `task:${wh.task_id}`].filter(Boolean).join(", ");
|
|
23410
23601
|
return { content: [{ type: "text", text: `Webhook created: ${wh.id.slice(0, 8)} | ${wh.url} | events: ${wh.events.length === 0 ? "all" : wh.events.join(",")}${scope ? ` | scope: ${scope}` : ""}` }] };
|
|
@@ -25688,7 +25879,7 @@ Blocks:
|
|
|
25688
25879
|
}
|
|
25689
25880
|
if (shouldRegisterTool("bulk_create_tasks")) {
|
|
25690
25881
|
server.tool("bulk_create_tasks", "Create multiple tasks atomically with dependency support.", {
|
|
25691
|
-
tasks: exports_external.array(exports_external.object({
|
|
25882
|
+
tasks: exports_external.preprocess((val) => typeof val === "string" ? JSON.parse(val) : val, exports_external.array(exports_external.object({
|
|
25692
25883
|
temp_id: exports_external.string().optional(),
|
|
25693
25884
|
title: exports_external.string(),
|
|
25694
25885
|
description: exports_external.string().optional(),
|
|
@@ -25702,7 +25893,7 @@ if (shouldRegisterTool("bulk_create_tasks")) {
|
|
|
25702
25893
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
25703
25894
|
estimated_minutes: exports_external.number().optional(),
|
|
25704
25895
|
depends_on_temp_ids: exports_external.array(exports_external.string()).optional()
|
|
25705
|
-
})),
|
|
25896
|
+
}))),
|
|
25706
25897
|
project_id: exports_external.string().optional(),
|
|
25707
25898
|
plan_id: exports_external.string().optional(),
|
|
25708
25899
|
task_list_id: exports_external.string().optional()
|
|
@@ -27438,6 +27629,143 @@ if (shouldRegisterTool("score_task")) {
|
|
|
27438
27629
|
}
|
|
27439
27630
|
});
|
|
27440
27631
|
}
|
|
27632
|
+
if (shouldRegisterTool("log_time")) {
|
|
27633
|
+
server.tool("log_time", "Log time spent on a task.", {
|
|
27634
|
+
task_id: exports_external.string().describe("Task ID to log time against"),
|
|
27635
|
+
minutes: exports_external.number().min(1).describe("Minutes spent"),
|
|
27636
|
+
agent_id: exports_external.string().optional().describe("Agent logging the time"),
|
|
27637
|
+
started_at: exports_external.string().optional().describe("ISO timestamp when work started"),
|
|
27638
|
+
ended_at: exports_external.string().optional().describe("ISO timestamp when work ended"),
|
|
27639
|
+
notes: exports_external.string().optional().describe("Notes about what was done")
|
|
27640
|
+
}, async ({ task_id, minutes, agent_id, started_at, ended_at, notes }) => {
|
|
27641
|
+
try {
|
|
27642
|
+
const { logTime: logTime2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27643
|
+
logTime2({ task_id: resolveId(task_id), minutes, agent_id, started_at, ended_at, notes });
|
|
27644
|
+
return { content: [{ type: "text", text: `Logged ${minutes} min on task ${task_id.slice(0, 8)}` }] };
|
|
27645
|
+
} catch (e) {
|
|
27646
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27647
|
+
}
|
|
27648
|
+
});
|
|
27649
|
+
}
|
|
27650
|
+
if (shouldRegisterTool("get_time_report")) {
|
|
27651
|
+
server.tool("get_time_report", "Get time tracking report: actual vs estimated minutes for completed tasks.", {
|
|
27652
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
27653
|
+
agent_id: exports_external.string().optional().describe("Filter by assignee"),
|
|
27654
|
+
since: exports_external.string().optional().describe("ISO date \u2014 only tasks completed after this date")
|
|
27655
|
+
}, async ({ project_id, agent_id, since }) => {
|
|
27656
|
+
try {
|
|
27657
|
+
const { getTimeReport: getTimeReport2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27658
|
+
const report = getTimeReport2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, agent_id, since });
|
|
27659
|
+
if (report.length === 0)
|
|
27660
|
+
return { content: [{ type: "text", text: "No completed tasks found." }] };
|
|
27661
|
+
const lines = report.map((r) => {
|
|
27662
|
+
const est = r.estimated_minutes ?? "?";
|
|
27663
|
+
const actual = r.actual_minutes ?? "?";
|
|
27664
|
+
const diff = r.estimated_minutes != null && r.actual_minutes != null ? ` (${r.actual_minutes - r.estimated_minutes >= 0 ? "+" : ""}${r.actual_minutes - r.estimated_minutes})` : "";
|
|
27665
|
+
return `${r.title.slice(0, 50)}: estimated ${est}min, actual ${actual}min${diff}`;
|
|
27666
|
+
});
|
|
27667
|
+
return { content: [{ type: "text", text: `Time Report:
|
|
27668
|
+
${lines.join(`
|
|
27669
|
+
`)}` }] };
|
|
27670
|
+
} catch (e) {
|
|
27671
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27672
|
+
}
|
|
27673
|
+
});
|
|
27674
|
+
}
|
|
27675
|
+
if (shouldRegisterTool("watch_task")) {
|
|
27676
|
+
server.tool("watch_task", "Subscribe to notifications for a task.", {
|
|
27677
|
+
task_id: exports_external.string().describe("Task ID to watch"),
|
|
27678
|
+
agent_id: exports_external.string().optional().describe("Agent subscribing (defaults to context agent)")
|
|
27679
|
+
}, async ({ task_id, agent_id }) => {
|
|
27680
|
+
try {
|
|
27681
|
+
const { watchTask: watchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27682
|
+
watchTask2(resolveId(task_id), agent_id || "");
|
|
27683
|
+
return { content: [{ type: "text", text: `Now watching task ${task_id.slice(0, 8)}` }] };
|
|
27684
|
+
} catch (e) {
|
|
27685
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27686
|
+
}
|
|
27687
|
+
});
|
|
27688
|
+
}
|
|
27689
|
+
if (shouldRegisterTool("unwatch_task")) {
|
|
27690
|
+
server.tool("unwatch_task", "Unsubscribe from notifications for a task.", {
|
|
27691
|
+
task_id: exports_external.string().describe("Task ID to unwatch"),
|
|
27692
|
+
agent_id: exports_external.string().optional().describe("Agent unsubscribing (defaults to context agent)")
|
|
27693
|
+
}, async ({ task_id, agent_id }) => {
|
|
27694
|
+
try {
|
|
27695
|
+
const { unwatchTask: unwatchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27696
|
+
unwatchTask2(resolveId(task_id), agent_id || "");
|
|
27697
|
+
return { content: [{ type: "text", text: `Stopped watching task ${task_id.slice(0, 8)}` }] };
|
|
27698
|
+
} catch (e) {
|
|
27699
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27700
|
+
}
|
|
27701
|
+
});
|
|
27702
|
+
}
|
|
27703
|
+
if (shouldRegisterTool("get_task_watchers")) {
|
|
27704
|
+
server.tool("get_task_watchers", "List agents watching a task.", {
|
|
27705
|
+
task_id: exports_external.string().describe("Task ID")
|
|
27706
|
+
}, async ({ task_id }) => {
|
|
27707
|
+
try {
|
|
27708
|
+
const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27709
|
+
const watchers = getTaskWatchers2(resolveId(task_id));
|
|
27710
|
+
if (watchers.length === 0)
|
|
27711
|
+
return { content: [{ type: "text", text: "No watchers." }] };
|
|
27712
|
+
return { content: [{ type: "text", text: `Watching (${watchers.length}): ${watchers.map((w) => w.agent_id).join(", ")}` }] };
|
|
27713
|
+
} catch (e) {
|
|
27714
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27715
|
+
}
|
|
27716
|
+
});
|
|
27717
|
+
}
|
|
27718
|
+
if (shouldRegisterTool("todos_retro")) {
|
|
27719
|
+
server.tool("todos_retro", "Post-completion retrospective: stats on completed tasks, low-confidence completions, avg time vs estimate, and patterns.", {
|
|
27720
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
27721
|
+
plan_id: exports_external.string().optional().describe("Filter by plan"),
|
|
27722
|
+
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
27723
|
+
since: exports_external.string().optional().describe("ISO date \u2014 only tasks completed after this date"),
|
|
27724
|
+
agent_id: exports_external.string().optional().describe("Filter by assignee")
|
|
27725
|
+
}, async ({ project_id, plan_id, task_list_id, since, agent_id }) => {
|
|
27726
|
+
try {
|
|
27727
|
+
const { listTasks: listTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27728
|
+
const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
27729
|
+
const completed = listTasks2({ status: "completed", project_id, plan_id, task_list_id, assigned_to: agent_id, limit: 500 }, undefined);
|
|
27730
|
+
const filtered = since ? completed.filter((t) => t.completed_at && t.completed_at >= since) : completed;
|
|
27731
|
+
const total = filtered.length;
|
|
27732
|
+
const lowConf = filtered.filter((t) => t.confidence != null && t.confidence < 0.7).length;
|
|
27733
|
+
const withEstimate = filtered.filter((t) => t.estimated_minutes != null && t.actual_minutes != null);
|
|
27734
|
+
const avgDiff = withEstimate.length > 0 ? withEstimate.reduce((acc, t) => acc + (t.actual_minutes - t.estimated_minutes), 0) / withEstimate.length : 0;
|
|
27735
|
+
const patrolResult = patrolTasks2({ project_id: project_id ? resolveId(project_id, "projects") : undefined });
|
|
27736
|
+
const stuck = patrolResult.issues.filter((i) => i.type === "stuck").length;
|
|
27737
|
+
const lines = [
|
|
27738
|
+
`Retro (${total} completed tasks${since ? ` since ${since}` : ""})`,
|
|
27739
|
+
`Low confidence: ${lowConf}/${total}`,
|
|
27740
|
+
`Avg time vs estimate: ${avgDiff >= 0 ? "+" : ""}${avgDiff.toFixed(1)}min`,
|
|
27741
|
+
`Currently stuck: ${stuck}`
|
|
27742
|
+
];
|
|
27743
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
27744
|
+
`) }] };
|
|
27745
|
+
} catch (e) {
|
|
27746
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27747
|
+
}
|
|
27748
|
+
});
|
|
27749
|
+
}
|
|
27750
|
+
if (shouldRegisterTool("todos_inbox")) {
|
|
27751
|
+
server.tool("todos_inbox", "Get unassigned tasks (GTD inbox) \u2014 tasks with no assignee, not yet started.", {
|
|
27752
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
27753
|
+
limit: exports_external.number().optional().describe("Max results (default: 20)")
|
|
27754
|
+
}, async ({ project_id, limit }) => {
|
|
27755
|
+
try {
|
|
27756
|
+
const { listTasks: listTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
27757
|
+
const tasks = listTasks2({ status: "pending", project_id, assigned_to: "", limit: limit || 20 }, undefined);
|
|
27758
|
+
if (tasks.length === 0)
|
|
27759
|
+
return { content: [{ type: "text", text: "Inbox is empty." }] };
|
|
27760
|
+
const lines = tasks.map((t) => `[${t.priority}] ${t.title.slice(0, 60)} (${t.id.slice(0, 8)})`);
|
|
27761
|
+
return { content: [{ type: "text", text: `Inbox (${tasks.length}):
|
|
27762
|
+
${lines.join(`
|
|
27763
|
+
`)}` }] };
|
|
27764
|
+
} catch (e) {
|
|
27765
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27766
|
+
}
|
|
27767
|
+
});
|
|
27768
|
+
}
|
|
27441
27769
|
if (shouldRegisterTool("get_agent_metrics")) {
|
|
27442
27770
|
server.tool("get_agent_metrics", "Get performance metrics for an agent: completion rate, speed, confidence, review scores.", {
|
|
27443
27771
|
agent_id: exports_external.string().describe("Agent ID or name"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,KAAK,OAAO,GAAG;IACb,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;CACrC,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,WAAW,EAAE,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,KAAK,OAAO,GAAG;IACb,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;CACrC,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,WAAW,EAAE,EAAE,OAAO,GAAG,IAAI,CAyD1G"}
|