@hasna/todos 0.11.10 → 0.11.13
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 +775 -44
- package/dist/db/builtin-templates.d.ts +22 -0
- package/dist/db/builtin-templates.d.ts.map +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/templates.d.ts +71 -5
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/db/webhooks.d.ts +11 -0
- package/dist/db/webhooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +492 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +633 -39
- package/dist/server/index.js +399 -35
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +28 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/server/index.js
CHANGED
|
@@ -378,6 +378,36 @@ function ensureSchema(db) {
|
|
|
378
378
|
ensureColumn("projects", "org_id", "TEXT");
|
|
379
379
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
380
380
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
381
|
+
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
382
|
+
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
383
|
+
ensureColumn("template_tasks", "condition", "TEXT");
|
|
384
|
+
ensureColumn("template_tasks", "include_template_id", "TEXT");
|
|
385
|
+
ensureTable("template_versions", `
|
|
386
|
+
CREATE TABLE template_versions (
|
|
387
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
388
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
389
|
+
version INTEGER NOT NULL,
|
|
390
|
+
snapshot TEXT NOT NULL,
|
|
391
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
392
|
+
)`);
|
|
393
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id)");
|
|
394
|
+
ensureColumn("webhooks", "project_id", "TEXT");
|
|
395
|
+
ensureColumn("webhooks", "task_list_id", "TEXT");
|
|
396
|
+
ensureColumn("webhooks", "agent_id", "TEXT");
|
|
397
|
+
ensureColumn("webhooks", "task_id", "TEXT");
|
|
398
|
+
ensureTable("webhook_deliveries", `
|
|
399
|
+
CREATE TABLE webhook_deliveries (
|
|
400
|
+
id TEXT PRIMARY KEY,
|
|
401
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
402
|
+
event TEXT NOT NULL,
|
|
403
|
+
payload TEXT NOT NULL,
|
|
404
|
+
status_code INTEGER,
|
|
405
|
+
response TEXT,
|
|
406
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
407
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
408
|
+
)`);
|
|
409
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook ON webhook_deliveries(webhook_id)");
|
|
410
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
381
411
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
382
412
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
383
413
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
|
|
@@ -1057,6 +1087,26 @@ var init_database = __esm(() => {
|
|
|
1057
1087
|
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
1058
1088
|
|
|
1059
1089
|
INSERT OR IGNORE INTO _migrations (id) VALUES (37);
|
|
1090
|
+
`,
|
|
1091
|
+
`
|
|
1092
|
+
ALTER TABLE task_templates ADD COLUMN variables TEXT DEFAULT '[]';
|
|
1093
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
1094
|
+
`,
|
|
1095
|
+
`
|
|
1096
|
+
ALTER TABLE template_tasks ADD COLUMN condition TEXT;
|
|
1097
|
+
ALTER TABLE template_tasks ADD COLUMN include_template_id TEXT;
|
|
1098
|
+
ALTER TABLE task_templates ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
|
|
1099
|
+
|
|
1100
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
1101
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
1102
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
1103
|
+
version INTEGER NOT NULL,
|
|
1104
|
+
snapshot TEXT NOT NULL,
|
|
1105
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1106
|
+
);
|
|
1107
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
1108
|
+
|
|
1109
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (39);
|
|
1060
1110
|
`
|
|
1061
1111
|
];
|
|
1062
1112
|
});
|
|
@@ -1482,18 +1532,37 @@ var init_recurrence = __esm(() => {
|
|
|
1482
1532
|
var exports_webhooks = {};
|
|
1483
1533
|
__export(exports_webhooks, {
|
|
1484
1534
|
listWebhooks: () => listWebhooks,
|
|
1535
|
+
listDeliveries: () => listDeliveries,
|
|
1485
1536
|
getWebhook: () => getWebhook,
|
|
1486
1537
|
dispatchWebhook: () => dispatchWebhook,
|
|
1487
1538
|
deleteWebhook: () => deleteWebhook,
|
|
1488
1539
|
createWebhook: () => createWebhook
|
|
1489
1540
|
});
|
|
1490
1541
|
function rowToWebhook(row) {
|
|
1491
|
-
return {
|
|
1542
|
+
return {
|
|
1543
|
+
...row,
|
|
1544
|
+
events: JSON.parse(row.events || "[]"),
|
|
1545
|
+
active: !!row.active,
|
|
1546
|
+
project_id: row.project_id || null,
|
|
1547
|
+
task_list_id: row.task_list_id || null,
|
|
1548
|
+
agent_id: row.agent_id || null,
|
|
1549
|
+
task_id: row.task_id || null
|
|
1550
|
+
};
|
|
1492
1551
|
}
|
|
1493
1552
|
function createWebhook(input, db) {
|
|
1494
1553
|
const d = db || getDatabase();
|
|
1495
1554
|
const id = uuid();
|
|
1496
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?,
|
|
1555
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, project_id, task_list_id, agent_id, task_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1556
|
+
id,
|
|
1557
|
+
input.url,
|
|
1558
|
+
JSON.stringify(input.events || []),
|
|
1559
|
+
input.secret || null,
|
|
1560
|
+
input.project_id || null,
|
|
1561
|
+
input.task_list_id || null,
|
|
1562
|
+
input.agent_id || null,
|
|
1563
|
+
input.task_id || null,
|
|
1564
|
+
now()
|
|
1565
|
+
]);
|
|
1497
1566
|
return getWebhook(id, d);
|
|
1498
1567
|
}
|
|
1499
1568
|
function getWebhook(id, db) {
|
|
@@ -1509,22 +1578,69 @@ function deleteWebhook(id, db) {
|
|
|
1509
1578
|
const d = db || getDatabase();
|
|
1510
1579
|
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
1511
1580
|
}
|
|
1581
|
+
function listDeliveries(webhookId, limit = 50, db) {
|
|
1582
|
+
const d = db || getDatabase();
|
|
1583
|
+
if (webhookId) {
|
|
1584
|
+
return d.query("SELECT * FROM webhook_deliveries WHERE webhook_id = ? ORDER BY created_at DESC LIMIT ?").all(webhookId, limit);
|
|
1585
|
+
}
|
|
1586
|
+
return d.query("SELECT * FROM webhook_deliveries ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
1587
|
+
}
|
|
1588
|
+
function logDelivery(d, webhookId, event, payload, statusCode, response, attempt) {
|
|
1589
|
+
const id = uuid();
|
|
1590
|
+
d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt, now()]);
|
|
1591
|
+
}
|
|
1592
|
+
function matchesScope(wh, payload) {
|
|
1593
|
+
if (wh.project_id && payload.project_id !== wh.project_id)
|
|
1594
|
+
return false;
|
|
1595
|
+
if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
|
|
1596
|
+
return false;
|
|
1597
|
+
if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
|
|
1598
|
+
return false;
|
|
1599
|
+
if (wh.task_id && payload.id !== wh.task_id)
|
|
1600
|
+
return false;
|
|
1601
|
+
return true;
|
|
1602
|
+
}
|
|
1603
|
+
async function deliverWebhook(wh, event, body, attempt, db) {
|
|
1604
|
+
try {
|
|
1605
|
+
const headers = { "Content-Type": "application/json" };
|
|
1606
|
+
if (wh.secret) {
|
|
1607
|
+
const encoder = new TextEncoder;
|
|
1608
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
1609
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
1610
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1611
|
+
}
|
|
1612
|
+
const resp = await fetch(wh.url, { method: "POST", headers, body });
|
|
1613
|
+
const respText = await resp.text().catch(() => "");
|
|
1614
|
+
logDelivery(db, wh.id, event, body, resp.status, respText.slice(0, 1000), attempt);
|
|
1615
|
+
if (resp.status >= 400 && attempt < MAX_RETRY_ATTEMPTS) {
|
|
1616
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1617
|
+
setTimeout(() => {
|
|
1618
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
1619
|
+
}, delay);
|
|
1620
|
+
}
|
|
1621
|
+
} catch (err) {
|
|
1622
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1623
|
+
logDelivery(db, wh.id, event, body, null, errorMsg.slice(0, 1000), attempt);
|
|
1624
|
+
if (attempt < MAX_RETRY_ATTEMPTS) {
|
|
1625
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1626
|
+
setTimeout(() => {
|
|
1627
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
1628
|
+
}, delay);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1512
1632
|
async function dispatchWebhook(event, payload, db) {
|
|
1513
|
-
const
|
|
1633
|
+
const d = db || getDatabase();
|
|
1634
|
+
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
1635
|
+
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
1514
1636
|
for (const wh of webhooks) {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
const encoder = new TextEncoder;
|
|
1520
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
1521
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
1522
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1523
|
-
}
|
|
1524
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
1525
|
-
} catch {}
|
|
1637
|
+
if (!matchesScope(wh, payloadObj))
|
|
1638
|
+
continue;
|
|
1639
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
1640
|
+
deliverWebhook(wh, event, body, 1, d).catch(() => {});
|
|
1526
1641
|
}
|
|
1527
1642
|
}
|
|
1643
|
+
var MAX_RETRY_ATTEMPTS = 3, RETRY_BASE_DELAY_MS = 1000;
|
|
1528
1644
|
var init_webhooks = __esm(() => {
|
|
1529
1645
|
init_database();
|
|
1530
1646
|
});
|
|
@@ -1535,10 +1651,17 @@ __export(exports_templates, {
|
|
|
1535
1651
|
updateTemplate: () => updateTemplate,
|
|
1536
1652
|
tasksFromTemplate: () => tasksFromTemplate,
|
|
1537
1653
|
taskFromTemplate: () => taskFromTemplate,
|
|
1654
|
+
resolveVariables: () => resolveVariables,
|
|
1655
|
+
previewTemplate: () => previewTemplate,
|
|
1538
1656
|
listTemplates: () => listTemplates,
|
|
1657
|
+
listTemplateVersions: () => listTemplateVersions,
|
|
1658
|
+
importTemplate: () => importTemplate,
|
|
1539
1659
|
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
1660
|
+
getTemplateVersion: () => getTemplateVersion,
|
|
1540
1661
|
getTemplateTasks: () => getTemplateTasks,
|
|
1541
1662
|
getTemplate: () => getTemplate,
|
|
1663
|
+
exportTemplate: () => exportTemplate,
|
|
1664
|
+
evaluateCondition: () => evaluateCondition,
|
|
1542
1665
|
deleteTemplate: () => deleteTemplate,
|
|
1543
1666
|
createTemplate: () => createTemplate,
|
|
1544
1667
|
addTemplateTasks: () => addTemplateTasks
|
|
@@ -1547,8 +1670,10 @@ function rowToTemplate(row) {
|
|
|
1547
1670
|
return {
|
|
1548
1671
|
...row,
|
|
1549
1672
|
tags: JSON.parse(row.tags || "[]"),
|
|
1673
|
+
variables: JSON.parse(row.variables || "[]"),
|
|
1550
1674
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
1551
|
-
priority: row.priority || "medium"
|
|
1675
|
+
priority: row.priority || "medium",
|
|
1676
|
+
version: row.version ?? 1
|
|
1552
1677
|
};
|
|
1553
1678
|
}
|
|
1554
1679
|
function rowToTemplateTask(row) {
|
|
@@ -1557,7 +1682,9 @@ function rowToTemplateTask(row) {
|
|
|
1557
1682
|
tags: JSON.parse(row.tags || "[]"),
|
|
1558
1683
|
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
1559
1684
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
1560
|
-
priority: row.priority || "medium"
|
|
1685
|
+
priority: row.priority || "medium",
|
|
1686
|
+
condition: row.condition ?? null,
|
|
1687
|
+
include_template_id: row.include_template_id ?? null
|
|
1561
1688
|
};
|
|
1562
1689
|
}
|
|
1563
1690
|
function resolveTemplateId(id, d) {
|
|
@@ -1566,14 +1693,15 @@ function resolveTemplateId(id, d) {
|
|
|
1566
1693
|
function createTemplate(input, db) {
|
|
1567
1694
|
const d = db || getDatabase();
|
|
1568
1695
|
const id = uuid();
|
|
1569
|
-
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
1570
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1696
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
|
|
1697
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1571
1698
|
id,
|
|
1572
1699
|
input.name,
|
|
1573
1700
|
input.title_pattern,
|
|
1574
1701
|
input.description || null,
|
|
1575
1702
|
input.priority || "medium",
|
|
1576
1703
|
JSON.stringify(input.tags || []),
|
|
1704
|
+
JSON.stringify(input.variables || []),
|
|
1577
1705
|
input.project_id || null,
|
|
1578
1706
|
input.plan_id || null,
|
|
1579
1707
|
JSON.stringify(input.metadata || {}),
|
|
@@ -1608,7 +1736,23 @@ function updateTemplate(id, updates, db) {
|
|
|
1608
1736
|
const resolved = resolveTemplateId(id, d);
|
|
1609
1737
|
if (!resolved)
|
|
1610
1738
|
return null;
|
|
1611
|
-
const
|
|
1739
|
+
const current = getTemplateWithTasks(resolved, d);
|
|
1740
|
+
if (current) {
|
|
1741
|
+
const snapshot = JSON.stringify({
|
|
1742
|
+
name: current.name,
|
|
1743
|
+
title_pattern: current.title_pattern,
|
|
1744
|
+
description: current.description,
|
|
1745
|
+
priority: current.priority,
|
|
1746
|
+
tags: current.tags,
|
|
1747
|
+
variables: current.variables,
|
|
1748
|
+
project_id: current.project_id,
|
|
1749
|
+
plan_id: current.plan_id,
|
|
1750
|
+
metadata: current.metadata,
|
|
1751
|
+
tasks: current.tasks
|
|
1752
|
+
});
|
|
1753
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
1754
|
+
}
|
|
1755
|
+
const sets = ["version = version + 1"];
|
|
1612
1756
|
const values = [];
|
|
1613
1757
|
if (updates.name !== undefined) {
|
|
1614
1758
|
sets.push("name = ?");
|
|
@@ -1630,6 +1774,10 @@ function updateTemplate(id, updates, db) {
|
|
|
1630
1774
|
sets.push("tags = ?");
|
|
1631
1775
|
values.push(JSON.stringify(updates.tags));
|
|
1632
1776
|
}
|
|
1777
|
+
if (updates.variables !== undefined) {
|
|
1778
|
+
sets.push("variables = ?");
|
|
1779
|
+
values.push(JSON.stringify(updates.variables));
|
|
1780
|
+
}
|
|
1633
1781
|
if (updates.project_id !== undefined) {
|
|
1634
1782
|
sets.push("project_id = ?");
|
|
1635
1783
|
values.push(updates.project_id);
|
|
@@ -1642,8 +1790,6 @@ function updateTemplate(id, updates, db) {
|
|
|
1642
1790
|
sets.push("metadata = ?");
|
|
1643
1791
|
values.push(JSON.stringify(updates.metadata));
|
|
1644
1792
|
}
|
|
1645
|
-
if (sets.length === 0)
|
|
1646
|
-
return getTemplate(resolved, d);
|
|
1647
1793
|
values.push(resolved);
|
|
1648
1794
|
d.run(`UPDATE task_templates SET ${sets.join(", ")} WHERE id = ?`, values);
|
|
1649
1795
|
return getTemplate(resolved, d);
|
|
@@ -1673,8 +1819,8 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
1673
1819
|
for (let i = 0;i < tasks.length; i++) {
|
|
1674
1820
|
const task = tasks[i];
|
|
1675
1821
|
const id = uuid();
|
|
1676
|
-
d.run(`INSERT INTO template_tasks (id, template_id, position, title_pattern, description, priority, tags, task_type, depends_on_positions, metadata, created_at)
|
|
1677
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1822
|
+
d.run(`INSERT INTO template_tasks (id, template_id, position, title_pattern, description, priority, tags, task_type, condition, include_template_id, depends_on_positions, metadata, created_at)
|
|
1823
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1678
1824
|
id,
|
|
1679
1825
|
templateId,
|
|
1680
1826
|
i,
|
|
@@ -1683,6 +1829,8 @@ function addTemplateTasks(templateId, tasks, db) {
|
|
|
1683
1829
|
task.priority || "medium",
|
|
1684
1830
|
JSON.stringify(task.tags || []),
|
|
1685
1831
|
task.task_type || null,
|
|
1832
|
+
task.condition || null,
|
|
1833
|
+
task.include_template_id || null,
|
|
1686
1834
|
JSON.stringify(task.depends_on || []),
|
|
1687
1835
|
JSON.stringify(task.metadata || {}),
|
|
1688
1836
|
now()
|
|
@@ -1710,11 +1858,142 @@ function getTemplateTasks(templateId, db) {
|
|
|
1710
1858
|
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(resolved);
|
|
1711
1859
|
return rows.map(rowToTemplateTask);
|
|
1712
1860
|
}
|
|
1713
|
-
function
|
|
1861
|
+
function evaluateCondition(condition, variables) {
|
|
1862
|
+
if (!condition || condition.trim() === "")
|
|
1863
|
+
return true;
|
|
1864
|
+
const trimmed = condition.trim();
|
|
1865
|
+
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
1866
|
+
if (eqMatch) {
|
|
1867
|
+
const varName = eqMatch[1];
|
|
1868
|
+
const expected = eqMatch[2].trim();
|
|
1869
|
+
return (variables[varName] ?? "") === expected;
|
|
1870
|
+
}
|
|
1871
|
+
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
1872
|
+
if (neqMatch) {
|
|
1873
|
+
const varName = neqMatch[1];
|
|
1874
|
+
const expected = neqMatch[2].trim();
|
|
1875
|
+
return (variables[varName] ?? "") !== expected;
|
|
1876
|
+
}
|
|
1877
|
+
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
1878
|
+
if (falsyMatch) {
|
|
1879
|
+
const varName = falsyMatch[1];
|
|
1880
|
+
const val = variables[varName];
|
|
1881
|
+
return !val || val === "" || val === "false";
|
|
1882
|
+
}
|
|
1883
|
+
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
1884
|
+
if (truthyMatch) {
|
|
1885
|
+
const varName = truthyMatch[1];
|
|
1886
|
+
const val = variables[varName];
|
|
1887
|
+
return !!val && val !== "" && val !== "false";
|
|
1888
|
+
}
|
|
1889
|
+
return true;
|
|
1890
|
+
}
|
|
1891
|
+
function exportTemplate(id, db) {
|
|
1892
|
+
const d = db || getDatabase();
|
|
1893
|
+
const template = getTemplateWithTasks(id, d);
|
|
1894
|
+
if (!template)
|
|
1895
|
+
throw new Error(`Template not found: ${id}`);
|
|
1896
|
+
return {
|
|
1897
|
+
name: template.name,
|
|
1898
|
+
title_pattern: template.title_pattern,
|
|
1899
|
+
description: template.description,
|
|
1900
|
+
priority: template.priority,
|
|
1901
|
+
tags: template.tags,
|
|
1902
|
+
variables: template.variables,
|
|
1903
|
+
project_id: template.project_id,
|
|
1904
|
+
plan_id: template.plan_id,
|
|
1905
|
+
metadata: template.metadata,
|
|
1906
|
+
tasks: template.tasks.map((t) => ({
|
|
1907
|
+
position: t.position,
|
|
1908
|
+
title_pattern: t.title_pattern,
|
|
1909
|
+
description: t.description,
|
|
1910
|
+
priority: t.priority,
|
|
1911
|
+
tags: t.tags,
|
|
1912
|
+
task_type: t.task_type,
|
|
1913
|
+
condition: t.condition,
|
|
1914
|
+
include_template_id: t.include_template_id,
|
|
1915
|
+
depends_on_positions: t.depends_on_positions,
|
|
1916
|
+
metadata: t.metadata
|
|
1917
|
+
}))
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function importTemplate(json, db) {
|
|
1921
|
+
const d = db || getDatabase();
|
|
1922
|
+
const taskInputs = (json.tasks || []).map((t) => ({
|
|
1923
|
+
title_pattern: t.title_pattern,
|
|
1924
|
+
description: t.description ?? undefined,
|
|
1925
|
+
priority: t.priority,
|
|
1926
|
+
tags: t.tags,
|
|
1927
|
+
task_type: t.task_type ?? undefined,
|
|
1928
|
+
condition: t.condition ?? undefined,
|
|
1929
|
+
include_template_id: t.include_template_id ?? undefined,
|
|
1930
|
+
depends_on: t.depends_on_positions,
|
|
1931
|
+
metadata: t.metadata
|
|
1932
|
+
}));
|
|
1933
|
+
return createTemplate({
|
|
1934
|
+
name: json.name,
|
|
1935
|
+
title_pattern: json.title_pattern,
|
|
1936
|
+
description: json.description ?? undefined,
|
|
1937
|
+
priority: json.priority,
|
|
1938
|
+
tags: json.tags,
|
|
1939
|
+
variables: json.variables,
|
|
1940
|
+
project_id: json.project_id ?? undefined,
|
|
1941
|
+
plan_id: json.plan_id ?? undefined,
|
|
1942
|
+
metadata: json.metadata,
|
|
1943
|
+
tasks: taskInputs
|
|
1944
|
+
}, d);
|
|
1945
|
+
}
|
|
1946
|
+
function getTemplateVersion(id, version, db) {
|
|
1947
|
+
const d = db || getDatabase();
|
|
1948
|
+
const resolved = resolveTemplateId(id, d);
|
|
1949
|
+
if (!resolved)
|
|
1950
|
+
return null;
|
|
1951
|
+
const row = d.query("SELECT * FROM template_versions WHERE template_id = ? AND version = ?").get(resolved, version);
|
|
1952
|
+
return row || null;
|
|
1953
|
+
}
|
|
1954
|
+
function listTemplateVersions(id, db) {
|
|
1955
|
+
const d = db || getDatabase();
|
|
1956
|
+
const resolved = resolveTemplateId(id, d);
|
|
1957
|
+
if (!resolved)
|
|
1958
|
+
return [];
|
|
1959
|
+
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
1960
|
+
}
|
|
1961
|
+
function resolveVariables(templateVars, provided) {
|
|
1962
|
+
const merged = { ...provided };
|
|
1963
|
+
for (const v of templateVars) {
|
|
1964
|
+
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
1965
|
+
merged[v.name] = v.default;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
const missing = [];
|
|
1969
|
+
for (const v of templateVars) {
|
|
1970
|
+
if (v.required && merged[v.name] === undefined) {
|
|
1971
|
+
missing.push(v.name);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
if (missing.length > 0) {
|
|
1975
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
1976
|
+
}
|
|
1977
|
+
return merged;
|
|
1978
|
+
}
|
|
1979
|
+
function substituteVars(text, variables) {
|
|
1980
|
+
let result = text;
|
|
1981
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
1982
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
1983
|
+
}
|
|
1984
|
+
return result;
|
|
1985
|
+
}
|
|
1986
|
+
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
1714
1987
|
const d = db || getDatabase();
|
|
1715
1988
|
const template = getTemplateWithTasks(templateId, d);
|
|
1716
1989
|
if (!template)
|
|
1717
1990
|
throw new Error(`Template not found: ${templateId}`);
|
|
1991
|
+
const visited = _visitedTemplateIds || new Set;
|
|
1992
|
+
if (visited.has(template.id)) {
|
|
1993
|
+
throw new Error(`Circular template reference detected: ${template.id}`);
|
|
1994
|
+
}
|
|
1995
|
+
visited.add(template.id);
|
|
1996
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
1718
1997
|
if (template.tasks.length === 0) {
|
|
1719
1998
|
const input = taskFromTemplate(templateId, { project_id: projectId, task_list_id: taskListId }, d);
|
|
1720
1999
|
const task = createTask(input, d);
|
|
@@ -1722,16 +2001,27 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
1722
2001
|
}
|
|
1723
2002
|
const createdTasks = [];
|
|
1724
2003
|
const positionToId = new Map;
|
|
2004
|
+
const skippedPositions = new Set;
|
|
1725
2005
|
for (const tt of template.tasks) {
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2006
|
+
if (tt.include_template_id) {
|
|
2007
|
+
const includedTasks = tasksFromTemplate(tt.include_template_id, projectId, resolved, taskListId, d, visited);
|
|
2008
|
+
createdTasks.push(...includedTasks);
|
|
2009
|
+
if (includedTasks.length > 0) {
|
|
2010
|
+
positionToId.set(tt.position, includedTasks[0].id);
|
|
2011
|
+
} else {
|
|
2012
|
+
skippedPositions.add(tt.position);
|
|
1733
2013
|
}
|
|
2014
|
+
continue;
|
|
1734
2015
|
}
|
|
2016
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved)) {
|
|
2017
|
+
skippedPositions.add(tt.position);
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
let title = tt.title_pattern;
|
|
2021
|
+
let desc = tt.description;
|
|
2022
|
+
title = substituteVars(title, resolved);
|
|
2023
|
+
if (desc)
|
|
2024
|
+
desc = substituteVars(desc, resolved);
|
|
1735
2025
|
const task = createTask({
|
|
1736
2026
|
title,
|
|
1737
2027
|
description: desc ?? undefined,
|
|
@@ -1746,8 +2036,14 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
1746
2036
|
positionToId.set(tt.position, task.id);
|
|
1747
2037
|
}
|
|
1748
2038
|
for (const tt of template.tasks) {
|
|
2039
|
+
if (skippedPositions.has(tt.position))
|
|
2040
|
+
continue;
|
|
2041
|
+
if (tt.include_template_id)
|
|
2042
|
+
continue;
|
|
1749
2043
|
const deps = tt.depends_on_positions;
|
|
1750
2044
|
for (const depPos of deps) {
|
|
2045
|
+
if (skippedPositions.has(depPos))
|
|
2046
|
+
continue;
|
|
1751
2047
|
const taskId = positionToId.get(tt.position);
|
|
1752
2048
|
const depId = positionToId.get(depPos);
|
|
1753
2049
|
if (taskId && depId) {
|
|
@@ -1757,6 +2053,47 @@ function tasksFromTemplate(templateId, projectId, variables, taskListId, db) {
|
|
|
1757
2053
|
}
|
|
1758
2054
|
return createdTasks;
|
|
1759
2055
|
}
|
|
2056
|
+
function previewTemplate(templateId, variables, db) {
|
|
2057
|
+
const d = db || getDatabase();
|
|
2058
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
2059
|
+
if (!template)
|
|
2060
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
2061
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
2062
|
+
const tasks = [];
|
|
2063
|
+
if (template.tasks.length === 0) {
|
|
2064
|
+
tasks.push({
|
|
2065
|
+
position: 0,
|
|
2066
|
+
title: substituteVars(template.title_pattern, resolved),
|
|
2067
|
+
description: template.description ? substituteVars(template.description, resolved) : null,
|
|
2068
|
+
priority: template.priority,
|
|
2069
|
+
tags: template.tags,
|
|
2070
|
+
task_type: null,
|
|
2071
|
+
depends_on_positions: []
|
|
2072
|
+
});
|
|
2073
|
+
} else {
|
|
2074
|
+
for (const tt of template.tasks) {
|
|
2075
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved))
|
|
2076
|
+
continue;
|
|
2077
|
+
tasks.push({
|
|
2078
|
+
position: tt.position,
|
|
2079
|
+
title: substituteVars(tt.title_pattern, resolved),
|
|
2080
|
+
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
2081
|
+
priority: tt.priority,
|
|
2082
|
+
tags: tt.tags,
|
|
2083
|
+
task_type: tt.task_type,
|
|
2084
|
+
depends_on_positions: tt.depends_on_positions
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return {
|
|
2089
|
+
template_id: template.id,
|
|
2090
|
+
template_name: template.name,
|
|
2091
|
+
description: template.description,
|
|
2092
|
+
variables: template.variables,
|
|
2093
|
+
resolved_variables: resolved,
|
|
2094
|
+
tasks
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
1760
2097
|
var init_templates = __esm(() => {
|
|
1761
2098
|
init_database();
|
|
1762
2099
|
init_tasks();
|
|
@@ -3538,6 +3875,8 @@ async function startServer(port, options) {
|
|
|
3538
3875
|
continue;
|
|
3539
3876
|
if (client.agentId && event.agent_id !== client.agentId)
|
|
3540
3877
|
continue;
|
|
3878
|
+
if (client.projectId && event.project_id !== client.projectId)
|
|
3879
|
+
continue;
|
|
3541
3880
|
try {
|
|
3542
3881
|
client.controller.enqueue(`event: ${eventName}
|
|
3543
3882
|
data: ${data}
|
|
@@ -3576,6 +3915,31 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
3576
3915
|
});
|
|
3577
3916
|
}
|
|
3578
3917
|
if (path === "/api/events" && method === "GET") {
|
|
3918
|
+
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
3919
|
+
const projectId = url.searchParams.get("project_id") || undefined;
|
|
3920
|
+
if (agentId || projectId) {
|
|
3921
|
+
const client = { controller: null, agentId, projectId, events: undefined };
|
|
3922
|
+
const stream2 = new ReadableStream({
|
|
3923
|
+
start(controller) {
|
|
3924
|
+
client.controller = controller;
|
|
3925
|
+
filteredSseClients.add(client);
|
|
3926
|
+
controller.enqueue(`data: ${JSON.stringify({ type: "connected", agent_id: agentId, project_id: projectId, timestamp: new Date().toISOString() })}
|
|
3927
|
+
|
|
3928
|
+
`);
|
|
3929
|
+
},
|
|
3930
|
+
cancel() {
|
|
3931
|
+
filteredSseClients.delete(client);
|
|
3932
|
+
}
|
|
3933
|
+
});
|
|
3934
|
+
return new Response(stream2, {
|
|
3935
|
+
headers: {
|
|
3936
|
+
"Content-Type": "text/event-stream",
|
|
3937
|
+
"Cache-Control": "no-cache",
|
|
3938
|
+
Connection: "keep-alive",
|
|
3939
|
+
"Access-Control-Allow-Origin": "*"
|
|
3940
|
+
}
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3579
3943
|
const stream = new ReadableStream({
|
|
3580
3944
|
start(controller) {
|
|
3581
3945
|
sseClients.add(controller);
|
|
@@ -3684,7 +4048,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
3684
4048
|
priority: body.priority,
|
|
3685
4049
|
project_id: body.project_id
|
|
3686
4050
|
});
|
|
3687
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id });
|
|
4051
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id, project_id: task.project_id });
|
|
3688
4052
|
return json(taskToSummary(task), 201, port);
|
|
3689
4053
|
} catch (e) {
|
|
3690
4054
|
return json({ error: e instanceof Error ? e.message : "Failed to create task" }, 500, port);
|
|
@@ -3905,7 +4269,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
3905
4269
|
const id = startMatch[1];
|
|
3906
4270
|
try {
|
|
3907
4271
|
const task = startTask(id, "dashboard");
|
|
3908
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "started", agent_id: "dashboard" });
|
|
4272
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "started", agent_id: "dashboard", project_id: task.project_id });
|
|
3909
4273
|
return json(taskToSummary(task), 200, port);
|
|
3910
4274
|
} catch (e) {
|
|
3911
4275
|
return json({ error: e instanceof Error ? e.message : "Failed to start task" }, 500, port);
|
|
@@ -3918,7 +4282,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
3918
4282
|
const body = await req.json().catch(() => ({}));
|
|
3919
4283
|
const { failTask: failTask2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
3920
4284
|
const result = failTask2(id, body.agent_id, body.reason, { retry: body.retry, error_code: body.error_code });
|
|
3921
|
-
broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null });
|
|
4285
|
+
broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null, project_id: result.task.project_id });
|
|
3922
4286
|
return json({ task: taskToSummary(result.task), retry_task: result.retryTask ? taskToSummary(result.retryTask) : null }, 200, port);
|
|
3923
4287
|
} catch (e) {
|
|
3924
4288
|
return json({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500, port);
|
|
@@ -3929,7 +4293,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
3929
4293
|
const id = completeMatch[1];
|
|
3930
4294
|
try {
|
|
3931
4295
|
const task = completeTask(id, "dashboard");
|
|
3932
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "completed", agent_id: "dashboard" });
|
|
4296
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "completed", agent_id: "dashboard", project_id: task.project_id });
|
|
3933
4297
|
return json(taskToSummary(task), 200, port);
|
|
3934
4298
|
} catch (e) {
|
|
3935
4299
|
return json({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500, port);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiHH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiHH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAk3B1G"}
|