@hasna/todos 0.11.72 → 0.11.74
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/README.md +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +2200 -1783
- package/dist/cli-mcp-parity.d.ts.map +1 -1
- package/dist/contracts.js +6500 -6251
- package/dist/db/api-keys.d.ts +9 -0
- package/dist/db/api-keys.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts +1 -0
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-status.d.ts.map +1 -1
- package/dist/index.js +9107 -8520
- package/dist/lib/db-backup.d.ts.map +1 -1
- package/dist/lib/task-routing.d.ts +48 -1
- package/dist/lib/task-routing.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1838 -1638
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp.js +6 -8
- package/dist/registry.js +6500 -6251
- package/dist/release-provenance.json +3 -3
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/index.js +6 -4
- package/dist/server/index.js +1838 -1638
- package/dist/server/routes.d.ts +2 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage.js +3298 -3022
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1807,6 +1807,11 @@ function ensureSchema(db) {
|
|
|
1807
1807
|
ensureColumn("tasks", "priority_score", "INTEGER");
|
|
1808
1808
|
ensureColumn("tasks", "priority_reason", "TEXT");
|
|
1809
1809
|
ensureColumn("tasks", "archived_at", "TEXT");
|
|
1810
|
+
ensureColumn("tasks", "runner_id", "TEXT");
|
|
1811
|
+
ensureColumn("tasks", "runner_started_at", "TEXT");
|
|
1812
|
+
ensureColumn("tasks", "runner_completed_at", "TEXT");
|
|
1813
|
+
ensureColumn("tasks", "current_step", "TEXT");
|
|
1814
|
+
ensureColumn("tasks", "total_steps", "INTEGER");
|
|
1810
1815
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
1811
1816
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
1812
1817
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -1814,6 +1819,10 @@ function ensureSchema(db) {
|
|
|
1814
1819
|
ensureColumn("agents", "level", "TEXT");
|
|
1815
1820
|
ensureColumn("agents", "org_id", "TEXT");
|
|
1816
1821
|
ensureColumn("agents", "capabilities", "TEXT DEFAULT '[]'");
|
|
1822
|
+
ensureColumn("agents", "session_id", "TEXT");
|
|
1823
|
+
ensureColumn("agents", "working_dir", "TEXT");
|
|
1824
|
+
ensureColumn("agents", "active_project_id", "TEXT");
|
|
1825
|
+
ensureColumn("agents", "status", "TEXT NOT NULL DEFAULT 'active'");
|
|
1817
1826
|
ensureColumn("projects", "org_id", "TEXT");
|
|
1818
1827
|
ensureColumn("plans", "slug", "TEXT");
|
|
1819
1828
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
@@ -2731,34 +2740,40 @@ function ensureDir(filePath) {
|
|
|
2731
2740
|
mkdirSync(dir, { recursive: true });
|
|
2732
2741
|
}
|
|
2733
2742
|
}
|
|
2743
|
+
function openDatabase(path) {
|
|
2744
|
+
ensureDir(path);
|
|
2745
|
+
const db = new Database(path);
|
|
2746
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
2747
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
2748
|
+
db.run("PRAGMA foreign_keys = ON");
|
|
2749
|
+
runMigrations(db);
|
|
2750
|
+
backfillTaskTags(db);
|
|
2751
|
+
backfillMachineId(db);
|
|
2752
|
+
return db;
|
|
2753
|
+
}
|
|
2734
2754
|
function getDatabase(dbPath) {
|
|
2735
2755
|
const path = dbPath || getDbPath();
|
|
2736
2756
|
if (_db && _dbPath === path)
|
|
2737
2757
|
return _db;
|
|
2738
|
-
|
|
2739
|
-
_db.close();
|
|
2740
|
-
_db = null;
|
|
2741
|
-
_dbPath = null;
|
|
2742
|
-
}
|
|
2743
|
-
ensureDir(path);
|
|
2744
|
-
_db = new Database(path);
|
|
2758
|
+
_db = openDatabase(path);
|
|
2745
2759
|
_dbPath = path;
|
|
2746
|
-
_db.run("PRAGMA journal_mode = WAL");
|
|
2747
|
-
_db.run("PRAGMA busy_timeout = 5000");
|
|
2748
|
-
_db.run("PRAGMA foreign_keys = ON");
|
|
2749
|
-
runMigrations(_db);
|
|
2750
|
-
backfillTaskTags(_db);
|
|
2751
|
-
backfillMachineId(_db);
|
|
2752
2760
|
return _db;
|
|
2753
2761
|
}
|
|
2754
2762
|
function closeDatabase() {
|
|
2755
2763
|
if (_db) {
|
|
2756
|
-
|
|
2764
|
+
try {
|
|
2765
|
+
_db.close();
|
|
2766
|
+
} catch {}
|
|
2757
2767
|
_db = null;
|
|
2758
2768
|
_dbPath = null;
|
|
2759
2769
|
}
|
|
2760
2770
|
}
|
|
2761
2771
|
function resetDatabase() {
|
|
2772
|
+
if (_db) {
|
|
2773
|
+
try {
|
|
2774
|
+
_db.close();
|
|
2775
|
+
} catch {}
|
|
2776
|
+
}
|
|
2762
2777
|
_db = null;
|
|
2763
2778
|
_dbPath = null;
|
|
2764
2779
|
}
|
|
@@ -10385,1767 +10400,1858 @@ var init_checklists = __esm(() => {
|
|
|
10385
10400
|
init_database();
|
|
10386
10401
|
});
|
|
10387
10402
|
|
|
10388
|
-
// src/
|
|
10389
|
-
function
|
|
10403
|
+
// src/lib/recurrence.ts
|
|
10404
|
+
function parseRecurrenceRule(rule) {
|
|
10405
|
+
const normalized = rule.trim().toLowerCase();
|
|
10406
|
+
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
10407
|
+
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
10408
|
+
}
|
|
10409
|
+
if (normalized === "every day" || normalized === "daily") {
|
|
10410
|
+
return { type: "interval", interval: 1, unit: "day" };
|
|
10411
|
+
}
|
|
10412
|
+
if (normalized === "every week" || normalized === "weekly") {
|
|
10413
|
+
return { type: "interval", interval: 1, unit: "week" };
|
|
10414
|
+
}
|
|
10415
|
+
if (normalized === "every month" || normalized === "monthly") {
|
|
10416
|
+
return { type: "interval", interval: 1, unit: "month" };
|
|
10417
|
+
}
|
|
10418
|
+
const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
|
|
10419
|
+
if (intervalMatch) {
|
|
10420
|
+
return {
|
|
10421
|
+
type: "interval",
|
|
10422
|
+
interval: parseInt(intervalMatch[1], 10),
|
|
10423
|
+
unit: intervalMatch[2]
|
|
10424
|
+
};
|
|
10425
|
+
}
|
|
10426
|
+
const daysMatch = normalized.match(/^every\s+(.+)$/);
|
|
10427
|
+
if (daysMatch) {
|
|
10428
|
+
const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
|
|
10429
|
+
const days = [];
|
|
10430
|
+
for (const part of dayParts) {
|
|
10431
|
+
const dayNum = DAY_NAMES[part];
|
|
10432
|
+
if (dayNum !== undefined) {
|
|
10433
|
+
days.push(dayNum);
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10436
|
+
if (days.length > 0) {
|
|
10437
|
+
return { type: "specific_days", days: days.sort((a, b) => a - b) };
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
|
|
10441
|
+
}
|
|
10442
|
+
function isValidRecurrenceRule(rule) {
|
|
10443
|
+
try {
|
|
10444
|
+
parseRecurrenceRule(rule);
|
|
10445
|
+
return true;
|
|
10446
|
+
} catch {
|
|
10447
|
+
return false;
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10450
|
+
function nextOccurrence(rule, from) {
|
|
10451
|
+
const parsed = parseRecurrenceRule(rule);
|
|
10452
|
+
const base = from || new Date;
|
|
10453
|
+
if (parsed.type === "interval") {
|
|
10454
|
+
const next = new Date(base);
|
|
10455
|
+
if (parsed.unit === "day") {
|
|
10456
|
+
next.setDate(next.getDate() + parsed.interval);
|
|
10457
|
+
} else if (parsed.unit === "week") {
|
|
10458
|
+
next.setDate(next.getDate() + parsed.interval * 7);
|
|
10459
|
+
} else if (parsed.unit === "month") {
|
|
10460
|
+
next.setMonth(next.getMonth() + parsed.interval);
|
|
10461
|
+
}
|
|
10462
|
+
return next.toISOString();
|
|
10463
|
+
}
|
|
10464
|
+
if (parsed.type === "specific_days") {
|
|
10465
|
+
const currentDay = base.getDay();
|
|
10466
|
+
const days = parsed.days;
|
|
10467
|
+
let daysToAdd = Infinity;
|
|
10468
|
+
for (const day of days) {
|
|
10469
|
+
let diff = day - currentDay;
|
|
10470
|
+
if (diff <= 0)
|
|
10471
|
+
diff += 7;
|
|
10472
|
+
if (diff < daysToAdd)
|
|
10473
|
+
daysToAdd = diff;
|
|
10474
|
+
}
|
|
10475
|
+
const next = new Date(base);
|
|
10476
|
+
next.setDate(next.getDate() + daysToAdd);
|
|
10477
|
+
return next.toISOString();
|
|
10478
|
+
}
|
|
10479
|
+
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
10480
|
+
}
|
|
10481
|
+
var DAY_NAMES;
|
|
10482
|
+
var init_recurrence = __esm(() => {
|
|
10483
|
+
DAY_NAMES = {
|
|
10484
|
+
sunday: 0,
|
|
10485
|
+
sun: 0,
|
|
10486
|
+
monday: 1,
|
|
10487
|
+
mon: 1,
|
|
10488
|
+
tuesday: 2,
|
|
10489
|
+
tue: 2,
|
|
10490
|
+
wednesday: 3,
|
|
10491
|
+
wed: 3,
|
|
10492
|
+
thursday: 4,
|
|
10493
|
+
thu: 4,
|
|
10494
|
+
friday: 5,
|
|
10495
|
+
fri: 5,
|
|
10496
|
+
saturday: 6,
|
|
10497
|
+
sat: 6
|
|
10498
|
+
};
|
|
10499
|
+
});
|
|
10500
|
+
|
|
10501
|
+
// src/db/templates.ts
|
|
10502
|
+
var exports_templates = {};
|
|
10503
|
+
__export(exports_templates, {
|
|
10504
|
+
updateTemplate: () => updateTemplate,
|
|
10505
|
+
tasksFromTemplate: () => tasksFromTemplate,
|
|
10506
|
+
taskFromTemplate: () => taskFromTemplate,
|
|
10507
|
+
resolveVariables: () => resolveVariables,
|
|
10508
|
+
previewTemplate: () => previewTemplate,
|
|
10509
|
+
listTemplates: () => listTemplates,
|
|
10510
|
+
listTemplateVersions: () => listTemplateVersions,
|
|
10511
|
+
importTemplate: () => importTemplate,
|
|
10512
|
+
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
10513
|
+
getTemplateVersion: () => getTemplateVersion,
|
|
10514
|
+
getTemplateTasks: () => getTemplateTasks,
|
|
10515
|
+
getTemplate: () => getTemplate,
|
|
10516
|
+
exportTemplate: () => exportTemplate,
|
|
10517
|
+
evaluateCondition: () => evaluateCondition,
|
|
10518
|
+
deleteTemplate: () => deleteTemplate,
|
|
10519
|
+
createTemplate: () => createTemplate,
|
|
10520
|
+
addTemplateTasks: () => addTemplateTasks
|
|
10521
|
+
});
|
|
10522
|
+
function rowToTemplate(row) {
|
|
10390
10523
|
return {
|
|
10391
10524
|
...row,
|
|
10392
10525
|
tags: JSON.parse(row.tags || "[]"),
|
|
10526
|
+
variables: JSON.parse(row.variables || "[]"),
|
|
10393
10527
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
requires_approval: !!row.requires_approval
|
|
10528
|
+
priority: row.priority || "medium",
|
|
10529
|
+
version: row.version ?? 1
|
|
10397
10530
|
};
|
|
10398
10531
|
}
|
|
10399
|
-
function
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
db.run("DELETE FROM task_tags WHERE task_id = ?", [taskId]);
|
|
10410
|
-
insertTaskTags(taskId, tags, db);
|
|
10532
|
+
function rowToTemplateTask(row) {
|
|
10533
|
+
return {
|
|
10534
|
+
...row,
|
|
10535
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
10536
|
+
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
10537
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
10538
|
+
priority: row.priority || "medium",
|
|
10539
|
+
condition: row.condition ?? null,
|
|
10540
|
+
include_template_id: row.include_template_id ?? null
|
|
10541
|
+
};
|
|
10411
10542
|
}
|
|
10412
|
-
function
|
|
10413
|
-
|
|
10414
|
-
return;
|
|
10415
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
10416
|
-
if (!/^[A-Za-z0-9_.-]+$/.test(key)) {
|
|
10417
|
-
throw new Error(`Invalid metadata filter key: ${key}`);
|
|
10418
|
-
}
|
|
10419
|
-
conditions.push(`json_extract(metadata, '$."${key}"') = ?`);
|
|
10420
|
-
params.push(value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value));
|
|
10421
|
-
}
|
|
10543
|
+
function resolveTemplateId(id, d) {
|
|
10544
|
+
return resolvePartialId(d, "task_templates", id);
|
|
10422
10545
|
}
|
|
10423
|
-
function
|
|
10546
|
+
function createTemplate(input, db) {
|
|
10424
10547
|
const d = db || getDatabase();
|
|
10425
|
-
const
|
|
10426
|
-
const tags = input.tags || [];
|
|
10548
|
+
const id = uuid();
|
|
10427
10549
|
const machineId = currentStorageMachineId(d);
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
input.priority || "medium",
|
|
10446
|
-
input.agent_id || null,
|
|
10447
|
-
input.assigned_to || null,
|
|
10448
|
-
input.session_id || null,
|
|
10449
|
-
input.working_dir || null,
|
|
10450
|
-
JSON.stringify(tags),
|
|
10451
|
-
JSON.stringify(input.metadata || {}),
|
|
10452
|
-
timestamp,
|
|
10453
|
-
timestamp,
|
|
10454
|
-
input.due_at || null,
|
|
10455
|
-
input.estimated_minutes || null,
|
|
10456
|
-
input.sla_minutes ?? null,
|
|
10457
|
-
input.confidence ?? null,
|
|
10458
|
-
input.retry_count ?? 0,
|
|
10459
|
-
input.max_retries ?? 3,
|
|
10460
|
-
input.retry_after ?? null,
|
|
10461
|
-
input.requires_approval ? 1 : 0,
|
|
10462
|
-
null,
|
|
10463
|
-
null,
|
|
10464
|
-
input.recurrence_rule || null,
|
|
10465
|
-
input.recurrence_parent_id || null,
|
|
10466
|
-
input.spawns_template_id || null,
|
|
10467
|
-
input.reason || null,
|
|
10468
|
-
input.spawned_from_session || null,
|
|
10469
|
-
assignedBy || null,
|
|
10470
|
-
assignedFromProject || null,
|
|
10471
|
-
input.task_type || null,
|
|
10472
|
-
machineId
|
|
10473
|
-
]);
|
|
10474
|
-
break;
|
|
10475
|
-
} catch (e) {
|
|
10476
|
-
if (attempt < 2 && e?.message?.includes("UNIQUE constraint failed: tasks.id")) {
|
|
10477
|
-
id = uuid();
|
|
10478
|
-
continue;
|
|
10479
|
-
}
|
|
10480
|
-
throw e;
|
|
10481
|
-
}
|
|
10482
|
-
}
|
|
10483
|
-
if (tags.length > 0) {
|
|
10484
|
-
insertTaskTags(id, tags, d);
|
|
10550
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
|
|
10551
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10552
|
+
id,
|
|
10553
|
+
input.name,
|
|
10554
|
+
input.title_pattern,
|
|
10555
|
+
input.description || null,
|
|
10556
|
+
input.priority || "medium",
|
|
10557
|
+
JSON.stringify(input.tags || []),
|
|
10558
|
+
JSON.stringify(input.variables || []),
|
|
10559
|
+
input.project_id || null,
|
|
10560
|
+
input.plan_id || null,
|
|
10561
|
+
JSON.stringify(input.metadata || {}),
|
|
10562
|
+
now(),
|
|
10563
|
+
machineId
|
|
10564
|
+
]);
|
|
10565
|
+
if (input.tasks && input.tasks.length > 0) {
|
|
10566
|
+
addTemplateTasks(id, input.tasks, d);
|
|
10485
10567
|
}
|
|
10486
|
-
|
|
10487
|
-
const payload = taskEventData(task);
|
|
10488
|
-
const databasePath = databasePathFromDatabase(d);
|
|
10489
|
-
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
10490
|
-
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
10491
|
-
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
10492
|
-
return task;
|
|
10568
|
+
return getTemplate(id, d);
|
|
10493
10569
|
}
|
|
10494
|
-
function
|
|
10570
|
+
function getTemplate(id, db) {
|
|
10495
10571
|
const d = db || getDatabase();
|
|
10496
|
-
const
|
|
10497
|
-
if (!
|
|
10572
|
+
const resolved = resolveTemplateId(id, d);
|
|
10573
|
+
if (!resolved)
|
|
10498
10574
|
return null;
|
|
10499
|
-
|
|
10575
|
+
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(resolved);
|
|
10576
|
+
return row ? rowToTemplate(row) : null;
|
|
10500
10577
|
}
|
|
10501
|
-
function
|
|
10578
|
+
function listTemplates(db) {
|
|
10502
10579
|
const d = db || getDatabase();
|
|
10503
|
-
|
|
10504
|
-
if (!task)
|
|
10505
|
-
return null;
|
|
10506
|
-
const subtaskRows = d.query("SELECT * FROM tasks WHERE parent_id = ? ORDER BY created_at").all(id);
|
|
10507
|
-
const subtasks = subtaskRows.map(rowToTask);
|
|
10508
|
-
const depRows = d.query(`SELECT t.* FROM tasks t
|
|
10509
|
-
JOIN task_dependencies td ON td.depends_on = t.id
|
|
10510
|
-
WHERE td.task_id = ?`).all(id);
|
|
10511
|
-
const dependencies = depRows.map(rowToTask);
|
|
10512
|
-
const blockedByRows = d.query(`SELECT t.* FROM tasks t
|
|
10513
|
-
JOIN task_dependencies td ON td.task_id = t.id
|
|
10514
|
-
WHERE td.depends_on = ?`).all(id);
|
|
10515
|
-
const blocked_by = blockedByRows.map(rowToTask);
|
|
10516
|
-
const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
|
|
10517
|
-
const parent = task.parent_id ? getTask(task.parent_id, d) : null;
|
|
10518
|
-
const checklist = getChecklist(id, d);
|
|
10519
|
-
return {
|
|
10520
|
-
...task,
|
|
10521
|
-
subtasks,
|
|
10522
|
-
dependencies,
|
|
10523
|
-
blocked_by,
|
|
10524
|
-
comments,
|
|
10525
|
-
parent,
|
|
10526
|
-
checklist
|
|
10527
|
-
};
|
|
10580
|
+
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
10528
10581
|
}
|
|
10529
|
-
function
|
|
10582
|
+
function deleteTemplate(id, db) {
|
|
10530
10583
|
const d = db || getDatabase();
|
|
10531
|
-
const
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
const
|
|
10535
|
-
if (
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
}
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
if (
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
conditions.push("priority = ?");
|
|
10566
|
-
params.push(filter.priority);
|
|
10567
|
-
}
|
|
10568
|
-
}
|
|
10569
|
-
if (filter.assigned_to) {
|
|
10570
|
-
conditions.push("assigned_to = ?");
|
|
10571
|
-
params.push(filter.assigned_to);
|
|
10572
|
-
}
|
|
10573
|
-
if (filter.agent_id) {
|
|
10574
|
-
conditions.push("agent_id = ?");
|
|
10575
|
-
params.push(filter.agent_id);
|
|
10584
|
+
const resolved = resolveTemplateId(id, d);
|
|
10585
|
+
if (!resolved)
|
|
10586
|
+
return false;
|
|
10587
|
+
const template = getTemplate(resolved, d);
|
|
10588
|
+
if (!template)
|
|
10589
|
+
return false;
|
|
10590
|
+
recordStorageTombstone({
|
|
10591
|
+
object_type: "templates",
|
|
10592
|
+
object_id: resolved,
|
|
10593
|
+
payload: template,
|
|
10594
|
+
version: template.version
|
|
10595
|
+
}, d);
|
|
10596
|
+
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
10597
|
+
}
|
|
10598
|
+
function updateTemplate(id, updates, db) {
|
|
10599
|
+
const d = db || getDatabase();
|
|
10600
|
+
const resolved = resolveTemplateId(id, d);
|
|
10601
|
+
if (!resolved)
|
|
10602
|
+
return null;
|
|
10603
|
+
const current = getTemplateWithTasks(resolved, d);
|
|
10604
|
+
if (current) {
|
|
10605
|
+
const snapshot = JSON.stringify({
|
|
10606
|
+
name: current.name,
|
|
10607
|
+
title_pattern: current.title_pattern,
|
|
10608
|
+
description: current.description,
|
|
10609
|
+
priority: current.priority,
|
|
10610
|
+
tags: current.tags,
|
|
10611
|
+
variables: current.variables,
|
|
10612
|
+
project_id: current.project_id,
|
|
10613
|
+
plan_id: current.plan_id,
|
|
10614
|
+
metadata: current.metadata,
|
|
10615
|
+
tasks: current.tasks
|
|
10616
|
+
});
|
|
10617
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
10576
10618
|
}
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10619
|
+
const sets = ["version = version + 1"];
|
|
10620
|
+
const values = [];
|
|
10621
|
+
if (updates.name !== undefined) {
|
|
10622
|
+
sets.push("name = ?");
|
|
10623
|
+
values.push(updates.name);
|
|
10580
10624
|
}
|
|
10581
|
-
if (
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
params.push(...filter.tags);
|
|
10625
|
+
if (updates.title_pattern !== undefined) {
|
|
10626
|
+
sets.push("title_pattern = ?");
|
|
10627
|
+
values.push(updates.title_pattern);
|
|
10585
10628
|
}
|
|
10586
|
-
if (
|
|
10587
|
-
|
|
10588
|
-
|
|
10629
|
+
if (updates.description !== undefined) {
|
|
10630
|
+
sets.push("description = ?");
|
|
10631
|
+
values.push(updates.description);
|
|
10589
10632
|
}
|
|
10590
|
-
if (
|
|
10591
|
-
|
|
10592
|
-
|
|
10633
|
+
if (updates.priority !== undefined) {
|
|
10634
|
+
sets.push("priority = ?");
|
|
10635
|
+
values.push(updates.priority);
|
|
10593
10636
|
}
|
|
10594
|
-
if (
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
conditions.push("recurrence_rule IS NULL");
|
|
10637
|
+
if (updates.tags !== undefined) {
|
|
10638
|
+
sets.push("tags = ?");
|
|
10639
|
+
values.push(JSON.stringify(updates.tags));
|
|
10598
10640
|
}
|
|
10599
|
-
if (
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
params.push(...filter.task_type);
|
|
10603
|
-
} else {
|
|
10604
|
-
conditions.push("task_type = ?");
|
|
10605
|
-
params.push(filter.task_type);
|
|
10606
|
-
}
|
|
10641
|
+
if (updates.variables !== undefined) {
|
|
10642
|
+
sets.push("variables = ?");
|
|
10643
|
+
values.push(JSON.stringify(updates.variables));
|
|
10607
10644
|
}
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
try {
|
|
10612
|
-
const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
|
|
10613
|
-
conditions.push(`(${PRIORITY_RANK} > ? OR (${PRIORITY_RANK} = ? AND created_at < ?) OR (${PRIORITY_RANK} = ? AND created_at = ? AND id > ?))`);
|
|
10614
|
-
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
10615
|
-
} catch {}
|
|
10645
|
+
if (updates.project_id !== undefined) {
|
|
10646
|
+
sets.push("project_id = ?");
|
|
10647
|
+
values.push(updates.project_id);
|
|
10616
10648
|
}
|
|
10617
|
-
if (
|
|
10618
|
-
|
|
10649
|
+
if (updates.plan_id !== undefined) {
|
|
10650
|
+
sets.push("plan_id = ?");
|
|
10651
|
+
values.push(updates.plan_id);
|
|
10619
10652
|
}
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
limitClause = " LIMIT ?";
|
|
10624
|
-
params.push(filter.limit);
|
|
10625
|
-
if (!filter.cursor && filter.offset) {
|
|
10626
|
-
limitClause += " OFFSET ?";
|
|
10627
|
-
params.push(filter.offset);
|
|
10628
|
-
}
|
|
10653
|
+
if (updates.metadata !== undefined) {
|
|
10654
|
+
sets.push("metadata = ?");
|
|
10655
|
+
values.push(JSON.stringify(updates.metadata));
|
|
10629
10656
|
}
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
function getTaskByFingerprint(fingerprint, db) {
|
|
10634
|
-
const tasks = listTasks({ metadata: { fingerprint }, limit: 1 }, db);
|
|
10635
|
-
return tasks[0] ?? null;
|
|
10657
|
+
values.push(resolved);
|
|
10658
|
+
d.run(`UPDATE task_templates SET ${sets.join(", ")} WHERE id = ?`, values);
|
|
10659
|
+
return getTemplate(resolved, d);
|
|
10636
10660
|
}
|
|
10637
|
-
function
|
|
10661
|
+
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
10662
|
+
const t = getTemplate(templateId, db);
|
|
10663
|
+
if (!t)
|
|
10664
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
10665
|
+
const cleanOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined));
|
|
10638
10666
|
return {
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10667
|
+
title: cleanOverrides.title || t.title_pattern,
|
|
10668
|
+
description: cleanOverrides.description ?? t.description ?? undefined,
|
|
10669
|
+
priority: cleanOverrides.priority ?? t.priority,
|
|
10670
|
+
tags: cleanOverrides.tags ?? t.tags,
|
|
10671
|
+
project_id: cleanOverrides.project_id ?? t.project_id ?? undefined,
|
|
10672
|
+
plan_id: cleanOverrides.plan_id ?? t.plan_id ?? undefined,
|
|
10673
|
+
metadata: cleanOverrides.metadata ?? t.metadata,
|
|
10674
|
+
...cleanOverrides
|
|
10642
10675
|
};
|
|
10643
10676
|
}
|
|
10644
|
-
function
|
|
10677
|
+
function addTemplateTasks(templateId, tasks, db) {
|
|
10645
10678
|
const d = db || getDatabase();
|
|
10646
|
-
const
|
|
10647
|
-
if (!
|
|
10648
|
-
throw new Error(
|
|
10649
|
-
|
|
10650
|
-
const
|
|
10651
|
-
|
|
10652
|
-
const
|
|
10653
|
-
|
|
10679
|
+
const template = getTemplate(templateId, d);
|
|
10680
|
+
if (!template)
|
|
10681
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
10682
|
+
d.run("DELETE FROM template_tasks WHERE template_id = ?", [templateId]);
|
|
10683
|
+
const results = [];
|
|
10684
|
+
for (let i = 0;i < tasks.length; i++) {
|
|
10685
|
+
const task = tasks[i];
|
|
10686
|
+
const id = uuid();
|
|
10687
|
+
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)
|
|
10688
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10689
|
+
id,
|
|
10690
|
+
templateId,
|
|
10691
|
+
i,
|
|
10692
|
+
task.title_pattern,
|
|
10693
|
+
task.description || null,
|
|
10694
|
+
task.priority || "medium",
|
|
10695
|
+
JSON.stringify(task.tags || []),
|
|
10696
|
+
task.task_type || null,
|
|
10697
|
+
task.condition || null,
|
|
10698
|
+
task.include_template_id || null,
|
|
10699
|
+
JSON.stringify(task.depends_on || []),
|
|
10700
|
+
JSON.stringify(task.metadata || {}),
|
|
10701
|
+
now()
|
|
10702
|
+
]);
|
|
10703
|
+
const row = d.query("SELECT * FROM template_tasks WHERE id = ?").get(id);
|
|
10704
|
+
if (row)
|
|
10705
|
+
results.push(rowToTemplateTask(row));
|
|
10654
10706
|
}
|
|
10655
|
-
|
|
10656
|
-
version: existing.version,
|
|
10657
|
-
title: input.title,
|
|
10658
|
-
description: input.description,
|
|
10659
|
-
status: input.status,
|
|
10660
|
-
priority: input.priority,
|
|
10661
|
-
project_id: input.project_id,
|
|
10662
|
-
assigned_to: input.assigned_to,
|
|
10663
|
-
working_dir: input.working_dir,
|
|
10664
|
-
plan_id: input.plan_id,
|
|
10665
|
-
task_list_id: input.task_list_id,
|
|
10666
|
-
tags: input.tags,
|
|
10667
|
-
metadata,
|
|
10668
|
-
due_at: input.due_at,
|
|
10669
|
-
estimated_minutes: input.estimated_minutes,
|
|
10670
|
-
sla_minutes: input.sla_minutes,
|
|
10671
|
-
confidence: input.confidence,
|
|
10672
|
-
retry_count: input.retry_count,
|
|
10673
|
-
max_retries: input.max_retries,
|
|
10674
|
-
retry_after: input.retry_after,
|
|
10675
|
-
requires_approval: input.requires_approval,
|
|
10676
|
-
recurrence_rule: input.recurrence_rule,
|
|
10677
|
-
task_type: input.task_type
|
|
10678
|
-
}, d);
|
|
10679
|
-
return { task, created: false };
|
|
10707
|
+
return results;
|
|
10680
10708
|
}
|
|
10681
|
-
function
|
|
10709
|
+
function getTemplateWithTasks(id, db) {
|
|
10682
10710
|
const d = db || getDatabase();
|
|
10683
|
-
const
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10711
|
+
const template = getTemplate(id, d);
|
|
10712
|
+
if (!template)
|
|
10713
|
+
return null;
|
|
10714
|
+
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(template.id);
|
|
10715
|
+
const tasks = rows.map(rowToTemplateTask);
|
|
10716
|
+
return { ...template, tasks };
|
|
10717
|
+
}
|
|
10718
|
+
function getTemplateTasks(templateId, db) {
|
|
10719
|
+
const d = db || getDatabase();
|
|
10720
|
+
const resolved = resolveTemplateId(templateId, d);
|
|
10721
|
+
if (!resolved)
|
|
10722
|
+
return [];
|
|
10723
|
+
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(resolved);
|
|
10724
|
+
return rows.map(rowToTemplateTask);
|
|
10725
|
+
}
|
|
10726
|
+
function evaluateCondition(condition, variables) {
|
|
10727
|
+
if (!condition || condition.trim() === "")
|
|
10728
|
+
return true;
|
|
10729
|
+
const trimmed = condition.trim();
|
|
10730
|
+
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
10731
|
+
if (eqMatch) {
|
|
10732
|
+
const varName = eqMatch[1];
|
|
10733
|
+
const expected = eqMatch[2].trim();
|
|
10734
|
+
return (variables[varName] ?? "") === expected;
|
|
10688
10735
|
}
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10736
|
+
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
10737
|
+
if (neqMatch) {
|
|
10738
|
+
const varName = neqMatch[1];
|
|
10739
|
+
const expected = neqMatch[2].trim();
|
|
10740
|
+
return (variables[varName] ?? "") !== expected;
|
|
10692
10741
|
}
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
params.push(filter.parent_id);
|
|
10699
|
-
}
|
|
10742
|
+
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
10743
|
+
if (falsyMatch) {
|
|
10744
|
+
const varName = falsyMatch[1];
|
|
10745
|
+
const val = variables[varName];
|
|
10746
|
+
return !val || val === "" || val === "false";
|
|
10700
10747
|
}
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
conditions.push("status = ?");
|
|
10707
|
-
params.push(filter.status);
|
|
10708
|
-
}
|
|
10748
|
+
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
10749
|
+
if (truthyMatch) {
|
|
10750
|
+
const varName = truthyMatch[1];
|
|
10751
|
+
const val = variables[varName];
|
|
10752
|
+
return !!val && val !== "" && val !== "false";
|
|
10709
10753
|
}
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10754
|
+
return true;
|
|
10755
|
+
}
|
|
10756
|
+
function exportTemplate(id, db) {
|
|
10757
|
+
const d = db || getDatabase();
|
|
10758
|
+
const template = getTemplateWithTasks(id, d);
|
|
10759
|
+
if (!template)
|
|
10760
|
+
throw new Error(`Template not found: ${id}`);
|
|
10761
|
+
return {
|
|
10762
|
+
name: template.name,
|
|
10763
|
+
title_pattern: template.title_pattern,
|
|
10764
|
+
description: template.description,
|
|
10765
|
+
priority: template.priority,
|
|
10766
|
+
tags: template.tags,
|
|
10767
|
+
variables: template.variables,
|
|
10768
|
+
project_id: template.project_id,
|
|
10769
|
+
plan_id: template.plan_id,
|
|
10770
|
+
metadata: template.metadata,
|
|
10771
|
+
tasks: template.tasks.map((t) => ({
|
|
10772
|
+
position: t.position,
|
|
10773
|
+
title_pattern: t.title_pattern,
|
|
10774
|
+
description: t.description,
|
|
10775
|
+
priority: t.priority,
|
|
10776
|
+
tags: t.tags,
|
|
10777
|
+
task_type: t.task_type,
|
|
10778
|
+
condition: t.condition,
|
|
10779
|
+
include_template_id: t.include_template_id,
|
|
10780
|
+
depends_on_positions: t.depends_on_positions,
|
|
10781
|
+
metadata: t.metadata
|
|
10782
|
+
}))
|
|
10783
|
+
};
|
|
10784
|
+
}
|
|
10785
|
+
function importTemplate(json, db) {
|
|
10786
|
+
const d = db || getDatabase();
|
|
10787
|
+
const taskInputs = (json.tasks || []).map((t) => ({
|
|
10788
|
+
title_pattern: t.title_pattern,
|
|
10789
|
+
description: t.description ?? undefined,
|
|
10790
|
+
priority: t.priority,
|
|
10791
|
+
tags: t.tags,
|
|
10792
|
+
task_type: t.task_type ?? undefined,
|
|
10793
|
+
condition: t.condition ?? undefined,
|
|
10794
|
+
include_template_id: t.include_template_id ?? undefined,
|
|
10795
|
+
depends_on: t.depends_on_positions,
|
|
10796
|
+
metadata: t.metadata
|
|
10797
|
+
}));
|
|
10798
|
+
return createTemplate({
|
|
10799
|
+
name: json.name,
|
|
10800
|
+
title_pattern: json.title_pattern,
|
|
10801
|
+
description: json.description ?? undefined,
|
|
10802
|
+
priority: json.priority,
|
|
10803
|
+
tags: json.tags,
|
|
10804
|
+
variables: json.variables,
|
|
10805
|
+
project_id: json.project_id ?? undefined,
|
|
10806
|
+
plan_id: json.plan_id ?? undefined,
|
|
10807
|
+
metadata: json.metadata,
|
|
10808
|
+
tasks: taskInputs
|
|
10809
|
+
}, d);
|
|
10810
|
+
}
|
|
10811
|
+
function getTemplateVersion(id, version, db) {
|
|
10812
|
+
const d = db || getDatabase();
|
|
10813
|
+
const resolved = resolveTemplateId(id, d);
|
|
10814
|
+
if (!resolved)
|
|
10815
|
+
return null;
|
|
10816
|
+
const row = d.query("SELECT * FROM template_versions WHERE template_id = ? AND version = ?").get(resolved, version);
|
|
10817
|
+
return row || null;
|
|
10818
|
+
}
|
|
10819
|
+
function listTemplateVersions(id, db) {
|
|
10820
|
+
const d = db || getDatabase();
|
|
10821
|
+
const resolved = resolveTemplateId(id, d);
|
|
10822
|
+
if (!resolved)
|
|
10823
|
+
return [];
|
|
10824
|
+
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
10825
|
+
}
|
|
10826
|
+
function resolveVariables(templateVars, provided) {
|
|
10827
|
+
const merged = { ...provided };
|
|
10828
|
+
for (const v of templateVars) {
|
|
10829
|
+
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
10830
|
+
merged[v.name] = v.default;
|
|
10717
10831
|
}
|
|
10718
10832
|
}
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
conditions.push("agent_id = ?");
|
|
10725
|
-
params.push(filter.agent_id);
|
|
10726
|
-
}
|
|
10727
|
-
if (filter.session_id) {
|
|
10728
|
-
conditions.push("session_id = ?");
|
|
10729
|
-
params.push(filter.session_id);
|
|
10730
|
-
}
|
|
10731
|
-
if (filter.tags && filter.tags.length > 0) {
|
|
10732
|
-
const placeholders = filter.tags.map(() => "?").join(",");
|
|
10733
|
-
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
10734
|
-
params.push(...filter.tags);
|
|
10735
|
-
}
|
|
10736
|
-
if (filter.plan_id) {
|
|
10737
|
-
conditions.push("plan_id = ?");
|
|
10738
|
-
params.push(filter.plan_id);
|
|
10833
|
+
const missing = [];
|
|
10834
|
+
for (const v of templateVars) {
|
|
10835
|
+
if (v.required && merged[v.name] === undefined) {
|
|
10836
|
+
missing.push(v.name);
|
|
10837
|
+
}
|
|
10739
10838
|
}
|
|
10740
|
-
if (
|
|
10741
|
-
|
|
10742
|
-
params.push(filter.task_list_id);
|
|
10839
|
+
if (missing.length > 0) {
|
|
10840
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
10743
10841
|
}
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10842
|
+
return merged;
|
|
10843
|
+
}
|
|
10844
|
+
function substituteVars(text, variables) {
|
|
10845
|
+
let result = text;
|
|
10846
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
10847
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
10747
10848
|
}
|
|
10748
|
-
|
|
10749
|
-
const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
10750
|
-
return row.count;
|
|
10849
|
+
return result;
|
|
10751
10850
|
}
|
|
10752
|
-
function
|
|
10851
|
+
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
10753
10852
|
const d = db || getDatabase();
|
|
10754
|
-
const
|
|
10755
|
-
if (!
|
|
10756
|
-
throw new
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
const timestamp = now();
|
|
10761
|
-
const completionTimestamp = input.completed_at ?? timestamp;
|
|
10762
|
-
const sets = ["version = version + 1", "updated_at = ?"];
|
|
10763
|
-
const params = [timestamp];
|
|
10764
|
-
if (input.title !== undefined) {
|
|
10765
|
-
sets.push("title = ?");
|
|
10766
|
-
params.push(input.title);
|
|
10853
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
10854
|
+
if (!template)
|
|
10855
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
10856
|
+
const visited = _visitedTemplateIds || new Set;
|
|
10857
|
+
if (visited.has(template.id)) {
|
|
10858
|
+
throw new Error(`Circular template reference detected: ${template.id}`);
|
|
10767
10859
|
}
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10860
|
+
visited.add(template.id);
|
|
10861
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
10862
|
+
if (template.tasks.length === 0) {
|
|
10863
|
+
const input = taskFromTemplate(templateId, { project_id: projectId, task_list_id: taskListId }, d);
|
|
10864
|
+
const task = createTask(input, d);
|
|
10865
|
+
return [task];
|
|
10771
10866
|
}
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10867
|
+
const createdTasks = [];
|
|
10868
|
+
const positionToId = new Map;
|
|
10869
|
+
const skippedPositions = new Set;
|
|
10870
|
+
for (const tt of template.tasks) {
|
|
10871
|
+
if (tt.include_template_id) {
|
|
10872
|
+
const includedTasks = tasksFromTemplate(tt.include_template_id, projectId, resolved, taskListId, d, visited);
|
|
10873
|
+
createdTasks.push(...includedTasks);
|
|
10874
|
+
if (includedTasks.length > 0) {
|
|
10875
|
+
positionToId.set(tt.position, includedTasks[0].id);
|
|
10876
|
+
} else {
|
|
10877
|
+
skippedPositions.add(tt.position);
|
|
10878
|
+
}
|
|
10879
|
+
continue;
|
|
10775
10880
|
}
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
sets.push("completed_at = ?");
|
|
10780
|
-
params.push(completionTimestamp);
|
|
10881
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved)) {
|
|
10882
|
+
skippedPositions.add(tt.position);
|
|
10883
|
+
continue;
|
|
10781
10884
|
}
|
|
10885
|
+
let title = tt.title_pattern;
|
|
10886
|
+
let desc = tt.description;
|
|
10887
|
+
title = substituteVars(title, resolved);
|
|
10888
|
+
if (desc)
|
|
10889
|
+
desc = substituteVars(desc, resolved);
|
|
10890
|
+
const task = createTask({
|
|
10891
|
+
title,
|
|
10892
|
+
description: desc ?? undefined,
|
|
10893
|
+
priority: tt.priority,
|
|
10894
|
+
tags: tt.tags,
|
|
10895
|
+
task_type: tt.task_type ?? undefined,
|
|
10896
|
+
project_id: projectId,
|
|
10897
|
+
task_list_id: taskListId,
|
|
10898
|
+
metadata: tt.metadata
|
|
10899
|
+
}, d);
|
|
10900
|
+
createdTasks.push(task);
|
|
10901
|
+
positionToId.set(tt.position, task.id);
|
|
10782
10902
|
}
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10903
|
+
for (const tt of template.tasks) {
|
|
10904
|
+
if (skippedPositions.has(tt.position))
|
|
10905
|
+
continue;
|
|
10906
|
+
if (tt.include_template_id)
|
|
10907
|
+
continue;
|
|
10908
|
+
const deps = tt.depends_on_positions;
|
|
10909
|
+
for (const depPos of deps) {
|
|
10910
|
+
if (skippedPositions.has(depPos))
|
|
10911
|
+
continue;
|
|
10912
|
+
const taskId = positionToId.get(tt.position);
|
|
10913
|
+
const depId = positionToId.get(depPos);
|
|
10914
|
+
if (taskId && depId) {
|
|
10915
|
+
addDependency(taskId, depId, d);
|
|
10916
|
+
}
|
|
10917
|
+
}
|
|
10790
10918
|
}
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10919
|
+
return createdTasks;
|
|
10920
|
+
}
|
|
10921
|
+
function previewTemplate(templateId, variables, db) {
|
|
10922
|
+
const d = db || getDatabase();
|
|
10923
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
10924
|
+
if (!template)
|
|
10925
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
10926
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
10927
|
+
const tasks = [];
|
|
10928
|
+
if (template.tasks.length === 0) {
|
|
10929
|
+
tasks.push({
|
|
10930
|
+
position: 0,
|
|
10931
|
+
title: substituteVars(template.title_pattern, resolved),
|
|
10932
|
+
description: template.description ? substituteVars(template.description, resolved) : null,
|
|
10933
|
+
priority: template.priority,
|
|
10934
|
+
tags: template.tags,
|
|
10935
|
+
task_type: null,
|
|
10936
|
+
depends_on_positions: []
|
|
10937
|
+
});
|
|
10938
|
+
} else {
|
|
10939
|
+
for (const tt of template.tasks) {
|
|
10940
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved))
|
|
10941
|
+
continue;
|
|
10942
|
+
tasks.push({
|
|
10943
|
+
position: tt.position,
|
|
10944
|
+
title: substituteVars(tt.title_pattern, resolved),
|
|
10945
|
+
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
10946
|
+
priority: tt.priority,
|
|
10947
|
+
tags: tt.tags,
|
|
10948
|
+
task_type: tt.task_type,
|
|
10949
|
+
depends_on_positions: tt.depends_on_positions
|
|
10950
|
+
});
|
|
10951
|
+
}
|
|
10794
10952
|
}
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10953
|
+
return {
|
|
10954
|
+
template_id: template.id,
|
|
10955
|
+
template_name: template.name,
|
|
10956
|
+
description: template.description,
|
|
10957
|
+
variables: template.variables,
|
|
10958
|
+
resolved_variables: resolved,
|
|
10959
|
+
tasks
|
|
10960
|
+
};
|
|
10961
|
+
}
|
|
10962
|
+
var init_templates = __esm(() => {
|
|
10963
|
+
init_database();
|
|
10964
|
+
init_tasks();
|
|
10965
|
+
init_storage_tombstones();
|
|
10966
|
+
});
|
|
10967
|
+
|
|
10968
|
+
// src/db/task-graph.ts
|
|
10969
|
+
function addDependency(taskId, dependsOn, db) {
|
|
10970
|
+
const d = db || getDatabase();
|
|
10971
|
+
if (!getTask(taskId, d))
|
|
10972
|
+
throw new TaskNotFoundError(taskId);
|
|
10973
|
+
if (!getTask(dependsOn, d))
|
|
10974
|
+
throw new TaskNotFoundError(dependsOn);
|
|
10975
|
+
if (wouldCreateCycle(taskId, dependsOn, d)) {
|
|
10976
|
+
throw new DependencyCycleError(taskId, dependsOn);
|
|
10798
10977
|
}
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
10978
|
+
d.run("INSERT OR IGNORE INTO task_dependencies (task_id, depends_on) VALUES (?, ?)", [taskId, dependsOn]);
|
|
10979
|
+
}
|
|
10980
|
+
function removeDependency(taskId, dependsOn, db) {
|
|
10981
|
+
const d = db || getDatabase();
|
|
10982
|
+
const result = d.run("DELETE FROM task_dependencies WHERE task_id = ? AND depends_on = ?", [taskId, dependsOn]);
|
|
10983
|
+
return result.changes > 0;
|
|
10984
|
+
}
|
|
10985
|
+
function getTaskDependencies(taskId, db) {
|
|
10986
|
+
const d = db || getDatabase();
|
|
10987
|
+
return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
|
|
10988
|
+
}
|
|
10989
|
+
function getTaskDependents(taskId, db) {
|
|
10990
|
+
const d = db || getDatabase();
|
|
10991
|
+
return d.query("SELECT * FROM task_dependencies WHERE depends_on = ?").all(taskId);
|
|
10992
|
+
}
|
|
10993
|
+
function cloneTask(taskId, overrides, db) {
|
|
10994
|
+
const d = db || getDatabase();
|
|
10995
|
+
const source = getTask(taskId, d);
|
|
10996
|
+
if (!source)
|
|
10997
|
+
throw new TaskNotFoundError(taskId);
|
|
10998
|
+
const input = {
|
|
10999
|
+
title: overrides?.title ?? source.title,
|
|
11000
|
+
description: overrides?.description ?? source.description ?? undefined,
|
|
11001
|
+
priority: overrides?.priority ?? source.priority,
|
|
11002
|
+
project_id: overrides?.project_id ?? source.project_id ?? undefined,
|
|
11003
|
+
parent_id: overrides?.parent_id ?? source.parent_id ?? undefined,
|
|
11004
|
+
plan_id: overrides?.plan_id ?? source.plan_id ?? undefined,
|
|
11005
|
+
task_list_id: overrides?.task_list_id ?? source.task_list_id ?? undefined,
|
|
11006
|
+
status: overrides?.status ?? "pending",
|
|
11007
|
+
agent_id: overrides?.agent_id ?? source.agent_id ?? undefined,
|
|
11008
|
+
assigned_to: overrides?.assigned_to ?? source.assigned_to ?? undefined,
|
|
11009
|
+
tags: overrides?.tags ?? source.tags,
|
|
11010
|
+
metadata: overrides?.metadata ?? source.metadata,
|
|
11011
|
+
estimated_minutes: overrides?.estimated_minutes ?? source.estimated_minutes ?? undefined,
|
|
11012
|
+
recurrence_rule: overrides?.recurrence_rule ?? source.recurrence_rule ?? undefined
|
|
11013
|
+
};
|
|
11014
|
+
return createTask(input, d);
|
|
11015
|
+
}
|
|
11016
|
+
function getTaskGraph(taskId, direction = "both", db) {
|
|
11017
|
+
const d = db || getDatabase();
|
|
11018
|
+
const task = getTask(taskId, d);
|
|
11019
|
+
if (!task)
|
|
11020
|
+
throw new TaskNotFoundError(taskId);
|
|
11021
|
+
function toNode(t) {
|
|
11022
|
+
const deps = getTaskDependencies(t.id, d);
|
|
11023
|
+
const hasUnfinishedDeps = deps.some((dep) => {
|
|
11024
|
+
const depTask = getTask(dep.depends_on, d);
|
|
11025
|
+
return depTask && depTask.status !== "completed";
|
|
11026
|
+
});
|
|
11027
|
+
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
10802
11028
|
}
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
11029
|
+
function buildUp(id, visited) {
|
|
11030
|
+
if (visited.has(id))
|
|
11031
|
+
return [];
|
|
11032
|
+
visited.add(id);
|
|
11033
|
+
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
11034
|
+
return deps.map((dep) => {
|
|
11035
|
+
const depTask = getTask(dep.depends_on, d);
|
|
11036
|
+
if (!depTask)
|
|
11037
|
+
return null;
|
|
11038
|
+
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
11039
|
+
}).filter(Boolean);
|
|
10806
11040
|
}
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
11041
|
+
function buildDown(id, visited) {
|
|
11042
|
+
if (visited.has(id))
|
|
11043
|
+
return [];
|
|
11044
|
+
visited.add(id);
|
|
11045
|
+
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
11046
|
+
return dependents.map((dep) => {
|
|
11047
|
+
const depTask = getTask(dep.task_id, d);
|
|
11048
|
+
if (!depTask)
|
|
11049
|
+
return null;
|
|
11050
|
+
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
11051
|
+
}).filter(Boolean);
|
|
10810
11052
|
}
|
|
10811
|
-
|
|
11053
|
+
const rootNode = toNode(task);
|
|
11054
|
+
const depends_on = direction === "up" || direction === "both" ? buildUp(taskId, new Set) : [];
|
|
11055
|
+
const blocks = direction === "down" || direction === "both" ? buildDown(taskId, new Set) : [];
|
|
11056
|
+
return { task: rootNode, depends_on, blocks };
|
|
11057
|
+
}
|
|
11058
|
+
function moveTask(taskId, target, db) {
|
|
11059
|
+
const d = db || getDatabase();
|
|
11060
|
+
const task = getTask(taskId, d);
|
|
11061
|
+
if (!task)
|
|
11062
|
+
throw new TaskNotFoundError(taskId);
|
|
11063
|
+
const sets = ["updated_at = ?", "version = version + 1"];
|
|
11064
|
+
const params = [now()];
|
|
11065
|
+
if (target.task_list_id !== undefined) {
|
|
10812
11066
|
sets.push("task_list_id = ?");
|
|
10813
|
-
params.push(
|
|
10814
|
-
}
|
|
10815
|
-
if (input.due_at !== undefined) {
|
|
10816
|
-
sets.push("due_at = ?");
|
|
10817
|
-
params.push(input.due_at);
|
|
10818
|
-
}
|
|
10819
|
-
if (input.estimated_minutes !== undefined) {
|
|
10820
|
-
sets.push("estimated_minutes = ?");
|
|
10821
|
-
params.push(input.estimated_minutes);
|
|
10822
|
-
}
|
|
10823
|
-
if (input.sla_minutes !== undefined) {
|
|
10824
|
-
sets.push("sla_minutes = ?");
|
|
10825
|
-
params.push(input.sla_minutes);
|
|
10826
|
-
}
|
|
10827
|
-
if (input.actual_minutes !== undefined) {
|
|
10828
|
-
sets.push("actual_minutes = ?");
|
|
10829
|
-
params.push(input.actual_minutes);
|
|
10830
|
-
}
|
|
10831
|
-
if (input.completed_at !== undefined && input.status !== "completed") {
|
|
10832
|
-
sets.push("completed_at = ?");
|
|
10833
|
-
params.push(input.completed_at);
|
|
10834
|
-
}
|
|
10835
|
-
if (input.confidence !== undefined) {
|
|
10836
|
-
sets.push("confidence = ?");
|
|
10837
|
-
params.push(input.confidence);
|
|
10838
|
-
}
|
|
10839
|
-
if (input.retry_count !== undefined) {
|
|
10840
|
-
sets.push("retry_count = ?");
|
|
10841
|
-
params.push(input.retry_count);
|
|
11067
|
+
params.push(target.task_list_id);
|
|
10842
11068
|
}
|
|
10843
|
-
if (
|
|
10844
|
-
sets.push("
|
|
10845
|
-
params.push(
|
|
11069
|
+
if (target.project_id !== undefined) {
|
|
11070
|
+
sets.push("project_id = ?");
|
|
11071
|
+
params.push(target.project_id);
|
|
10846
11072
|
}
|
|
10847
|
-
if (
|
|
10848
|
-
sets.push("
|
|
10849
|
-
params.push(
|
|
11073
|
+
if (target.plan_id !== undefined) {
|
|
11074
|
+
sets.push("plan_id = ?");
|
|
11075
|
+
params.push(target.plan_id);
|
|
10850
11076
|
}
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
11077
|
+
params.push(taskId);
|
|
11078
|
+
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
11079
|
+
return getTask(taskId, d);
|
|
11080
|
+
}
|
|
11081
|
+
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
11082
|
+
const visited = new Set;
|
|
11083
|
+
const queue = [dependsOn];
|
|
11084
|
+
while (queue.length > 0) {
|
|
11085
|
+
const current = queue.shift();
|
|
11086
|
+
if (current === taskId)
|
|
11087
|
+
return true;
|
|
11088
|
+
if (visited.has(current))
|
|
11089
|
+
continue;
|
|
11090
|
+
visited.add(current);
|
|
11091
|
+
const deps = db.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(current);
|
|
11092
|
+
for (const dep of deps) {
|
|
11093
|
+
queue.push(dep.depends_on);
|
|
11094
|
+
}
|
|
10854
11095
|
}
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
11096
|
+
return false;
|
|
11097
|
+
}
|
|
11098
|
+
var init_task_graph = __esm(() => {
|
|
11099
|
+
init_types();
|
|
11100
|
+
init_database();
|
|
11101
|
+
init_task_crud();
|
|
11102
|
+
});
|
|
11103
|
+
|
|
11104
|
+
// src/db/task-lifecycle.ts
|
|
11105
|
+
var exports_task_lifecycle = {};
|
|
11106
|
+
__export(exports_task_lifecycle, {
|
|
11107
|
+
unlockTask: () => unlockTask,
|
|
11108
|
+
stealTask: () => stealTask,
|
|
11109
|
+
startTask: () => startTask,
|
|
11110
|
+
spawnNextRecurrence: () => spawnNextRecurrence,
|
|
11111
|
+
lockTask: () => lockTask,
|
|
11112
|
+
getTasksChangedSince: () => getTasksChangedSince,
|
|
11113
|
+
getTaskLockStatus: () => getTaskLockStatus,
|
|
11114
|
+
getStaleTasks: () => getStaleTasks,
|
|
11115
|
+
getNextTask: () => getNextTask,
|
|
11116
|
+
getBlockingDeps: () => getBlockingDeps,
|
|
11117
|
+
getActiveWork: () => getActiveWork,
|
|
11118
|
+
failTask: () => failTask,
|
|
11119
|
+
completeTask: () => completeTask,
|
|
11120
|
+
claimOrSteal: () => claimOrSteal,
|
|
11121
|
+
claimNextTask: () => claimNextTask
|
|
11122
|
+
});
|
|
11123
|
+
function lockExpiresAt(lockedAt) {
|
|
11124
|
+
if (!lockedAt)
|
|
11125
|
+
return null;
|
|
11126
|
+
return new Date(new Date(lockedAt).getTime() + LOCK_EXPIRY_MINUTES * 60 * 1000).toISOString();
|
|
11127
|
+
}
|
|
11128
|
+
function assertStartable(task, agentId) {
|
|
11129
|
+
if (task.status === "pending")
|
|
11130
|
+
return;
|
|
11131
|
+
if (task.status === "in_progress")
|
|
11132
|
+
return;
|
|
11133
|
+
throw new Error(`Task is ${task.status} and cannot be started by ${agentId}`);
|
|
11134
|
+
}
|
|
11135
|
+
function getBlockingDeps(id, db) {
|
|
11136
|
+
const d = db || getDatabase();
|
|
11137
|
+
const deps = getTaskDependencies(id, d);
|
|
11138
|
+
if (deps.length === 0)
|
|
11139
|
+
return [];
|
|
11140
|
+
const blocking = [];
|
|
11141
|
+
for (const dep of deps) {
|
|
11142
|
+
const task = getTask(dep.depends_on, d);
|
|
11143
|
+
if (task && task.status !== "completed")
|
|
11144
|
+
blocking.push(task);
|
|
10864
11145
|
}
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
11146
|
+
return blocking;
|
|
11147
|
+
}
|
|
11148
|
+
function startTask(id, agentId, db) {
|
|
11149
|
+
const d = db || getDatabase();
|
|
11150
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11151
|
+
const task = getTask(id, d);
|
|
11152
|
+
if (!task)
|
|
11153
|
+
throw new TaskNotFoundError(id);
|
|
11154
|
+
assertStartable(task, agentId);
|
|
11155
|
+
const blocking = getBlockingDeps(id, d);
|
|
11156
|
+
if (blocking.length > 0) {
|
|
11157
|
+
const blockerIds = blocking.map((b) => b.id.slice(0, 8)).join(", ");
|
|
11158
|
+
emitLocalEventHooksQuiet({
|
|
11159
|
+
type: "task.blocked",
|
|
11160
|
+
payload: {
|
|
11161
|
+
id,
|
|
11162
|
+
agent_id: agentId,
|
|
11163
|
+
title: task.title,
|
|
11164
|
+
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
11165
|
+
},
|
|
11166
|
+
databasePath
|
|
11167
|
+
});
|
|
11168
|
+
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
10868
11169
|
}
|
|
10869
|
-
|
|
10870
|
-
const
|
|
11170
|
+
const cutoff = lockExpiryCutoff();
|
|
11171
|
+
const timestamp = now();
|
|
11172
|
+
const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
|
|
11173
|
+
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, timestamp, id, agentId, cutoff]);
|
|
10871
11174
|
if (result.changes === 0) {
|
|
10872
11175
|
const current = getTask(id, d);
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
11176
|
+
if (!current)
|
|
11177
|
+
throw new TaskNotFoundError(id);
|
|
11178
|
+
assertStartable(current, agentId);
|
|
11179
|
+
if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
|
|
11180
|
+
throw new LockError(id, current.locked_by);
|
|
11181
|
+
}
|
|
11182
|
+
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
10877
11183
|
}
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
logTaskChange(id, "update", "working_dir", task.working_dir, input.working_dir, agentId, d);
|
|
10889
|
-
if (input.approved_by !== undefined)
|
|
10890
|
-
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
10891
|
-
const updatedTask = {
|
|
10892
|
-
...task,
|
|
10893
|
-
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
10894
|
-
tags: input.tags ?? task.tags,
|
|
10895
|
-
metadata: input.metadata ?? task.metadata,
|
|
10896
|
-
version: task.version + 1,
|
|
10897
|
-
updated_at: timestamp,
|
|
10898
|
-
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
10899
|
-
sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
|
|
10900
|
-
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
10901
|
-
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
10902
|
-
retry_count: input.retry_count ?? task.retry_count,
|
|
10903
|
-
max_retries: input.max_retries ?? task.max_retries,
|
|
10904
|
-
retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
|
|
10905
|
-
requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
|
|
10906
|
-
approved_by: input.approved_by ?? task.approved_by,
|
|
10907
|
-
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
10908
|
-
};
|
|
11184
|
+
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
11185
|
+
const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, started_at: task.started_at || timestamp, version: task.version + 1, updated_at: timestamp };
|
|
11186
|
+
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
11187
|
+
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
11188
|
+
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
11189
|
+
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
|
|
11190
|
+
return startedTask;
|
|
11191
|
+
}
|
|
11192
|
+
function completeTask(id, agentId, db, options) {
|
|
11193
|
+
const d = db || getDatabase();
|
|
10909
11194
|
const databasePath = databasePathFromDatabase(d);
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
11195
|
+
const task = getTask(id, d);
|
|
11196
|
+
if (!task)
|
|
11197
|
+
throw new TaskNotFoundError(id);
|
|
11198
|
+
if (task.status === "completed") {
|
|
11199
|
+
return task;
|
|
10915
11200
|
}
|
|
10916
|
-
if (
|
|
10917
|
-
|
|
10918
|
-
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
10919
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
|
|
10920
|
-
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
|
|
11201
|
+
if (task.status === "cancelled") {
|
|
11202
|
+
throw new Error(`Task ${id} is cancelled and cannot be completed`);
|
|
10921
11203
|
}
|
|
10922
|
-
if (
|
|
10923
|
-
|
|
11204
|
+
if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
|
|
11205
|
+
throw new LockError(id, task.locked_by);
|
|
10924
11206
|
}
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
10934
|
-
if (!row)
|
|
10935
|
-
return false;
|
|
10936
|
-
recordStorageTombstone({
|
|
10937
|
-
object_type: "tasks",
|
|
10938
|
-
object_id: id,
|
|
10939
|
-
payload: rowToTask(row),
|
|
10940
|
-
version: row.version
|
|
10941
|
-
}, d);
|
|
10942
|
-
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
10943
|
-
return result.changes > 0;
|
|
10944
|
-
}
|
|
10945
|
-
var init_task_crud = __esm(() => {
|
|
10946
|
-
init_types();
|
|
10947
|
-
init_database();
|
|
10948
|
-
init_completion_guard();
|
|
10949
|
-
init_event_emission_safety();
|
|
10950
|
-
init_event_hooks();
|
|
10951
|
-
init_shared_events();
|
|
10952
|
-
init_audit();
|
|
10953
|
-
init_webhooks();
|
|
10954
|
-
init_checklists();
|
|
10955
|
-
init_storage_tombstones();
|
|
10956
|
-
});
|
|
10957
|
-
|
|
10958
|
-
// src/lib/recurrence.ts
|
|
10959
|
-
function parseRecurrenceRule(rule) {
|
|
10960
|
-
const normalized = rule.trim().toLowerCase();
|
|
10961
|
-
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
10962
|
-
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
11207
|
+
checkCompletionGuard(task, agentId || null, d);
|
|
11208
|
+
const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes, attachment_ids: options.attachment_ids } : undefined;
|
|
11209
|
+
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes || evidence.attachment_ids);
|
|
11210
|
+
const completionMeta = {};
|
|
11211
|
+
if (hasEvidence)
|
|
11212
|
+
completionMeta._evidence = evidence;
|
|
11213
|
+
if (options?.confidence !== undefined) {
|
|
11214
|
+
completionMeta._completion = { confidence: options.confidence };
|
|
10963
11215
|
}
|
|
10964
|
-
|
|
10965
|
-
|
|
11216
|
+
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
11217
|
+
const timestamp = options?.completed_at || now();
|
|
11218
|
+
const confidence = options?.confidence !== undefined ? options.confidence : task.confidence;
|
|
11219
|
+
const versionBeforeStatus = task.version + (hasMeta ? 1 : 0);
|
|
11220
|
+
const finalVersion = versionBeforeStatus + 1;
|
|
11221
|
+
const tx = d.transaction(() => {
|
|
11222
|
+
if (hasMeta) {
|
|
11223
|
+
const meta2 = { ...task.metadata, ...completionMeta };
|
|
11224
|
+
const metaResult = d.run("UPDATE tasks SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ? AND version = ?", [JSON.stringify(meta2), timestamp, id, task.version]);
|
|
11225
|
+
if (metaResult.changes === 0) {
|
|
11226
|
+
const current = getTask(id, d);
|
|
11227
|
+
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
const statusResult = d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, confidence = ?, version = version + 1, updated_at = ?
|
|
11231
|
+
WHERE id = ? AND version = ?`, [timestamp, confidence, timestamp, id, versionBeforeStatus]);
|
|
11232
|
+
if (statusResult.changes === 0) {
|
|
11233
|
+
const current = getTask(id, d);
|
|
11234
|
+
throw new VersionConflictError(id, versionBeforeStatus, current?.version ?? -1);
|
|
11235
|
+
}
|
|
11236
|
+
});
|
|
11237
|
+
tx();
|
|
11238
|
+
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
11239
|
+
const completedTaskForEvent = {
|
|
11240
|
+
...task,
|
|
11241
|
+
status: "completed",
|
|
11242
|
+
locked_by: null,
|
|
11243
|
+
locked_at: null,
|
|
11244
|
+
completed_at: timestamp,
|
|
11245
|
+
confidence,
|
|
11246
|
+
version: finalVersion,
|
|
11247
|
+
updated_at: timestamp,
|
|
11248
|
+
metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
|
|
11249
|
+
};
|
|
11250
|
+
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
11251
|
+
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
11252
|
+
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
11253
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
|
|
11254
|
+
let spawnedTask = null;
|
|
11255
|
+
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
11256
|
+
try {
|
|
11257
|
+
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
11258
|
+
} catch (e) {
|
|
11259
|
+
spawnedTask = null;
|
|
11260
|
+
console.warn(`[tasks] failed to spawn next recurrence for ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
11261
|
+
}
|
|
10966
11262
|
}
|
|
10967
|
-
|
|
10968
|
-
|
|
11263
|
+
let spawnedFromTemplate = null;
|
|
11264
|
+
if (task.spawns_template_id) {
|
|
11265
|
+
const spawnDepth = task.metadata?._spawn_depth || 0;
|
|
11266
|
+
if (spawnDepth >= MAX_SPAWN_DEPTH) {
|
|
11267
|
+
console.warn(`[tasks] Task ${id} exceeded max spawn depth (${MAX_SPAWN_DEPTH}), skipping template spawn`);
|
|
11268
|
+
} else {
|
|
11269
|
+
try {
|
|
11270
|
+
const input = taskFromTemplate(task.spawns_template_id, {
|
|
11271
|
+
project_id: task.project_id ?? undefined,
|
|
11272
|
+
plan_id: task.plan_id ?? undefined,
|
|
11273
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
11274
|
+
assigned_to: task.assigned_to ?? undefined
|
|
11275
|
+
}, d);
|
|
11276
|
+
input.metadata = { ...input.metadata || {}, _spawn_depth: spawnDepth + 1 };
|
|
11277
|
+
spawnedFromTemplate = createTask(input, d);
|
|
11278
|
+
} catch {}
|
|
11279
|
+
}
|
|
10969
11280
|
}
|
|
10970
|
-
|
|
10971
|
-
|
|
11281
|
+
const meta = hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata;
|
|
11282
|
+
if (spawnedTask) {
|
|
11283
|
+
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
10972
11284
|
}
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
return {
|
|
10976
|
-
type: "interval",
|
|
10977
|
-
interval: parseInt(intervalMatch[1], 10),
|
|
10978
|
-
unit: intervalMatch[2]
|
|
10979
|
-
};
|
|
11285
|
+
if (spawnedFromTemplate) {
|
|
11286
|
+
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
10980
11287
|
}
|
|
10981
|
-
const
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
}
|
|
10991
|
-
|
|
10992
|
-
|
|
11288
|
+
const unblockedDeps = d.query(`SELECT DISTINCT t.id, t.short_id, t.title FROM tasks t
|
|
11289
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
11290
|
+
WHERE td.depends_on = ? AND t.status = 'pending'
|
|
11291
|
+
AND NOT EXISTS (
|
|
11292
|
+
SELECT 1 FROM task_dependencies td2
|
|
11293
|
+
JOIN tasks dep2 ON dep2.id = td2.depends_on
|
|
11294
|
+
WHERE td2.task_id = t.id AND dep2.status NOT IN ('completed', 'cancelled') AND dep2.id != ?
|
|
11295
|
+
)`).all(id, id);
|
|
11296
|
+
if (unblockedDeps.length > 0) {
|
|
11297
|
+
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
11298
|
+
for (const dep of unblockedDeps) {
|
|
11299
|
+
const depTask = getTask(dep.id, d);
|
|
11300
|
+
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
11301
|
+
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
11302
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
|
|
11303
|
+
if (depTask)
|
|
11304
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
10993
11305
|
}
|
|
10994
11306
|
}
|
|
10995
|
-
|
|
11307
|
+
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: finalVersion, updated_at: timestamp, metadata: meta };
|
|
10996
11308
|
}
|
|
10997
|
-
function
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11309
|
+
function lockTask(id, agentId, db) {
|
|
11310
|
+
const d = db || getDatabase();
|
|
11311
|
+
const task = getTask(id, d);
|
|
11312
|
+
if (!task)
|
|
11313
|
+
throw new TaskNotFoundError(id);
|
|
11314
|
+
if (task.status === "completed" || task.status === "cancelled") {
|
|
11315
|
+
return {
|
|
11316
|
+
success: false,
|
|
11317
|
+
error: `Task is ${task.status} and cannot be locked`
|
|
11318
|
+
};
|
|
11003
11319
|
}
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
const next = new Date(base);
|
|
11010
|
-
if (parsed.unit === "day") {
|
|
11011
|
-
next.setDate(next.getDate() + parsed.interval);
|
|
11012
|
-
} else if (parsed.unit === "week") {
|
|
11013
|
-
next.setDate(next.getDate() + parsed.interval * 7);
|
|
11014
|
-
} else if (parsed.unit === "month") {
|
|
11015
|
-
next.setMonth(next.getMonth() + parsed.interval);
|
|
11016
|
-
}
|
|
11017
|
-
return next.toISOString();
|
|
11320
|
+
if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
|
|
11321
|
+
const timestamp2 = now();
|
|
11322
|
+
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [timestamp2, timestamp2, id, agentId]);
|
|
11323
|
+
logTaskChange(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
|
|
11324
|
+
return { success: true, locked_by: agentId, locked_at: timestamp2, expires_at: lockExpiresAt(timestamp2) };
|
|
11018
11325
|
}
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11326
|
+
const cutoff = lockExpiryCutoff();
|
|
11327
|
+
const timestamp = now();
|
|
11328
|
+
const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
|
|
11329
|
+
WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp, timestamp, id, agentId, cutoff]);
|
|
11330
|
+
if (result.changes === 0) {
|
|
11331
|
+
const current = getTask(id, d);
|
|
11332
|
+
if (!current)
|
|
11333
|
+
throw new TaskNotFoundError(id);
|
|
11334
|
+
if (current.status === "completed" || current.status === "cancelled") {
|
|
11335
|
+
return {
|
|
11336
|
+
success: false,
|
|
11337
|
+
error: `Task is ${current.status} and cannot be locked`
|
|
11338
|
+
};
|
|
11029
11339
|
}
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11340
|
+
if (current.locked_by && !isLockExpired(current.locked_at)) {
|
|
11341
|
+
return {
|
|
11342
|
+
success: false,
|
|
11343
|
+
locked_by: current.locked_by,
|
|
11344
|
+
locked_at: current.locked_at,
|
|
11345
|
+
error: `Task is locked by ${current.locked_by}`
|
|
11346
|
+
};
|
|
11347
|
+
}
|
|
11348
|
+
return {
|
|
11349
|
+
success: false,
|
|
11350
|
+
error: `Task ${id} could not be locked because it changed during lock acquisition`
|
|
11351
|
+
};
|
|
11033
11352
|
}
|
|
11034
|
-
|
|
11035
|
-
}
|
|
11036
|
-
var DAY_NAMES;
|
|
11037
|
-
var init_recurrence = __esm(() => {
|
|
11038
|
-
DAY_NAMES = {
|
|
11039
|
-
sunday: 0,
|
|
11040
|
-
sun: 0,
|
|
11041
|
-
monday: 1,
|
|
11042
|
-
mon: 1,
|
|
11043
|
-
tuesday: 2,
|
|
11044
|
-
tue: 2,
|
|
11045
|
-
wednesday: 3,
|
|
11046
|
-
wed: 3,
|
|
11047
|
-
thursday: 4,
|
|
11048
|
-
thu: 4,
|
|
11049
|
-
friday: 5,
|
|
11050
|
-
fri: 5,
|
|
11051
|
-
saturday: 6,
|
|
11052
|
-
sat: 6
|
|
11053
|
-
};
|
|
11054
|
-
});
|
|
11055
|
-
|
|
11056
|
-
// src/db/templates.ts
|
|
11057
|
-
var exports_templates = {};
|
|
11058
|
-
__export(exports_templates, {
|
|
11059
|
-
updateTemplate: () => updateTemplate,
|
|
11060
|
-
tasksFromTemplate: () => tasksFromTemplate,
|
|
11061
|
-
taskFromTemplate: () => taskFromTemplate,
|
|
11062
|
-
resolveVariables: () => resolveVariables,
|
|
11063
|
-
previewTemplate: () => previewTemplate,
|
|
11064
|
-
listTemplates: () => listTemplates,
|
|
11065
|
-
listTemplateVersions: () => listTemplateVersions,
|
|
11066
|
-
importTemplate: () => importTemplate,
|
|
11067
|
-
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
11068
|
-
getTemplateVersion: () => getTemplateVersion,
|
|
11069
|
-
getTemplateTasks: () => getTemplateTasks,
|
|
11070
|
-
getTemplate: () => getTemplate,
|
|
11071
|
-
exportTemplate: () => exportTemplate,
|
|
11072
|
-
evaluateCondition: () => evaluateCondition,
|
|
11073
|
-
deleteTemplate: () => deleteTemplate,
|
|
11074
|
-
createTemplate: () => createTemplate,
|
|
11075
|
-
addTemplateTasks: () => addTemplateTasks
|
|
11076
|
-
});
|
|
11077
|
-
function rowToTemplate(row) {
|
|
11078
|
-
return {
|
|
11079
|
-
...row,
|
|
11080
|
-
tags: JSON.parse(row.tags || "[]"),
|
|
11081
|
-
variables: JSON.parse(row.variables || "[]"),
|
|
11082
|
-
metadata: JSON.parse(row.metadata || "{}"),
|
|
11083
|
-
priority: row.priority || "medium",
|
|
11084
|
-
version: row.version ?? 1
|
|
11085
|
-
};
|
|
11086
|
-
}
|
|
11087
|
-
function rowToTemplateTask(row) {
|
|
11088
|
-
return {
|
|
11089
|
-
...row,
|
|
11090
|
-
tags: JSON.parse(row.tags || "[]"),
|
|
11091
|
-
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
11092
|
-
metadata: JSON.parse(row.metadata || "{}"),
|
|
11093
|
-
priority: row.priority || "medium",
|
|
11094
|
-
condition: row.condition ?? null,
|
|
11095
|
-
include_template_id: row.include_template_id ?? null
|
|
11096
|
-
};
|
|
11097
|
-
}
|
|
11098
|
-
function resolveTemplateId(id, d) {
|
|
11099
|
-
return resolvePartialId(d, "task_templates", id);
|
|
11353
|
+
logTaskChange(id, "lock", "locked_by", task.locked_by, agentId, agentId, d);
|
|
11354
|
+
return { success: true, locked_by: agentId, locked_at: timestamp, expires_at: lockExpiresAt(timestamp) };
|
|
11100
11355
|
}
|
|
11101
|
-
function
|
|
11356
|
+
function unlockTask(id, agentId, db) {
|
|
11102
11357
|
const d = db || getDatabase();
|
|
11103
|
-
const
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
id,
|
|
11108
|
-
input.name,
|
|
11109
|
-
input.title_pattern,
|
|
11110
|
-
input.description || null,
|
|
11111
|
-
input.priority || "medium",
|
|
11112
|
-
JSON.stringify(input.tags || []),
|
|
11113
|
-
JSON.stringify(input.variables || []),
|
|
11114
|
-
input.project_id || null,
|
|
11115
|
-
input.plan_id || null,
|
|
11116
|
-
JSON.stringify(input.metadata || {}),
|
|
11117
|
-
now(),
|
|
11118
|
-
machineId
|
|
11119
|
-
]);
|
|
11120
|
-
if (input.tasks && input.tasks.length > 0) {
|
|
11121
|
-
addTemplateTasks(id, input.tasks, d);
|
|
11358
|
+
const task = getTask(id, d);
|
|
11359
|
+
if (!task)
|
|
11360
|
+
throw new TaskNotFoundError(id);
|
|
11361
|
+
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
11362
|
+
throw new LockError(id, task.locked_by);
|
|
11122
11363
|
}
|
|
11123
|
-
|
|
11364
|
+
const timestamp = now();
|
|
11365
|
+
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, version = version + 1, updated_at = ?
|
|
11366
|
+
WHERE id = ?`, [timestamp, id]);
|
|
11367
|
+
return true;
|
|
11124
11368
|
}
|
|
11125
|
-
function
|
|
11369
|
+
function getTaskLockStatus(id, db) {
|
|
11126
11370
|
const d = db || getDatabase();
|
|
11127
|
-
const
|
|
11128
|
-
if (!
|
|
11129
|
-
|
|
11130
|
-
const
|
|
11131
|
-
return
|
|
11371
|
+
const task = getTask(id, d);
|
|
11372
|
+
if (!task)
|
|
11373
|
+
throw new TaskNotFoundError(id);
|
|
11374
|
+
const expired = isLockExpired(task.locked_at);
|
|
11375
|
+
return {
|
|
11376
|
+
task_id: id,
|
|
11377
|
+
locked: !!task.locked_by && !expired,
|
|
11378
|
+
locked_by: task.locked_by,
|
|
11379
|
+
locked_at: task.locked_at,
|
|
11380
|
+
expires_at: lockExpiresAt(task.locked_at),
|
|
11381
|
+
expired
|
|
11382
|
+
};
|
|
11132
11383
|
}
|
|
11133
|
-
function
|
|
11384
|
+
function claimNextTask(agentId, filters, db) {
|
|
11134
11385
|
const d = db || getDatabase();
|
|
11135
|
-
|
|
11386
|
+
const MAX_ATTEMPTS = 25;
|
|
11387
|
+
const tried = new Set;
|
|
11388
|
+
for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt++) {
|
|
11389
|
+
const outcome = d.transaction(() => {
|
|
11390
|
+
const task = getNextTask(agentId, filters, d);
|
|
11391
|
+
if (!task)
|
|
11392
|
+
return { done: true, task: null };
|
|
11393
|
+
if (tried.has(task.id))
|
|
11394
|
+
return { done: true, task: null };
|
|
11395
|
+
tried.add(task.id);
|
|
11396
|
+
try {
|
|
11397
|
+
return { done: true, task: startTask(task.id, agentId, d) };
|
|
11398
|
+
} catch {
|
|
11399
|
+
return { done: false, task: null };
|
|
11400
|
+
}
|
|
11401
|
+
})();
|
|
11402
|
+
if (outcome.done)
|
|
11403
|
+
return outcome.task;
|
|
11404
|
+
}
|
|
11405
|
+
return null;
|
|
11136
11406
|
}
|
|
11137
|
-
function
|
|
11407
|
+
function getNextTask(agentId, filters, db) {
|
|
11138
11408
|
const d = db || getDatabase();
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
recordStorageTombstone({
|
|
11146
|
-
object_type: "templates",
|
|
11147
|
-
object_id: resolved,
|
|
11148
|
-
payload: template,
|
|
11149
|
-
version: template.version
|
|
11150
|
-
}, d);
|
|
11151
|
-
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
11152
|
-
}
|
|
11153
|
-
function updateTemplate(id, updates, db) {
|
|
11154
|
-
const d = db || getDatabase();
|
|
11155
|
-
const resolved = resolveTemplateId(id, d);
|
|
11156
|
-
if (!resolved)
|
|
11157
|
-
return null;
|
|
11158
|
-
const current = getTemplateWithTasks(resolved, d);
|
|
11159
|
-
if (current) {
|
|
11160
|
-
const snapshot = JSON.stringify({
|
|
11161
|
-
name: current.name,
|
|
11162
|
-
title_pattern: current.title_pattern,
|
|
11163
|
-
description: current.description,
|
|
11164
|
-
priority: current.priority,
|
|
11165
|
-
tags: current.tags,
|
|
11166
|
-
variables: current.variables,
|
|
11167
|
-
project_id: current.project_id,
|
|
11168
|
-
plan_id: current.plan_id,
|
|
11169
|
-
metadata: current.metadata,
|
|
11170
|
-
tasks: current.tasks
|
|
11171
|
-
});
|
|
11172
|
-
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
11173
|
-
}
|
|
11174
|
-
const sets = ["version = version + 1"];
|
|
11175
|
-
const values = [];
|
|
11176
|
-
if (updates.name !== undefined) {
|
|
11177
|
-
sets.push("name = ?");
|
|
11178
|
-
values.push(updates.name);
|
|
11179
|
-
}
|
|
11180
|
-
if (updates.title_pattern !== undefined) {
|
|
11181
|
-
sets.push("title_pattern = ?");
|
|
11182
|
-
values.push(updates.title_pattern);
|
|
11183
|
-
}
|
|
11184
|
-
if (updates.description !== undefined) {
|
|
11185
|
-
sets.push("description = ?");
|
|
11186
|
-
values.push(updates.description);
|
|
11409
|
+
clearExpiredLocks(d);
|
|
11410
|
+
const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
|
|
11411
|
+
const params = [lockExpiryCutoff()];
|
|
11412
|
+
if (filters?.project_id) {
|
|
11413
|
+
conditions.push("project_id = ?");
|
|
11414
|
+
params.push(filters.project_id);
|
|
11187
11415
|
}
|
|
11188
|
-
if (
|
|
11189
|
-
|
|
11190
|
-
|
|
11416
|
+
if (filters?.task_list_id) {
|
|
11417
|
+
conditions.push("task_list_id = ?");
|
|
11418
|
+
params.push(filters.task_list_id);
|
|
11191
11419
|
}
|
|
11192
|
-
if (
|
|
11193
|
-
|
|
11194
|
-
|
|
11420
|
+
if (filters?.plan_id) {
|
|
11421
|
+
conditions.push("plan_id = ?");
|
|
11422
|
+
params.push(filters.plan_id);
|
|
11195
11423
|
}
|
|
11196
|
-
if (
|
|
11197
|
-
|
|
11198
|
-
|
|
11424
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
11425
|
+
const placeholders = filters.tags.map(() => "?").join(",");
|
|
11426
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
11427
|
+
params.push(...filters.tags);
|
|
11199
11428
|
}
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11429
|
+
conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
|
|
11430
|
+
const where = conditions.join(" AND ");
|
|
11431
|
+
let recentProjectIds = [];
|
|
11432
|
+
if (agentId) {
|
|
11433
|
+
const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE assigned_to = ? AND status = 'completed' AND project_id IS NOT NULL ORDER BY completed_at DESC LIMIT 3`).all(agentId);
|
|
11434
|
+
recentProjectIds = recentRows.map((r) => r.project_id);
|
|
11203
11435
|
}
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11436
|
+
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
11437
|
+
if (agentId) {
|
|
11438
|
+
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
11439
|
+
params.push(agentId);
|
|
11207
11440
|
}
|
|
11208
|
-
if (
|
|
11209
|
-
|
|
11210
|
-
|
|
11441
|
+
if (recentProjectIds.length > 0) {
|
|
11442
|
+
const placeholders = recentProjectIds.map(() => "?").join(",");
|
|
11443
|
+
sql += `CASE WHEN project_id IN (${placeholders}) THEN 0 ELSE 1 END, `;
|
|
11444
|
+
params.push(...recentProjectIds);
|
|
11211
11445
|
}
|
|
11212
|
-
|
|
11213
|
-
|
|
11214
|
-
return
|
|
11215
|
-
}
|
|
11216
|
-
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
11217
|
-
const t = getTemplate(templateId, db);
|
|
11218
|
-
if (!t)
|
|
11219
|
-
throw new Error(`Template not found: ${templateId}`);
|
|
11220
|
-
const cleanOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined));
|
|
11221
|
-
return {
|
|
11222
|
-
title: cleanOverrides.title || t.title_pattern,
|
|
11223
|
-
description: cleanOverrides.description ?? t.description ?? undefined,
|
|
11224
|
-
priority: cleanOverrides.priority ?? t.priority,
|
|
11225
|
-
tags: cleanOverrides.tags ?? t.tags,
|
|
11226
|
-
project_id: cleanOverrides.project_id ?? t.project_id ?? undefined,
|
|
11227
|
-
plan_id: cleanOverrides.plan_id ?? t.plan_id ?? undefined,
|
|
11228
|
-
metadata: cleanOverrides.metadata ?? t.metadata,
|
|
11229
|
-
...cleanOverrides
|
|
11230
|
-
};
|
|
11446
|
+
sql += `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END, created_at ASC LIMIT 1`;
|
|
11447
|
+
const row = d.query(sql).get(...params);
|
|
11448
|
+
return row ? rowToTask(row) : null;
|
|
11231
11449
|
}
|
|
11232
|
-
function
|
|
11450
|
+
function getActiveWork(filters, db) {
|
|
11233
11451
|
const d = db || getDatabase();
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
const task = tasks[i];
|
|
11241
|
-
const id = uuid();
|
|
11242
|
-
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)
|
|
11243
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
11244
|
-
id,
|
|
11245
|
-
templateId,
|
|
11246
|
-
i,
|
|
11247
|
-
task.title_pattern,
|
|
11248
|
-
task.description || null,
|
|
11249
|
-
task.priority || "medium",
|
|
11250
|
-
JSON.stringify(task.tags || []),
|
|
11251
|
-
task.task_type || null,
|
|
11252
|
-
task.condition || null,
|
|
11253
|
-
task.include_template_id || null,
|
|
11254
|
-
JSON.stringify(task.depends_on || []),
|
|
11255
|
-
JSON.stringify(task.metadata || {}),
|
|
11256
|
-
now()
|
|
11257
|
-
]);
|
|
11258
|
-
const row = d.query("SELECT * FROM template_tasks WHERE id = ?").get(id);
|
|
11259
|
-
if (row)
|
|
11260
|
-
results.push(rowToTemplateTask(row));
|
|
11452
|
+
clearExpiredLocks(d);
|
|
11453
|
+
const conditions = ["status = 'in_progress'"];
|
|
11454
|
+
const params = [];
|
|
11455
|
+
if (filters?.project_id) {
|
|
11456
|
+
conditions.push("project_id = ?");
|
|
11457
|
+
params.push(filters.project_id);
|
|
11261
11458
|
}
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
const
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
return { ...template, tasks };
|
|
11459
|
+
if (filters?.task_list_id) {
|
|
11460
|
+
conditions.push("task_list_id = ?");
|
|
11461
|
+
params.push(filters.task_list_id);
|
|
11462
|
+
}
|
|
11463
|
+
const where = conditions.join(" AND ");
|
|
11464
|
+
const rows = d.query(`SELECT id, short_id, title, priority, assigned_to, locked_by, locked_at, updated_at FROM tasks WHERE ${where} ORDER BY
|
|
11465
|
+
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
11466
|
+
updated_at DESC`).all(...params);
|
|
11467
|
+
return rows;
|
|
11272
11468
|
}
|
|
11273
|
-
function
|
|
11469
|
+
function getTasksChangedSince(since, filters, db) {
|
|
11274
11470
|
const d = db || getDatabase();
|
|
11275
|
-
const
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
}
|
|
11281
|
-
function evaluateCondition(condition, variables) {
|
|
11282
|
-
if (!condition || condition.trim() === "")
|
|
11283
|
-
return true;
|
|
11284
|
-
const trimmed = condition.trim();
|
|
11285
|
-
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
11286
|
-
if (eqMatch) {
|
|
11287
|
-
const varName = eqMatch[1];
|
|
11288
|
-
const expected = eqMatch[2].trim();
|
|
11289
|
-
return (variables[varName] ?? "") === expected;
|
|
11290
|
-
}
|
|
11291
|
-
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
11292
|
-
if (neqMatch) {
|
|
11293
|
-
const varName = neqMatch[1];
|
|
11294
|
-
const expected = neqMatch[2].trim();
|
|
11295
|
-
return (variables[varName] ?? "") !== expected;
|
|
11296
|
-
}
|
|
11297
|
-
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
11298
|
-
if (falsyMatch) {
|
|
11299
|
-
const varName = falsyMatch[1];
|
|
11300
|
-
const val = variables[varName];
|
|
11301
|
-
return !val || val === "" || val === "false";
|
|
11471
|
+
const conditions = ["updated_at > ?"];
|
|
11472
|
+
const params = [since];
|
|
11473
|
+
if (filters?.project_id) {
|
|
11474
|
+
conditions.push("project_id = ?");
|
|
11475
|
+
params.push(filters.project_id);
|
|
11302
11476
|
}
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
const val = variables[varName];
|
|
11307
|
-
return !!val && val !== "" && val !== "false";
|
|
11477
|
+
if (filters?.task_list_id) {
|
|
11478
|
+
conditions.push("task_list_id = ?");
|
|
11479
|
+
params.push(filters.task_list_id);
|
|
11308
11480
|
}
|
|
11309
|
-
|
|
11481
|
+
const where = conditions.join(" AND ");
|
|
11482
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at DESC`).all(...params);
|
|
11483
|
+
return rows.map(rowToTask);
|
|
11310
11484
|
}
|
|
11311
|
-
function
|
|
11485
|
+
function failTask(id, agentId, reason, options, db) {
|
|
11312
11486
|
const d = db || getDatabase();
|
|
11313
|
-
const
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11487
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11488
|
+
const task = getTask(id, d);
|
|
11489
|
+
if (!task)
|
|
11490
|
+
throw new TaskNotFoundError(id);
|
|
11491
|
+
const meta = {
|
|
11492
|
+
...task.metadata,
|
|
11493
|
+
_failure: {
|
|
11494
|
+
reason: reason || "Unknown failure",
|
|
11495
|
+
error_code: options?.error_code || null,
|
|
11496
|
+
failed_by: agentId || null,
|
|
11497
|
+
failed_at: now(),
|
|
11498
|
+
retry_requested: options?.retry || false
|
|
11499
|
+
}
|
|
11500
|
+
};
|
|
11501
|
+
const timestamp = now();
|
|
11502
|
+
const failTx = d.transaction(() => {
|
|
11503
|
+
const res = d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
11504
|
+
WHERE id = ? AND version = ?`, [JSON.stringify(meta), timestamp, id, task.version]);
|
|
11505
|
+
if (res.changes === 0) {
|
|
11506
|
+
const current = getTask(id, d);
|
|
11507
|
+
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
11508
|
+
}
|
|
11509
|
+
});
|
|
11510
|
+
failTx();
|
|
11511
|
+
const failedTask = {
|
|
11512
|
+
...task,
|
|
11513
|
+
status: "failed",
|
|
11514
|
+
locked_by: null,
|
|
11515
|
+
locked_at: null,
|
|
11516
|
+
metadata: meta,
|
|
11517
|
+
version: task.version + 1,
|
|
11518
|
+
updated_at: timestamp
|
|
11338
11519
|
};
|
|
11520
|
+
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
11521
|
+
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
11522
|
+
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
11523
|
+
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
|
|
11524
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
|
|
11525
|
+
let retryTask;
|
|
11526
|
+
if (options?.retry) {
|
|
11527
|
+
const retryCount = (task.retry_count || 0) + 1;
|
|
11528
|
+
const maxRetries = task.max_retries || 3;
|
|
11529
|
+
if (retryCount > maxRetries) {
|
|
11530
|
+
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
11531
|
+
JSON.stringify({ ...meta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }),
|
|
11532
|
+
id
|
|
11533
|
+
]);
|
|
11534
|
+
} else {
|
|
11535
|
+
const backoffMinutes = Math.pow(5, retryCount - 1);
|
|
11536
|
+
const retryAfter = options.retry_after || new Date(Date.now() + backoffMinutes * 60 * 1000).toISOString();
|
|
11537
|
+
let title = task.title;
|
|
11538
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
11539
|
+
title = title.slice(task.short_id.length + 2);
|
|
11540
|
+
}
|
|
11541
|
+
retryTask = createTask({
|
|
11542
|
+
title,
|
|
11543
|
+
description: task.description ?? undefined,
|
|
11544
|
+
priority: task.priority,
|
|
11545
|
+
project_id: task.project_id ?? undefined,
|
|
11546
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
11547
|
+
plan_id: task.plan_id ?? undefined,
|
|
11548
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
11549
|
+
tags: task.tags,
|
|
11550
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: reason } },
|
|
11551
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
11552
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
11553
|
+
due_at: retryAfter
|
|
11554
|
+
}, d);
|
|
11555
|
+
d.run("UPDATE tasks SET retry_count = ?, max_retries = ?, retry_after = ? WHERE id = ?", [retryCount, maxRetries, retryAfter, retryTask.id]);
|
|
11556
|
+
}
|
|
11557
|
+
}
|
|
11558
|
+
return { task: failedTask, retryTask };
|
|
11339
11559
|
}
|
|
11340
|
-
function
|
|
11560
|
+
function getStaleTasks(staleQuery = 30, filters, db) {
|
|
11341
11561
|
const d = db || getDatabase();
|
|
11342
|
-
const
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
plan_id: json.plan_id ?? undefined,
|
|
11362
|
-
metadata: json.metadata,
|
|
11363
|
-
tasks: taskInputs
|
|
11364
|
-
}, d);
|
|
11562
|
+
const staleMinutes = typeof staleQuery === "number" ? staleQuery : staleQuery.minutes ?? (staleQuery.hours !== undefined ? staleQuery.hours * 60 : 30);
|
|
11563
|
+
const effectiveFilters = typeof staleQuery === "number" ? filters : { project_id: staleQuery.project_id, task_list_id: staleQuery.task_list_id };
|
|
11564
|
+
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
11565
|
+
const conditions = [
|
|
11566
|
+
"status = 'in_progress'",
|
|
11567
|
+
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
11568
|
+
];
|
|
11569
|
+
const params = [cutoff, cutoff];
|
|
11570
|
+
if (effectiveFilters?.project_id) {
|
|
11571
|
+
conditions.push("project_id = ?");
|
|
11572
|
+
params.push(effectiveFilters.project_id);
|
|
11573
|
+
}
|
|
11574
|
+
if (effectiveFilters?.task_list_id) {
|
|
11575
|
+
conditions.push("task_list_id = ?");
|
|
11576
|
+
params.push(effectiveFilters.task_list_id);
|
|
11577
|
+
}
|
|
11578
|
+
const where = conditions.join(" AND ");
|
|
11579
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
11580
|
+
return rows.map(rowToTask);
|
|
11365
11581
|
}
|
|
11366
|
-
function
|
|
11582
|
+
function stealTask(agentId, opts, db) {
|
|
11367
11583
|
const d = db || getDatabase();
|
|
11368
|
-
const
|
|
11369
|
-
|
|
11584
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11585
|
+
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
11586
|
+
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
11587
|
+
if (staleTasks.length === 0)
|
|
11370
11588
|
return null;
|
|
11371
|
-
const
|
|
11372
|
-
|
|
11589
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
11590
|
+
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
11591
|
+
const target = staleTasks[0];
|
|
11592
|
+
const timestamp = now();
|
|
11593
|
+
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
11594
|
+
const result = d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1
|
|
11595
|
+
WHERE id = ? AND status = 'in_progress' AND (updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))`, [agentId, agentId, timestamp, timestamp, target.id, cutoff, cutoff]);
|
|
11596
|
+
if (result.changes === 0)
|
|
11597
|
+
return null;
|
|
11598
|
+
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
11599
|
+
logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
|
|
11600
|
+
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
11601
|
+
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
11602
|
+
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
11603
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
11604
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
|
|
11605
|
+
return stolenTask;
|
|
11373
11606
|
}
|
|
11374
|
-
function
|
|
11607
|
+
function claimOrSteal(agentId, filters, db) {
|
|
11375
11608
|
const d = db || getDatabase();
|
|
11376
|
-
const
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
}
|
|
11381
|
-
function resolveVariables(templateVars, provided) {
|
|
11382
|
-
const merged = { ...provided };
|
|
11383
|
-
for (const v of templateVars) {
|
|
11384
|
-
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
11385
|
-
merged[v.name] = v.default;
|
|
11386
|
-
}
|
|
11387
|
-
}
|
|
11388
|
-
const missing = [];
|
|
11389
|
-
for (const v of templateVars) {
|
|
11390
|
-
if (v.required && merged[v.name] === undefined) {
|
|
11391
|
-
missing.push(v.name);
|
|
11609
|
+
const tx = d.transaction(() => {
|
|
11610
|
+
const next = getNextTask(agentId, filters, d);
|
|
11611
|
+
if (next) {
|
|
11612
|
+
const started = startTask(next.id, agentId, d);
|
|
11613
|
+
return { task: started, stolen: false };
|
|
11392
11614
|
}
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11615
|
+
const stolen = stealTask(agentId, { stale_minutes: filters?.stale_minutes, project_id: filters?.project_id, task_list_id: filters?.task_list_id }, d);
|
|
11616
|
+
if (stolen)
|
|
11617
|
+
return { task: stolen, stolen: true };
|
|
11618
|
+
return null;
|
|
11619
|
+
});
|
|
11620
|
+
return tx();
|
|
11398
11621
|
}
|
|
11399
|
-
function
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11622
|
+
function spawnNextRecurrence(completedTask, db, completedAt) {
|
|
11623
|
+
const recurrenceBase = completedTask.due_at ? new Date(completedTask.due_at) : new Date(completedAt);
|
|
11624
|
+
const dueAt = nextOccurrence(completedTask.recurrence_rule, recurrenceBase);
|
|
11625
|
+
let title = completedTask.title;
|
|
11626
|
+
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
11627
|
+
title = title.slice(completedTask.short_id.length + 2);
|
|
11403
11628
|
}
|
|
11404
|
-
|
|
11629
|
+
const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
|
|
11630
|
+
return createTask({
|
|
11631
|
+
title,
|
|
11632
|
+
description: completedTask.description ?? undefined,
|
|
11633
|
+
priority: completedTask.priority,
|
|
11634
|
+
project_id: completedTask.project_id ?? undefined,
|
|
11635
|
+
task_list_id: completedTask.task_list_id ?? undefined,
|
|
11636
|
+
plan_id: completedTask.plan_id ?? undefined,
|
|
11637
|
+
assigned_to: completedTask.assigned_to ?? undefined,
|
|
11638
|
+
tags: completedTask.tags,
|
|
11639
|
+
metadata: completedTask.metadata,
|
|
11640
|
+
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
11641
|
+
sla_minutes: completedTask.sla_minutes ?? undefined,
|
|
11642
|
+
recurrence_rule: completedTask.recurrence_rule,
|
|
11643
|
+
recurrence_parent_id: recurrenceParentId,
|
|
11644
|
+
due_at: dueAt
|
|
11645
|
+
}, db);
|
|
11405
11646
|
}
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
let title = tt.title_pattern;
|
|
11441
|
-
let desc = tt.description;
|
|
11442
|
-
title = substituteVars(title, resolved);
|
|
11443
|
-
if (desc)
|
|
11444
|
-
desc = substituteVars(desc, resolved);
|
|
11445
|
-
const task = createTask({
|
|
11446
|
-
title,
|
|
11447
|
-
description: desc ?? undefined,
|
|
11448
|
-
priority: tt.priority,
|
|
11449
|
-
tags: tt.tags,
|
|
11450
|
-
task_type: tt.task_type ?? undefined,
|
|
11451
|
-
project_id: projectId,
|
|
11452
|
-
task_list_id: taskListId,
|
|
11453
|
-
metadata: tt.metadata
|
|
11454
|
-
}, d);
|
|
11455
|
-
createdTasks.push(task);
|
|
11456
|
-
positionToId.set(tt.position, task.id);
|
|
11647
|
+
var MAX_SPAWN_DEPTH = 10;
|
|
11648
|
+
var init_task_lifecycle = __esm(() => {
|
|
11649
|
+
init_types();
|
|
11650
|
+
init_database();
|
|
11651
|
+
init_completion_guard();
|
|
11652
|
+
init_event_emission_safety();
|
|
11653
|
+
init_event_hooks();
|
|
11654
|
+
init_shared_events();
|
|
11655
|
+
init_audit();
|
|
11656
|
+
init_recurrence();
|
|
11657
|
+
init_webhooks();
|
|
11658
|
+
init_templates();
|
|
11659
|
+
init_task_crud();
|
|
11660
|
+
init_task_graph();
|
|
11661
|
+
});
|
|
11662
|
+
|
|
11663
|
+
// src/db/task-crud.ts
|
|
11664
|
+
function rowToTask(row) {
|
|
11665
|
+
return {
|
|
11666
|
+
...row,
|
|
11667
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
11668
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
11669
|
+
status: row.status,
|
|
11670
|
+
priority: row.priority,
|
|
11671
|
+
requires_approval: !!row.requires_approval
|
|
11672
|
+
};
|
|
11673
|
+
}
|
|
11674
|
+
function insertTaskTags(taskId, tags, db) {
|
|
11675
|
+
if (tags.length === 0)
|
|
11676
|
+
return;
|
|
11677
|
+
const stmt = db.prepare("INSERT OR IGNORE INTO task_tags (task_id, tag) VALUES (?, ?)");
|
|
11678
|
+
for (const tag of tags) {
|
|
11679
|
+
if (tag)
|
|
11680
|
+
stmt.run(taskId, tag);
|
|
11457
11681
|
}
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
if (taskId && depId) {
|
|
11470
|
-
addDependency(taskId, depId, d);
|
|
11471
|
-
}
|
|
11682
|
+
}
|
|
11683
|
+
function replaceTaskTags(taskId, tags, db) {
|
|
11684
|
+
db.run("DELETE FROM task_tags WHERE task_id = ?", [taskId]);
|
|
11685
|
+
insertTaskTags(taskId, tags, db);
|
|
11686
|
+
}
|
|
11687
|
+
function addMetadataConditions(metadata, conditions, params) {
|
|
11688
|
+
if (!metadata)
|
|
11689
|
+
return;
|
|
11690
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
11691
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key)) {
|
|
11692
|
+
throw new Error(`Invalid metadata filter key: ${key}`);
|
|
11472
11693
|
}
|
|
11694
|
+
conditions.push(`json_extract(metadata, '$."${key}"') = ?`);
|
|
11695
|
+
params.push(value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value));
|
|
11473
11696
|
}
|
|
11474
|
-
return createdTasks;
|
|
11475
11697
|
}
|
|
11476
|
-
function
|
|
11698
|
+
function createTask(input, db) {
|
|
11477
11699
|
const d = db || getDatabase();
|
|
11478
|
-
const
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
const
|
|
11482
|
-
const
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11700
|
+
const timestamp = now();
|
|
11701
|
+
const tags = input.tags || [];
|
|
11702
|
+
const machineId = currentStorageMachineId(d);
|
|
11703
|
+
const assignedBy = input.assigned_by || input.agent_id;
|
|
11704
|
+
const assignedFromProject = input.assigned_from_project || null;
|
|
11705
|
+
let id = uuid();
|
|
11706
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
11707
|
+
try {
|
|
11708
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type, machine_id)
|
|
11709
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
11710
|
+
id,
|
|
11711
|
+
null,
|
|
11712
|
+
input.project_id || null,
|
|
11713
|
+
input.parent_id || null,
|
|
11714
|
+
input.plan_id || null,
|
|
11715
|
+
input.task_list_id || null,
|
|
11716
|
+
input.cycle_id || null,
|
|
11717
|
+
input.title,
|
|
11718
|
+
input.description || null,
|
|
11719
|
+
input.status || "pending",
|
|
11720
|
+
input.priority || "medium",
|
|
11721
|
+
input.agent_id || null,
|
|
11722
|
+
input.assigned_to || null,
|
|
11723
|
+
input.session_id || null,
|
|
11724
|
+
input.working_dir || null,
|
|
11725
|
+
JSON.stringify(tags),
|
|
11726
|
+
JSON.stringify(input.metadata || {}),
|
|
11727
|
+
timestamp,
|
|
11728
|
+
timestamp,
|
|
11729
|
+
input.due_at || null,
|
|
11730
|
+
input.estimated_minutes || null,
|
|
11731
|
+
input.sla_minutes ?? null,
|
|
11732
|
+
input.confidence ?? null,
|
|
11733
|
+
input.retry_count ?? 0,
|
|
11734
|
+
input.max_retries ?? 3,
|
|
11735
|
+
input.retry_after ?? null,
|
|
11736
|
+
input.requires_approval ? 1 : 0,
|
|
11737
|
+
null,
|
|
11738
|
+
null,
|
|
11739
|
+
input.recurrence_rule || null,
|
|
11740
|
+
input.recurrence_parent_id || null,
|
|
11741
|
+
input.spawns_template_id || null,
|
|
11742
|
+
input.reason || null,
|
|
11743
|
+
input.spawned_from_session || null,
|
|
11744
|
+
assignedBy || null,
|
|
11745
|
+
assignedFromProject || null,
|
|
11746
|
+
input.task_type || null,
|
|
11747
|
+
machineId
|
|
11748
|
+
]);
|
|
11749
|
+
break;
|
|
11750
|
+
} catch (e) {
|
|
11751
|
+
if (attempt < 2 && e?.message?.includes("UNIQUE constraint failed: tasks.id")) {
|
|
11752
|
+
id = uuid();
|
|
11496
11753
|
continue;
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
title: substituteVars(tt.title_pattern, resolved),
|
|
11500
|
-
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
11501
|
-
priority: tt.priority,
|
|
11502
|
-
tags: tt.tags,
|
|
11503
|
-
task_type: tt.task_type,
|
|
11504
|
-
depends_on_positions: tt.depends_on_positions
|
|
11505
|
-
});
|
|
11754
|
+
}
|
|
11755
|
+
throw e;
|
|
11506
11756
|
}
|
|
11507
11757
|
}
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
template_name: template.name,
|
|
11511
|
-
description: template.description,
|
|
11512
|
-
variables: template.variables,
|
|
11513
|
-
resolved_variables: resolved,
|
|
11514
|
-
tasks
|
|
11515
|
-
};
|
|
11516
|
-
}
|
|
11517
|
-
var init_templates = __esm(() => {
|
|
11518
|
-
init_database();
|
|
11519
|
-
init_tasks();
|
|
11520
|
-
init_storage_tombstones();
|
|
11521
|
-
});
|
|
11522
|
-
|
|
11523
|
-
// src/db/task-graph.ts
|
|
11524
|
-
function addDependency(taskId, dependsOn, db) {
|
|
11525
|
-
const d = db || getDatabase();
|
|
11526
|
-
if (!getTask(taskId, d))
|
|
11527
|
-
throw new TaskNotFoundError(taskId);
|
|
11528
|
-
if (!getTask(dependsOn, d))
|
|
11529
|
-
throw new TaskNotFoundError(dependsOn);
|
|
11530
|
-
if (wouldCreateCycle(taskId, dependsOn, d)) {
|
|
11531
|
-
throw new DependencyCycleError(taskId, dependsOn);
|
|
11758
|
+
if (tags.length > 0) {
|
|
11759
|
+
insertTaskTags(id, tags, d);
|
|
11532
11760
|
}
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
function getTaskDependencies(taskId, db) {
|
|
11541
|
-
const d = db || getDatabase();
|
|
11542
|
-
return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
|
|
11761
|
+
const task = getTask(id, d);
|
|
11762
|
+
const payload = taskEventData(task);
|
|
11763
|
+
const databasePath = databasePathFromDatabase(d);
|
|
11764
|
+
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
11765
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
|
|
11766
|
+
emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
|
|
11767
|
+
return task;
|
|
11543
11768
|
}
|
|
11544
|
-
function
|
|
11769
|
+
function getTask(id, db) {
|
|
11545
11770
|
const d = db || getDatabase();
|
|
11546
|
-
|
|
11771
|
+
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
11772
|
+
if (!row)
|
|
11773
|
+
return null;
|
|
11774
|
+
return rowToTask(row);
|
|
11547
11775
|
}
|
|
11548
|
-
function
|
|
11776
|
+
function getTaskWithRelations(id, db) {
|
|
11549
11777
|
const d = db || getDatabase();
|
|
11550
|
-
const
|
|
11551
|
-
if (!
|
|
11552
|
-
|
|
11553
|
-
const
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11778
|
+
const task = getTask(id, d);
|
|
11779
|
+
if (!task)
|
|
11780
|
+
return null;
|
|
11781
|
+
const subtaskRows = d.query("SELECT * FROM tasks WHERE parent_id = ? ORDER BY created_at").all(id);
|
|
11782
|
+
const subtasks = subtaskRows.map(rowToTask);
|
|
11783
|
+
const depRows = d.query(`SELECT t.* FROM tasks t
|
|
11784
|
+
JOIN task_dependencies td ON td.depends_on = t.id
|
|
11785
|
+
WHERE td.task_id = ?`).all(id);
|
|
11786
|
+
const dependencies = depRows.map(rowToTask);
|
|
11787
|
+
const blockedByRows = d.query(`SELECT t.* FROM tasks t
|
|
11788
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
11789
|
+
WHERE td.depends_on = ?`).all(id);
|
|
11790
|
+
const blocked_by = blockedByRows.map(rowToTask);
|
|
11791
|
+
const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
|
|
11792
|
+
const parent = task.parent_id ? getTask(task.parent_id, d) : null;
|
|
11793
|
+
const checklist = getChecklist(id, d);
|
|
11794
|
+
return {
|
|
11795
|
+
...task,
|
|
11796
|
+
subtasks,
|
|
11797
|
+
dependencies,
|
|
11798
|
+
blocked_by,
|
|
11799
|
+
comments,
|
|
11800
|
+
parent,
|
|
11801
|
+
checklist
|
|
11568
11802
|
};
|
|
11569
|
-
return createTask(input, d);
|
|
11570
11803
|
}
|
|
11571
|
-
function
|
|
11804
|
+
function listTasks(filter = {}, db) {
|
|
11572
11805
|
const d = db || getDatabase();
|
|
11573
|
-
const
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
return depTask && depTask.status !== "completed";
|
|
11581
|
-
});
|
|
11582
|
-
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
11806
|
+
const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
|
|
11807
|
+
clearExpiredLocks2(d);
|
|
11808
|
+
const conditions = [];
|
|
11809
|
+
const params = [];
|
|
11810
|
+
if (filter.project_id) {
|
|
11811
|
+
conditions.push("project_id = ?");
|
|
11812
|
+
params.push(filter.project_id);
|
|
11583
11813
|
}
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
visited.add(id);
|
|
11588
|
-
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
11589
|
-
return deps.map((dep) => {
|
|
11590
|
-
const depTask = getTask(dep.depends_on, d);
|
|
11591
|
-
if (!depTask)
|
|
11592
|
-
return null;
|
|
11593
|
-
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
11594
|
-
}).filter(Boolean);
|
|
11814
|
+
if (filter.ids && filter.ids.length > 0) {
|
|
11815
|
+
conditions.push(`id IN (${filter.ids.map(() => "?").join(",")})`);
|
|
11816
|
+
params.push(...filter.ids);
|
|
11595
11817
|
}
|
|
11596
|
-
|
|
11597
|
-
if (
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
if (!depTask)
|
|
11604
|
-
return null;
|
|
11605
|
-
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
11606
|
-
}).filter(Boolean);
|
|
11818
|
+
if (filter.parent_id !== undefined) {
|
|
11819
|
+
if (filter.parent_id === null) {
|
|
11820
|
+
conditions.push("parent_id IS NULL");
|
|
11821
|
+
} else {
|
|
11822
|
+
conditions.push("parent_id = ?");
|
|
11823
|
+
params.push(filter.parent_id);
|
|
11824
|
+
}
|
|
11607
11825
|
}
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
}
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
if (!task)
|
|
11617
|
-
throw new TaskNotFoundError(taskId);
|
|
11618
|
-
const sets = ["updated_at = ?", "version = version + 1"];
|
|
11619
|
-
const params = [now()];
|
|
11620
|
-
if (target.task_list_id !== undefined) {
|
|
11621
|
-
sets.push("task_list_id = ?");
|
|
11622
|
-
params.push(target.task_list_id);
|
|
11826
|
+
if (filter.status) {
|
|
11827
|
+
if (Array.isArray(filter.status)) {
|
|
11828
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
11829
|
+
params.push(...filter.status);
|
|
11830
|
+
} else {
|
|
11831
|
+
conditions.push("status = ?");
|
|
11832
|
+
params.push(filter.status);
|
|
11833
|
+
}
|
|
11623
11834
|
}
|
|
11624
|
-
if (
|
|
11625
|
-
|
|
11626
|
-
|
|
11835
|
+
if (filter.priority) {
|
|
11836
|
+
if (Array.isArray(filter.priority)) {
|
|
11837
|
+
conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
|
|
11838
|
+
params.push(...filter.priority);
|
|
11839
|
+
} else {
|
|
11840
|
+
conditions.push("priority = ?");
|
|
11841
|
+
params.push(filter.priority);
|
|
11842
|
+
}
|
|
11627
11843
|
}
|
|
11628
|
-
if (
|
|
11629
|
-
|
|
11630
|
-
params.push(
|
|
11844
|
+
if (filter.assigned_to) {
|
|
11845
|
+
conditions.push("assigned_to = ?");
|
|
11846
|
+
params.push(filter.assigned_to);
|
|
11631
11847
|
}
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
}
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11848
|
+
if (filter.agent_id) {
|
|
11849
|
+
conditions.push("agent_id = ?");
|
|
11850
|
+
params.push(filter.agent_id);
|
|
11851
|
+
}
|
|
11852
|
+
if (filter.session_id) {
|
|
11853
|
+
conditions.push("session_id = ?");
|
|
11854
|
+
params.push(filter.session_id);
|
|
11855
|
+
}
|
|
11856
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
11857
|
+
const placeholders = filter.tags.map(() => "?").join(",");
|
|
11858
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
11859
|
+
params.push(...filter.tags);
|
|
11860
|
+
}
|
|
11861
|
+
if (filter.plan_id) {
|
|
11862
|
+
conditions.push("plan_id = ?");
|
|
11863
|
+
params.push(filter.plan_id);
|
|
11864
|
+
}
|
|
11865
|
+
if (filter.task_list_id) {
|
|
11866
|
+
conditions.push("task_list_id = ?");
|
|
11867
|
+
params.push(filter.task_list_id);
|
|
11868
|
+
}
|
|
11869
|
+
if (filter.has_recurrence === true) {
|
|
11870
|
+
conditions.push("recurrence_rule IS NOT NULL");
|
|
11871
|
+
} else if (filter.has_recurrence === false) {
|
|
11872
|
+
conditions.push("recurrence_rule IS NULL");
|
|
11873
|
+
}
|
|
11874
|
+
if (filter.task_type) {
|
|
11875
|
+
if (Array.isArray(filter.task_type)) {
|
|
11876
|
+
conditions.push(`task_type IN (${filter.task_type.map(() => "?").join(",")})`);
|
|
11877
|
+
params.push(...filter.task_type);
|
|
11878
|
+
} else {
|
|
11879
|
+
conditions.push("task_type = ?");
|
|
11880
|
+
params.push(filter.task_type);
|
|
11649
11881
|
}
|
|
11650
11882
|
}
|
|
11651
|
-
|
|
11883
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
11884
|
+
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
11885
|
+
if (filter.cursor) {
|
|
11886
|
+
try {
|
|
11887
|
+
const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
|
|
11888
|
+
conditions.push(`(${PRIORITY_RANK} > ? OR (${PRIORITY_RANK} = ? AND created_at < ?) OR (${PRIORITY_RANK} = ? AND created_at = ? AND id > ?))`);
|
|
11889
|
+
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
11890
|
+
} catch {}
|
|
11891
|
+
}
|
|
11892
|
+
if (!filter.include_archived) {
|
|
11893
|
+
conditions.push("archived_at IS NULL");
|
|
11894
|
+
}
|
|
11895
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
11896
|
+
let limitClause = "";
|
|
11897
|
+
if (filter.limit) {
|
|
11898
|
+
limitClause = " LIMIT ?";
|
|
11899
|
+
params.push(filter.limit);
|
|
11900
|
+
if (!filter.cursor && filter.offset) {
|
|
11901
|
+
limitClause += " OFFSET ?";
|
|
11902
|
+
params.push(filter.offset);
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC, id ASC${limitClause}`).all(...params);
|
|
11906
|
+
return rows.map(rowToTask);
|
|
11652
11907
|
}
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
init_task_crud();
|
|
11657
|
-
});
|
|
11658
|
-
|
|
11659
|
-
// src/db/task-lifecycle.ts
|
|
11660
|
-
function lockExpiresAt(lockedAt) {
|
|
11661
|
-
if (!lockedAt)
|
|
11662
|
-
return null;
|
|
11663
|
-
return new Date(new Date(lockedAt).getTime() + LOCK_EXPIRY_MINUTES * 60 * 1000).toISOString();
|
|
11908
|
+
function getTaskByFingerprint(fingerprint, db) {
|
|
11909
|
+
const tasks = listTasks({ metadata: { fingerprint }, limit: 1, include_archived: true }, db);
|
|
11910
|
+
return tasks[0] ?? null;
|
|
11664
11911
|
}
|
|
11665
|
-
function
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11912
|
+
function mergeTaskMetadata(current, next, fingerprint) {
|
|
11913
|
+
return {
|
|
11914
|
+
...current,
|
|
11915
|
+
...next ?? {},
|
|
11916
|
+
fingerprint
|
|
11917
|
+
};
|
|
11671
11918
|
}
|
|
11672
|
-
function
|
|
11919
|
+
function upsertTaskByFingerprint(input, db) {
|
|
11673
11920
|
const d = db || getDatabase();
|
|
11674
|
-
const
|
|
11675
|
-
if (
|
|
11676
|
-
|
|
11677
|
-
const
|
|
11678
|
-
|
|
11679
|
-
const
|
|
11680
|
-
if (
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
11921
|
+
const fingerprint = input.fingerprint.trim();
|
|
11922
|
+
if (!fingerprint)
|
|
11923
|
+
throw new Error("fingerprint is required");
|
|
11924
|
+
const tx = d.transaction(() => {
|
|
11925
|
+
const existing = getTaskByFingerprint(fingerprint, d);
|
|
11926
|
+
const metadata = mergeTaskMetadata(existing?.metadata ?? {}, input.metadata, fingerprint);
|
|
11927
|
+
if (!existing) {
|
|
11928
|
+
const task2 = createTask({ ...input, metadata }, d);
|
|
11929
|
+
return { task: task2, created: true };
|
|
11930
|
+
}
|
|
11931
|
+
const task = updateTask(existing.id, {
|
|
11932
|
+
version: existing.version,
|
|
11933
|
+
title: input.title,
|
|
11934
|
+
description: input.description,
|
|
11935
|
+
status: input.status,
|
|
11936
|
+
priority: input.priority,
|
|
11937
|
+
project_id: input.project_id,
|
|
11938
|
+
assigned_to: input.assigned_to,
|
|
11939
|
+
working_dir: input.working_dir,
|
|
11940
|
+
plan_id: input.plan_id,
|
|
11941
|
+
task_list_id: input.task_list_id,
|
|
11942
|
+
tags: input.tags,
|
|
11943
|
+
metadata,
|
|
11944
|
+
due_at: input.due_at,
|
|
11945
|
+
estimated_minutes: input.estimated_minutes,
|
|
11946
|
+
sla_minutes: input.sla_minutes,
|
|
11947
|
+
confidence: input.confidence,
|
|
11948
|
+
retry_count: input.retry_count,
|
|
11949
|
+
max_retries: input.max_retries,
|
|
11950
|
+
retry_after: input.retry_after,
|
|
11951
|
+
requires_approval: input.requires_approval,
|
|
11952
|
+
recurrence_rule: input.recurrence_rule,
|
|
11953
|
+
task_type: input.task_type
|
|
11954
|
+
}, d);
|
|
11955
|
+
return { task, created: false };
|
|
11956
|
+
});
|
|
11957
|
+
return tx();
|
|
11684
11958
|
}
|
|
11685
|
-
function
|
|
11959
|
+
function countTasks(filter = {}, db) {
|
|
11686
11960
|
const d = db || getDatabase();
|
|
11687
|
-
const
|
|
11688
|
-
const
|
|
11689
|
-
if (
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
const blocking = getBlockingDeps(id, d);
|
|
11693
|
-
if (blocking.length > 0) {
|
|
11694
|
-
const blockerIds = blocking.map((b) => b.id.slice(0, 8)).join(", ");
|
|
11695
|
-
emitLocalEventHooksQuiet({
|
|
11696
|
-
type: "task.blocked",
|
|
11697
|
-
payload: {
|
|
11698
|
-
id,
|
|
11699
|
-
agent_id: agentId,
|
|
11700
|
-
title: task.title,
|
|
11701
|
-
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
11702
|
-
},
|
|
11703
|
-
databasePath
|
|
11704
|
-
});
|
|
11705
|
-
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
11706
|
-
}
|
|
11707
|
-
const cutoff = lockExpiryCutoff();
|
|
11708
|
-
const timestamp = now();
|
|
11709
|
-
const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
|
|
11710
|
-
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, timestamp, id, agentId, cutoff]);
|
|
11711
|
-
if (result.changes === 0) {
|
|
11712
|
-
const current = getTask(id, d);
|
|
11713
|
-
if (!current)
|
|
11714
|
-
throw new TaskNotFoundError(id);
|
|
11715
|
-
assertStartable(current, agentId);
|
|
11716
|
-
if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
|
|
11717
|
-
throw new LockError(id, current.locked_by);
|
|
11718
|
-
}
|
|
11719
|
-
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
11961
|
+
const conditions = [];
|
|
11962
|
+
const params = [];
|
|
11963
|
+
if (filter.project_id) {
|
|
11964
|
+
conditions.push("project_id = ?");
|
|
11965
|
+
params.push(filter.project_id);
|
|
11720
11966
|
}
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
11725
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
11726
|
-
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
|
|
11727
|
-
return startedTask;
|
|
11728
|
-
}
|
|
11729
|
-
function completeTask(id, agentId, db, options) {
|
|
11730
|
-
const d = db || getDatabase();
|
|
11731
|
-
const databasePath = databasePathFromDatabase(d);
|
|
11732
|
-
const task = getTask(id, d);
|
|
11733
|
-
if (!task)
|
|
11734
|
-
throw new TaskNotFoundError(id);
|
|
11735
|
-
if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
|
|
11736
|
-
throw new LockError(id, task.locked_by);
|
|
11967
|
+
if (filter.ids && filter.ids.length > 0) {
|
|
11968
|
+
conditions.push(`id IN (${filter.ids.map(() => "?").join(",")})`);
|
|
11969
|
+
params.push(...filter.ids);
|
|
11737
11970
|
}
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
completionMeta._completion = { confidence: options.confidence };
|
|
11971
|
+
if (filter.parent_id !== undefined) {
|
|
11972
|
+
if (filter.parent_id === null) {
|
|
11973
|
+
conditions.push("parent_id IS NULL");
|
|
11974
|
+
} else {
|
|
11975
|
+
conditions.push("parent_id = ?");
|
|
11976
|
+
params.push(filter.parent_id);
|
|
11977
|
+
}
|
|
11746
11978
|
}
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
if (metaResult.changes === 0) {
|
|
11755
|
-
const current = getTask(id, d);
|
|
11756
|
-
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
11757
|
-
}
|
|
11979
|
+
if (filter.status) {
|
|
11980
|
+
if (Array.isArray(filter.status)) {
|
|
11981
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
11982
|
+
params.push(...filter.status);
|
|
11983
|
+
} else {
|
|
11984
|
+
conditions.push("status = ?");
|
|
11985
|
+
params.push(filter.status);
|
|
11758
11986
|
}
|
|
11759
|
-
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, confidence = ?, version = version + 1, updated_at = ?
|
|
11760
|
-
WHERE id = ?`, [timestamp, confidence, timestamp, id]);
|
|
11761
|
-
});
|
|
11762
|
-
tx();
|
|
11763
|
-
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
11764
|
-
const completedTaskForEvent = {
|
|
11765
|
-
...task,
|
|
11766
|
-
status: "completed",
|
|
11767
|
-
locked_by: null,
|
|
11768
|
-
locked_at: null,
|
|
11769
|
-
completed_at: timestamp,
|
|
11770
|
-
confidence,
|
|
11771
|
-
version: task.version + 1,
|
|
11772
|
-
updated_at: timestamp,
|
|
11773
|
-
metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
|
|
11774
|
-
};
|
|
11775
|
-
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
11776
|
-
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
11777
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
11778
|
-
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
|
|
11779
|
-
let spawnedTask = null;
|
|
11780
|
-
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
11781
|
-
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
11782
11987
|
}
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
console.warn(`[tasks] Task ${id} exceeded max spawn depth (${MAX_SPAWN_DEPTH}), skipping template spawn`);
|
|
11988
|
+
if (filter.priority) {
|
|
11989
|
+
if (Array.isArray(filter.priority)) {
|
|
11990
|
+
conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
|
|
11991
|
+
params.push(...filter.priority);
|
|
11788
11992
|
} else {
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
project_id: task.project_id ?? undefined,
|
|
11792
|
-
plan_id: task.plan_id ?? undefined,
|
|
11793
|
-
task_list_id: task.task_list_id ?? undefined,
|
|
11794
|
-
assigned_to: task.assigned_to ?? undefined
|
|
11795
|
-
}, d);
|
|
11796
|
-
input.metadata = { ...input.metadata || {}, _spawn_depth: spawnDepth + 1 };
|
|
11797
|
-
spawnedFromTemplate = createTask(input, d);
|
|
11798
|
-
} catch {}
|
|
11993
|
+
conditions.push("priority = ?");
|
|
11994
|
+
params.push(filter.priority);
|
|
11799
11995
|
}
|
|
11800
11996
|
}
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11997
|
+
if (filter.assigned_to) {
|
|
11998
|
+
conditions.push("assigned_to = ?");
|
|
11999
|
+
params.push(filter.assigned_to);
|
|
11804
12000
|
}
|
|
11805
|
-
if (
|
|
11806
|
-
|
|
12001
|
+
if (filter.agent_id) {
|
|
12002
|
+
conditions.push("agent_id = ?");
|
|
12003
|
+
params.push(filter.agent_id);
|
|
11807
12004
|
}
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
12005
|
+
if (filter.session_id) {
|
|
12006
|
+
conditions.push("session_id = ?");
|
|
12007
|
+
params.push(filter.session_id);
|
|
12008
|
+
}
|
|
12009
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
12010
|
+
const placeholders = filter.tags.map(() => "?").join(",");
|
|
12011
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
12012
|
+
params.push(...filter.tags);
|
|
12013
|
+
}
|
|
12014
|
+
if (filter.plan_id) {
|
|
12015
|
+
conditions.push("plan_id = ?");
|
|
12016
|
+
params.push(filter.plan_id);
|
|
12017
|
+
}
|
|
12018
|
+
if (filter.task_list_id) {
|
|
12019
|
+
conditions.push("task_list_id = ?");
|
|
12020
|
+
params.push(filter.task_list_id);
|
|
12021
|
+
}
|
|
12022
|
+
if (filter.has_recurrence === true) {
|
|
12023
|
+
conditions.push("recurrence_rule IS NOT NULL");
|
|
12024
|
+
} else if (filter.has_recurrence === false) {
|
|
12025
|
+
conditions.push("recurrence_rule IS NULL");
|
|
12026
|
+
}
|
|
12027
|
+
if (filter.task_type) {
|
|
12028
|
+
if (Array.isArray(filter.task_type)) {
|
|
12029
|
+
conditions.push(`task_type IN (${filter.task_type.map(() => "?").join(",")})`);
|
|
12030
|
+
params.push(...filter.task_type);
|
|
12031
|
+
} else {
|
|
12032
|
+
conditions.push("task_type = ?");
|
|
12033
|
+
params.push(filter.task_type);
|
|
11825
12034
|
}
|
|
11826
12035
|
}
|
|
11827
|
-
|
|
12036
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
12037
|
+
if (!filter.include_archived) {
|
|
12038
|
+
conditions.push("archived_at IS NULL");
|
|
12039
|
+
}
|
|
12040
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
12041
|
+
const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
12042
|
+
return row.count;
|
|
11828
12043
|
}
|
|
11829
|
-
function
|
|
12044
|
+
function updateTask(id, input, db) {
|
|
11830
12045
|
const d = db || getDatabase();
|
|
11831
12046
|
const task = getTask(id, d);
|
|
11832
12047
|
if (!task)
|
|
11833
12048
|
throw new TaskNotFoundError(id);
|
|
11834
|
-
if (task.
|
|
11835
|
-
|
|
11836
|
-
success: false,
|
|
11837
|
-
error: `Task is ${task.status} and cannot be locked`
|
|
11838
|
-
};
|
|
11839
|
-
}
|
|
11840
|
-
if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
|
|
11841
|
-
const timestamp2 = now();
|
|
11842
|
-
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [timestamp2, timestamp2, id, agentId]);
|
|
11843
|
-
logTaskChange(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
|
|
11844
|
-
return { success: true, locked_by: agentId, locked_at: timestamp2, expires_at: lockExpiresAt(timestamp2) };
|
|
12049
|
+
if (task.version !== input.version) {
|
|
12050
|
+
throw new VersionConflictError(id, input.version, task.version);
|
|
11845
12051
|
}
|
|
11846
|
-
const cutoff = lockExpiryCutoff();
|
|
11847
12052
|
const timestamp = now();
|
|
11848
|
-
const
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
12053
|
+
const completionTimestamp = input.completed_at ?? timestamp;
|
|
12054
|
+
const sets = ["version = version + 1", "updated_at = ?"];
|
|
12055
|
+
const params = [timestamp];
|
|
12056
|
+
if (input.title !== undefined) {
|
|
12057
|
+
sets.push("title = ?");
|
|
12058
|
+
params.push(input.title);
|
|
12059
|
+
}
|
|
12060
|
+
if (input.description !== undefined) {
|
|
12061
|
+
sets.push("description = ?");
|
|
12062
|
+
params.push(input.description);
|
|
12063
|
+
}
|
|
12064
|
+
if (input.status !== undefined) {
|
|
12065
|
+
if (input.status === "completed") {
|
|
12066
|
+
checkCompletionGuard(task, task.assigned_to || task.agent_id || null, d);
|
|
11859
12067
|
}
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
12068
|
+
sets.push("status = ?");
|
|
12069
|
+
params.push(input.status);
|
|
12070
|
+
if (input.status === "completed") {
|
|
12071
|
+
sets.push("completed_at = ?");
|
|
12072
|
+
params.push(completionTimestamp);
|
|
12073
|
+
sets.push("locked_by = NULL");
|
|
12074
|
+
sets.push("locked_at = NULL");
|
|
12075
|
+
} else if (task.status === "completed" && input.completed_at === undefined) {
|
|
12076
|
+
sets.push("completed_at = NULL");
|
|
11867
12077
|
}
|
|
11868
|
-
return {
|
|
11869
|
-
success: false,
|
|
11870
|
-
error: `Task ${id} could not be locked because it changed during lock acquisition`
|
|
11871
|
-
};
|
|
11872
12078
|
}
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
function unlockTask(id, agentId, db) {
|
|
11877
|
-
const d = db || getDatabase();
|
|
11878
|
-
const task = getTask(id, d);
|
|
11879
|
-
if (!task)
|
|
11880
|
-
throw new TaskNotFoundError(id);
|
|
11881
|
-
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
11882
|
-
throw new LockError(id, task.locked_by);
|
|
12079
|
+
if (input.priority !== undefined) {
|
|
12080
|
+
sets.push("priority = ?");
|
|
12081
|
+
params.push(input.priority);
|
|
11883
12082
|
}
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
return true;
|
|
11888
|
-
}
|
|
11889
|
-
function getTaskLockStatus(id, db) {
|
|
11890
|
-
const d = db || getDatabase();
|
|
11891
|
-
const task = getTask(id, d);
|
|
11892
|
-
if (!task)
|
|
11893
|
-
throw new TaskNotFoundError(id);
|
|
11894
|
-
const expired = isLockExpired(task.locked_at);
|
|
11895
|
-
return {
|
|
11896
|
-
task_id: id,
|
|
11897
|
-
locked: !!task.locked_by && !expired,
|
|
11898
|
-
locked_by: task.locked_by,
|
|
11899
|
-
locked_at: task.locked_at,
|
|
11900
|
-
expires_at: lockExpiresAt(task.locked_at),
|
|
11901
|
-
expired
|
|
11902
|
-
};
|
|
11903
|
-
}
|
|
11904
|
-
function claimNextTask(agentId, filters, db) {
|
|
11905
|
-
const d = db || getDatabase();
|
|
11906
|
-
const tx = d.transaction(() => {
|
|
11907
|
-
const task = getNextTask(agentId, filters, d);
|
|
11908
|
-
if (!task)
|
|
11909
|
-
return null;
|
|
11910
|
-
return startTask(task.id, agentId, d);
|
|
11911
|
-
});
|
|
11912
|
-
return tx();
|
|
11913
|
-
}
|
|
11914
|
-
function getNextTask(agentId, filters, db) {
|
|
11915
|
-
const d = db || getDatabase();
|
|
11916
|
-
clearExpiredLocks(d);
|
|
11917
|
-
const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
|
|
11918
|
-
const params = [lockExpiryCutoff()];
|
|
11919
|
-
if (filters?.project_id) {
|
|
11920
|
-
conditions.push("project_id = ?");
|
|
11921
|
-
params.push(filters.project_id);
|
|
12083
|
+
if (input.project_id !== undefined) {
|
|
12084
|
+
sets.push("project_id = ?");
|
|
12085
|
+
params.push(input.project_id);
|
|
11922
12086
|
}
|
|
11923
|
-
if (
|
|
11924
|
-
|
|
11925
|
-
params.push(
|
|
12087
|
+
if (input.assigned_to !== undefined) {
|
|
12088
|
+
sets.push("assigned_to = ?");
|
|
12089
|
+
params.push(input.assigned_to);
|
|
11926
12090
|
}
|
|
11927
|
-
if (
|
|
11928
|
-
|
|
11929
|
-
params.push(
|
|
12091
|
+
if (input.working_dir !== undefined) {
|
|
12092
|
+
sets.push("working_dir = ?");
|
|
12093
|
+
params.push(input.working_dir);
|
|
11930
12094
|
}
|
|
11931
|
-
if (
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
params.push(...filters.tags);
|
|
12095
|
+
if (input.tags !== undefined) {
|
|
12096
|
+
sets.push("tags = ?");
|
|
12097
|
+
params.push(JSON.stringify(input.tags));
|
|
11935
12098
|
}
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
if (agentId) {
|
|
11940
|
-
const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE assigned_to = ? AND status = 'completed' AND project_id IS NOT NULL ORDER BY completed_at DESC LIMIT 3`).all(agentId);
|
|
11941
|
-
recentProjectIds = recentRows.map((r) => r.project_id);
|
|
12099
|
+
if (input.metadata !== undefined) {
|
|
12100
|
+
sets.push("metadata = ?");
|
|
12101
|
+
params.push(JSON.stringify(input.metadata));
|
|
11942
12102
|
}
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
params.push(agentId);
|
|
12103
|
+
if (input.plan_id !== undefined) {
|
|
12104
|
+
sets.push("plan_id = ?");
|
|
12105
|
+
params.push(input.plan_id);
|
|
11947
12106
|
}
|
|
11948
|
-
if (
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
params.push(...recentProjectIds);
|
|
12107
|
+
if (input.task_list_id !== undefined) {
|
|
12108
|
+
sets.push("task_list_id = ?");
|
|
12109
|
+
params.push(input.task_list_id);
|
|
11952
12110
|
}
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
}
|
|
11957
|
-
function getActiveWork(filters, db) {
|
|
11958
|
-
const d = db || getDatabase();
|
|
11959
|
-
clearExpiredLocks(d);
|
|
11960
|
-
const conditions = ["status = 'in_progress'"];
|
|
11961
|
-
const params = [];
|
|
11962
|
-
if (filters?.project_id) {
|
|
11963
|
-
conditions.push("project_id = ?");
|
|
11964
|
-
params.push(filters.project_id);
|
|
12111
|
+
if (input.due_at !== undefined) {
|
|
12112
|
+
sets.push("due_at = ?");
|
|
12113
|
+
params.push(input.due_at);
|
|
11965
12114
|
}
|
|
11966
|
-
if (
|
|
11967
|
-
|
|
11968
|
-
params.push(
|
|
12115
|
+
if (input.estimated_minutes !== undefined) {
|
|
12116
|
+
sets.push("estimated_minutes = ?");
|
|
12117
|
+
params.push(input.estimated_minutes);
|
|
11969
12118
|
}
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
updated_at DESC`).all(...params);
|
|
11974
|
-
return rows;
|
|
11975
|
-
}
|
|
11976
|
-
function getTasksChangedSince(since, filters, db) {
|
|
11977
|
-
const d = db || getDatabase();
|
|
11978
|
-
const conditions = ["updated_at > ?"];
|
|
11979
|
-
const params = [since];
|
|
11980
|
-
if (filters?.project_id) {
|
|
11981
|
-
conditions.push("project_id = ?");
|
|
11982
|
-
params.push(filters.project_id);
|
|
12119
|
+
if (input.sla_minutes !== undefined) {
|
|
12120
|
+
sets.push("sla_minutes = ?");
|
|
12121
|
+
params.push(input.sla_minutes);
|
|
11983
12122
|
}
|
|
11984
|
-
if (
|
|
11985
|
-
|
|
11986
|
-
params.push(
|
|
12123
|
+
if (input.actual_minutes !== undefined) {
|
|
12124
|
+
sets.push("actual_minutes = ?");
|
|
12125
|
+
params.push(input.actual_minutes);
|
|
11987
12126
|
}
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
}
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
if (
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12127
|
+
if (input.completed_at !== undefined && input.status !== "completed") {
|
|
12128
|
+
sets.push("completed_at = ?");
|
|
12129
|
+
params.push(input.completed_at);
|
|
12130
|
+
}
|
|
12131
|
+
if (input.confidence !== undefined) {
|
|
12132
|
+
sets.push("confidence = ?");
|
|
12133
|
+
params.push(input.confidence);
|
|
12134
|
+
}
|
|
12135
|
+
if (input.retry_count !== undefined) {
|
|
12136
|
+
sets.push("retry_count = ?");
|
|
12137
|
+
params.push(input.retry_count);
|
|
12138
|
+
}
|
|
12139
|
+
if (input.max_retries !== undefined) {
|
|
12140
|
+
sets.push("max_retries = ?");
|
|
12141
|
+
params.push(input.max_retries);
|
|
12142
|
+
}
|
|
12143
|
+
if (input.retry_after !== undefined) {
|
|
12144
|
+
sets.push("retry_after = ?");
|
|
12145
|
+
params.push(input.retry_after);
|
|
12146
|
+
}
|
|
12147
|
+
if (input.requires_approval !== undefined) {
|
|
12148
|
+
sets.push("requires_approval = ?");
|
|
12149
|
+
params.push(input.requires_approval ? 1 : 0);
|
|
12150
|
+
}
|
|
12151
|
+
if (input.approved_by !== undefined) {
|
|
12152
|
+
sets.push("approved_by = ?");
|
|
12153
|
+
params.push(input.approved_by);
|
|
12154
|
+
sets.push("approved_at = ?");
|
|
12155
|
+
params.push(now());
|
|
12156
|
+
}
|
|
12157
|
+
if (input.recurrence_rule !== undefined) {
|
|
12158
|
+
sets.push("recurrence_rule = ?");
|
|
12159
|
+
params.push(input.recurrence_rule);
|
|
12160
|
+
}
|
|
12161
|
+
if (input.task_type !== undefined) {
|
|
12162
|
+
sets.push("task_type = ?");
|
|
12163
|
+
params.push(input.task_type ?? null);
|
|
12164
|
+
}
|
|
12165
|
+
params.push(id, input.version);
|
|
12166
|
+
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
12167
|
+
if (result.changes === 0) {
|
|
12168
|
+
const current = getTask(id, d);
|
|
12169
|
+
throw new VersionConflictError(id, input.version, current?.version ?? -1);
|
|
12170
|
+
}
|
|
12171
|
+
if (input.tags !== undefined) {
|
|
12172
|
+
replaceTaskTags(id, input.tags, d);
|
|
12173
|
+
}
|
|
12174
|
+
const transitionedToCompleted = input.status === "completed" && task.status !== "completed";
|
|
12175
|
+
if (transitionedToCompleted && task.recurrence_rule) {
|
|
12176
|
+
try {
|
|
12177
|
+
const { spawnNextRecurrence: spawnNextRecurrence2 } = (init_task_lifecycle(), __toCommonJS(exports_task_lifecycle));
|
|
12178
|
+
spawnNextRecurrence2(task, d, completionTimestamp);
|
|
12179
|
+
} catch (e) {
|
|
12180
|
+
console.warn(`[tasks] failed to spawn next recurrence for ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
12006
12181
|
}
|
|
12007
|
-
}
|
|
12008
|
-
const
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12182
|
+
}
|
|
12183
|
+
const agentId = task.assigned_to || task.agent_id || null;
|
|
12184
|
+
if (input.status !== undefined && input.status !== task.status)
|
|
12185
|
+
logTaskChange(id, "update", "status", task.status, input.status, agentId, d);
|
|
12186
|
+
if (input.priority !== undefined && input.priority !== task.priority)
|
|
12187
|
+
logTaskChange(id, "update", "priority", task.priority, input.priority, agentId, d);
|
|
12188
|
+
if (input.title !== undefined && input.title !== task.title)
|
|
12189
|
+
logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
|
|
12190
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
|
|
12191
|
+
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
12192
|
+
if (input.working_dir !== undefined && input.working_dir !== task.working_dir)
|
|
12193
|
+
logTaskChange(id, "update", "working_dir", task.working_dir, input.working_dir, agentId, d);
|
|
12194
|
+
if (input.approved_by !== undefined)
|
|
12195
|
+
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
12196
|
+
const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
|
|
12197
|
+
const completedNow = input.status === "completed";
|
|
12198
|
+
const updatedTask = {
|
|
12012
12199
|
...task,
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
metadata: meta,
|
|
12200
|
+
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
12201
|
+
tags: input.tags ?? task.tags,
|
|
12202
|
+
metadata: input.metadata ?? task.metadata,
|
|
12017
12203
|
version: task.version + 1,
|
|
12018
|
-
updated_at: timestamp
|
|
12204
|
+
updated_at: timestamp,
|
|
12205
|
+
locked_by: completedNow ? null : task.locked_by,
|
|
12206
|
+
locked_at: completedNow ? null : task.locked_at,
|
|
12207
|
+
completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
12208
|
+
sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
|
|
12209
|
+
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
12210
|
+
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
12211
|
+
retry_count: input.retry_count ?? task.retry_count,
|
|
12212
|
+
max_retries: input.max_retries ?? task.max_retries,
|
|
12213
|
+
retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
|
|
12214
|
+
requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
|
|
12215
|
+
approved_by: input.approved_by ?? task.approved_by,
|
|
12216
|
+
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
12019
12217
|
};
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
if (options?.retry) {
|
|
12027
|
-
const retryCount = (task.retry_count || 0) + 1;
|
|
12028
|
-
const maxRetries = task.max_retries || 3;
|
|
12029
|
-
if (retryCount > maxRetries) {
|
|
12030
|
-
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
12031
|
-
JSON.stringify({ ...meta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }),
|
|
12032
|
-
id
|
|
12033
|
-
]);
|
|
12034
|
-
} else {
|
|
12035
|
-
const backoffMinutes = Math.pow(5, retryCount - 1);
|
|
12036
|
-
const retryAfter = options.retry_after || new Date(Date.now() + backoffMinutes * 60 * 1000).toISOString();
|
|
12037
|
-
let title = task.title;
|
|
12038
|
-
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
12039
|
-
title = title.slice(task.short_id.length + 2);
|
|
12040
|
-
}
|
|
12041
|
-
retryTask = createTask({
|
|
12042
|
-
title,
|
|
12043
|
-
description: task.description ?? undefined,
|
|
12044
|
-
priority: task.priority,
|
|
12045
|
-
project_id: task.project_id ?? undefined,
|
|
12046
|
-
task_list_id: task.task_list_id ?? undefined,
|
|
12047
|
-
plan_id: task.plan_id ?? undefined,
|
|
12048
|
-
assigned_to: task.assigned_to ?? undefined,
|
|
12049
|
-
tags: task.tags,
|
|
12050
|
-
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: reason } },
|
|
12051
|
-
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
12052
|
-
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
12053
|
-
due_at: retryAfter
|
|
12054
|
-
}, d);
|
|
12055
|
-
d.run("UPDATE tasks SET retry_count = ?, max_retries = ?, retry_after = ? WHERE id = ?", [retryCount, maxRetries, retryAfter, retryTask.id]);
|
|
12056
|
-
}
|
|
12218
|
+
const databasePath = databasePathFromDatabase(d);
|
|
12219
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
12220
|
+
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
12221
|
+
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
12222
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
12223
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
|
|
12057
12224
|
}
|
|
12058
|
-
|
|
12059
|
-
}
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
const effectiveFilters = typeof staleQuery === "number" ? filters : { project_id: staleQuery.project_id, task_list_id: staleQuery.task_list_id };
|
|
12064
|
-
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
12065
|
-
const conditions = [
|
|
12066
|
-
"status = 'in_progress'",
|
|
12067
|
-
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
12068
|
-
];
|
|
12069
|
-
const params = [cutoff, cutoff];
|
|
12070
|
-
if (effectiveFilters?.project_id) {
|
|
12071
|
-
conditions.push("project_id = ?");
|
|
12072
|
-
params.push(effectiveFilters.project_id);
|
|
12225
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
12226
|
+
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
12227
|
+
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
12228
|
+
emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
|
|
12229
|
+
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
|
|
12073
12230
|
}
|
|
12074
|
-
if (
|
|
12075
|
-
|
|
12076
|
-
params.push(effectiveFilters.task_list_id);
|
|
12231
|
+
if (input.approved_by !== undefined) {
|
|
12232
|
+
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
|
|
12077
12233
|
}
|
|
12078
|
-
const
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
}
|
|
12082
|
-
|
|
12083
|
-
const d = db || getDatabase();
|
|
12084
|
-
const databasePath = databasePathFromDatabase(d);
|
|
12085
|
-
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
12086
|
-
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
12087
|
-
if (staleTasks.length === 0)
|
|
12088
|
-
return null;
|
|
12089
|
-
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
12090
|
-
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
12091
|
-
const target = staleTasks[0];
|
|
12092
|
-
const timestamp = now();
|
|
12093
|
-
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
12094
|
-
const result = d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1
|
|
12095
|
-
WHERE id = ? AND status = 'in_progress' AND (updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))`, [agentId, agentId, timestamp, timestamp, target.id, cutoff, cutoff]);
|
|
12096
|
-
if (result.changes === 0)
|
|
12097
|
-
return null;
|
|
12098
|
-
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
12099
|
-
logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
|
|
12100
|
-
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
12101
|
-
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
12102
|
-
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
12103
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
12104
|
-
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
|
|
12105
|
-
return stolenTask;
|
|
12234
|
+
const updatePayload = taskEventData(updatedTask);
|
|
12235
|
+
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
12236
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
|
|
12237
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
|
|
12238
|
+
return updatedTask;
|
|
12106
12239
|
}
|
|
12107
|
-
function
|
|
12240
|
+
function deleteTask(id, db) {
|
|
12108
12241
|
const d = db || getDatabase();
|
|
12109
|
-
const
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
return tx();
|
|
12121
|
-
}
|
|
12122
|
-
function spawnNextRecurrence(completedTask, db, completedAt) {
|
|
12123
|
-
const recurrenceBase = completedTask.due_at ? new Date(completedTask.due_at) : new Date(completedAt);
|
|
12124
|
-
const dueAt = nextOccurrence(completedTask.recurrence_rule, recurrenceBase);
|
|
12125
|
-
let title = completedTask.title;
|
|
12126
|
-
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
12127
|
-
title = title.slice(completedTask.short_id.length + 2);
|
|
12128
|
-
}
|
|
12129
|
-
const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
|
|
12130
|
-
return createTask({
|
|
12131
|
-
title,
|
|
12132
|
-
description: completedTask.description ?? undefined,
|
|
12133
|
-
priority: completedTask.priority,
|
|
12134
|
-
project_id: completedTask.project_id ?? undefined,
|
|
12135
|
-
task_list_id: completedTask.task_list_id ?? undefined,
|
|
12136
|
-
plan_id: completedTask.plan_id ?? undefined,
|
|
12137
|
-
assigned_to: completedTask.assigned_to ?? undefined,
|
|
12138
|
-
tags: completedTask.tags,
|
|
12139
|
-
metadata: completedTask.metadata,
|
|
12140
|
-
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
12141
|
-
sla_minutes: completedTask.sla_minutes ?? undefined,
|
|
12142
|
-
recurrence_rule: completedTask.recurrence_rule,
|
|
12143
|
-
recurrence_parent_id: recurrenceParentId,
|
|
12144
|
-
due_at: dueAt
|
|
12145
|
-
}, db);
|
|
12242
|
+
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
12243
|
+
if (!row)
|
|
12244
|
+
return false;
|
|
12245
|
+
recordStorageTombstone({
|
|
12246
|
+
object_type: "tasks",
|
|
12247
|
+
object_id: id,
|
|
12248
|
+
payload: rowToTask(row),
|
|
12249
|
+
version: row.version
|
|
12250
|
+
}, d);
|
|
12251
|
+
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
12252
|
+
return result.changes > 0;
|
|
12146
12253
|
}
|
|
12147
|
-
var
|
|
12148
|
-
var init_task_lifecycle = __esm(() => {
|
|
12254
|
+
var init_task_crud = __esm(() => {
|
|
12149
12255
|
init_types();
|
|
12150
12256
|
init_database();
|
|
12151
12257
|
init_completion_guard();
|
|
@@ -12153,11 +12259,9 @@ var init_task_lifecycle = __esm(() => {
|
|
|
12153
12259
|
init_event_hooks();
|
|
12154
12260
|
init_shared_events();
|
|
12155
12261
|
init_audit();
|
|
12156
|
-
init_recurrence();
|
|
12157
12262
|
init_webhooks();
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
init_task_graph();
|
|
12263
|
+
init_checklists();
|
|
12264
|
+
init_storage_tombstones();
|
|
12161
12265
|
});
|
|
12162
12266
|
|
|
12163
12267
|
// src/db/task-status.ts
|
|
@@ -12247,6 +12351,15 @@ function setTaskStatus(id, status, _agentId, db) {
|
|
|
12247
12351
|
throw new TaskNotFoundError(id);
|
|
12248
12352
|
if (task.status === status)
|
|
12249
12353
|
return task;
|
|
12354
|
+
if (status === "completed") {
|
|
12355
|
+
try {
|
|
12356
|
+
return completeTask(id, _agentId, d);
|
|
12357
|
+
} catch (e) {
|
|
12358
|
+
if (e instanceof VersionConflictError && attempt < 2)
|
|
12359
|
+
continue;
|
|
12360
|
+
throw e;
|
|
12361
|
+
}
|
|
12362
|
+
}
|
|
12250
12363
|
try {
|
|
12251
12364
|
return updateTask(id, { status, version: task.version }, d);
|
|
12252
12365
|
} catch (e) {
|
|
@@ -15632,10 +15745,8 @@ var init_token_utils = __esm(() => {
|
|
|
15632
15745
|
"cancel_task",
|
|
15633
15746
|
"check_task_done_contract",
|
|
15634
15747
|
"claim_task",
|
|
15635
|
-
"clone_task",
|
|
15636
15748
|
"delete_task",
|
|
15637
15749
|
"extend_task",
|
|
15638
|
-
"get_active_work",
|
|
15639
15750
|
"get_archived_tasks",
|
|
15640
15751
|
"get_blocked_tasks",
|
|
15641
15752
|
"get_blocking_tasks",
|
|
@@ -15671,7 +15782,8 @@ var init_token_utils = __esm(() => {
|
|
|
15671
15782
|
"task_context",
|
|
15672
15783
|
"unlock_task",
|
|
15673
15784
|
"unarchive_task",
|
|
15674
|
-
"update_task"
|
|
15785
|
+
"update_task",
|
|
15786
|
+
"upsert_task"
|
|
15675
15787
|
],
|
|
15676
15788
|
projects: [
|
|
15677
15789
|
"bootstrap_project",
|
|
@@ -15930,12 +16042,8 @@ var init_token_utils = __esm(() => {
|
|
|
15930
16042
|
"delete_tag",
|
|
15931
16043
|
"get_label",
|
|
15932
16044
|
"get_activity_timeline",
|
|
15933
|
-
"get_recent_activity",
|
|
15934
16045
|
"get_tag",
|
|
15935
16046
|
"get_task_fields",
|
|
15936
|
-
"get_task_graph",
|
|
15937
|
-
"get_task_history",
|
|
15938
|
-
"get_task_stats",
|
|
15939
16047
|
"list_workflow_states",
|
|
15940
16048
|
"list_labels",
|
|
15941
16049
|
"list_tags",
|
|
@@ -15946,6 +16054,10 @@ var init_token_utils = __esm(() => {
|
|
|
15946
16054
|
"describe_tools",
|
|
15947
16055
|
"set_task_workflow_state",
|
|
15948
16056
|
"set_task_fields",
|
|
16057
|
+
"assign_label_to_task",
|
|
16058
|
+
"create_custom_field",
|
|
16059
|
+
"set_task_custom_field",
|
|
16060
|
+
"set_task_priority_meta",
|
|
15949
16061
|
"update_label",
|
|
15950
16062
|
"update_tag"
|
|
15951
16063
|
],
|
|
@@ -15971,7 +16083,6 @@ var init_token_utils = __esm(() => {
|
|
|
15971
16083
|
"update_template",
|
|
15972
16084
|
"write_template_library"
|
|
15973
16085
|
],
|
|
15974
|
-
webhooks: ["create_webhook", "delete_webhook", "list_webhooks"],
|
|
15975
16086
|
machines: [
|
|
15976
16087
|
"machines_archive",
|
|
15977
16088
|
"machines_delete",
|
|
@@ -38545,6 +38656,11 @@ function safeEqualHex(a, b) {
|
|
|
38545
38656
|
return false;
|
|
38546
38657
|
return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
38547
38658
|
}
|
|
38659
|
+
function safeEqualStrings(a, b) {
|
|
38660
|
+
const ah = createHash12("sha256").update(a, "utf8").digest();
|
|
38661
|
+
const bh = createHash12("sha256").update(b, "utf8").digest();
|
|
38662
|
+
return timingSafeEqual3(ah, bh);
|
|
38663
|
+
}
|
|
38548
38664
|
function hasActiveApiKeys(db) {
|
|
38549
38665
|
const d = db || getDatabase();
|
|
38550
38666
|
const row = d.query("SELECT COUNT(*) AS count FROM api_keys WHERE revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)").get(now());
|
|
@@ -38717,6 +38833,37 @@ function parseBoundedLimit(value, fallback, max) {
|
|
|
38717
38833
|
return fallback;
|
|
38718
38834
|
return Math.min(parsed, max);
|
|
38719
38835
|
}
|
|
38836
|
+
function mapTaskError(e, json2) {
|
|
38837
|
+
if (e instanceof VersionConflictError) {
|
|
38838
|
+
return json2({
|
|
38839
|
+
error: e.message,
|
|
38840
|
+
code: VersionConflictError.code,
|
|
38841
|
+
expected_version: e.expectedVersion,
|
|
38842
|
+
current_version: e.actualVersion
|
|
38843
|
+
}, 409);
|
|
38844
|
+
}
|
|
38845
|
+
if (e instanceof TaskNotFoundError) {
|
|
38846
|
+
return json2({ error: e.message, code: TaskNotFoundError.code }, 404);
|
|
38847
|
+
}
|
|
38848
|
+
if (e instanceof LockError) {
|
|
38849
|
+
return json2({ error: e.message, code: LockError.code }, 409);
|
|
38850
|
+
}
|
|
38851
|
+
if (e instanceof CompletionGuardError) {
|
|
38852
|
+
return json2({
|
|
38853
|
+
error: e.message,
|
|
38854
|
+
code: CompletionGuardError.code,
|
|
38855
|
+
retry_after: e.retryAfterSeconds ?? null
|
|
38856
|
+
}, 409);
|
|
38857
|
+
}
|
|
38858
|
+
if (e instanceof Error && (/ is blocked by /.test(e.message) || /cannot be started/.test(e.message))) {
|
|
38859
|
+
return json2({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
38860
|
+
}
|
|
38861
|
+
return null;
|
|
38862
|
+
}
|
|
38863
|
+
function countRecurringTasks() {
|
|
38864
|
+
const row = getDatabase().query("SELECT COUNT(*) as count FROM tasks WHERE recurrence_rule IS NOT NULL AND recurrence_rule != '' AND archived_at IS NULL").get();
|
|
38865
|
+
return row?.count ?? 0;
|
|
38866
|
+
}
|
|
38720
38867
|
function handleSseEvents(_req, url, ctx) {
|
|
38721
38868
|
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
38722
38869
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
@@ -38795,35 +38942,40 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
38795
38942
|
});
|
|
38796
38943
|
}
|
|
38797
38944
|
function handleHealth(_ctx, json2) {
|
|
38798
|
-
const
|
|
38799
|
-
const
|
|
38800
|
-
const
|
|
38801
|
-
return json2({
|
|
38945
|
+
const stats2 = getTaskStats();
|
|
38946
|
+
const staleCount = getStaleTasks(30).length;
|
|
38947
|
+
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
38948
|
+
return json2({
|
|
38949
|
+
status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
|
|
38950
|
+
tasks: stats2.total,
|
|
38951
|
+
stale: staleCount,
|
|
38952
|
+
overdue_recurring: overdueRecurring,
|
|
38953
|
+
timestamp: new Date().toISOString()
|
|
38954
|
+
});
|
|
38802
38955
|
}
|
|
38803
38956
|
function handleHeadlessBoundary(_ctx, json2) {
|
|
38804
38957
|
const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
|
|
38805
38958
|
return json2(getHeadlessBoundaryManifest2());
|
|
38806
38959
|
}
|
|
38807
38960
|
function handleStats(_ctx, json2) {
|
|
38808
|
-
const
|
|
38961
|
+
const stats2 = getTaskStats();
|
|
38962
|
+
const byStatus = stats2.by_status;
|
|
38809
38963
|
const projects = listProjects();
|
|
38810
38964
|
const agents = listAgents();
|
|
38811
|
-
const
|
|
38812
|
-
const
|
|
38813
|
-
const overdueRecurring = all.filter((t) => t.recurrence_rule && t.status === "pending" && t.due_at && t.due_at < nowStr).length;
|
|
38814
|
-
const recurringTasks = all.filter((t) => t.recurrence_rule).length;
|
|
38965
|
+
const staleCount = getStaleTasks(30).length;
|
|
38966
|
+
const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
|
|
38815
38967
|
return json2({
|
|
38816
|
-
total_tasks:
|
|
38817
|
-
pending:
|
|
38818
|
-
in_progress:
|
|
38819
|
-
completed:
|
|
38820
|
-
failed:
|
|
38821
|
-
cancelled:
|
|
38968
|
+
total_tasks: stats2.total,
|
|
38969
|
+
pending: byStatus["pending"] ?? 0,
|
|
38970
|
+
in_progress: byStatus["in_progress"] ?? 0,
|
|
38971
|
+
completed: byStatus["completed"] ?? 0,
|
|
38972
|
+
failed: byStatus["failed"] ?? 0,
|
|
38973
|
+
cancelled: byStatus["cancelled"] ?? 0,
|
|
38822
38974
|
projects: projects.length,
|
|
38823
38975
|
agents: agents.length,
|
|
38824
|
-
stale_count:
|
|
38976
|
+
stale_count: staleCount,
|
|
38825
38977
|
overdue_recurring: overdueRecurring,
|
|
38826
|
-
recurring_tasks:
|
|
38978
|
+
recurring_tasks: countRecurringTasks()
|
|
38827
38979
|
});
|
|
38828
38980
|
}
|
|
38829
38981
|
async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
|
|
@@ -38902,27 +39054,34 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
|
38902
39054
|
const summaries = tasks.map((t) => taskToSummary2(t));
|
|
38903
39055
|
if (format === "csv") {
|
|
38904
39056
|
const headers = ["id", "short_id", "title", "status", "priority", "project_id", "assigned_to", "agent_id", "created_at", "updated_at", "completed_at", "due_at"];
|
|
38905
|
-
const
|
|
38906
|
-
const val = t[h];
|
|
39057
|
+
const csvCell = (val) => {
|
|
38907
39058
|
if (val === null || val === undefined)
|
|
38908
39059
|
return "";
|
|
38909
|
-
|
|
38910
|
-
|
|
38911
|
-
|
|
38912
|
-
|
|
39060
|
+
let str = String(val);
|
|
39061
|
+
if (/^[=+\-@\t\r]/.test(str))
|
|
39062
|
+
str = `'${str}`;
|
|
39063
|
+
if (str.includes(",") || str.includes('"') || str.includes(`
|
|
39064
|
+
`) || str.includes("\r")) {
|
|
39065
|
+
str = `"${str.replace(/"/g, '""')}"`;
|
|
39066
|
+
}
|
|
39067
|
+
return str;
|
|
39068
|
+
};
|
|
39069
|
+
const rows = summaries.map((t) => headers.map((h) => csvCell(t[h])).join(","));
|
|
38913
39070
|
const csv = [headers.join(","), ...rows].join(`
|
|
38914
39071
|
`);
|
|
38915
39072
|
return new Response(csv, {
|
|
38916
39073
|
headers: {
|
|
38917
39074
|
"Content-Type": "text/csv",
|
|
38918
|
-
"Content-Disposition": "attachment; filename=tasks.csv"
|
|
39075
|
+
"Content-Disposition": "attachment; filename=tasks.csv",
|
|
39076
|
+
...SECURITY_HEADERS
|
|
38919
39077
|
}
|
|
38920
39078
|
});
|
|
38921
39079
|
}
|
|
38922
39080
|
return new Response(JSON.stringify(summaries, null, 2), {
|
|
38923
39081
|
headers: {
|
|
38924
39082
|
"Content-Type": "application/json",
|
|
38925
|
-
"Content-Disposition": "attachment; filename=tasks.json"
|
|
39083
|
+
"Content-Disposition": "attachment; filename=tasks.json",
|
|
39084
|
+
...SECURITY_HEADERS
|
|
38926
39085
|
}
|
|
38927
39086
|
});
|
|
38928
39087
|
}
|
|
@@ -39096,12 +39255,16 @@ async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
|
|
|
39096
39255
|
if (ALLOWED.has(key))
|
|
39097
39256
|
safeBody[key] = value;
|
|
39098
39257
|
}
|
|
39258
|
+
const clientVersion = typeof body["version"] === "number" ? body["version"] : task2.version;
|
|
39099
39259
|
const updated = updateTask(id, {
|
|
39100
39260
|
...safeBody,
|
|
39101
|
-
version:
|
|
39261
|
+
version: clientVersion
|
|
39102
39262
|
});
|
|
39103
39263
|
return json2(taskToSummary2(updated));
|
|
39104
39264
|
} catch (e) {
|
|
39265
|
+
const mapped = mapTaskError(e, json2);
|
|
39266
|
+
if (mapped)
|
|
39267
|
+
return mapped;
|
|
39105
39268
|
return json2({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
|
|
39106
39269
|
}
|
|
39107
39270
|
}
|
|
@@ -39117,6 +39280,9 @@ function handleStartTask(id, ctx, json2, taskToSummary2) {
|
|
|
39117
39280
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "started", agent_id: "dashboard", project_id: task2.project_id });
|
|
39118
39281
|
return json2(taskToSummary2(task2));
|
|
39119
39282
|
} catch (e) {
|
|
39283
|
+
const mapped = mapTaskError(e, json2);
|
|
39284
|
+
if (mapped)
|
|
39285
|
+
return mapped;
|
|
39120
39286
|
return json2({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
|
|
39121
39287
|
}
|
|
39122
39288
|
}
|
|
@@ -39136,6 +39302,9 @@ function handleCompleteTask(id, ctx, json2, taskToSummary2) {
|
|
|
39136
39302
|
ctx.broadcastEvent({ type: "task", task_id: task2.id, action: "completed", agent_id: "dashboard", project_id: task2.project_id });
|
|
39137
39303
|
return json2(taskToSummary2(task2));
|
|
39138
39304
|
} catch (e) {
|
|
39305
|
+
const mapped = mapTaskError(e, json2);
|
|
39306
|
+
if (mapped)
|
|
39307
|
+
return mapped;
|
|
39139
39308
|
return json2({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
|
|
39140
39309
|
}
|
|
39141
39310
|
}
|
|
@@ -39460,6 +39629,8 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
39460
39629
|
}
|
|
39461
39630
|
var init_routes = __esm(() => {
|
|
39462
39631
|
init_tasks();
|
|
39632
|
+
init_database();
|
|
39633
|
+
init_types();
|
|
39463
39634
|
init_projects();
|
|
39464
39635
|
init_agents();
|
|
39465
39636
|
init_plans();
|
|
@@ -39517,7 +39688,7 @@ function checkAuth(req, apiKey) {
|
|
|
39517
39688
|
if (!apiKey && !generatedKeysEnabled)
|
|
39518
39689
|
return null;
|
|
39519
39690
|
const provided = getProvidedApiKey(req);
|
|
39520
|
-
const matchesEnvKey = Boolean(apiKey && provided && provided
|
|
39691
|
+
const matchesEnvKey = Boolean(apiKey && provided && safeEqualStrings(provided, apiKey));
|
|
39521
39692
|
const matchesGeneratedKey = Boolean(provided && verifyApiKey(provided));
|
|
39522
39693
|
if (!matchesEnvKey && !matchesGeneratedKey) {
|
|
39523
39694
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
@@ -39527,6 +39698,15 @@ function checkAuth(req, apiKey) {
|
|
|
39527
39698
|
}
|
|
39528
39699
|
return null;
|
|
39529
39700
|
}
|
|
39701
|
+
function resolveClientIp(req, server) {
|
|
39702
|
+
const trustProxy = process.env["TODOS_TRUST_PROXY"] === "1" || process.env["TODOS_TRUST_PROXY"] === "true";
|
|
39703
|
+
if (trustProxy) {
|
|
39704
|
+
const forwarded = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip")?.trim();
|
|
39705
|
+
if (forwarded)
|
|
39706
|
+
return forwarded;
|
|
39707
|
+
}
|
|
39708
|
+
return server.requestIP(req)?.address || "unknown";
|
|
39709
|
+
}
|
|
39530
39710
|
function checkRateLimit(ip) {
|
|
39531
39711
|
const now4 = Date.now();
|
|
39532
39712
|
const entry = rateLimitMap.get(ip);
|
|
@@ -39654,7 +39834,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39654
39834
|
const server = Bun.serve({
|
|
39655
39835
|
port,
|
|
39656
39836
|
hostname: hostname3,
|
|
39657
|
-
async fetch(req) {
|
|
39837
|
+
async fetch(req, server2) {
|
|
39658
39838
|
const url = new URL(req.url);
|
|
39659
39839
|
const path = url.pathname;
|
|
39660
39840
|
const method = req.method;
|
|
@@ -39666,15 +39846,6 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39666
39846
|
Vary: "Origin"
|
|
39667
39847
|
} : undefined;
|
|
39668
39848
|
const jsonWithCors = (data, status = 200) => json(data, status, corsHeaders);
|
|
39669
|
-
if (path === "/health" && method === "GET") {
|
|
39670
|
-
const { healthResponse: healthResponse2 } = await Promise.resolve().then(() => (init_http(), exports_http));
|
|
39671
|
-
return healthResponse2("todos");
|
|
39672
|
-
}
|
|
39673
|
-
if (path === "/mcp") {
|
|
39674
|
-
const { handleMcpHttpRequest: handleMcpHttpRequest2 } = await Promise.resolve().then(() => (init_http(), exports_http));
|
|
39675
|
-
const { buildServer: buildServer2 } = await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
|
|
39676
|
-
return handleMcpHttpRequest2(req, buildServer2);
|
|
39677
|
-
}
|
|
39678
39849
|
if (method === "OPTIONS") {
|
|
39679
39850
|
return new Response(null, {
|
|
39680
39851
|
headers: corsHeaders || {
|
|
@@ -39682,7 +39853,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39682
39853
|
}
|
|
39683
39854
|
});
|
|
39684
39855
|
}
|
|
39685
|
-
const ip = req
|
|
39856
|
+
const ip = resolveClientIp(req, server2);
|
|
39686
39857
|
const rl = checkRateLimit(ip);
|
|
39687
39858
|
if (!rl.allowed) {
|
|
39688
39859
|
return new Response(JSON.stringify({ error: "Too many requests", retry_after: rl.retryAfter }), {
|
|
@@ -39690,6 +39861,18 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39690
39861
|
headers: { "Content-Type": "application/json", "Retry-After": String(rl.retryAfter ?? 60), ...SECURITY_HEADERS }
|
|
39691
39862
|
});
|
|
39692
39863
|
}
|
|
39864
|
+
if (path === "/health" && method === "GET") {
|
|
39865
|
+
const { healthResponse: healthResponse2 } = await Promise.resolve().then(() => (init_http(), exports_http));
|
|
39866
|
+
return healthResponse2("todos");
|
|
39867
|
+
}
|
|
39868
|
+
if (path === "/mcp") {
|
|
39869
|
+
const authError = checkAuth(req, apiKey);
|
|
39870
|
+
if (authError)
|
|
39871
|
+
return authError;
|
|
39872
|
+
const { handleMcpHttpRequest: handleMcpHttpRequest2 } = await Promise.resolve().then(() => (init_http(), exports_http));
|
|
39873
|
+
const { buildServer: buildServer2 } = await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
|
|
39874
|
+
return handleMcpHttpRequest2(req, buildServer2);
|
|
39875
|
+
}
|
|
39693
39876
|
if (path.startsWith("/api/")) {
|
|
39694
39877
|
const authError = checkAuth(req, apiKey);
|
|
39695
39878
|
if (authError)
|
|
@@ -39969,14 +40152,16 @@ function printHelp() {
|
|
|
39969
40152
|
Start the @hasna/todos MCP server.
|
|
39970
40153
|
|
|
39971
40154
|
Options:
|
|
39972
|
-
--stdio Use stdio transport
|
|
39973
|
-
--
|
|
40155
|
+
--stdio Use stdio transport (default)
|
|
40156
|
+
--http Use Streamable HTTP transport
|
|
40157
|
+
--port <port> Use Streamable HTTP on the given port (implies --http)
|
|
39974
40158
|
-V, --version output the version number
|
|
39975
40159
|
-h, --help display help for command
|
|
39976
40160
|
|
|
39977
40161
|
Environment:
|
|
39978
|
-
|
|
39979
|
-
|
|
40162
|
+
MCP_STDIO=1 Force stdio transport
|
|
40163
|
+
MCP_HTTP=1 Use Streamable HTTP transport
|
|
40164
|
+
MCP_HTTP_PORT=<port> HTTP port when using HTTP transport
|
|
39980
40165
|
TODOS_PROFILE=<profile> Tool profile filter
|
|
39981
40166
|
TODOS_TOOL_GROUPS=<list> Comma-separated tool group filter`);
|
|
39982
40167
|
}
|
|
@@ -40065,8 +40250,22 @@ function formatError(error) {
|
|
|
40065
40250
|
function resolveId(partialId, table = "tasks") {
|
|
40066
40251
|
const db = getDatabase();
|
|
40067
40252
|
const id = resolvePartialId(db, table, partialId);
|
|
40068
|
-
if (!id)
|
|
40069
|
-
|
|
40253
|
+
if (!id) {
|
|
40254
|
+
switch (table) {
|
|
40255
|
+
case "tasks":
|
|
40256
|
+
throw new TaskNotFoundError(partialId);
|
|
40257
|
+
case "projects":
|
|
40258
|
+
throw new ProjectNotFoundError(partialId);
|
|
40259
|
+
case "plans":
|
|
40260
|
+
throw new PlanNotFoundError(partialId);
|
|
40261
|
+
case "task_lists":
|
|
40262
|
+
throw new TaskListNotFoundError(partialId);
|
|
40263
|
+
case "agents":
|
|
40264
|
+
throw new AgentNotFoundError(partialId);
|
|
40265
|
+
default:
|
|
40266
|
+
throw new TaskNotFoundError(partialId);
|
|
40267
|
+
}
|
|
40268
|
+
}
|
|
40070
40269
|
return id;
|
|
40071
40270
|
}
|
|
40072
40271
|
function formatTask(task2) {
|
|
@@ -40155,8 +40354,9 @@ function buildServer() {
|
|
|
40155
40354
|
return server;
|
|
40156
40355
|
}
|
|
40157
40356
|
async function main() {
|
|
40158
|
-
const {
|
|
40159
|
-
|
|
40357
|
+
const { isHttpMode: isHttpMode2, resolveHttpPort: resolveHttpPort2 } = await Promise.resolve().then(() => (init_http(), exports_http));
|
|
40358
|
+
const portRequested = process.argv.some((arg) => arg === "--port" || arg.startsWith("--port="));
|
|
40359
|
+
if (!isHttpMode2() && !portRequested) {
|
|
40160
40360
|
const server = buildServer();
|
|
40161
40361
|
const transport = new StdioServerTransport;
|
|
40162
40362
|
await server.connect(transport);
|