@hasna/todos 0.11.94 → 0.11.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cloud-router.d.ts +12 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/index.js +696 -63
- package/dist/contracts.js +56 -50
- package/dist/db/storage-tombstones.d.ts +1 -1
- package/dist/db/storage-tombstones.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.js +169 -55
- package/dist/lib/template-semantics.d.ts +8 -0
- package/dist/lib/template-semantics.d.ts.map +1 -0
- package/dist/mcp/index.js +461 -59
- package/dist/mcp.js +1 -1
- package/dist/registry.js +56 -50
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.js +35 -0
- package/dist/sdk/v1.generated.d.ts +103 -1
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +467 -65
- package/dist/server/openapi.d.ts +488 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +3 -2
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +1 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/shadow-outbox.d.ts.map +1 -1
- package/dist/storage/shadow.d.ts +1 -1
- package/dist/storage/shadow.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +168 -54
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -70,7 +70,7 @@ var package_default;
|
|
|
70
70
|
var init_package = __esm(() => {
|
|
71
71
|
package_default = {
|
|
72
72
|
name: "@hasna/todos",
|
|
73
|
-
version: "0.11.
|
|
73
|
+
version: "0.11.95",
|
|
74
74
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
75
75
|
type: "module",
|
|
76
76
|
main: "dist/index.js",
|
|
@@ -898,6 +898,7 @@ function snapshotEntries(snapshot) {
|
|
|
898
898
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
899
899
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
900
900
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
901
|
+
...(snapshot.templateTasks ?? []).map((payload) => entry("template_tasks", payload, snapshot.exportedAt)),
|
|
901
902
|
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
|
|
902
903
|
...(snapshot.tombstones ?? []).map((tombstone) => ({
|
|
903
904
|
type: tombstone.object_type,
|
|
@@ -949,6 +950,7 @@ function rowsToSnapshot(rows) {
|
|
|
949
950
|
agents: [],
|
|
950
951
|
taskLists: [],
|
|
951
952
|
templates: [],
|
|
953
|
+
templateTasks: [],
|
|
952
954
|
auditHistory: [],
|
|
953
955
|
tombstones: []
|
|
954
956
|
};
|
|
@@ -983,6 +985,8 @@ function rowsToSnapshot(rows) {
|
|
|
983
985
|
snapshot.taskLists.push(payload);
|
|
984
986
|
else if (row.object_type === "templates")
|
|
985
987
|
snapshot.templates.push(payload);
|
|
988
|
+
else if (row.object_type === "template_tasks")
|
|
989
|
+
snapshot.templateTasks.push(payload);
|
|
986
990
|
else if (row.object_type === "audit_history")
|
|
987
991
|
snapshot.auditHistory.push(payload);
|
|
988
992
|
}
|
|
@@ -1316,10 +1320,13 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
1316
1320
|
get: (id) => store.get("templates", id),
|
|
1317
1321
|
list: async () => (await store.list("templates")).sort((a, b) => a.name.localeCompare(b.name)),
|
|
1318
1322
|
update: (id, input) => updateTemplate(id, input, store),
|
|
1319
|
-
delete: (id, context) =>
|
|
1323
|
+
delete: (id, context) => deleteTemplate(id, store, context),
|
|
1320
1324
|
getWithTasks: async (id) => {
|
|
1321
1325
|
const template = await store.get("templates", id);
|
|
1322
|
-
|
|
1326
|
+
if (!template)
|
|
1327
|
+
return null;
|
|
1328
|
+
const tasks = (await store.list("template_tasks")).filter((task) => task.template_id === id).sort((left, right) => left.position - right.position || left.id.localeCompare(right.id));
|
|
1329
|
+
return { ...template, tasks };
|
|
1323
1330
|
}
|
|
1324
1331
|
},
|
|
1325
1332
|
audit: {
|
|
@@ -1607,6 +1614,57 @@ class PostgresJsonRecordStore {
|
|
|
1607
1614
|
}
|
|
1608
1615
|
return value;
|
|
1609
1616
|
}
|
|
1617
|
+
async createTemplateWithTasks(template, tasks, context = {}) {
|
|
1618
|
+
await this.ensureSchema();
|
|
1619
|
+
const records = [
|
|
1620
|
+
{ object_type: "templates", object_id: template.id, payload: template, updated_at: template.created_at, version: template.version },
|
|
1621
|
+
...tasks.map((task) => ({ object_type: "template_tasks", object_id: task.id, payload: task, updated_at: task.created_at, version: 1 }))
|
|
1622
|
+
];
|
|
1623
|
+
const result = await this.options.client.query(`/* todos:create-template-with-tasks-atomic */ WITH input AS (
|
|
1624
|
+
SELECT value->>'object_type' AS object_type,
|
|
1625
|
+
value->>'object_id' AS object_id,
|
|
1626
|
+
value->'payload' AS payload,
|
|
1627
|
+
value->>'updated_at' AS updated_at,
|
|
1628
|
+
COALESCE((value->>'version')::integer, 1) AS version
|
|
1629
|
+
FROM jsonb_array_elements($2::jsonb) AS value
|
|
1630
|
+
) INSERT INTO ${this.tableName} (
|
|
1631
|
+
service, object_type, object_id, payload, updated_at,
|
|
1632
|
+
deleted_at, source_machine_id, version
|
|
1633
|
+
) SELECT $1, object_type, object_id, payload, updated_at::timestamptz,
|
|
1634
|
+
NULL, $3, version
|
|
1635
|
+
FROM input
|
|
1636
|
+
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
1637
|
+
payload = EXCLUDED.payload,
|
|
1638
|
+
updated_at = EXCLUDED.updated_at,
|
|
1639
|
+
deleted_at = NULL,
|
|
1640
|
+
source_machine_id = EXCLUDED.source_machine_id,
|
|
1641
|
+
version = EXCLUDED.version
|
|
1642
|
+
WHERE ${this.tableName}.updated_at IS NULL
|
|
1643
|
+
OR ${this.tableName}.updated_at < EXCLUDED.updated_at
|
|
1644
|
+
OR (${this.tableName}.updated_at = EXCLUDED.updated_at
|
|
1645
|
+
AND COALESCE(${this.tableName}.version, 0) <= COALESCE(EXCLUDED.version, 0))
|
|
1646
|
+
RETURNING object_type, object_id`, [this.service, jsonbParam(records), this.machineId(context)]);
|
|
1647
|
+
if (result.rows.length !== records.length) {
|
|
1648
|
+
throw new Error("Template checklist write was rejected before completion; no partial template was committed");
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
async deleteTemplateWithTasks(id, context = {}) {
|
|
1652
|
+
await this.ensureSchema();
|
|
1653
|
+
const timestamp = new Date().toISOString();
|
|
1654
|
+
const result = await this.options.client.query(`/* todos:delete-template-with-tasks-atomic */ WITH target AS (
|
|
1655
|
+
SELECT 1 FROM ${this.tableName}
|
|
1656
|
+
WHERE service = $1 AND object_type = 'templates' AND object_id = $2 AND deleted_at IS NULL
|
|
1657
|
+
) UPDATE ${this.tableName} AS record SET
|
|
1658
|
+
deleted_at = $3::timestamptz,
|
|
1659
|
+
updated_at = $3::timestamptz,
|
|
1660
|
+
source_machine_id = COALESCE($4, record.source_machine_id),
|
|
1661
|
+
version = COALESCE(record.version, 0) + 1
|
|
1662
|
+
WHERE record.service = $1 AND record.deleted_at IS NULL AND EXISTS (SELECT 1 FROM target)
|
|
1663
|
+
AND (record.object_type = 'templates' AND record.object_id = $2
|
|
1664
|
+
OR record.object_type = 'template_tasks' AND record.payload->>'template_id' = $2)
|
|
1665
|
+
RETURNING record.object_type`, [this.service, id, timestamp, this.machineId(context)]);
|
|
1666
|
+
return result.rows.some((row) => row.object_type === "templates");
|
|
1667
|
+
}
|
|
1610
1668
|
async completeTask(id, agentId, options) {
|
|
1611
1669
|
await this.ensureSchema();
|
|
1612
1670
|
const operationTimestamp = new Date().toISOString();
|
|
@@ -2369,7 +2427,7 @@ async function updateTaskList(id, input, store) {
|
|
|
2369
2427
|
}
|
|
2370
2428
|
async function createTemplate(input, store, context) {
|
|
2371
2429
|
const timestamp = new Date().toISOString();
|
|
2372
|
-
|
|
2430
|
+
const template = {
|
|
2373
2431
|
id: randomUUID(),
|
|
2374
2432
|
name: input.name,
|
|
2375
2433
|
title_pattern: input.title_pattern,
|
|
@@ -2384,7 +2442,30 @@ async function createTemplate(input, store, context) {
|
|
|
2384
2442
|
created_at: timestamp,
|
|
2385
2443
|
machine_id: store.machineId(context),
|
|
2386
2444
|
synced_at: null
|
|
2387
|
-
}
|
|
2445
|
+
};
|
|
2446
|
+
const tasks = buildTemplateTasks(template.id, input.tasks ?? [], timestamp);
|
|
2447
|
+
await store.createTemplateWithTasks(template, tasks, context);
|
|
2448
|
+
return template;
|
|
2449
|
+
}
|
|
2450
|
+
function buildTemplateTasks(templateId, inputs, timestamp) {
|
|
2451
|
+
return inputs.map((input, position) => ({
|
|
2452
|
+
id: randomUUID(),
|
|
2453
|
+
template_id: templateId,
|
|
2454
|
+
position,
|
|
2455
|
+
title_pattern: input.title_pattern,
|
|
2456
|
+
description: input.description ?? null,
|
|
2457
|
+
priority: input.priority ?? "medium",
|
|
2458
|
+
tags: input.tags ?? [],
|
|
2459
|
+
task_type: input.task_type ?? null,
|
|
2460
|
+
condition: input.condition ?? null,
|
|
2461
|
+
include_template_id: input.include_template_id ?? null,
|
|
2462
|
+
depends_on_positions: input.depends_on ?? [],
|
|
2463
|
+
metadata: input.metadata ?? {},
|
|
2464
|
+
created_at: timestamp
|
|
2465
|
+
}));
|
|
2466
|
+
}
|
|
2467
|
+
async function deleteTemplate(id, store, context) {
|
|
2468
|
+
return store.deleteTemplateWithTasks(id, context);
|
|
2388
2469
|
}
|
|
2389
2470
|
async function updateTemplate(id, input, store) {
|
|
2390
2471
|
const template = await store.get("templates", id);
|
|
@@ -2440,6 +2521,7 @@ async function exportSnapshot(store) {
|
|
|
2440
2521
|
agents: await store.list("agents"),
|
|
2441
2522
|
taskLists: await store.list("task_lists"),
|
|
2442
2523
|
templates: await store.list("templates"),
|
|
2524
|
+
templateTasks: await store.list("template_tasks"),
|
|
2443
2525
|
auditHistory: await store.list("audit_history"),
|
|
2444
2526
|
tombstones: await store.listTombstones()
|
|
2445
2527
|
};
|
|
@@ -2464,6 +2546,7 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
2464
2546
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
2465
2547
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
2466
2548
|
...snapshot.templates.map((row) => ["templates", row]),
|
|
2549
|
+
...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row]),
|
|
2467
2550
|
...snapshot.auditHistory.map((row) => ["audit_history", row])
|
|
2468
2551
|
];
|
|
2469
2552
|
for (const [type, row] of entries) {
|
|
@@ -8562,6 +8645,50 @@ var init_recurrence = __esm(() => {
|
|
|
8562
8645
|
};
|
|
8563
8646
|
});
|
|
8564
8647
|
|
|
8648
|
+
// src/lib/template-semantics.ts
|
|
8649
|
+
function resolveTemplateVariables(templateVars, provided) {
|
|
8650
|
+
const merged = { ...provided };
|
|
8651
|
+
for (const variable of templateVars) {
|
|
8652
|
+
if (merged[variable.name] === undefined && variable.default !== undefined) {
|
|
8653
|
+
merged[variable.name] = variable.default;
|
|
8654
|
+
}
|
|
8655
|
+
}
|
|
8656
|
+
const missing = templateVars.filter((variable) => variable.required && merged[variable.name] === undefined).map((variable) => variable.name);
|
|
8657
|
+
if (missing.length > 0) {
|
|
8658
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
8659
|
+
}
|
|
8660
|
+
return merged;
|
|
8661
|
+
}
|
|
8662
|
+
function substituteTemplateVariables(text, variables) {
|
|
8663
|
+
let result = text;
|
|
8664
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
8665
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
8666
|
+
}
|
|
8667
|
+
return result;
|
|
8668
|
+
}
|
|
8669
|
+
function evaluateTemplateCondition(condition, variables) {
|
|
8670
|
+
if (!condition || condition.trim() === "")
|
|
8671
|
+
return true;
|
|
8672
|
+
const trimmed = condition.trim();
|
|
8673
|
+
const equal = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
8674
|
+
if (equal)
|
|
8675
|
+
return (variables[equal[1]] ?? "") === equal[2].trim();
|
|
8676
|
+
const unequal = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
8677
|
+
if (unequal)
|
|
8678
|
+
return (variables[unequal[1]] ?? "") !== unequal[2].trim();
|
|
8679
|
+
const falsy = trimmed.match(/^!\{([^}]+)\}$/);
|
|
8680
|
+
if (falsy) {
|
|
8681
|
+
const value = variables[falsy[1]];
|
|
8682
|
+
return !value || value === "" || value === "false";
|
|
8683
|
+
}
|
|
8684
|
+
const truthy = trimmed.match(/^\{([^}]+)\}$/);
|
|
8685
|
+
if (truthy) {
|
|
8686
|
+
const value = variables[truthy[1]];
|
|
8687
|
+
return !!value && value !== "" && value !== "false";
|
|
8688
|
+
}
|
|
8689
|
+
return true;
|
|
8690
|
+
}
|
|
8691
|
+
|
|
8565
8692
|
// src/db/templates.ts
|
|
8566
8693
|
var exports_templates = {};
|
|
8567
8694
|
__export(exports_templates, {
|
|
@@ -8579,7 +8706,7 @@ __export(exports_templates, {
|
|
|
8579
8706
|
getTemplate: () => getTemplate,
|
|
8580
8707
|
exportTemplate: () => exportTemplate,
|
|
8581
8708
|
evaluateCondition: () => evaluateCondition,
|
|
8582
|
-
deleteTemplate: () =>
|
|
8709
|
+
deleteTemplate: () => deleteTemplate2,
|
|
8583
8710
|
createTemplate: () => createTemplate2,
|
|
8584
8711
|
addTemplateTasks: () => addTemplateTasks
|
|
8585
8712
|
});
|
|
@@ -8643,7 +8770,7 @@ function listTemplates(db) {
|
|
|
8643
8770
|
const d = db || getDatabase();
|
|
8644
8771
|
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
8645
8772
|
}
|
|
8646
|
-
function
|
|
8773
|
+
function deleteTemplate2(id, db) {
|
|
8647
8774
|
const d = db || getDatabase();
|
|
8648
8775
|
const resolved = resolveTemplateId(id, d);
|
|
8649
8776
|
if (!resolved)
|
|
@@ -8657,6 +8784,14 @@ function deleteTemplate(id, db) {
|
|
|
8657
8784
|
payload: template,
|
|
8658
8785
|
version: template.version
|
|
8659
8786
|
}, d);
|
|
8787
|
+
for (const task of getTemplateTasks(resolved, d)) {
|
|
8788
|
+
recordStorageTombstone({
|
|
8789
|
+
object_type: "template_tasks",
|
|
8790
|
+
object_id: task.id,
|
|
8791
|
+
payload: task,
|
|
8792
|
+
version: 1
|
|
8793
|
+
}, d);
|
|
8794
|
+
}
|
|
8660
8795
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
8661
8796
|
}
|
|
8662
8797
|
function updateTemplate2(id, updates, db) {
|
|
@@ -8788,34 +8923,7 @@ function getTemplateTasks(templateId, db) {
|
|
|
8788
8923
|
return rows.map(rowToTemplateTask);
|
|
8789
8924
|
}
|
|
8790
8925
|
function evaluateCondition(condition, variables) {
|
|
8791
|
-
|
|
8792
|
-
return true;
|
|
8793
|
-
const trimmed = condition.trim();
|
|
8794
|
-
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
8795
|
-
if (eqMatch) {
|
|
8796
|
-
const varName = eqMatch[1];
|
|
8797
|
-
const expected = eqMatch[2].trim();
|
|
8798
|
-
return (variables[varName] ?? "") === expected;
|
|
8799
|
-
}
|
|
8800
|
-
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
8801
|
-
if (neqMatch) {
|
|
8802
|
-
const varName = neqMatch[1];
|
|
8803
|
-
const expected = neqMatch[2].trim();
|
|
8804
|
-
return (variables[varName] ?? "") !== expected;
|
|
8805
|
-
}
|
|
8806
|
-
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
8807
|
-
if (falsyMatch) {
|
|
8808
|
-
const varName = falsyMatch[1];
|
|
8809
|
-
const val = variables[varName];
|
|
8810
|
-
return !val || val === "" || val === "false";
|
|
8811
|
-
}
|
|
8812
|
-
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
8813
|
-
if (truthyMatch) {
|
|
8814
|
-
const varName = truthyMatch[1];
|
|
8815
|
-
const val = variables[varName];
|
|
8816
|
-
return !!val && val !== "" && val !== "false";
|
|
8817
|
-
}
|
|
8818
|
-
return true;
|
|
8926
|
+
return evaluateTemplateCondition(condition, variables);
|
|
8819
8927
|
}
|
|
8820
8928
|
function exportTemplate(id, db) {
|
|
8821
8929
|
const d = db || getDatabase();
|
|
@@ -8888,29 +8996,10 @@ function listTemplateVersions(id, db) {
|
|
|
8888
8996
|
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
8889
8997
|
}
|
|
8890
8998
|
function resolveVariables(templateVars, provided) {
|
|
8891
|
-
|
|
8892
|
-
for (const v of templateVars) {
|
|
8893
|
-
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
8894
|
-
merged[v.name] = v.default;
|
|
8895
|
-
}
|
|
8896
|
-
}
|
|
8897
|
-
const missing = [];
|
|
8898
|
-
for (const v of templateVars) {
|
|
8899
|
-
if (v.required && merged[v.name] === undefined) {
|
|
8900
|
-
missing.push(v.name);
|
|
8901
|
-
}
|
|
8902
|
-
}
|
|
8903
|
-
if (missing.length > 0) {
|
|
8904
|
-
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
8905
|
-
}
|
|
8906
|
-
return merged;
|
|
8999
|
+
return resolveTemplateVariables(templateVars, provided);
|
|
8907
9000
|
}
|
|
8908
9001
|
function substituteVars(text, variables) {
|
|
8909
|
-
|
|
8910
|
-
for (const [key, val] of Object.entries(variables)) {
|
|
8911
|
-
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
8912
|
-
}
|
|
8913
|
-
return result;
|
|
9002
|
+
return substituteTemplateVariables(text, variables);
|
|
8914
9003
|
}
|
|
8915
9004
|
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
8916
9005
|
const d = db || getDatabase();
|
|
@@ -15090,7 +15179,7 @@ async function handleCreateTemplate(req, _ctx, json2) {
|
|
|
15090
15179
|
}
|
|
15091
15180
|
}
|
|
15092
15181
|
function handleDeleteTemplate(id, _ctx, json2) {
|
|
15093
|
-
const deleted =
|
|
15182
|
+
const deleted = deleteTemplate2(id);
|
|
15094
15183
|
return json2(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
|
|
15095
15184
|
}
|
|
15096
15185
|
function handleListPlans(url, _ctx, json2) {
|
|
@@ -15202,6 +15291,7 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
15202
15291
|
agents: listAgents({ include_archived: true }, d),
|
|
15203
15292
|
taskLists: listTaskLists(undefined, d),
|
|
15204
15293
|
templates: listTemplates(d),
|
|
15294
|
+
templateTasks: listTemplates(d).flatMap((template) => getTemplateTasks(template.id, d)),
|
|
15205
15295
|
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
15206
15296
|
tombstones: listStorageTombstones(d)
|
|
15207
15297
|
};
|
|
@@ -15251,6 +15341,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
15251
15341
|
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
15252
15342
|
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
15253
15343
|
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
15344
|
+
applyRows("template_tasks", "template_tasks", TEMPLATE_TASK_COLUMNS, snapshot.templateTasks ?? []);
|
|
15254
15345
|
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks(snapshot.tasks), "updated_at", (row, changed) => {
|
|
15255
15346
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
15256
15347
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
@@ -15355,6 +15446,8 @@ function tableForTombstone(objectType) {
|
|
|
15355
15446
|
return "task_lists";
|
|
15356
15447
|
if (objectType === "templates")
|
|
15357
15448
|
return "task_templates";
|
|
15449
|
+
if (objectType === "template_tasks")
|
|
15450
|
+
return "template_tasks";
|
|
15358
15451
|
return "task_history";
|
|
15359
15452
|
}
|
|
15360
15453
|
function listRows(db, table, columns) {
|
|
@@ -15391,7 +15484,7 @@ function clockColumnsForTable(table) {
|
|
|
15391
15484
|
return ["created_at"];
|
|
15392
15485
|
return ["updated_at", "created_at"];
|
|
15393
15486
|
}
|
|
15394
|
-
var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
|
|
15487
|
+
var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TEMPLATE_TASK_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
|
|
15395
15488
|
var init_sqlite_snapshot = __esm(() => {
|
|
15396
15489
|
init_database();
|
|
15397
15490
|
init_agents();
|
|
@@ -15485,6 +15578,21 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
15485
15578
|
"machine_id",
|
|
15486
15579
|
"synced_at"
|
|
15487
15580
|
];
|
|
15581
|
+
TEMPLATE_TASK_COLUMNS = [
|
|
15582
|
+
"id",
|
|
15583
|
+
"template_id",
|
|
15584
|
+
"position",
|
|
15585
|
+
"title_pattern",
|
|
15586
|
+
"description",
|
|
15587
|
+
"priority",
|
|
15588
|
+
"tags",
|
|
15589
|
+
"task_type",
|
|
15590
|
+
"condition",
|
|
15591
|
+
"include_template_id",
|
|
15592
|
+
"depends_on_positions",
|
|
15593
|
+
"metadata",
|
|
15594
|
+
"created_at"
|
|
15595
|
+
];
|
|
15488
15596
|
TASK_COLUMNS = [
|
|
15489
15597
|
"id",
|
|
15490
15598
|
"short_id",
|
|
@@ -15553,7 +15661,7 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
15553
15661
|
"created_at",
|
|
15554
15662
|
"machine_id"
|
|
15555
15663
|
];
|
|
15556
|
-
JSON_COLUMNS = new Set(["tags", "metadata", "permissions", "capabilities", "variables"]);
|
|
15664
|
+
JSON_COLUMNS = new Set(["tags", "metadata", "permissions", "capabilities", "variables", "depends_on_positions"]);
|
|
15557
15665
|
BOOLEAN_COLUMNS = new Set(["requires_approval"]);
|
|
15558
15666
|
});
|
|
15559
15667
|
|
|
@@ -15646,7 +15754,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
15646
15754
|
get: (id) => getTemplate(id, database()),
|
|
15647
15755
|
list: () => listTemplates(database()),
|
|
15648
15756
|
update: (id, input) => updateTemplate2(id, input, database()),
|
|
15649
|
-
delete: (id) =>
|
|
15757
|
+
delete: (id) => deleteTemplate2(id, database()),
|
|
15650
15758
|
getWithTasks: (id) => getTemplateWithTasks(id, database())
|
|
15651
15759
|
},
|
|
15652
15760
|
audit: {
|
|
@@ -15869,6 +15977,9 @@ class TodosShadowOutbox {
|
|
|
15869
15977
|
case "templates":
|
|
15870
15978
|
snapshot.templates.push(record);
|
|
15871
15979
|
break;
|
|
15980
|
+
case "template_tasks":
|
|
15981
|
+
snapshot.templateTasks.push(record);
|
|
15982
|
+
break;
|
|
15872
15983
|
case "audit_history":
|
|
15873
15984
|
snapshot.auditHistory.push(record);
|
|
15874
15985
|
break;
|
|
@@ -15935,6 +16046,7 @@ function emptySnapshot() {
|
|
|
15935
16046
|
agents: [],
|
|
15936
16047
|
taskLists: [],
|
|
15937
16048
|
templates: [],
|
|
16049
|
+
templateTasks: [],
|
|
15938
16050
|
auditHistory: [],
|
|
15939
16051
|
tombstones: []
|
|
15940
16052
|
};
|
|
@@ -16060,12 +16172,16 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
16060
16172
|
TaskList: taskListSchema,
|
|
16061
16173
|
TaskComment: taskCommentSchema,
|
|
16062
16174
|
Plan: planSchema,
|
|
16175
|
+
Template: templateSchema,
|
|
16176
|
+
TemplateTask: templateTaskSchema,
|
|
16177
|
+
TemplateVariable: templateVariableSchema,
|
|
16178
|
+
CreateTemplateTaskInput: createTemplateTaskInputSchema,
|
|
16063
16179
|
CreateTaskInput: {
|
|
16064
16180
|
type: "object",
|
|
16065
16181
|
required: ["title"],
|
|
16066
16182
|
properties: {
|
|
16067
16183
|
title: { type: "string" },
|
|
16068
|
-
description: { type: "string" },
|
|
16184
|
+
description: { type: "string", nullable: true },
|
|
16069
16185
|
status: { type: "string" },
|
|
16070
16186
|
priority: { type: "string" },
|
|
16071
16187
|
project_id: { type: "string" },
|
|
@@ -16200,6 +16316,39 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
16200
16316
|
agent_id: { type: "string", minLength: 1 },
|
|
16201
16317
|
status: { type: "string", enum: ["active", "completed", "archived"] }
|
|
16202
16318
|
}
|
|
16319
|
+
},
|
|
16320
|
+
CreateTemplateInput: {
|
|
16321
|
+
type: "object",
|
|
16322
|
+
additionalProperties: false,
|
|
16323
|
+
required: ["name", "title_pattern"],
|
|
16324
|
+
properties: {
|
|
16325
|
+
name: { type: "string", minLength: 1 },
|
|
16326
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
16327
|
+
description: { type: "string", nullable: true },
|
|
16328
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
16329
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
16330
|
+
variables: { type: "array", items: { $ref: "#/components/schemas/TemplateVariable" } },
|
|
16331
|
+
project_id: { type: "string", minLength: 1, nullable: true },
|
|
16332
|
+
plan_id: { type: "string", minLength: 1, nullable: true },
|
|
16333
|
+
metadata: { type: "object", additionalProperties: true },
|
|
16334
|
+
tasks: { type: "array", items: { $ref: "#/components/schemas/CreateTemplateTaskInput" } }
|
|
16335
|
+
}
|
|
16336
|
+
},
|
|
16337
|
+
UpdateTemplateInput: {
|
|
16338
|
+
type: "object",
|
|
16339
|
+
additionalProperties: false,
|
|
16340
|
+
minProperties: 1,
|
|
16341
|
+
properties: {
|
|
16342
|
+
name: { type: "string", minLength: 1 },
|
|
16343
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
16344
|
+
description: { type: "string", nullable: true },
|
|
16345
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
16346
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
16347
|
+
variables: { type: "array", items: { type: "object" } },
|
|
16348
|
+
project_id: { type: "string", nullable: true },
|
|
16349
|
+
plan_id: { type: "string", nullable: true },
|
|
16350
|
+
metadata: { type: "object", additionalProperties: true }
|
|
16351
|
+
}
|
|
16203
16352
|
}
|
|
16204
16353
|
}
|
|
16205
16354
|
},
|
|
@@ -16502,6 +16651,41 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
16502
16651
|
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
|
|
16503
16652
|
}
|
|
16504
16653
|
},
|
|
16654
|
+
"/v1/templates": {
|
|
16655
|
+
get: {
|
|
16656
|
+
operationId: "listTemplates",
|
|
16657
|
+
summary: "List reusable task templates",
|
|
16658
|
+
parameters: [{ name: "project_id", in: "query", schema: { type: "string" } }],
|
|
16659
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { templates: { type: "array", items: { $ref: "#/components/schemas/Template" } }, count: { type: "number" } } } } } } }
|
|
16660
|
+
},
|
|
16661
|
+
post: {
|
|
16662
|
+
operationId: "createTemplate",
|
|
16663
|
+
summary: "Create a reusable task template",
|
|
16664
|
+
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTemplateInput" } } } },
|
|
16665
|
+
responses: { "201": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
16666
|
+
}
|
|
16667
|
+
},
|
|
16668
|
+
"/v1/templates/{id}": {
|
|
16669
|
+
get: {
|
|
16670
|
+
operationId: "getTemplate",
|
|
16671
|
+
summary: "Get one reusable task template with its checklist steps",
|
|
16672
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
16673
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
16674
|
+
},
|
|
16675
|
+
patch: {
|
|
16676
|
+
operationId: "updateTemplate",
|
|
16677
|
+
summary: "Update reusable template metadata and defaults",
|
|
16678
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
16679
|
+
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateTemplateInput" } } } },
|
|
16680
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
16681
|
+
},
|
|
16682
|
+
delete: {
|
|
16683
|
+
operationId: "deleteTemplate",
|
|
16684
|
+
summary: "Delete a reusable task template and its checklist steps",
|
|
16685
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
16686
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
|
|
16687
|
+
}
|
|
16688
|
+
},
|
|
16505
16689
|
"/v1/task-lists": {
|
|
16506
16690
|
get: {
|
|
16507
16691
|
operationId: "listTaskLists",
|
|
@@ -16579,6 +16763,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
16579
16763
|
agents: { type: "array", items: { type: "object" } },
|
|
16580
16764
|
taskLists: { type: "array", items: { type: "object" } },
|
|
16581
16765
|
templates: { type: "array", items: { type: "object" } },
|
|
16766
|
+
templateTasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } },
|
|
16582
16767
|
auditHistory: { type: "array", items: { type: "object" } },
|
|
16583
16768
|
tombstones: { type: "array", items: { type: "object" } }
|
|
16584
16769
|
}
|
|
@@ -16615,7 +16800,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
16615
16800
|
}
|
|
16616
16801
|
};
|
|
16617
16802
|
}
|
|
16618
|
-
var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema;
|
|
16803
|
+
var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
16619
16804
|
var init_openapi = __esm(() => {
|
|
16620
16805
|
init_package_version();
|
|
16621
16806
|
taskSchema = {
|
|
@@ -16692,6 +16877,72 @@ var init_openapi = __esm(() => {
|
|
|
16692
16877
|
updated_at: { type: "string", format: "date-time" }
|
|
16693
16878
|
}
|
|
16694
16879
|
};
|
|
16880
|
+
templateTaskSchema = {
|
|
16881
|
+
type: "object",
|
|
16882
|
+
required: ["id", "template_id", "position", "title_pattern", "priority", "tags", "depends_on_positions", "metadata", "created_at"],
|
|
16883
|
+
properties: {
|
|
16884
|
+
id: { type: "string" },
|
|
16885
|
+
template_id: { type: "string" },
|
|
16886
|
+
position: { type: "integer", minimum: 0 },
|
|
16887
|
+
title_pattern: { type: "string" },
|
|
16888
|
+
description: { type: "string", nullable: true },
|
|
16889
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
16890
|
+
tags: { type: "array", items: { type: "string" } },
|
|
16891
|
+
task_type: { type: "string", nullable: true },
|
|
16892
|
+
condition: { type: "string", nullable: true },
|
|
16893
|
+
include_template_id: { type: "string", nullable: true },
|
|
16894
|
+
depends_on_positions: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
16895
|
+
metadata: { type: "object", additionalProperties: true },
|
|
16896
|
+
created_at: { type: "string", format: "date-time" }
|
|
16897
|
+
}
|
|
16898
|
+
};
|
|
16899
|
+
templateSchema = {
|
|
16900
|
+
type: "object",
|
|
16901
|
+
required: ["id", "name", "title_pattern", "priority", "tags", "variables", "version", "metadata", "created_at"],
|
|
16902
|
+
properties: {
|
|
16903
|
+
id: { type: "string" },
|
|
16904
|
+
name: { type: "string" },
|
|
16905
|
+
title_pattern: { type: "string" },
|
|
16906
|
+
description: { type: "string", nullable: true },
|
|
16907
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
16908
|
+
tags: { type: "array", items: { type: "string" } },
|
|
16909
|
+
variables: { type: "array", items: { type: "object", properties: { name: { type: "string" }, required: { type: "boolean" }, default: { type: "string" }, description: { type: "string" } } } },
|
|
16910
|
+
version: { type: "integer", minimum: 1 },
|
|
16911
|
+
project_id: { type: "string", nullable: true },
|
|
16912
|
+
plan_id: { type: "string", nullable: true },
|
|
16913
|
+
metadata: { type: "object", additionalProperties: true },
|
|
16914
|
+
created_at: { type: "string", format: "date-time" },
|
|
16915
|
+
tasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } }
|
|
16916
|
+
}
|
|
16917
|
+
};
|
|
16918
|
+
templateVariableSchema = {
|
|
16919
|
+
type: "object",
|
|
16920
|
+
required: ["name", "required"],
|
|
16921
|
+
properties: {
|
|
16922
|
+
name: { type: "string" },
|
|
16923
|
+
required: { type: "boolean" },
|
|
16924
|
+
default: { type: "string" },
|
|
16925
|
+
description: { type: "string" }
|
|
16926
|
+
}
|
|
16927
|
+
};
|
|
16928
|
+
createTemplateTaskInputSchema = {
|
|
16929
|
+
type: "object",
|
|
16930
|
+
additionalProperties: false,
|
|
16931
|
+
required: ["title_pattern"],
|
|
16932
|
+
properties: {
|
|
16933
|
+
position: { type: "integer", minimum: 0 },
|
|
16934
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
16935
|
+
description: { type: "string", nullable: true },
|
|
16936
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
16937
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
16938
|
+
task_type: { type: "string", nullable: true },
|
|
16939
|
+
condition: { type: "string", nullable: true },
|
|
16940
|
+
include_template_id: { type: "string", nullable: true },
|
|
16941
|
+
depends_on: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
16942
|
+
depends_on_positions: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
16943
|
+
metadata: { type: "object", additionalProperties: true }
|
|
16944
|
+
}
|
|
16945
|
+
};
|
|
16695
16946
|
});
|
|
16696
16947
|
|
|
16697
16948
|
// src/server/v1.ts
|
|
@@ -16831,6 +17082,122 @@ function validatePlanCreate(value) {
|
|
|
16831
17082
|
}
|
|
16832
17083
|
};
|
|
16833
17084
|
}
|
|
17085
|
+
function validateTemplateTask(value) {
|
|
17086
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17087
|
+
return null;
|
|
17088
|
+
const body = value;
|
|
17089
|
+
const allowed = new Set(["position", "title_pattern", "description", "priority", "tags", "task_type", "condition", "include_template_id", "depends_on", "depends_on_positions", "metadata"]);
|
|
17090
|
+
if (Object.keys(body).some((key) => !allowed.has(key)))
|
|
17091
|
+
return null;
|
|
17092
|
+
if (typeof body.title_pattern !== "string" || !body.title_pattern.trim())
|
|
17093
|
+
return null;
|
|
17094
|
+
if (body.position !== undefined && (typeof body.position !== "number" || !Number.isSafeInteger(body.position) || body.position < 0))
|
|
17095
|
+
return null;
|
|
17096
|
+
if (body.description !== undefined && body.description !== null && typeof body.description !== "string")
|
|
17097
|
+
return null;
|
|
17098
|
+
if (body.priority !== undefined && (typeof body.priority !== "string" || !["low", "medium", "high", "critical"].includes(body.priority)))
|
|
17099
|
+
return null;
|
|
17100
|
+
if (body.tags !== undefined && (!Array.isArray(body.tags) || body.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
17101
|
+
return null;
|
|
17102
|
+
for (const field of ["task_type", "condition", "include_template_id"]) {
|
|
17103
|
+
if (body[field] !== undefined && body[field] !== null && (typeof body[field] !== "string" || !body[field].trim()))
|
|
17104
|
+
return null;
|
|
17105
|
+
}
|
|
17106
|
+
if (body.depends_on !== undefined && body.depends_on_positions !== undefined)
|
|
17107
|
+
return null;
|
|
17108
|
+
const dependencies = body.depends_on ?? body.depends_on_positions;
|
|
17109
|
+
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.some((position) => !Number.isSafeInteger(position) || position < 0)))
|
|
17110
|
+
return null;
|
|
17111
|
+
if (body.metadata !== undefined && (!body.metadata || typeof body.metadata !== "object" || Array.isArray(body.metadata)))
|
|
17112
|
+
return null;
|
|
17113
|
+
return {
|
|
17114
|
+
title_pattern: body.title_pattern,
|
|
17115
|
+
...typeof body.description === "string" ? { description: body.description } : {},
|
|
17116
|
+
...typeof body.priority === "string" ? { priority: body.priority } : {},
|
|
17117
|
+
...Array.isArray(body.tags) ? { tags: body.tags } : {},
|
|
17118
|
+
...typeof body.task_type === "string" ? { task_type: body.task_type } : {},
|
|
17119
|
+
...typeof body.condition === "string" ? { condition: body.condition } : {},
|
|
17120
|
+
...typeof body.include_template_id === "string" ? { include_template_id: body.include_template_id } : {},
|
|
17121
|
+
...Array.isArray(dependencies) ? { depends_on: dependencies } : {},
|
|
17122
|
+
...body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? { metadata: body.metadata } : {}
|
|
17123
|
+
};
|
|
17124
|
+
}
|
|
17125
|
+
function validateTemplateCreate(value) {
|
|
17126
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17127
|
+
return { ok: false, message: "template body must be an object" };
|
|
17128
|
+
const body = value;
|
|
17129
|
+
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata", "tasks"]);
|
|
17130
|
+
const unknown = Object.keys(body).find((key) => !allowed.has(key));
|
|
17131
|
+
if (unknown)
|
|
17132
|
+
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
17133
|
+
if (typeof body.name !== "string" || !body.name.trim())
|
|
17134
|
+
return { ok: false, message: "name must be a non-empty string" };
|
|
17135
|
+
if (typeof body.title_pattern !== "string" || !body.title_pattern.trim())
|
|
17136
|
+
return { ok: false, message: "title_pattern must be a non-empty string" };
|
|
17137
|
+
if (body.description !== undefined && body.description !== null && typeof body.description !== "string")
|
|
17138
|
+
return { ok: false, message: "description must be a string or null" };
|
|
17139
|
+
if (body.priority !== undefined && (typeof body.priority !== "string" || !["low", "medium", "high", "critical"].includes(body.priority)))
|
|
17140
|
+
return { ok: false, message: "priority must be low, medium, high, or critical" };
|
|
17141
|
+
if (body.tags !== undefined && (!Array.isArray(body.tags) || body.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
17142
|
+
return { ok: false, message: "tags must be an array of non-empty strings" };
|
|
17143
|
+
if (body.variables !== undefined && (!Array.isArray(body.variables) || body.variables.some((variable) => !variable || typeof variable !== "object" || Array.isArray(variable) || typeof variable.name !== "string" || !variable.name || typeof variable.required !== "boolean" || variable.default !== undefined && typeof variable.default !== "string" || variable.description !== undefined && typeof variable.description !== "string"))) {
|
|
17144
|
+
return { ok: false, message: "variables must be valid template variable objects" };
|
|
17145
|
+
}
|
|
17146
|
+
for (const field of ["project_id", "plan_id"]) {
|
|
17147
|
+
if (body[field] !== undefined && body[field] !== null && (typeof body[field] !== "string" || !body[field].trim()))
|
|
17148
|
+
return { ok: false, message: `${field} must be a non-empty string or null` };
|
|
17149
|
+
}
|
|
17150
|
+
if (body.metadata !== undefined && (!body.metadata || typeof body.metadata !== "object" || Array.isArray(body.metadata)))
|
|
17151
|
+
return { ok: false, message: "metadata must be an object" };
|
|
17152
|
+
const tasks = body.tasks === undefined ? [] : Array.isArray(body.tasks) ? body.tasks.map(validateTemplateTask) : null;
|
|
17153
|
+
if (tasks === null || tasks.some((task) => task === null))
|
|
17154
|
+
return { ok: false, message: "tasks must be valid template task objects" };
|
|
17155
|
+
const taskInputs = tasks;
|
|
17156
|
+
for (const [position, task] of taskInputs.entries()) {
|
|
17157
|
+
if ((task.depends_on ?? []).some((dependency) => dependency >= position)) {
|
|
17158
|
+
return { ok: false, message: "template task dependencies must reference earlier task positions" };
|
|
17159
|
+
}
|
|
17160
|
+
}
|
|
17161
|
+
return {
|
|
17162
|
+
ok: true,
|
|
17163
|
+
input: {
|
|
17164
|
+
name: body.name,
|
|
17165
|
+
title_pattern: body.title_pattern,
|
|
17166
|
+
...typeof body.description === "string" ? { description: body.description } : {},
|
|
17167
|
+
...typeof body.priority === "string" ? { priority: body.priority } : {},
|
|
17168
|
+
...Array.isArray(body.tags) ? { tags: body.tags } : {},
|
|
17169
|
+
...Array.isArray(body.variables) ? { variables: body.variables } : {},
|
|
17170
|
+
...typeof body.project_id === "string" ? { project_id: body.project_id } : {},
|
|
17171
|
+
...typeof body.plan_id === "string" ? { plan_id: body.plan_id } : {},
|
|
17172
|
+
...body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? { metadata: body.metadata } : {},
|
|
17173
|
+
tasks: taskInputs
|
|
17174
|
+
}
|
|
17175
|
+
};
|
|
17176
|
+
}
|
|
17177
|
+
function validateTemplatePatch(value) {
|
|
17178
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17179
|
+
return { ok: false, message: "template patch must be an object" };
|
|
17180
|
+
const body = value;
|
|
17181
|
+
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata"]);
|
|
17182
|
+
const unknown = Object.keys(body).find((key) => !allowed.has(key));
|
|
17183
|
+
if (unknown)
|
|
17184
|
+
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
17185
|
+
if (Object.keys(body).length === 0)
|
|
17186
|
+
return { ok: false, message: "template patch must not be empty" };
|
|
17187
|
+
const templateLike = { name: body.name ?? "template", title_pattern: body.title_pattern ?? "template", ...body };
|
|
17188
|
+
const validated = validateTemplateCreate(templateLike);
|
|
17189
|
+
if (!validated.ok)
|
|
17190
|
+
return validated;
|
|
17191
|
+
const { name: _name, title_pattern: _title, tasks: _tasks, ...patch } = validated.input;
|
|
17192
|
+
return { ok: true, patch: {
|
|
17193
|
+
...body.name !== undefined ? { name: validated.input.name } : {},
|
|
17194
|
+
...body.title_pattern !== undefined ? { title_pattern: validated.input.title_pattern } : {},
|
|
17195
|
+
...patch,
|
|
17196
|
+
...body.description === null ? { description: null } : {},
|
|
17197
|
+
...body.project_id === null ? { project_id: null } : {},
|
|
17198
|
+
...body.plan_id === null ? { plan_id: null } : {}
|
|
17199
|
+
} };
|
|
17200
|
+
}
|
|
16834
17201
|
async function readJson(req) {
|
|
16835
17202
|
try {
|
|
16836
17203
|
const text = await req.text();
|
|
@@ -16891,12 +17258,13 @@ function normalizeImportSnapshot(raw) {
|
|
|
16891
17258
|
agents: arr(body["agents"]),
|
|
16892
17259
|
taskLists: arr(body["taskLists"]),
|
|
16893
17260
|
templates: arr(body["templates"]),
|
|
17261
|
+
templateTasks: arr(body["templateTasks"]),
|
|
16894
17262
|
auditHistory: arr(body["auditHistory"]),
|
|
16895
17263
|
tombstones: arr(body["tombstones"])
|
|
16896
17264
|
};
|
|
16897
17265
|
}
|
|
16898
17266
|
function countSnapshotRecords(s) {
|
|
16899
|
-
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
17267
|
+
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.templateTasks.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
16900
17268
|
}
|
|
16901
17269
|
async function handleV1Request(req, url, dependencies = {}) {
|
|
16902
17270
|
const path = url.pathname;
|
|
@@ -17466,6 +17834,40 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
17466
17834
|
if (id)
|
|
17467
17835
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
17468
17836
|
}
|
|
17837
|
+
if (resource === "templates") {
|
|
17838
|
+
if (!id && method === "GET") {
|
|
17839
|
+
const projectId = url.searchParams.get("project_id");
|
|
17840
|
+
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
17841
|
+
return json2({ templates, count: templates.length });
|
|
17842
|
+
}
|
|
17843
|
+
if (!id && method === "POST") {
|
|
17844
|
+
const body = await readJson(req);
|
|
17845
|
+
const validated = validateTemplateCreate(body);
|
|
17846
|
+
if (!validated.ok)
|
|
17847
|
+
return error(400, validated.message);
|
|
17848
|
+
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
17849
|
+
return json2({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
17850
|
+
}
|
|
17851
|
+
if (!id)
|
|
17852
|
+
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
17853
|
+
if (method === "GET") {
|
|
17854
|
+
const template = await store.templates.getWithTasks(id);
|
|
17855
|
+
return template ? json2({ template }) : error(404, "template not found");
|
|
17856
|
+
}
|
|
17857
|
+
if (method === "PATCH" || method === "PUT") {
|
|
17858
|
+
const body = await readJson(req);
|
|
17859
|
+
const validated = validateTemplatePatch(body);
|
|
17860
|
+
if (!validated.ok)
|
|
17861
|
+
return error(400, validated.message);
|
|
17862
|
+
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
17863
|
+
return template ? json2({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
17864
|
+
}
|
|
17865
|
+
if (method === "DELETE") {
|
|
17866
|
+
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
17867
|
+
return deleted ? json2({ deleted: true, id }) : error(404, "template not found");
|
|
17868
|
+
}
|
|
17869
|
+
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
17870
|
+
}
|
|
17469
17871
|
if (resource === "agents") {
|
|
17470
17872
|
if (!id && method === "GET") {
|
|
17471
17873
|
const agents = await store.agents.list();
|
|
@@ -85086,9 +85488,9 @@ ${task2.id.slice(0, 8)} | ${task2.priority} | ${task2.title}` }] };
|
|
|
85086
85488
|
if (shouldRegisterTool("delete_template")) {
|
|
85087
85489
|
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
85088
85490
|
try {
|
|
85089
|
-
const { deleteTemplate:
|
|
85491
|
+
const { deleteTemplate: deleteTemplate3 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
85090
85492
|
const resolvedId = resolveId(id, "task_templates");
|
|
85091
|
-
const deleted =
|
|
85493
|
+
const deleted = deleteTemplate3(resolvedId);
|
|
85092
85494
|
return { content: [{ type: "text", text: deleted ? "Template deleted." : "Template not found." }] };
|
|
85093
85495
|
} catch (e) {
|
|
85094
85496
|
return { content: [{ type: "text", text: formatError2(e) }], isError: true };
|