@hasna/todos 0.9.35 → 0.9.38
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 +265 -52
- package/dist/db/tasks.d.ts +14 -0
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -40
- package/dist/mcp/index.js +140 -52
- package/dist/server/index.js +63 -52
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3101,6 +3101,57 @@ var init_recurrence = __esm(() => {
|
|
|
3101
3101
|
};
|
|
3102
3102
|
});
|
|
3103
3103
|
|
|
3104
|
+
// src/db/webhooks.ts
|
|
3105
|
+
var exports_webhooks = {};
|
|
3106
|
+
__export(exports_webhooks, {
|
|
3107
|
+
listWebhooks: () => listWebhooks,
|
|
3108
|
+
getWebhook: () => getWebhook,
|
|
3109
|
+
dispatchWebhook: () => dispatchWebhook,
|
|
3110
|
+
deleteWebhook: () => deleteWebhook,
|
|
3111
|
+
createWebhook: () => createWebhook
|
|
3112
|
+
});
|
|
3113
|
+
function rowToWebhook(row) {
|
|
3114
|
+
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
3115
|
+
}
|
|
3116
|
+
function createWebhook(input, db) {
|
|
3117
|
+
const d = db || getDatabase();
|
|
3118
|
+
const id = uuid();
|
|
3119
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
3120
|
+
return getWebhook(id, d);
|
|
3121
|
+
}
|
|
3122
|
+
function getWebhook(id, db) {
|
|
3123
|
+
const d = db || getDatabase();
|
|
3124
|
+
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
3125
|
+
return row ? rowToWebhook(row) : null;
|
|
3126
|
+
}
|
|
3127
|
+
function listWebhooks(db) {
|
|
3128
|
+
const d = db || getDatabase();
|
|
3129
|
+
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
3130
|
+
}
|
|
3131
|
+
function deleteWebhook(id, db) {
|
|
3132
|
+
const d = db || getDatabase();
|
|
3133
|
+
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
3134
|
+
}
|
|
3135
|
+
async function dispatchWebhook(event, payload, db) {
|
|
3136
|
+
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
3137
|
+
for (const wh of webhooks) {
|
|
3138
|
+
try {
|
|
3139
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
3140
|
+
const headers = { "Content-Type": "application/json" };
|
|
3141
|
+
if (wh.secret) {
|
|
3142
|
+
const encoder = new TextEncoder;
|
|
3143
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
3144
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
3145
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3146
|
+
}
|
|
3147
|
+
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
3148
|
+
} catch {}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
var init_webhooks = __esm(() => {
|
|
3152
|
+
init_database();
|
|
3153
|
+
});
|
|
3154
|
+
|
|
3104
3155
|
// src/db/tasks.ts
|
|
3105
3156
|
function rowToTask(row) {
|
|
3106
3157
|
return {
|
|
@@ -3163,7 +3214,9 @@ function createTask(input, db) {
|
|
|
3163
3214
|
if (tags.length > 0) {
|
|
3164
3215
|
insertTaskTags(id, tags, d);
|
|
3165
3216
|
}
|
|
3166
|
-
|
|
3217
|
+
const task = getTask(id, d);
|
|
3218
|
+
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
3219
|
+
return task;
|
|
3167
3220
|
}
|
|
3168
3221
|
function getTask(id, db) {
|
|
3169
3222
|
const d = db || getDatabase();
|
|
@@ -3436,6 +3489,12 @@ function updateTask(id, input, db) {
|
|
|
3436
3489
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
3437
3490
|
if (input.approved_by !== undefined)
|
|
3438
3491
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
3492
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
3493
|
+
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
3494
|
+
}
|
|
3495
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
3496
|
+
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
3497
|
+
}
|
|
3439
3498
|
return {
|
|
3440
3499
|
...task,
|
|
3441
3500
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
@@ -3487,6 +3546,7 @@ function startTask(id, agentId, db) {
|
|
|
3487
3546
|
}
|
|
3488
3547
|
}
|
|
3489
3548
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
3549
|
+
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
3490
3550
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
3491
3551
|
}
|
|
3492
3552
|
function completeTask(id, agentId, db, options) {
|
|
@@ -3508,6 +3568,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
3508
3568
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
3509
3569
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
3510
3570
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
3571
|
+
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
3511
3572
|
let spawnedTask = null;
|
|
3512
3573
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
3513
3574
|
spawnedTask = spawnNextRecurrence(task, d);
|
|
@@ -3786,6 +3847,7 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
3786
3847
|
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
3787
3848
|
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
3788
3849
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
3850
|
+
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
3789
3851
|
const failedTask = {
|
|
3790
3852
|
...task,
|
|
3791
3853
|
status: "failed",
|
|
@@ -3838,6 +3900,37 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
3838
3900
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
3839
3901
|
return rows.map(rowToTask);
|
|
3840
3902
|
}
|
|
3903
|
+
function getStatus(filters, agentId, db) {
|
|
3904
|
+
const d = db || getDatabase();
|
|
3905
|
+
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
3906
|
+
const in_progress = countTasks({ ...filters, status: "in_progress" }, d);
|
|
3907
|
+
const completed = countTasks({ ...filters, status: "completed" }, d);
|
|
3908
|
+
const total = countTasks(filters || {}, d);
|
|
3909
|
+
const active_work = getActiveWork(filters, d);
|
|
3910
|
+
const next_task = getNextTask(agentId, filters, d);
|
|
3911
|
+
const stale = getStaleTasks(30, filters, d);
|
|
3912
|
+
const conditions = ["recurrence_rule IS NOT NULL", "status = 'pending'", "due_at < ?"];
|
|
3913
|
+
const params = [now()];
|
|
3914
|
+
if (filters?.project_id) {
|
|
3915
|
+
conditions.push("project_id = ?");
|
|
3916
|
+
params.push(filters.project_id);
|
|
3917
|
+
}
|
|
3918
|
+
if (filters?.task_list_id) {
|
|
3919
|
+
conditions.push("task_list_id = ?");
|
|
3920
|
+
params.push(filters.task_list_id);
|
|
3921
|
+
}
|
|
3922
|
+
const overdueRow = d.query(`SELECT COUNT(*) as count FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params);
|
|
3923
|
+
return {
|
|
3924
|
+
pending,
|
|
3925
|
+
in_progress,
|
|
3926
|
+
completed,
|
|
3927
|
+
total,
|
|
3928
|
+
active_work,
|
|
3929
|
+
next_task,
|
|
3930
|
+
stale_count: stale.length,
|
|
3931
|
+
overdue_recurring: overdueRow.count
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3841
3934
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
3842
3935
|
const visited = new Set;
|
|
3843
3936
|
const queue = [dependsOn];
|
|
@@ -3947,6 +4040,7 @@ var init_tasks = __esm(() => {
|
|
|
3947
4040
|
init_completion_guard();
|
|
3948
4041
|
init_audit();
|
|
3949
4042
|
init_recurrence();
|
|
4043
|
+
init_webhooks();
|
|
3950
4044
|
});
|
|
3951
4045
|
|
|
3952
4046
|
// src/db/agents.ts
|
|
@@ -8905,57 +8999,6 @@ var init_zod = __esm(() => {
|
|
|
8905
8999
|
init_external();
|
|
8906
9000
|
});
|
|
8907
9001
|
|
|
8908
|
-
// src/db/webhooks.ts
|
|
8909
|
-
var exports_webhooks = {};
|
|
8910
|
-
__export(exports_webhooks, {
|
|
8911
|
-
listWebhooks: () => listWebhooks,
|
|
8912
|
-
getWebhook: () => getWebhook,
|
|
8913
|
-
dispatchWebhook: () => dispatchWebhook,
|
|
8914
|
-
deleteWebhook: () => deleteWebhook,
|
|
8915
|
-
createWebhook: () => createWebhook
|
|
8916
|
-
});
|
|
8917
|
-
function rowToWebhook(row) {
|
|
8918
|
-
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
8919
|
-
}
|
|
8920
|
-
function createWebhook(input, db) {
|
|
8921
|
-
const d = db || getDatabase();
|
|
8922
|
-
const id = uuid();
|
|
8923
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
8924
|
-
return getWebhook(id, d);
|
|
8925
|
-
}
|
|
8926
|
-
function getWebhook(id, db) {
|
|
8927
|
-
const d = db || getDatabase();
|
|
8928
|
-
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
8929
|
-
return row ? rowToWebhook(row) : null;
|
|
8930
|
-
}
|
|
8931
|
-
function listWebhooks(db) {
|
|
8932
|
-
const d = db || getDatabase();
|
|
8933
|
-
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
8934
|
-
}
|
|
8935
|
-
function deleteWebhook(id, db) {
|
|
8936
|
-
const d = db || getDatabase();
|
|
8937
|
-
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
8938
|
-
}
|
|
8939
|
-
async function dispatchWebhook(event, payload, db) {
|
|
8940
|
-
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
8941
|
-
for (const wh of webhooks) {
|
|
8942
|
-
try {
|
|
8943
|
-
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
8944
|
-
const headers = { "Content-Type": "application/json" };
|
|
8945
|
-
if (wh.secret) {
|
|
8946
|
-
const encoder = new TextEncoder;
|
|
8947
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
8948
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
8949
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8950
|
-
}
|
|
8951
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
8952
|
-
} catch {}
|
|
8953
|
-
}
|
|
8954
|
-
}
|
|
8955
|
-
var init_webhooks = __esm(() => {
|
|
8956
|
-
init_database();
|
|
8957
|
-
});
|
|
8958
|
-
|
|
8959
9002
|
// src/mcp/index.ts
|
|
8960
9003
|
var exports_mcp = {};
|
|
8961
9004
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -10376,6 +10419,47 @@ ${text}` }] };
|
|
|
10376
10419
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10377
10420
|
}
|
|
10378
10421
|
});
|
|
10422
|
+
server.tool("get_status", "Get a full project health snapshot \u2014 counts, active work, next task, stale/overdue summary.", {
|
|
10423
|
+
agent_id: exports_external.string().optional(),
|
|
10424
|
+
project_id: exports_external.string().optional(),
|
|
10425
|
+
task_list_id: exports_external.string().optional()
|
|
10426
|
+
}, async ({ agent_id, project_id, task_list_id }) => {
|
|
10427
|
+
try {
|
|
10428
|
+
const filters = {};
|
|
10429
|
+
if (project_id)
|
|
10430
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10431
|
+
if (task_list_id)
|
|
10432
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10433
|
+
const status = getStatus(Object.keys(filters).length > 0 ? filters : undefined, agent_id);
|
|
10434
|
+
const lines = [
|
|
10435
|
+
`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done | ${status.total} total`
|
|
10436
|
+
];
|
|
10437
|
+
if (status.stale_count > 0)
|
|
10438
|
+
lines.push(`\u26A0\uFE0F ${status.stale_count} stale (stuck in_progress)`);
|
|
10439
|
+
if (status.overdue_recurring > 0)
|
|
10440
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
10441
|
+
if (status.active_work.length > 0) {
|
|
10442
|
+
lines.push(`
|
|
10443
|
+
Active (${status.active_work.length}):`);
|
|
10444
|
+
for (const w of status.active_work.slice(0, 5)) {
|
|
10445
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
10446
|
+
lines.push(` ${id} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
if (status.next_task) {
|
|
10450
|
+
lines.push(`
|
|
10451
|
+
Next up:`);
|
|
10452
|
+
lines.push(` ${formatTask(status.next_task)}`);
|
|
10453
|
+
} else {
|
|
10454
|
+
lines.push(`
|
|
10455
|
+
No pending tasks available.`);
|
|
10456
|
+
}
|
|
10457
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
10458
|
+
`) }] };
|
|
10459
|
+
} catch (e) {
|
|
10460
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10461
|
+
}
|
|
10462
|
+
});
|
|
10379
10463
|
server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
|
|
10380
10464
|
const all = [
|
|
10381
10465
|
"create_task",
|
|
@@ -10434,6 +10518,7 @@ ${text}` }] };
|
|
|
10434
10518
|
"get_active_work",
|
|
10435
10519
|
"get_tasks_changed_since",
|
|
10436
10520
|
"get_stale_tasks",
|
|
10521
|
+
"get_status",
|
|
10437
10522
|
"search_tools",
|
|
10438
10523
|
"describe_tools"
|
|
10439
10524
|
];
|
|
@@ -10601,6 +10686,9 @@ ${text}` }] };
|
|
|
10601
10686
|
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
10602
10687
|
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
10603
10688
|
Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
|
|
10689
|
+
get_status: `Get a full project health snapshot \u2014 pending/in_progress/completed counts, active work, next recommended task, stale task count, overdue recurring tasks. Saves 4+ round trips at session start.
|
|
10690
|
+
Params: agent_id(string, optional \u2014 prefers tasks assigned to this agent for next_task), project_id(string, optional), task_list_id(string, optional)
|
|
10691
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
10604
10692
|
search_tools: `List all tool names or filter by substring.
|
|
10605
10693
|
Params: query(string, optional)
|
|
10606
10694
|
Example: {query: 'task'}`,
|
|
@@ -13882,6 +13970,131 @@ program2.command("interactive").description("Launch interactive TUI").action(asy
|
|
|
13882
13970
|
const projectId = autoProject(globalOpts);
|
|
13883
13971
|
renderApp2(projectId);
|
|
13884
13972
|
});
|
|
13973
|
+
program2.command("next").description("Show the best pending task to work on next").option("--agent <id>", "Prefer tasks assigned to this agent").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
13974
|
+
const db = getDatabase();
|
|
13975
|
+
const filters = {};
|
|
13976
|
+
if (opts.project)
|
|
13977
|
+
filters.project_id = opts.project;
|
|
13978
|
+
const task = getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
|
|
13979
|
+
if (!task) {
|
|
13980
|
+
console.log(chalk.dim("No tasks available."));
|
|
13981
|
+
return;
|
|
13982
|
+
}
|
|
13983
|
+
if (opts.json) {
|
|
13984
|
+
console.log(JSON.stringify(task, null, 2));
|
|
13985
|
+
return;
|
|
13986
|
+
}
|
|
13987
|
+
console.log(chalk.bold("Next task:"));
|
|
13988
|
+
console.log(` ${chalk.cyan(task.short_id || task.id.slice(0, 8))} ${chalk.yellow(task.priority)} ${task.title}`);
|
|
13989
|
+
if (task.description)
|
|
13990
|
+
console.log(chalk.dim(` ${task.description.slice(0, 100)}`));
|
|
13991
|
+
});
|
|
13992
|
+
program2.command("claim <agent>").description("Atomically claim the best pending task for an agent").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (agent, opts) => {
|
|
13993
|
+
const db = getDatabase();
|
|
13994
|
+
const filters = {};
|
|
13995
|
+
if (opts.project)
|
|
13996
|
+
filters.project_id = opts.project;
|
|
13997
|
+
const task = claimNextTask(agent, Object.keys(filters).length ? filters : undefined, db);
|
|
13998
|
+
if (!task) {
|
|
13999
|
+
console.log(chalk.dim("No tasks available to claim."));
|
|
14000
|
+
return;
|
|
14001
|
+
}
|
|
14002
|
+
if (opts.json) {
|
|
14003
|
+
console.log(JSON.stringify(task, null, 2));
|
|
14004
|
+
return;
|
|
14005
|
+
}
|
|
14006
|
+
console.log(chalk.green(`Claimed: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
14007
|
+
});
|
|
14008
|
+
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) => {
|
|
14009
|
+
const db = getDatabase();
|
|
14010
|
+
const filters = {};
|
|
14011
|
+
if (opts.project)
|
|
14012
|
+
filters.project_id = opts.project;
|
|
14013
|
+
const s = getStatus(Object.keys(filters).length ? filters : undefined, opts.agent, db);
|
|
14014
|
+
if (opts.json) {
|
|
14015
|
+
console.log(JSON.stringify(s, null, 2));
|
|
14016
|
+
return;
|
|
14017
|
+
}
|
|
14018
|
+
console.log(`Tasks: ${chalk.yellow(s.pending)} pending | ${chalk.blue(s.in_progress)} active | ${chalk.green(s.completed)} done | ${s.total} total`);
|
|
14019
|
+
if (s.stale_count > 0)
|
|
14020
|
+
console.log(chalk.red(`\u26A0\uFE0F ${s.stale_count} stale tasks (stuck in_progress)`));
|
|
14021
|
+
if (s.overdue_recurring > 0)
|
|
14022
|
+
console.log(chalk.yellow(`\uD83D\uDD01 ${s.overdue_recurring} overdue recurring`));
|
|
14023
|
+
if (s.active_work.length > 0) {
|
|
14024
|
+
console.log(chalk.bold(`
|
|
14025
|
+
Active:`));
|
|
14026
|
+
for (const w of s.active_work.slice(0, 5)) {
|
|
14027
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
14028
|
+
console.log(` ${chalk.cyan(id)} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
14029
|
+
}
|
|
14030
|
+
}
|
|
14031
|
+
if (s.next_task) {
|
|
14032
|
+
console.log(chalk.bold(`
|
|
14033
|
+
Next up:`));
|
|
14034
|
+
const t = s.next_task;
|
|
14035
|
+
console.log(` ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${chalk.yellow(t.priority)} ${t.title}`);
|
|
14036
|
+
}
|
|
14037
|
+
});
|
|
14038
|
+
program2.command("fail <id>").description("Mark a task as failed with optional reason and retry").option("--reason <text>", "Why it failed").option("--agent <id>", "Agent reporting the failure").option("--retry", "Auto-create a retry copy").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
14039
|
+
const db = getDatabase();
|
|
14040
|
+
const resolvedId = resolvePartialId(db, "tasks", id);
|
|
14041
|
+
if (!resolvedId) {
|
|
14042
|
+
console.error(chalk.red(`Task not found: ${id}`));
|
|
14043
|
+
process.exit(1);
|
|
14044
|
+
}
|
|
14045
|
+
const result = failTask(resolvedId, opts.agent, opts.reason, { retry: opts.retry }, db);
|
|
14046
|
+
if (opts.json) {
|
|
14047
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14048
|
+
return;
|
|
14049
|
+
}
|
|
14050
|
+
console.log(chalk.red(`Failed: ${result.task.short_id || result.task.id.slice(0, 8)} | ${result.task.title}`));
|
|
14051
|
+
if (opts.reason)
|
|
14052
|
+
console.log(chalk.dim(`Reason: ${opts.reason}`));
|
|
14053
|
+
if (result.retryTask)
|
|
14054
|
+
console.log(chalk.yellow(`Retry created: ${result.retryTask.short_id || result.retryTask.id.slice(0, 8)} | ${result.retryTask.title}`));
|
|
14055
|
+
});
|
|
14056
|
+
program2.command("active").description("Show all currently in-progress tasks").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
14057
|
+
const db = getDatabase();
|
|
14058
|
+
const filters = {};
|
|
14059
|
+
if (opts.project)
|
|
14060
|
+
filters.project_id = opts.project;
|
|
14061
|
+
const work = getActiveWork(Object.keys(filters).length ? filters : undefined, db);
|
|
14062
|
+
if (opts.json) {
|
|
14063
|
+
console.log(JSON.stringify(work, null, 2));
|
|
14064
|
+
return;
|
|
14065
|
+
}
|
|
14066
|
+
if (work.length === 0) {
|
|
14067
|
+
console.log(chalk.dim("No active work."));
|
|
14068
|
+
return;
|
|
14069
|
+
}
|
|
14070
|
+
console.log(chalk.bold(`Active work (${work.length}):`));
|
|
14071
|
+
for (const w of work) {
|
|
14072
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
14073
|
+
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
14074
|
+
console.log(` ${chalk.cyan(id)} | ${chalk.yellow(w.priority)} | ${agent.padEnd(12)} | ${w.title}`);
|
|
14075
|
+
}
|
|
14076
|
+
});
|
|
14077
|
+
program2.command("stale").description("Find tasks stuck in_progress with no recent activity").option("--minutes <n>", "Stale threshold in minutes", "30").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
14078
|
+
const db = getDatabase();
|
|
14079
|
+
const filters = {};
|
|
14080
|
+
if (opts.project)
|
|
14081
|
+
filters.project_id = opts.project;
|
|
14082
|
+
const tasks = getStaleTasks(parseInt(opts.minutes, 10), Object.keys(filters).length ? filters : undefined, db);
|
|
14083
|
+
if (opts.json) {
|
|
14084
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
14085
|
+
return;
|
|
14086
|
+
}
|
|
14087
|
+
if (tasks.length === 0) {
|
|
14088
|
+
console.log(chalk.dim("No stale tasks."));
|
|
14089
|
+
return;
|
|
14090
|
+
}
|
|
14091
|
+
console.log(chalk.bold(`Stale tasks (${tasks.length}):`));
|
|
14092
|
+
for (const t of tasks) {
|
|
14093
|
+
const id = t.short_id || t.id.slice(0, 8);
|
|
14094
|
+
const staleMin = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
14095
|
+
console.log(` ${chalk.cyan(id)} | ${t.locked_by || t.assigned_to || "?"} | ${t.title} ${chalk.dim(`(${staleMin}min stale)`)}`);
|
|
14096
|
+
}
|
|
14097
|
+
});
|
|
13885
14098
|
program2.action(async () => {
|
|
13886
14099
|
if (process.stdout.isTTY) {
|
|
13887
14100
|
try {
|
package/dist/db/tasks.d.ts
CHANGED
|
@@ -84,6 +84,20 @@ export declare function getStaleTasks(staleMinutes?: number, filters?: {
|
|
|
84
84
|
project_id?: string;
|
|
85
85
|
task_list_id?: string;
|
|
86
86
|
}, db?: Database): Task[];
|
|
87
|
+
export interface StatusSummary {
|
|
88
|
+
pending: number;
|
|
89
|
+
in_progress: number;
|
|
90
|
+
completed: number;
|
|
91
|
+
total: number;
|
|
92
|
+
active_work: ActiveWorkItem[];
|
|
93
|
+
next_task: Task | null;
|
|
94
|
+
stale_count: number;
|
|
95
|
+
overdue_recurring: number;
|
|
96
|
+
}
|
|
97
|
+
export declare function getStatus(filters?: {
|
|
98
|
+
project_id?: string;
|
|
99
|
+
task_list_id?: string;
|
|
100
|
+
}, agentId?: string, db?: Database): StatusSummary;
|
|
87
101
|
export declare function getTaskStats(filters?: {
|
|
88
102
|
project_id?: string;
|
|
89
103
|
task_list_id?: string;
|
package/dist/db/tasks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/db/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,UAAU,EACV,IAAI,EACJ,cAAc,EACd,UAAU,EAEV,iBAAiB,EACjB,eAAe,EAChB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/db/tasks.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { getDatabase, closeDatabase, resetDatabase, resolvePartialId, now, uuid } from "./db/database.js";
|
|
2
|
-
export { createTask, getTask, getTaskWithRelations, listTasks, countTasks, updateTask, deleteTask, startTask, completeTask, lockTask, unlockTask, addDependency, removeDependency, getTaskDependencies, getTaskDependents, getBlockingDeps, bulkUpdateTasks, bulkCreateTasks, cloneTask, getTaskStats, getTaskGraph, moveTask, getNextTask, claimNextTask, getActiveWork, failTask, getTasksChangedSince, getStaleTasks, } from "./db/tasks.js";
|
|
3
|
-
export type { TaskGraphNode, TaskGraph, BulkCreateTaskInput, ActiveWorkItem } from "./db/tasks.js";
|
|
2
|
+
export { createTask, getTask, getTaskWithRelations, listTasks, countTasks, updateTask, deleteTask, startTask, completeTask, lockTask, unlockTask, addDependency, removeDependency, getTaskDependencies, getTaskDependents, getBlockingDeps, bulkUpdateTasks, bulkCreateTasks, cloneTask, getTaskStats, getTaskGraph, moveTask, getNextTask, claimNextTask, getActiveWork, failTask, getTasksChangedSince, getStaleTasks, getStatus, } from "./db/tasks.js";
|
|
3
|
+
export type { TaskGraphNode, TaskGraph, BulkCreateTaskInput, ActiveWorkItem, StatusSummary } from "./db/tasks.js";
|
|
4
4
|
export { createProject, getProject, getProjectByPath, listProjects, updateProject, deleteProject, ensureProject, nextTaskShortId, slugify, } from "./db/projects.js";
|
|
5
5
|
export { createPlan, getPlan, listPlans, updatePlan, deletePlan, } from "./db/plans.js";
|
|
6
6
|
export { addComment, getComment, listComments, deleteComment, } from "./db/comments.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAG1G,OAAO,EACL,UAAU,EACV,OAAO,EACP,oBAAoB,EACpB,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,aAAa,EACb,aAAa,EACb,QAAQ,EACR,oBAAoB,EACpB,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1015,6 +1015,46 @@ function nextOccurrence(rule, from) {
|
|
|
1015
1015
|
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
1018
|
+
// src/db/webhooks.ts
|
|
1019
|
+
function rowToWebhook(row) {
|
|
1020
|
+
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
1021
|
+
}
|
|
1022
|
+
function createWebhook(input, db) {
|
|
1023
|
+
const d = db || getDatabase();
|
|
1024
|
+
const id = uuid();
|
|
1025
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
1026
|
+
return getWebhook(id, d);
|
|
1027
|
+
}
|
|
1028
|
+
function getWebhook(id, db) {
|
|
1029
|
+
const d = db || getDatabase();
|
|
1030
|
+
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
1031
|
+
return row ? rowToWebhook(row) : null;
|
|
1032
|
+
}
|
|
1033
|
+
function listWebhooks(db) {
|
|
1034
|
+
const d = db || getDatabase();
|
|
1035
|
+
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
1036
|
+
}
|
|
1037
|
+
function deleteWebhook(id, db) {
|
|
1038
|
+
const d = db || getDatabase();
|
|
1039
|
+
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
1040
|
+
}
|
|
1041
|
+
async function dispatchWebhook(event, payload, db) {
|
|
1042
|
+
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
1043
|
+
for (const wh of webhooks) {
|
|
1044
|
+
try {
|
|
1045
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
1046
|
+
const headers = { "Content-Type": "application/json" };
|
|
1047
|
+
if (wh.secret) {
|
|
1048
|
+
const encoder = new TextEncoder;
|
|
1049
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
1050
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
1051
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1052
|
+
}
|
|
1053
|
+
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
1054
|
+
} catch {}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1018
1058
|
// src/db/tasks.ts
|
|
1019
1059
|
function rowToTask(row) {
|
|
1020
1060
|
return {
|
|
@@ -1077,7 +1117,9 @@ function createTask(input, db) {
|
|
|
1077
1117
|
if (tags.length > 0) {
|
|
1078
1118
|
insertTaskTags(id, tags, d);
|
|
1079
1119
|
}
|
|
1080
|
-
|
|
1120
|
+
const task = getTask(id, d);
|
|
1121
|
+
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
1122
|
+
return task;
|
|
1081
1123
|
}
|
|
1082
1124
|
function getTask(id, db) {
|
|
1083
1125
|
const d = db || getDatabase();
|
|
@@ -1350,6 +1392,12 @@ function updateTask(id, input, db) {
|
|
|
1350
1392
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
1351
1393
|
if (input.approved_by !== undefined)
|
|
1352
1394
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
1395
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
1396
|
+
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
1397
|
+
}
|
|
1398
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
1399
|
+
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
1400
|
+
}
|
|
1353
1401
|
return {
|
|
1354
1402
|
...task,
|
|
1355
1403
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
@@ -1401,6 +1449,7 @@ function startTask(id, agentId, db) {
|
|
|
1401
1449
|
}
|
|
1402
1450
|
}
|
|
1403
1451
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
1452
|
+
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
1404
1453
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
1405
1454
|
}
|
|
1406
1455
|
function completeTask(id, agentId, db, options) {
|
|
@@ -1422,6 +1471,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
1422
1471
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
1423
1472
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
1424
1473
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
1474
|
+
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
1425
1475
|
let spawnedTask = null;
|
|
1426
1476
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
1427
1477
|
spawnedTask = spawnNextRecurrence(task, d);
|
|
@@ -1704,6 +1754,7 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
1704
1754
|
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
1705
1755
|
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
1706
1756
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
1757
|
+
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
1707
1758
|
const failedTask = {
|
|
1708
1759
|
...task,
|
|
1709
1760
|
status: "failed",
|
|
@@ -1756,6 +1807,37 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
1756
1807
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
1757
1808
|
return rows.map(rowToTask);
|
|
1758
1809
|
}
|
|
1810
|
+
function getStatus(filters, agentId, db) {
|
|
1811
|
+
const d = db || getDatabase();
|
|
1812
|
+
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
1813
|
+
const in_progress = countTasks({ ...filters, status: "in_progress" }, d);
|
|
1814
|
+
const completed = countTasks({ ...filters, status: "completed" }, d);
|
|
1815
|
+
const total = countTasks(filters || {}, d);
|
|
1816
|
+
const active_work = getActiveWork(filters, d);
|
|
1817
|
+
const next_task = getNextTask(agentId, filters, d);
|
|
1818
|
+
const stale = getStaleTasks(30, filters, d);
|
|
1819
|
+
const conditions = ["recurrence_rule IS NOT NULL", "status = 'pending'", "due_at < ?"];
|
|
1820
|
+
const params = [now()];
|
|
1821
|
+
if (filters?.project_id) {
|
|
1822
|
+
conditions.push("project_id = ?");
|
|
1823
|
+
params.push(filters.project_id);
|
|
1824
|
+
}
|
|
1825
|
+
if (filters?.task_list_id) {
|
|
1826
|
+
conditions.push("task_list_id = ?");
|
|
1827
|
+
params.push(filters.task_list_id);
|
|
1828
|
+
}
|
|
1829
|
+
const overdueRow = d.query(`SELECT COUNT(*) as count FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params);
|
|
1830
|
+
return {
|
|
1831
|
+
pending,
|
|
1832
|
+
in_progress,
|
|
1833
|
+
completed,
|
|
1834
|
+
total,
|
|
1835
|
+
active_work,
|
|
1836
|
+
next_task,
|
|
1837
|
+
stale_count: stale.length,
|
|
1838
|
+
overdue_recurring: overdueRow.count
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1759
1841
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
1760
1842
|
const visited = new Set;
|
|
1761
1843
|
const queue = [dependsOn];
|
|
@@ -2206,45 +2288,6 @@ function deleteSession(id, db) {
|
|
|
2206
2288
|
const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
2207
2289
|
return result.changes > 0;
|
|
2208
2290
|
}
|
|
2209
|
-
// src/db/webhooks.ts
|
|
2210
|
-
function rowToWebhook(row) {
|
|
2211
|
-
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
2212
|
-
}
|
|
2213
|
-
function createWebhook(input, db) {
|
|
2214
|
-
const d = db || getDatabase();
|
|
2215
|
-
const id = uuid();
|
|
2216
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
2217
|
-
return getWebhook(id, d);
|
|
2218
|
-
}
|
|
2219
|
-
function getWebhook(id, db) {
|
|
2220
|
-
const d = db || getDatabase();
|
|
2221
|
-
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
2222
|
-
return row ? rowToWebhook(row) : null;
|
|
2223
|
-
}
|
|
2224
|
-
function listWebhooks(db) {
|
|
2225
|
-
const d = db || getDatabase();
|
|
2226
|
-
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
2227
|
-
}
|
|
2228
|
-
function deleteWebhook(id, db) {
|
|
2229
|
-
const d = db || getDatabase();
|
|
2230
|
-
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
2231
|
-
}
|
|
2232
|
-
async function dispatchWebhook(event, payload, db) {
|
|
2233
|
-
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
2234
|
-
for (const wh of webhooks) {
|
|
2235
|
-
try {
|
|
2236
|
-
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
2237
|
-
const headers = { "Content-Type": "application/json" };
|
|
2238
|
-
if (wh.secret) {
|
|
2239
|
-
const encoder = new TextEncoder;
|
|
2240
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
2241
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
2242
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2243
|
-
}
|
|
2244
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
2245
|
-
} catch {}
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
2291
|
// src/db/templates.ts
|
|
2249
2292
|
function rowToTemplate(row) {
|
|
2250
2293
|
return {
|
|
@@ -2996,6 +3039,7 @@ export {
|
|
|
2996
3039
|
getTaskDependents,
|
|
2997
3040
|
getTaskDependencies,
|
|
2998
3041
|
getTask,
|
|
3042
|
+
getStatus,
|
|
2999
3043
|
getStaleTasks,
|
|
3000
3044
|
getSession,
|
|
3001
3045
|
getRecentActivity,
|
package/dist/mcp/index.js
CHANGED
|
@@ -557,6 +557,57 @@ var init_audit = __esm(() => {
|
|
|
557
557
|
init_database();
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
+
// src/db/webhooks.ts
|
|
561
|
+
var exports_webhooks = {};
|
|
562
|
+
__export(exports_webhooks, {
|
|
563
|
+
listWebhooks: () => listWebhooks,
|
|
564
|
+
getWebhook: () => getWebhook,
|
|
565
|
+
dispatchWebhook: () => dispatchWebhook,
|
|
566
|
+
deleteWebhook: () => deleteWebhook,
|
|
567
|
+
createWebhook: () => createWebhook
|
|
568
|
+
});
|
|
569
|
+
function rowToWebhook(row) {
|
|
570
|
+
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
571
|
+
}
|
|
572
|
+
function createWebhook(input, db) {
|
|
573
|
+
const d = db || getDatabase();
|
|
574
|
+
const id = uuid();
|
|
575
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
576
|
+
return getWebhook(id, d);
|
|
577
|
+
}
|
|
578
|
+
function getWebhook(id, db) {
|
|
579
|
+
const d = db || getDatabase();
|
|
580
|
+
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
581
|
+
return row ? rowToWebhook(row) : null;
|
|
582
|
+
}
|
|
583
|
+
function listWebhooks(db) {
|
|
584
|
+
const d = db || getDatabase();
|
|
585
|
+
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
586
|
+
}
|
|
587
|
+
function deleteWebhook(id, db) {
|
|
588
|
+
const d = db || getDatabase();
|
|
589
|
+
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
590
|
+
}
|
|
591
|
+
async function dispatchWebhook(event, payload, db) {
|
|
592
|
+
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
593
|
+
for (const wh of webhooks) {
|
|
594
|
+
try {
|
|
595
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
596
|
+
const headers = { "Content-Type": "application/json" };
|
|
597
|
+
if (wh.secret) {
|
|
598
|
+
const encoder = new TextEncoder;
|
|
599
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
600
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
601
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
602
|
+
}
|
|
603
|
+
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
604
|
+
} catch {}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
var init_webhooks = __esm(() => {
|
|
608
|
+
init_database();
|
|
609
|
+
});
|
|
610
|
+
|
|
560
611
|
// src/db/agents.ts
|
|
561
612
|
var exports_agents = {};
|
|
562
613
|
__export(exports_agents, {
|
|
@@ -700,57 +751,6 @@ var init_agents = __esm(() => {
|
|
|
700
751
|
init_database();
|
|
701
752
|
});
|
|
702
753
|
|
|
703
|
-
// src/db/webhooks.ts
|
|
704
|
-
var exports_webhooks = {};
|
|
705
|
-
__export(exports_webhooks, {
|
|
706
|
-
listWebhooks: () => listWebhooks,
|
|
707
|
-
getWebhook: () => getWebhook,
|
|
708
|
-
dispatchWebhook: () => dispatchWebhook,
|
|
709
|
-
deleteWebhook: () => deleteWebhook,
|
|
710
|
-
createWebhook: () => createWebhook
|
|
711
|
-
});
|
|
712
|
-
function rowToWebhook(row) {
|
|
713
|
-
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
714
|
-
}
|
|
715
|
-
function createWebhook(input, db) {
|
|
716
|
-
const d = db || getDatabase();
|
|
717
|
-
const id = uuid();
|
|
718
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
719
|
-
return getWebhook(id, d);
|
|
720
|
-
}
|
|
721
|
-
function getWebhook(id, db) {
|
|
722
|
-
const d = db || getDatabase();
|
|
723
|
-
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
724
|
-
return row ? rowToWebhook(row) : null;
|
|
725
|
-
}
|
|
726
|
-
function listWebhooks(db) {
|
|
727
|
-
const d = db || getDatabase();
|
|
728
|
-
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
729
|
-
}
|
|
730
|
-
function deleteWebhook(id, db) {
|
|
731
|
-
const d = db || getDatabase();
|
|
732
|
-
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
733
|
-
}
|
|
734
|
-
async function dispatchWebhook(event, payload, db) {
|
|
735
|
-
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
736
|
-
for (const wh of webhooks) {
|
|
737
|
-
try {
|
|
738
|
-
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
739
|
-
const headers = { "Content-Type": "application/json" };
|
|
740
|
-
if (wh.secret) {
|
|
741
|
-
const encoder = new TextEncoder;
|
|
742
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
743
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
744
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
745
|
-
}
|
|
746
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
747
|
-
} catch {}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
var init_webhooks = __esm(() => {
|
|
751
|
-
init_database();
|
|
752
|
-
});
|
|
753
|
-
|
|
754
754
|
// src/db/templates.ts
|
|
755
755
|
var exports_templates = {};
|
|
756
756
|
__export(exports_templates, {
|
|
@@ -5212,6 +5212,7 @@ function nextOccurrence(rule, from) {
|
|
|
5212
5212
|
}
|
|
5213
5213
|
|
|
5214
5214
|
// src/db/tasks.ts
|
|
5215
|
+
init_webhooks();
|
|
5215
5216
|
function rowToTask(row) {
|
|
5216
5217
|
return {
|
|
5217
5218
|
...row,
|
|
@@ -5273,7 +5274,9 @@ function createTask(input, db) {
|
|
|
5273
5274
|
if (tags.length > 0) {
|
|
5274
5275
|
insertTaskTags(id, tags, d);
|
|
5275
5276
|
}
|
|
5276
|
-
|
|
5277
|
+
const task = getTask(id, d);
|
|
5278
|
+
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
5279
|
+
return task;
|
|
5277
5280
|
}
|
|
5278
5281
|
function getTask(id, db) {
|
|
5279
5282
|
const d = db || getDatabase();
|
|
@@ -5546,6 +5549,12 @@ function updateTask(id, input, db) {
|
|
|
5546
5549
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
5547
5550
|
if (input.approved_by !== undefined)
|
|
5548
5551
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
5552
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
5553
|
+
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
5554
|
+
}
|
|
5555
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
5556
|
+
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
5557
|
+
}
|
|
5549
5558
|
return {
|
|
5550
5559
|
...task,
|
|
5551
5560
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
@@ -5597,6 +5606,7 @@ function startTask(id, agentId, db) {
|
|
|
5597
5606
|
}
|
|
5598
5607
|
}
|
|
5599
5608
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
5609
|
+
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
5600
5610
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
5601
5611
|
}
|
|
5602
5612
|
function completeTask(id, agentId, db, options) {
|
|
@@ -5618,6 +5628,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
5618
5628
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
5619
5629
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
5620
5630
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
5631
|
+
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
5621
5632
|
let spawnedTask = null;
|
|
5622
5633
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
5623
5634
|
spawnedTask = spawnNextRecurrence(task, d);
|
|
@@ -5896,6 +5907,7 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
5896
5907
|
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
5897
5908
|
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
5898
5909
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
5910
|
+
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
5899
5911
|
const failedTask = {
|
|
5900
5912
|
...task,
|
|
5901
5913
|
status: "failed",
|
|
@@ -5948,6 +5960,37 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
5948
5960
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
5949
5961
|
return rows.map(rowToTask);
|
|
5950
5962
|
}
|
|
5963
|
+
function getStatus(filters, agentId, db) {
|
|
5964
|
+
const d = db || getDatabase();
|
|
5965
|
+
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
5966
|
+
const in_progress = countTasks({ ...filters, status: "in_progress" }, d);
|
|
5967
|
+
const completed = countTasks({ ...filters, status: "completed" }, d);
|
|
5968
|
+
const total = countTasks(filters || {}, d);
|
|
5969
|
+
const active_work = getActiveWork(filters, d);
|
|
5970
|
+
const next_task = getNextTask(agentId, filters, d);
|
|
5971
|
+
const stale = getStaleTasks(30, filters, d);
|
|
5972
|
+
const conditions = ["recurrence_rule IS NOT NULL", "status = 'pending'", "due_at < ?"];
|
|
5973
|
+
const params = [now()];
|
|
5974
|
+
if (filters?.project_id) {
|
|
5975
|
+
conditions.push("project_id = ?");
|
|
5976
|
+
params.push(filters.project_id);
|
|
5977
|
+
}
|
|
5978
|
+
if (filters?.task_list_id) {
|
|
5979
|
+
conditions.push("task_list_id = ?");
|
|
5980
|
+
params.push(filters.task_list_id);
|
|
5981
|
+
}
|
|
5982
|
+
const overdueRow = d.query(`SELECT COUNT(*) as count FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params);
|
|
5983
|
+
return {
|
|
5984
|
+
pending,
|
|
5985
|
+
in_progress,
|
|
5986
|
+
completed,
|
|
5987
|
+
total,
|
|
5988
|
+
active_work,
|
|
5989
|
+
next_task,
|
|
5990
|
+
stale_count: stale.length,
|
|
5991
|
+
overdue_recurring: overdueRow.count
|
|
5992
|
+
};
|
|
5993
|
+
}
|
|
5951
5994
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
5952
5995
|
const visited = new Set;
|
|
5953
5996
|
const queue = [dependsOn];
|
|
@@ -8206,6 +8249,47 @@ ${text}` }] };
|
|
|
8206
8249
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8207
8250
|
}
|
|
8208
8251
|
});
|
|
8252
|
+
server.tool("get_status", "Get a full project health snapshot \u2014 counts, active work, next task, stale/overdue summary.", {
|
|
8253
|
+
agent_id: exports_external.string().optional(),
|
|
8254
|
+
project_id: exports_external.string().optional(),
|
|
8255
|
+
task_list_id: exports_external.string().optional()
|
|
8256
|
+
}, async ({ agent_id, project_id, task_list_id }) => {
|
|
8257
|
+
try {
|
|
8258
|
+
const filters = {};
|
|
8259
|
+
if (project_id)
|
|
8260
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8261
|
+
if (task_list_id)
|
|
8262
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8263
|
+
const status = getStatus(Object.keys(filters).length > 0 ? filters : undefined, agent_id);
|
|
8264
|
+
const lines = [
|
|
8265
|
+
`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done | ${status.total} total`
|
|
8266
|
+
];
|
|
8267
|
+
if (status.stale_count > 0)
|
|
8268
|
+
lines.push(`\u26A0\uFE0F ${status.stale_count} stale (stuck in_progress)`);
|
|
8269
|
+
if (status.overdue_recurring > 0)
|
|
8270
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
8271
|
+
if (status.active_work.length > 0) {
|
|
8272
|
+
lines.push(`
|
|
8273
|
+
Active (${status.active_work.length}):`);
|
|
8274
|
+
for (const w of status.active_work.slice(0, 5)) {
|
|
8275
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
8276
|
+
lines.push(` ${id} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
8277
|
+
}
|
|
8278
|
+
}
|
|
8279
|
+
if (status.next_task) {
|
|
8280
|
+
lines.push(`
|
|
8281
|
+
Next up:`);
|
|
8282
|
+
lines.push(` ${formatTask(status.next_task)}`);
|
|
8283
|
+
} else {
|
|
8284
|
+
lines.push(`
|
|
8285
|
+
No pending tasks available.`);
|
|
8286
|
+
}
|
|
8287
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
8288
|
+
`) }] };
|
|
8289
|
+
} catch (e) {
|
|
8290
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8291
|
+
}
|
|
8292
|
+
});
|
|
8209
8293
|
server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
|
|
8210
8294
|
const all = [
|
|
8211
8295
|
"create_task",
|
|
@@ -8264,6 +8348,7 @@ server.tool("search_tools", "List all tool names, optionally filtered by substri
|
|
|
8264
8348
|
"get_active_work",
|
|
8265
8349
|
"get_tasks_changed_since",
|
|
8266
8350
|
"get_stale_tasks",
|
|
8351
|
+
"get_status",
|
|
8267
8352
|
"search_tools",
|
|
8268
8353
|
"describe_tools"
|
|
8269
8354
|
];
|
|
@@ -8431,6 +8516,9 @@ server.tool("describe_tools", "Get detailed parameter info for specific tools by
|
|
|
8431
8516
|
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
8432
8517
|
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
8433
8518
|
Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
|
|
8519
|
+
get_status: `Get a full project health snapshot \u2014 pending/in_progress/completed counts, active work, next recommended task, stale task count, overdue recurring tasks. Saves 4+ round trips at session start.
|
|
8520
|
+
Params: agent_id(string, optional \u2014 prefers tasks assigned to this agent for next_task), project_id(string, optional), task_list_id(string, optional)
|
|
8521
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
8434
8522
|
search_tools: `List all tool names or filter by substring.
|
|
8435
8523
|
Params: query(string, optional)
|
|
8436
8524
|
Example: {query: 'task'}`,
|
package/dist/server/index.js
CHANGED
|
@@ -742,6 +742,57 @@ var init_audit = __esm(() => {
|
|
|
742
742
|
init_database();
|
|
743
743
|
});
|
|
744
744
|
|
|
745
|
+
// src/db/webhooks.ts
|
|
746
|
+
var exports_webhooks = {};
|
|
747
|
+
__export(exports_webhooks, {
|
|
748
|
+
listWebhooks: () => listWebhooks,
|
|
749
|
+
getWebhook: () => getWebhook,
|
|
750
|
+
dispatchWebhook: () => dispatchWebhook,
|
|
751
|
+
deleteWebhook: () => deleteWebhook,
|
|
752
|
+
createWebhook: () => createWebhook
|
|
753
|
+
});
|
|
754
|
+
function rowToWebhook(row) {
|
|
755
|
+
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
756
|
+
}
|
|
757
|
+
function createWebhook(input, db) {
|
|
758
|
+
const d = db || getDatabase();
|
|
759
|
+
const id = uuid();
|
|
760
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
761
|
+
return getWebhook(id, d);
|
|
762
|
+
}
|
|
763
|
+
function getWebhook(id, db) {
|
|
764
|
+
const d = db || getDatabase();
|
|
765
|
+
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
766
|
+
return row ? rowToWebhook(row) : null;
|
|
767
|
+
}
|
|
768
|
+
function listWebhooks(db) {
|
|
769
|
+
const d = db || getDatabase();
|
|
770
|
+
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
771
|
+
}
|
|
772
|
+
function deleteWebhook(id, db) {
|
|
773
|
+
const d = db || getDatabase();
|
|
774
|
+
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
775
|
+
}
|
|
776
|
+
async function dispatchWebhook(event, payload, db) {
|
|
777
|
+
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
778
|
+
for (const wh of webhooks) {
|
|
779
|
+
try {
|
|
780
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
781
|
+
const headers = { "Content-Type": "application/json" };
|
|
782
|
+
if (wh.secret) {
|
|
783
|
+
const encoder = new TextEncoder;
|
|
784
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
785
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
786
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
787
|
+
}
|
|
788
|
+
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
789
|
+
} catch {}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
var init_webhooks = __esm(() => {
|
|
793
|
+
init_database();
|
|
794
|
+
});
|
|
795
|
+
|
|
745
796
|
// src/db/agents.ts
|
|
746
797
|
var exports_agents = {};
|
|
747
798
|
__export(exports_agents, {
|
|
@@ -950,57 +1001,6 @@ var init_orgs = __esm(() => {
|
|
|
950
1001
|
init_database();
|
|
951
1002
|
});
|
|
952
1003
|
|
|
953
|
-
// src/db/webhooks.ts
|
|
954
|
-
var exports_webhooks = {};
|
|
955
|
-
__export(exports_webhooks, {
|
|
956
|
-
listWebhooks: () => listWebhooks,
|
|
957
|
-
getWebhook: () => getWebhook,
|
|
958
|
-
dispatchWebhook: () => dispatchWebhook,
|
|
959
|
-
deleteWebhook: () => deleteWebhook,
|
|
960
|
-
createWebhook: () => createWebhook
|
|
961
|
-
});
|
|
962
|
-
function rowToWebhook(row) {
|
|
963
|
-
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
964
|
-
}
|
|
965
|
-
function createWebhook(input, db) {
|
|
966
|
-
const d = db || getDatabase();
|
|
967
|
-
const id = uuid();
|
|
968
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
969
|
-
return getWebhook(id, d);
|
|
970
|
-
}
|
|
971
|
-
function getWebhook(id, db) {
|
|
972
|
-
const d = db || getDatabase();
|
|
973
|
-
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
974
|
-
return row ? rowToWebhook(row) : null;
|
|
975
|
-
}
|
|
976
|
-
function listWebhooks(db) {
|
|
977
|
-
const d = db || getDatabase();
|
|
978
|
-
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
979
|
-
}
|
|
980
|
-
function deleteWebhook(id, db) {
|
|
981
|
-
const d = db || getDatabase();
|
|
982
|
-
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
983
|
-
}
|
|
984
|
-
async function dispatchWebhook(event, payload, db) {
|
|
985
|
-
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
986
|
-
for (const wh of webhooks) {
|
|
987
|
-
try {
|
|
988
|
-
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
989
|
-
const headers = { "Content-Type": "application/json" };
|
|
990
|
-
if (wh.secret) {
|
|
991
|
-
const encoder = new TextEncoder;
|
|
992
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
993
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
994
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
995
|
-
}
|
|
996
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
997
|
-
} catch {}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
var init_webhooks = __esm(() => {
|
|
1001
|
-
init_database();
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
1004
|
// src/db/templates.ts
|
|
1005
1005
|
var exports_templates = {};
|
|
1006
1006
|
__export(exports_templates, {
|
|
@@ -1265,6 +1265,7 @@ function nextOccurrence(rule, from) {
|
|
|
1265
1265
|
}
|
|
1266
1266
|
|
|
1267
1267
|
// src/db/tasks.ts
|
|
1268
|
+
init_webhooks();
|
|
1268
1269
|
function rowToTask(row) {
|
|
1269
1270
|
return {
|
|
1270
1271
|
...row,
|
|
@@ -1326,7 +1327,9 @@ function createTask(input, db) {
|
|
|
1326
1327
|
if (tags.length > 0) {
|
|
1327
1328
|
insertTaskTags(id, tags, d);
|
|
1328
1329
|
}
|
|
1329
|
-
|
|
1330
|
+
const task = getTask(id, d);
|
|
1331
|
+
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
1332
|
+
return task;
|
|
1330
1333
|
}
|
|
1331
1334
|
function getTask(id, db) {
|
|
1332
1335
|
const d = db || getDatabase();
|
|
@@ -1510,6 +1513,12 @@ function updateTask(id, input, db) {
|
|
|
1510
1513
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
1511
1514
|
if (input.approved_by !== undefined)
|
|
1512
1515
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
1516
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
1517
|
+
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
1518
|
+
}
|
|
1519
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
1520
|
+
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
1521
|
+
}
|
|
1513
1522
|
return {
|
|
1514
1523
|
...task,
|
|
1515
1524
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
@@ -1561,6 +1570,7 @@ function startTask(id, agentId, db) {
|
|
|
1561
1570
|
}
|
|
1562
1571
|
}
|
|
1563
1572
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
1573
|
+
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
1564
1574
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
1565
1575
|
}
|
|
1566
1576
|
function completeTask(id, agentId, db, options) {
|
|
@@ -1582,6 +1592,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
1582
1592
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
1583
1593
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
1584
1594
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
1595
|
+
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
1585
1596
|
let spawnedTask = null;
|
|
1586
1597
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
1587
1598
|
spawnedTask = spawnNextRecurrence(task, d);
|