@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/mcp/index.js
CHANGED
|
@@ -11043,6 +11043,50 @@ var init_recurrence = __esm(() => {
|
|
|
11043
11043
|
};
|
|
11044
11044
|
});
|
|
11045
11045
|
|
|
11046
|
+
// src/lib/template-semantics.ts
|
|
11047
|
+
function resolveTemplateVariables(templateVars, provided) {
|
|
11048
|
+
const merged = { ...provided };
|
|
11049
|
+
for (const variable of templateVars) {
|
|
11050
|
+
if (merged[variable.name] === undefined && variable.default !== undefined) {
|
|
11051
|
+
merged[variable.name] = variable.default;
|
|
11052
|
+
}
|
|
11053
|
+
}
|
|
11054
|
+
const missing = templateVars.filter((variable) => variable.required && merged[variable.name] === undefined).map((variable) => variable.name);
|
|
11055
|
+
if (missing.length > 0) {
|
|
11056
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
11057
|
+
}
|
|
11058
|
+
return merged;
|
|
11059
|
+
}
|
|
11060
|
+
function substituteTemplateVariables(text, variables) {
|
|
11061
|
+
let result = text;
|
|
11062
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
11063
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
11064
|
+
}
|
|
11065
|
+
return result;
|
|
11066
|
+
}
|
|
11067
|
+
function evaluateTemplateCondition(condition, variables) {
|
|
11068
|
+
if (!condition || condition.trim() === "")
|
|
11069
|
+
return true;
|
|
11070
|
+
const trimmed = condition.trim();
|
|
11071
|
+
const equal = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
11072
|
+
if (equal)
|
|
11073
|
+
return (variables[equal[1]] ?? "") === equal[2].trim();
|
|
11074
|
+
const unequal = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
11075
|
+
if (unequal)
|
|
11076
|
+
return (variables[unequal[1]] ?? "") !== unequal[2].trim();
|
|
11077
|
+
const falsy = trimmed.match(/^!\{([^}]+)\}$/);
|
|
11078
|
+
if (falsy) {
|
|
11079
|
+
const value = variables[falsy[1]];
|
|
11080
|
+
return !value || value === "" || value === "false";
|
|
11081
|
+
}
|
|
11082
|
+
const truthy = trimmed.match(/^\{([^}]+)\}$/);
|
|
11083
|
+
if (truthy) {
|
|
11084
|
+
const value = variables[truthy[1]];
|
|
11085
|
+
return !!value && value !== "" && value !== "false";
|
|
11086
|
+
}
|
|
11087
|
+
return true;
|
|
11088
|
+
}
|
|
11089
|
+
|
|
11046
11090
|
// src/db/templates.ts
|
|
11047
11091
|
var exports_templates = {};
|
|
11048
11092
|
__export(exports_templates, {
|
|
@@ -11138,6 +11182,14 @@ function deleteTemplate(id, db) {
|
|
|
11138
11182
|
payload: template,
|
|
11139
11183
|
version: template.version
|
|
11140
11184
|
}, d);
|
|
11185
|
+
for (const task of getTemplateTasks(resolved, d)) {
|
|
11186
|
+
recordStorageTombstone({
|
|
11187
|
+
object_type: "template_tasks",
|
|
11188
|
+
object_id: task.id,
|
|
11189
|
+
payload: task,
|
|
11190
|
+
version: 1
|
|
11191
|
+
}, d);
|
|
11192
|
+
}
|
|
11141
11193
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
11142
11194
|
}
|
|
11143
11195
|
function updateTemplate(id, updates, db) {
|
|
@@ -11269,34 +11321,7 @@ function getTemplateTasks(templateId, db) {
|
|
|
11269
11321
|
return rows.map(rowToTemplateTask);
|
|
11270
11322
|
}
|
|
11271
11323
|
function evaluateCondition(condition, variables) {
|
|
11272
|
-
|
|
11273
|
-
return true;
|
|
11274
|
-
const trimmed = condition.trim();
|
|
11275
|
-
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
11276
|
-
if (eqMatch) {
|
|
11277
|
-
const varName = eqMatch[1];
|
|
11278
|
-
const expected = eqMatch[2].trim();
|
|
11279
|
-
return (variables[varName] ?? "") === expected;
|
|
11280
|
-
}
|
|
11281
|
-
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
11282
|
-
if (neqMatch) {
|
|
11283
|
-
const varName = neqMatch[1];
|
|
11284
|
-
const expected = neqMatch[2].trim();
|
|
11285
|
-
return (variables[varName] ?? "") !== expected;
|
|
11286
|
-
}
|
|
11287
|
-
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
11288
|
-
if (falsyMatch) {
|
|
11289
|
-
const varName = falsyMatch[1];
|
|
11290
|
-
const val = variables[varName];
|
|
11291
|
-
return !val || val === "" || val === "false";
|
|
11292
|
-
}
|
|
11293
|
-
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
11294
|
-
if (truthyMatch) {
|
|
11295
|
-
const varName = truthyMatch[1];
|
|
11296
|
-
const val = variables[varName];
|
|
11297
|
-
return !!val && val !== "" && val !== "false";
|
|
11298
|
-
}
|
|
11299
|
-
return true;
|
|
11324
|
+
return evaluateTemplateCondition(condition, variables);
|
|
11300
11325
|
}
|
|
11301
11326
|
function exportTemplate(id, db) {
|
|
11302
11327
|
const d = db || getDatabase();
|
|
@@ -11369,29 +11394,10 @@ function listTemplateVersions(id, db) {
|
|
|
11369
11394
|
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
11370
11395
|
}
|
|
11371
11396
|
function resolveVariables(templateVars, provided) {
|
|
11372
|
-
|
|
11373
|
-
for (const v of templateVars) {
|
|
11374
|
-
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
11375
|
-
merged[v.name] = v.default;
|
|
11376
|
-
}
|
|
11377
|
-
}
|
|
11378
|
-
const missing = [];
|
|
11379
|
-
for (const v of templateVars) {
|
|
11380
|
-
if (v.required && merged[v.name] === undefined) {
|
|
11381
|
-
missing.push(v.name);
|
|
11382
|
-
}
|
|
11383
|
-
}
|
|
11384
|
-
if (missing.length > 0) {
|
|
11385
|
-
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
11386
|
-
}
|
|
11387
|
-
return merged;
|
|
11397
|
+
return resolveTemplateVariables(templateVars, provided);
|
|
11388
11398
|
}
|
|
11389
11399
|
function substituteVars(text, variables) {
|
|
11390
|
-
|
|
11391
|
-
for (const [key, val] of Object.entries(variables)) {
|
|
11392
|
-
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
11393
|
-
}
|
|
11394
|
-
return result;
|
|
11400
|
+
return substituteTemplateVariables(text, variables);
|
|
11395
11401
|
}
|
|
11396
11402
|
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
11397
11403
|
const d = db || getDatabase();
|
|
@@ -30787,7 +30793,7 @@ var package_default;
|
|
|
30787
30793
|
var init_package = __esm(() => {
|
|
30788
30794
|
package_default = {
|
|
30789
30795
|
name: "@hasna/todos",
|
|
30790
|
-
version: "0.11.
|
|
30796
|
+
version: "0.11.95",
|
|
30791
30797
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
30792
30798
|
type: "module",
|
|
30793
30799
|
main: "dist/index.js",
|
|
@@ -40131,6 +40137,7 @@ function exportSqliteTodosStorageSnapshot(db) {
|
|
|
40131
40137
|
agents: listAgents({ include_archived: true }, d),
|
|
40132
40138
|
taskLists: listTaskLists(undefined, d),
|
|
40133
40139
|
templates: listTemplates(d),
|
|
40140
|
+
templateTasks: listTemplates(d).flatMap((template) => getTemplateTasks(template.id, d)),
|
|
40134
40141
|
auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
|
|
40135
40142
|
tombstones: listStorageTombstones(d)
|
|
40136
40143
|
};
|
|
@@ -40180,6 +40187,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
40180
40187
|
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
40181
40188
|
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
40182
40189
|
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
40190
|
+
applyRows("template_tasks", "template_tasks", TEMPLATE_TASK_COLUMNS, snapshot.templateTasks ?? []);
|
|
40183
40191
|
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
|
|
40184
40192
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
40185
40193
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
@@ -40284,6 +40292,8 @@ function tableForTombstone(objectType2) {
|
|
|
40284
40292
|
return "task_lists";
|
|
40285
40293
|
if (objectType2 === "templates")
|
|
40286
40294
|
return "task_templates";
|
|
40295
|
+
if (objectType2 === "template_tasks")
|
|
40296
|
+
return "template_tasks";
|
|
40287
40297
|
return "task_history";
|
|
40288
40298
|
}
|
|
40289
40299
|
function listRows(db, table, columns) {
|
|
@@ -40320,7 +40330,7 @@ function clockColumnsForTable(table) {
|
|
|
40320
40330
|
return ["created_at"];
|
|
40321
40331
|
return ["updated_at", "created_at"];
|
|
40322
40332
|
}
|
|
40323
|
-
var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
|
|
40333
|
+
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;
|
|
40324
40334
|
var init_sqlite_snapshot = __esm(() => {
|
|
40325
40335
|
init_database();
|
|
40326
40336
|
init_agents();
|
|
@@ -40414,6 +40424,21 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
40414
40424
|
"machine_id",
|
|
40415
40425
|
"synced_at"
|
|
40416
40426
|
];
|
|
40427
|
+
TEMPLATE_TASK_COLUMNS = [
|
|
40428
|
+
"id",
|
|
40429
|
+
"template_id",
|
|
40430
|
+
"position",
|
|
40431
|
+
"title_pattern",
|
|
40432
|
+
"description",
|
|
40433
|
+
"priority",
|
|
40434
|
+
"tags",
|
|
40435
|
+
"task_type",
|
|
40436
|
+
"condition",
|
|
40437
|
+
"include_template_id",
|
|
40438
|
+
"depends_on_positions",
|
|
40439
|
+
"metadata",
|
|
40440
|
+
"created_at"
|
|
40441
|
+
];
|
|
40417
40442
|
TASK_COLUMNS = [
|
|
40418
40443
|
"id",
|
|
40419
40444
|
"short_id",
|
|
@@ -40482,7 +40507,7 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
40482
40507
|
"created_at",
|
|
40483
40508
|
"machine_id"
|
|
40484
40509
|
];
|
|
40485
|
-
JSON_COLUMNS = new Set(["tags", "metadata", "permissions", "capabilities", "variables"]);
|
|
40510
|
+
JSON_COLUMNS = new Set(["tags", "metadata", "permissions", "capabilities", "variables", "depends_on_positions"]);
|
|
40486
40511
|
BOOLEAN_COLUMNS = new Set(["requires_approval"]);
|
|
40487
40512
|
});
|
|
40488
40513
|
|
|
@@ -40872,6 +40897,7 @@ function snapshotEntries(snapshot) {
|
|
|
40872
40897
|
...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
|
|
40873
40898
|
...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
|
|
40874
40899
|
...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
|
|
40900
|
+
...(snapshot.templateTasks ?? []).map((payload) => entry("template_tasks", payload, snapshot.exportedAt)),
|
|
40875
40901
|
...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
|
|
40876
40902
|
...(snapshot.tombstones ?? []).map((tombstone) => ({
|
|
40877
40903
|
type: tombstone.object_type,
|
|
@@ -40923,6 +40949,7 @@ function rowsToSnapshot(rows) {
|
|
|
40923
40949
|
agents: [],
|
|
40924
40950
|
taskLists: [],
|
|
40925
40951
|
templates: [],
|
|
40952
|
+
templateTasks: [],
|
|
40926
40953
|
auditHistory: [],
|
|
40927
40954
|
tombstones: []
|
|
40928
40955
|
};
|
|
@@ -40957,6 +40984,8 @@ function rowsToSnapshot(rows) {
|
|
|
40957
40984
|
snapshot.taskLists.push(payload);
|
|
40958
40985
|
else if (row.object_type === "templates")
|
|
40959
40986
|
snapshot.templates.push(payload);
|
|
40987
|
+
else if (row.object_type === "template_tasks")
|
|
40988
|
+
snapshot.templateTasks.push(payload);
|
|
40960
40989
|
else if (row.object_type === "audit_history")
|
|
40961
40990
|
snapshot.auditHistory.push(payload);
|
|
40962
40991
|
}
|
|
@@ -41174,6 +41203,9 @@ class TodosShadowOutbox {
|
|
|
41174
41203
|
case "templates":
|
|
41175
41204
|
snapshot.templates.push(record);
|
|
41176
41205
|
break;
|
|
41206
|
+
case "template_tasks":
|
|
41207
|
+
snapshot.templateTasks.push(record);
|
|
41208
|
+
break;
|
|
41177
41209
|
case "audit_history":
|
|
41178
41210
|
snapshot.auditHistory.push(record);
|
|
41179
41211
|
break;
|
|
@@ -41240,6 +41272,7 @@ function emptySnapshot() {
|
|
|
41240
41272
|
agents: [],
|
|
41241
41273
|
taskLists: [],
|
|
41242
41274
|
templates: [],
|
|
41275
|
+
templateTasks: [],
|
|
41243
41276
|
auditHistory: [],
|
|
41244
41277
|
tombstones: []
|
|
41245
41278
|
};
|
|
@@ -42439,10 +42472,13 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
42439
42472
|
get: (id) => store.get("templates", id),
|
|
42440
42473
|
list: async () => (await store.list("templates")).sort((a, b) => a.name.localeCompare(b.name)),
|
|
42441
42474
|
update: (id, input) => updateTemplate2(id, input, store),
|
|
42442
|
-
delete: (id, context) =>
|
|
42475
|
+
delete: (id, context) => deleteTemplate2(id, store, context),
|
|
42443
42476
|
getWithTasks: async (id) => {
|
|
42444
42477
|
const template = await store.get("templates", id);
|
|
42445
|
-
|
|
42478
|
+
if (!template)
|
|
42479
|
+
return null;
|
|
42480
|
+
const tasks = (await store.list("template_tasks")).filter((task2) => task2.template_id === id).sort((left, right) => left.position - right.position || left.id.localeCompare(right.id));
|
|
42481
|
+
return { ...template, tasks };
|
|
42446
42482
|
}
|
|
42447
42483
|
},
|
|
42448
42484
|
audit: {
|
|
@@ -42730,6 +42766,57 @@ class PostgresJsonRecordStore {
|
|
|
42730
42766
|
}
|
|
42731
42767
|
return value;
|
|
42732
42768
|
}
|
|
42769
|
+
async createTemplateWithTasks(template, tasks, context = {}) {
|
|
42770
|
+
await this.ensureSchema();
|
|
42771
|
+
const records = [
|
|
42772
|
+
{ object_type: "templates", object_id: template.id, payload: template, updated_at: template.created_at, version: template.version },
|
|
42773
|
+
...tasks.map((task2) => ({ object_type: "template_tasks", object_id: task2.id, payload: task2, updated_at: task2.created_at, version: 1 }))
|
|
42774
|
+
];
|
|
42775
|
+
const result = await this.options.client.query(`/* todos:create-template-with-tasks-atomic */ WITH input AS (
|
|
42776
|
+
SELECT value->>'object_type' AS object_type,
|
|
42777
|
+
value->>'object_id' AS object_id,
|
|
42778
|
+
value->'payload' AS payload,
|
|
42779
|
+
value->>'updated_at' AS updated_at,
|
|
42780
|
+
COALESCE((value->>'version')::integer, 1) AS version
|
|
42781
|
+
FROM jsonb_array_elements($2::jsonb) AS value
|
|
42782
|
+
) INSERT INTO ${this.tableName} (
|
|
42783
|
+
service, object_type, object_id, payload, updated_at,
|
|
42784
|
+
deleted_at, source_machine_id, version
|
|
42785
|
+
) SELECT $1, object_type, object_id, payload, updated_at::timestamptz,
|
|
42786
|
+
NULL, $3, version
|
|
42787
|
+
FROM input
|
|
42788
|
+
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
42789
|
+
payload = EXCLUDED.payload,
|
|
42790
|
+
updated_at = EXCLUDED.updated_at,
|
|
42791
|
+
deleted_at = NULL,
|
|
42792
|
+
source_machine_id = EXCLUDED.source_machine_id,
|
|
42793
|
+
version = EXCLUDED.version
|
|
42794
|
+
WHERE ${this.tableName}.updated_at IS NULL
|
|
42795
|
+
OR ${this.tableName}.updated_at < EXCLUDED.updated_at
|
|
42796
|
+
OR (${this.tableName}.updated_at = EXCLUDED.updated_at
|
|
42797
|
+
AND COALESCE(${this.tableName}.version, 0) <= COALESCE(EXCLUDED.version, 0))
|
|
42798
|
+
RETURNING object_type, object_id`, [this.service, jsonbParam(records), this.machineId(context)]);
|
|
42799
|
+
if (result.rows.length !== records.length) {
|
|
42800
|
+
throw new Error("Template checklist write was rejected before completion; no partial template was committed");
|
|
42801
|
+
}
|
|
42802
|
+
}
|
|
42803
|
+
async deleteTemplateWithTasks(id, context = {}) {
|
|
42804
|
+
await this.ensureSchema();
|
|
42805
|
+
const timestamp3 = new Date().toISOString();
|
|
42806
|
+
const result = await this.options.client.query(`/* todos:delete-template-with-tasks-atomic */ WITH target AS (
|
|
42807
|
+
SELECT 1 FROM ${this.tableName}
|
|
42808
|
+
WHERE service = $1 AND object_type = 'templates' AND object_id = $2 AND deleted_at IS NULL
|
|
42809
|
+
) UPDATE ${this.tableName} AS record SET
|
|
42810
|
+
deleted_at = $3::timestamptz,
|
|
42811
|
+
updated_at = $3::timestamptz,
|
|
42812
|
+
source_machine_id = COALESCE($4, record.source_machine_id),
|
|
42813
|
+
version = COALESCE(record.version, 0) + 1
|
|
42814
|
+
WHERE record.service = $1 AND record.deleted_at IS NULL AND EXISTS (SELECT 1 FROM target)
|
|
42815
|
+
AND (record.object_type = 'templates' AND record.object_id = $2
|
|
42816
|
+
OR record.object_type = 'template_tasks' AND record.payload->>'template_id' = $2)
|
|
42817
|
+
RETURNING record.object_type`, [this.service, id, timestamp3, this.machineId(context)]);
|
|
42818
|
+
return result.rows.some((row) => row.object_type === "templates");
|
|
42819
|
+
}
|
|
42733
42820
|
async completeTask(id, agentId, options) {
|
|
42734
42821
|
await this.ensureSchema();
|
|
42735
42822
|
const operationTimestamp = new Date().toISOString();
|
|
@@ -43492,7 +43579,7 @@ async function updateTaskList2(id, input, store) {
|
|
|
43492
43579
|
}
|
|
43493
43580
|
async function createTemplate2(input, store, context) {
|
|
43494
43581
|
const timestamp3 = new Date().toISOString();
|
|
43495
|
-
|
|
43582
|
+
const template = {
|
|
43496
43583
|
id: randomUUID3(),
|
|
43497
43584
|
name: input.name,
|
|
43498
43585
|
title_pattern: input.title_pattern,
|
|
@@ -43507,7 +43594,30 @@ async function createTemplate2(input, store, context) {
|
|
|
43507
43594
|
created_at: timestamp3,
|
|
43508
43595
|
machine_id: store.machineId(context),
|
|
43509
43596
|
synced_at: null
|
|
43510
|
-
}
|
|
43597
|
+
};
|
|
43598
|
+
const tasks = buildTemplateTasks(template.id, input.tasks ?? [], timestamp3);
|
|
43599
|
+
await store.createTemplateWithTasks(template, tasks, context);
|
|
43600
|
+
return template;
|
|
43601
|
+
}
|
|
43602
|
+
function buildTemplateTasks(templateId, inputs, timestamp3) {
|
|
43603
|
+
return inputs.map((input, position) => ({
|
|
43604
|
+
id: randomUUID3(),
|
|
43605
|
+
template_id: templateId,
|
|
43606
|
+
position,
|
|
43607
|
+
title_pattern: input.title_pattern,
|
|
43608
|
+
description: input.description ?? null,
|
|
43609
|
+
priority: input.priority ?? "medium",
|
|
43610
|
+
tags: input.tags ?? [],
|
|
43611
|
+
task_type: input.task_type ?? null,
|
|
43612
|
+
condition: input.condition ?? null,
|
|
43613
|
+
include_template_id: input.include_template_id ?? null,
|
|
43614
|
+
depends_on_positions: input.depends_on ?? [],
|
|
43615
|
+
metadata: input.metadata ?? {},
|
|
43616
|
+
created_at: timestamp3
|
|
43617
|
+
}));
|
|
43618
|
+
}
|
|
43619
|
+
async function deleteTemplate2(id, store, context) {
|
|
43620
|
+
return store.deleteTemplateWithTasks(id, context);
|
|
43511
43621
|
}
|
|
43512
43622
|
async function updateTemplate2(id, input, store) {
|
|
43513
43623
|
const template = await store.get("templates", id);
|
|
@@ -43563,6 +43673,7 @@ async function exportSnapshot(store) {
|
|
|
43563
43673
|
agents: await store.list("agents"),
|
|
43564
43674
|
taskLists: await store.list("task_lists"),
|
|
43565
43675
|
templates: await store.list("templates"),
|
|
43676
|
+
templateTasks: await store.list("template_tasks"),
|
|
43566
43677
|
auditHistory: await store.list("audit_history"),
|
|
43567
43678
|
tombstones: await store.listTombstones()
|
|
43568
43679
|
};
|
|
@@ -43587,6 +43698,7 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
43587
43698
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
43588
43699
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
43589
43700
|
...snapshot.templates.map((row) => ["templates", row]),
|
|
43701
|
+
...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row]),
|
|
43590
43702
|
...snapshot.auditHistory.map((row) => ["audit_history", row])
|
|
43591
43703
|
];
|
|
43592
43704
|
for (const [type, row] of entries) {
|
|
@@ -43993,12 +44105,16 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
43993
44105
|
TaskList: taskListSchema,
|
|
43994
44106
|
TaskComment: taskCommentSchema,
|
|
43995
44107
|
Plan: planSchema,
|
|
44108
|
+
Template: templateSchema,
|
|
44109
|
+
TemplateTask: templateTaskSchema,
|
|
44110
|
+
TemplateVariable: templateVariableSchema,
|
|
44111
|
+
CreateTemplateTaskInput: createTemplateTaskInputSchema,
|
|
43996
44112
|
CreateTaskInput: {
|
|
43997
44113
|
type: "object",
|
|
43998
44114
|
required: ["title"],
|
|
43999
44115
|
properties: {
|
|
44000
44116
|
title: { type: "string" },
|
|
44001
|
-
description: { type: "string" },
|
|
44117
|
+
description: { type: "string", nullable: true },
|
|
44002
44118
|
status: { type: "string" },
|
|
44003
44119
|
priority: { type: "string" },
|
|
44004
44120
|
project_id: { type: "string" },
|
|
@@ -44133,6 +44249,39 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44133
44249
|
agent_id: { type: "string", minLength: 1 },
|
|
44134
44250
|
status: { type: "string", enum: ["active", "completed", "archived"] }
|
|
44135
44251
|
}
|
|
44252
|
+
},
|
|
44253
|
+
CreateTemplateInput: {
|
|
44254
|
+
type: "object",
|
|
44255
|
+
additionalProperties: false,
|
|
44256
|
+
required: ["name", "title_pattern"],
|
|
44257
|
+
properties: {
|
|
44258
|
+
name: { type: "string", minLength: 1 },
|
|
44259
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
44260
|
+
description: { type: "string", nullable: true },
|
|
44261
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44262
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
44263
|
+
variables: { type: "array", items: { $ref: "#/components/schemas/TemplateVariable" } },
|
|
44264
|
+
project_id: { type: "string", minLength: 1, nullable: true },
|
|
44265
|
+
plan_id: { type: "string", minLength: 1, nullable: true },
|
|
44266
|
+
metadata: { type: "object", additionalProperties: true },
|
|
44267
|
+
tasks: { type: "array", items: { $ref: "#/components/schemas/CreateTemplateTaskInput" } }
|
|
44268
|
+
}
|
|
44269
|
+
},
|
|
44270
|
+
UpdateTemplateInput: {
|
|
44271
|
+
type: "object",
|
|
44272
|
+
additionalProperties: false,
|
|
44273
|
+
minProperties: 1,
|
|
44274
|
+
properties: {
|
|
44275
|
+
name: { type: "string", minLength: 1 },
|
|
44276
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
44277
|
+
description: { type: "string", nullable: true },
|
|
44278
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44279
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
44280
|
+
variables: { type: "array", items: { type: "object" } },
|
|
44281
|
+
project_id: { type: "string", nullable: true },
|
|
44282
|
+
plan_id: { type: "string", nullable: true },
|
|
44283
|
+
metadata: { type: "object", additionalProperties: true }
|
|
44284
|
+
}
|
|
44136
44285
|
}
|
|
44137
44286
|
}
|
|
44138
44287
|
},
|
|
@@ -44435,6 +44584,41 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44435
44584
|
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
|
|
44436
44585
|
}
|
|
44437
44586
|
},
|
|
44587
|
+
"/v1/templates": {
|
|
44588
|
+
get: {
|
|
44589
|
+
operationId: "listTemplates",
|
|
44590
|
+
summary: "List reusable task templates",
|
|
44591
|
+
parameters: [{ name: "project_id", in: "query", schema: { type: "string" } }],
|
|
44592
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { templates: { type: "array", items: { $ref: "#/components/schemas/Template" } }, count: { type: "number" } } } } } } }
|
|
44593
|
+
},
|
|
44594
|
+
post: {
|
|
44595
|
+
operationId: "createTemplate",
|
|
44596
|
+
summary: "Create a reusable task template",
|
|
44597
|
+
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTemplateInput" } } } },
|
|
44598
|
+
responses: { "201": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
44599
|
+
}
|
|
44600
|
+
},
|
|
44601
|
+
"/v1/templates/{id}": {
|
|
44602
|
+
get: {
|
|
44603
|
+
operationId: "getTemplate",
|
|
44604
|
+
summary: "Get one reusable task template with its checklist steps",
|
|
44605
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
44606
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
44607
|
+
},
|
|
44608
|
+
patch: {
|
|
44609
|
+
operationId: "updateTemplate",
|
|
44610
|
+
summary: "Update reusable template metadata and defaults",
|
|
44611
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
44612
|
+
requestBody: { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateTemplateInput" } } } },
|
|
44613
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { template: { $ref: "#/components/schemas/Template" } } } } } } }
|
|
44614
|
+
},
|
|
44615
|
+
delete: {
|
|
44616
|
+
operationId: "deleteTemplate",
|
|
44617
|
+
summary: "Delete a reusable task template and its checklist steps",
|
|
44618
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
|
|
44619
|
+
responses: { "200": { content: { "application/json": { schema: { type: "object", properties: { deleted: { type: "boolean" }, id: { type: "string" } } } } } } }
|
|
44620
|
+
}
|
|
44621
|
+
},
|
|
44438
44622
|
"/v1/task-lists": {
|
|
44439
44623
|
get: {
|
|
44440
44624
|
operationId: "listTaskLists",
|
|
@@ -44512,6 +44696,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44512
44696
|
agents: { type: "array", items: { type: "object" } },
|
|
44513
44697
|
taskLists: { type: "array", items: { type: "object" } },
|
|
44514
44698
|
templates: { type: "array", items: { type: "object" } },
|
|
44699
|
+
templateTasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } },
|
|
44515
44700
|
auditHistory: { type: "array", items: { type: "object" } },
|
|
44516
44701
|
tombstones: { type: "array", items: { type: "object" } }
|
|
44517
44702
|
}
|
|
@@ -44548,7 +44733,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
44548
44733
|
}
|
|
44549
44734
|
};
|
|
44550
44735
|
}
|
|
44551
|
-
var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema;
|
|
44736
|
+
var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
44552
44737
|
var init_openapi = __esm(() => {
|
|
44553
44738
|
init_package_version();
|
|
44554
44739
|
taskSchema = {
|
|
@@ -44625,6 +44810,72 @@ var init_openapi = __esm(() => {
|
|
|
44625
44810
|
updated_at: { type: "string", format: "date-time" }
|
|
44626
44811
|
}
|
|
44627
44812
|
};
|
|
44813
|
+
templateTaskSchema = {
|
|
44814
|
+
type: "object",
|
|
44815
|
+
required: ["id", "template_id", "position", "title_pattern", "priority", "tags", "depends_on_positions", "metadata", "created_at"],
|
|
44816
|
+
properties: {
|
|
44817
|
+
id: { type: "string" },
|
|
44818
|
+
template_id: { type: "string" },
|
|
44819
|
+
position: { type: "integer", minimum: 0 },
|
|
44820
|
+
title_pattern: { type: "string" },
|
|
44821
|
+
description: { type: "string", nullable: true },
|
|
44822
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44823
|
+
tags: { type: "array", items: { type: "string" } },
|
|
44824
|
+
task_type: { type: "string", nullable: true },
|
|
44825
|
+
condition: { type: "string", nullable: true },
|
|
44826
|
+
include_template_id: { type: "string", nullable: true },
|
|
44827
|
+
depends_on_positions: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
44828
|
+
metadata: { type: "object", additionalProperties: true },
|
|
44829
|
+
created_at: { type: "string", format: "date-time" }
|
|
44830
|
+
}
|
|
44831
|
+
};
|
|
44832
|
+
templateSchema = {
|
|
44833
|
+
type: "object",
|
|
44834
|
+
required: ["id", "name", "title_pattern", "priority", "tags", "variables", "version", "metadata", "created_at"],
|
|
44835
|
+
properties: {
|
|
44836
|
+
id: { type: "string" },
|
|
44837
|
+
name: { type: "string" },
|
|
44838
|
+
title_pattern: { type: "string" },
|
|
44839
|
+
description: { type: "string", nullable: true },
|
|
44840
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44841
|
+
tags: { type: "array", items: { type: "string" } },
|
|
44842
|
+
variables: { type: "array", items: { type: "object", properties: { name: { type: "string" }, required: { type: "boolean" }, default: { type: "string" }, description: { type: "string" } } } },
|
|
44843
|
+
version: { type: "integer", minimum: 1 },
|
|
44844
|
+
project_id: { type: "string", nullable: true },
|
|
44845
|
+
plan_id: { type: "string", nullable: true },
|
|
44846
|
+
metadata: { type: "object", additionalProperties: true },
|
|
44847
|
+
created_at: { type: "string", format: "date-time" },
|
|
44848
|
+
tasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } }
|
|
44849
|
+
}
|
|
44850
|
+
};
|
|
44851
|
+
templateVariableSchema = {
|
|
44852
|
+
type: "object",
|
|
44853
|
+
required: ["name", "required"],
|
|
44854
|
+
properties: {
|
|
44855
|
+
name: { type: "string" },
|
|
44856
|
+
required: { type: "boolean" },
|
|
44857
|
+
default: { type: "string" },
|
|
44858
|
+
description: { type: "string" }
|
|
44859
|
+
}
|
|
44860
|
+
};
|
|
44861
|
+
createTemplateTaskInputSchema = {
|
|
44862
|
+
type: "object",
|
|
44863
|
+
additionalProperties: false,
|
|
44864
|
+
required: ["title_pattern"],
|
|
44865
|
+
properties: {
|
|
44866
|
+
position: { type: "integer", minimum: 0 },
|
|
44867
|
+
title_pattern: { type: "string", minLength: 1 },
|
|
44868
|
+
description: { type: "string", nullable: true },
|
|
44869
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
44870
|
+
tags: { type: "array", items: { type: "string", minLength: 1 } },
|
|
44871
|
+
task_type: { type: "string", nullable: true },
|
|
44872
|
+
condition: { type: "string", nullable: true },
|
|
44873
|
+
include_template_id: { type: "string", nullable: true },
|
|
44874
|
+
depends_on: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
44875
|
+
depends_on_positions: { type: "array", items: { type: "integer", minimum: 0 } },
|
|
44876
|
+
metadata: { type: "object", additionalProperties: true }
|
|
44877
|
+
}
|
|
44878
|
+
};
|
|
44628
44879
|
});
|
|
44629
44880
|
|
|
44630
44881
|
// src/server/v1.ts
|
|
@@ -44764,6 +45015,122 @@ function validatePlanCreate(value) {
|
|
|
44764
45015
|
}
|
|
44765
45016
|
};
|
|
44766
45017
|
}
|
|
45018
|
+
function validateTemplateTask(value) {
|
|
45019
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45020
|
+
return null;
|
|
45021
|
+
const body = value;
|
|
45022
|
+
const allowed = new Set(["position", "title_pattern", "description", "priority", "tags", "task_type", "condition", "include_template_id", "depends_on", "depends_on_positions", "metadata"]);
|
|
45023
|
+
if (Object.keys(body).some((key) => !allowed.has(key)))
|
|
45024
|
+
return null;
|
|
45025
|
+
if (typeof body.title_pattern !== "string" || !body.title_pattern.trim())
|
|
45026
|
+
return null;
|
|
45027
|
+
if (body.position !== undefined && (typeof body.position !== "number" || !Number.isSafeInteger(body.position) || body.position < 0))
|
|
45028
|
+
return null;
|
|
45029
|
+
if (body.description !== undefined && body.description !== null && typeof body.description !== "string")
|
|
45030
|
+
return null;
|
|
45031
|
+
if (body.priority !== undefined && (typeof body.priority !== "string" || !["low", "medium", "high", "critical"].includes(body.priority)))
|
|
45032
|
+
return null;
|
|
45033
|
+
if (body.tags !== undefined && (!Array.isArray(body.tags) || body.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
45034
|
+
return null;
|
|
45035
|
+
for (const field of ["task_type", "condition", "include_template_id"]) {
|
|
45036
|
+
if (body[field] !== undefined && body[field] !== null && (typeof body[field] !== "string" || !body[field].trim()))
|
|
45037
|
+
return null;
|
|
45038
|
+
}
|
|
45039
|
+
if (body.depends_on !== undefined && body.depends_on_positions !== undefined)
|
|
45040
|
+
return null;
|
|
45041
|
+
const dependencies = body.depends_on ?? body.depends_on_positions;
|
|
45042
|
+
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.some((position) => !Number.isSafeInteger(position) || position < 0)))
|
|
45043
|
+
return null;
|
|
45044
|
+
if (body.metadata !== undefined && (!body.metadata || typeof body.metadata !== "object" || Array.isArray(body.metadata)))
|
|
45045
|
+
return null;
|
|
45046
|
+
return {
|
|
45047
|
+
title_pattern: body.title_pattern,
|
|
45048
|
+
...typeof body.description === "string" ? { description: body.description } : {},
|
|
45049
|
+
...typeof body.priority === "string" ? { priority: body.priority } : {},
|
|
45050
|
+
...Array.isArray(body.tags) ? { tags: body.tags } : {},
|
|
45051
|
+
...typeof body.task_type === "string" ? { task_type: body.task_type } : {},
|
|
45052
|
+
...typeof body.condition === "string" ? { condition: body.condition } : {},
|
|
45053
|
+
...typeof body.include_template_id === "string" ? { include_template_id: body.include_template_id } : {},
|
|
45054
|
+
...Array.isArray(dependencies) ? { depends_on: dependencies } : {},
|
|
45055
|
+
...body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? { metadata: body.metadata } : {}
|
|
45056
|
+
};
|
|
45057
|
+
}
|
|
45058
|
+
function validateTemplateCreate(value) {
|
|
45059
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45060
|
+
return { ok: false, message: "template body must be an object" };
|
|
45061
|
+
const body = value;
|
|
45062
|
+
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata", "tasks"]);
|
|
45063
|
+
const unknown = Object.keys(body).find((key) => !allowed.has(key));
|
|
45064
|
+
if (unknown)
|
|
45065
|
+
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
45066
|
+
if (typeof body.name !== "string" || !body.name.trim())
|
|
45067
|
+
return { ok: false, message: "name must be a non-empty string" };
|
|
45068
|
+
if (typeof body.title_pattern !== "string" || !body.title_pattern.trim())
|
|
45069
|
+
return { ok: false, message: "title_pattern must be a non-empty string" };
|
|
45070
|
+
if (body.description !== undefined && body.description !== null && typeof body.description !== "string")
|
|
45071
|
+
return { ok: false, message: "description must be a string or null" };
|
|
45072
|
+
if (body.priority !== undefined && (typeof body.priority !== "string" || !["low", "medium", "high", "critical"].includes(body.priority)))
|
|
45073
|
+
return { ok: false, message: "priority must be low, medium, high, or critical" };
|
|
45074
|
+
if (body.tags !== undefined && (!Array.isArray(body.tags) || body.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
45075
|
+
return { ok: false, message: "tags must be an array of non-empty strings" };
|
|
45076
|
+
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"))) {
|
|
45077
|
+
return { ok: false, message: "variables must be valid template variable objects" };
|
|
45078
|
+
}
|
|
45079
|
+
for (const field of ["project_id", "plan_id"]) {
|
|
45080
|
+
if (body[field] !== undefined && body[field] !== null && (typeof body[field] !== "string" || !body[field].trim()))
|
|
45081
|
+
return { ok: false, message: `${field} must be a non-empty string or null` };
|
|
45082
|
+
}
|
|
45083
|
+
if (body.metadata !== undefined && (!body.metadata || typeof body.metadata !== "object" || Array.isArray(body.metadata)))
|
|
45084
|
+
return { ok: false, message: "metadata must be an object" };
|
|
45085
|
+
const tasks = body.tasks === undefined ? [] : Array.isArray(body.tasks) ? body.tasks.map(validateTemplateTask) : null;
|
|
45086
|
+
if (tasks === null || tasks.some((task2) => task2 === null))
|
|
45087
|
+
return { ok: false, message: "tasks must be valid template task objects" };
|
|
45088
|
+
const taskInputs = tasks;
|
|
45089
|
+
for (const [position, task2] of taskInputs.entries()) {
|
|
45090
|
+
if ((task2.depends_on ?? []).some((dependency) => dependency >= position)) {
|
|
45091
|
+
return { ok: false, message: "template task dependencies must reference earlier task positions" };
|
|
45092
|
+
}
|
|
45093
|
+
}
|
|
45094
|
+
return {
|
|
45095
|
+
ok: true,
|
|
45096
|
+
input: {
|
|
45097
|
+
name: body.name,
|
|
45098
|
+
title_pattern: body.title_pattern,
|
|
45099
|
+
...typeof body.description === "string" ? { description: body.description } : {},
|
|
45100
|
+
...typeof body.priority === "string" ? { priority: body.priority } : {},
|
|
45101
|
+
...Array.isArray(body.tags) ? { tags: body.tags } : {},
|
|
45102
|
+
...Array.isArray(body.variables) ? { variables: body.variables } : {},
|
|
45103
|
+
...typeof body.project_id === "string" ? { project_id: body.project_id } : {},
|
|
45104
|
+
...typeof body.plan_id === "string" ? { plan_id: body.plan_id } : {},
|
|
45105
|
+
...body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) ? { metadata: body.metadata } : {},
|
|
45106
|
+
tasks: taskInputs
|
|
45107
|
+
}
|
|
45108
|
+
};
|
|
45109
|
+
}
|
|
45110
|
+
function validateTemplatePatch(value) {
|
|
45111
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45112
|
+
return { ok: false, message: "template patch must be an object" };
|
|
45113
|
+
const body = value;
|
|
45114
|
+
const allowed = new Set(["name", "title_pattern", "description", "priority", "tags", "variables", "project_id", "plan_id", "metadata"]);
|
|
45115
|
+
const unknown = Object.keys(body).find((key) => !allowed.has(key));
|
|
45116
|
+
if (unknown)
|
|
45117
|
+
return { ok: false, message: `unknown template field: ${unknown}` };
|
|
45118
|
+
if (Object.keys(body).length === 0)
|
|
45119
|
+
return { ok: false, message: "template patch must not be empty" };
|
|
45120
|
+
const templateLike = { name: body.name ?? "template", title_pattern: body.title_pattern ?? "template", ...body };
|
|
45121
|
+
const validated = validateTemplateCreate(templateLike);
|
|
45122
|
+
if (!validated.ok)
|
|
45123
|
+
return validated;
|
|
45124
|
+
const { name: _name, title_pattern: _title, tasks: _tasks, ...patch } = validated.input;
|
|
45125
|
+
return { ok: true, patch: {
|
|
45126
|
+
...body.name !== undefined ? { name: validated.input.name } : {},
|
|
45127
|
+
...body.title_pattern !== undefined ? { title_pattern: validated.input.title_pattern } : {},
|
|
45128
|
+
...patch,
|
|
45129
|
+
...body.description === null ? { description: null } : {},
|
|
45130
|
+
...body.project_id === null ? { project_id: null } : {},
|
|
45131
|
+
...body.plan_id === null ? { plan_id: null } : {}
|
|
45132
|
+
} };
|
|
45133
|
+
}
|
|
44767
45134
|
async function readJson(req) {
|
|
44768
45135
|
try {
|
|
44769
45136
|
const text2 = await req.text();
|
|
@@ -44824,12 +45191,13 @@ function normalizeImportSnapshot(raw) {
|
|
|
44824
45191
|
agents: arr(body["agents"]),
|
|
44825
45192
|
taskLists: arr(body["taskLists"]),
|
|
44826
45193
|
templates: arr(body["templates"]),
|
|
45194
|
+
templateTasks: arr(body["templateTasks"]),
|
|
44827
45195
|
auditHistory: arr(body["auditHistory"]),
|
|
44828
45196
|
tombstones: arr(body["tombstones"])
|
|
44829
45197
|
};
|
|
44830
45198
|
}
|
|
44831
45199
|
function countSnapshotRecords(s) {
|
|
44832
|
-
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);
|
|
45200
|
+
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);
|
|
44833
45201
|
}
|
|
44834
45202
|
async function handleV1Request(req, url, dependencies = {}) {
|
|
44835
45203
|
const path = url.pathname;
|
|
@@ -45399,6 +45767,40 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
45399
45767
|
if (id)
|
|
45400
45768
|
return error(405, `method ${method} not allowed on /v1/plans/:id`);
|
|
45401
45769
|
}
|
|
45770
|
+
if (resource === "templates") {
|
|
45771
|
+
if (!id && method === "GET") {
|
|
45772
|
+
const projectId = url.searchParams.get("project_id");
|
|
45773
|
+
const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
|
|
45774
|
+
return json2({ templates, count: templates.length });
|
|
45775
|
+
}
|
|
45776
|
+
if (!id && method === "POST") {
|
|
45777
|
+
const body = await readJson(req);
|
|
45778
|
+
const validated = validateTemplateCreate(body);
|
|
45779
|
+
if (!validated.ok)
|
|
45780
|
+
return error(400, validated.message);
|
|
45781
|
+
const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
|
|
45782
|
+
return json2({ template: await store.templates.getWithTasks(template.id) }, 201);
|
|
45783
|
+
}
|
|
45784
|
+
if (!id)
|
|
45785
|
+
return error(405, `method ${method} not allowed on /v1/templates`);
|
|
45786
|
+
if (method === "GET") {
|
|
45787
|
+
const template = await store.templates.getWithTasks(id);
|
|
45788
|
+
return template ? json2({ template }) : error(404, "template not found");
|
|
45789
|
+
}
|
|
45790
|
+
if (method === "PATCH" || method === "PUT") {
|
|
45791
|
+
const body = await readJson(req);
|
|
45792
|
+
const validated = validateTemplatePatch(body);
|
|
45793
|
+
if (!validated.ok)
|
|
45794
|
+
return error(400, validated.message);
|
|
45795
|
+
const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
|
|
45796
|
+
return template ? json2({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
|
|
45797
|
+
}
|
|
45798
|
+
if (method === "DELETE") {
|
|
45799
|
+
const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
|
|
45800
|
+
return deleted ? json2({ deleted: true, id }) : error(404, "template not found");
|
|
45801
|
+
}
|
|
45802
|
+
return error(405, `method ${method} not allowed on /v1/templates/:id`);
|
|
45803
|
+
}
|
|
45402
45804
|
if (resource === "agents") {
|
|
45403
45805
|
if (!id && method === "GET") {
|
|
45404
45806
|
const agents = await store.agents.list();
|