@hasna/todos 0.9.18 → 0.9.20

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/index.js CHANGED
@@ -285,29 +285,20 @@ function runMigrations(db) {
285
285
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
286
286
  const currentLevel = result?.max_id ?? 0;
287
287
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
288
- db.exec(MIGRATIONS[i]);
288
+ try {
289
+ db.exec(MIGRATIONS[i]);
290
+ } catch {}
289
291
  }
290
292
  } catch {
291
293
  for (const migration of MIGRATIONS) {
292
- db.exec(migration);
294
+ try {
295
+ db.exec(migration);
296
+ } catch {}
293
297
  }
294
298
  }
295
- ensureTableMigrations(db);
299
+ ensureSchema(db);
296
300
  }
297
- function ensureTableMigrations(db) {
298
- try {
299
- const hasAgents = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
300
- if (!hasAgents) {
301
- db.exec(MIGRATIONS[4]);
302
- }
303
- } catch {}
304
- try {
305
- db.query("SELECT task_prefix FROM projects LIMIT 0").get();
306
- } catch {
307
- try {
308
- db.exec(MIGRATIONS[5]);
309
- } catch {}
310
- }
301
+ function ensureSchema(db) {
311
302
  const ensureColumn = (table, column, type) => {
312
303
  try {
313
304
  db.query(`SELECT ${column} FROM ${table} LIMIT 0`).get();
@@ -317,6 +308,76 @@ function ensureTableMigrations(db) {
317
308
  } catch {}
318
309
  }
319
310
  };
311
+ const ensureTable = (name, sql) => {
312
+ try {
313
+ const exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
314
+ if (!exists)
315
+ db.exec(sql);
316
+ } catch {}
317
+ };
318
+ const ensureIndex = (sql) => {
319
+ try {
320
+ db.exec(sql);
321
+ } catch {}
322
+ };
323
+ ensureTable("agents", `
324
+ CREATE TABLE agents (
325
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
326
+ role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
327
+ metadata TEXT DEFAULT '{}',
328
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
329
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
330
+ )`);
331
+ ensureTable("task_lists", `
332
+ CREATE TABLE task_lists (
333
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
334
+ slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT,
335
+ metadata TEXT DEFAULT '{}',
336
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
337
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
338
+ UNIQUE(project_id, slug)
339
+ )`);
340
+ ensureTable("plans", `
341
+ CREATE TABLE plans (
342
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
343
+ task_list_id TEXT, agent_id TEXT,
344
+ name TEXT NOT NULL, description TEXT,
345
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
346
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
347
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
348
+ )`);
349
+ ensureTable("task_tags", `
350
+ CREATE TABLE task_tags (
351
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
352
+ tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
353
+ )`);
354
+ ensureTable("task_history", `
355
+ CREATE TABLE task_history (
356
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
357
+ action TEXT NOT NULL, field TEXT, old_value TEXT, new_value TEXT, agent_id TEXT,
358
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
359
+ )`);
360
+ ensureTable("webhooks", `
361
+ CREATE TABLE webhooks (
362
+ id TEXT PRIMARY KEY, url TEXT NOT NULL, events TEXT NOT NULL DEFAULT '[]',
363
+ secret TEXT, active INTEGER NOT NULL DEFAULT 1,
364
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
365
+ )`);
366
+ ensureTable("task_templates", `
367
+ CREATE TABLE task_templates (
368
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, title_pattern TEXT NOT NULL,
369
+ description TEXT, priority TEXT DEFAULT 'medium', tags TEXT DEFAULT '[]',
370
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
371
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
372
+ metadata TEXT DEFAULT '{}',
373
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
374
+ )`);
375
+ ensureColumn("projects", "task_list_id", "TEXT");
376
+ ensureColumn("projects", "task_prefix", "TEXT");
377
+ ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
378
+ ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
379
+ ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
380
+ ensureColumn("tasks", "short_id", "TEXT");
320
381
  ensureColumn("tasks", "due_at", "TEXT");
321
382
  ensureColumn("tasks", "estimated_minutes", "INTEGER");
322
383
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
@@ -326,6 +387,21 @@ function ensureTableMigrations(db) {
326
387
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
327
388
  ensureColumn("plans", "task_list_id", "TEXT");
328
389
  ensureColumn("plans", "agent_id", "TEXT");
390
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
391
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
392
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
393
+ ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL");
394
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)");
395
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id)");
396
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
397
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
398
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
399
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
400
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
401
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
402
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
403
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
404
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
329
405
  }
330
406
  function backfillTaskTags(db) {
331
407
  try {
@@ -760,6 +836,24 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
760
836
  }
761
837
  }
762
838
 
839
+ // src/db/audit.ts
840
+ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
841
+ const d = db || getDatabase();
842
+ const id = uuid();
843
+ const timestamp = now();
844
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
845
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
846
+ return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
847
+ }
848
+ function getTaskHistory(taskId, db) {
849
+ const d = db || getDatabase();
850
+ return d.query("SELECT * FROM task_history WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
851
+ }
852
+ function getRecentActivity(limit = 50, db) {
853
+ const d = db || getDatabase();
854
+ return d.query("SELECT * FROM task_history ORDER BY created_at DESC LIMIT ?").all(limit);
855
+ }
856
+
763
857
  // src/db/tasks.ts
764
858
  function rowToTask(row) {
765
859
  return {
@@ -1010,6 +1104,17 @@ function updateTask(id, input, db) {
1010
1104
  if (input.tags !== undefined) {
1011
1105
  replaceTaskTags(id, input.tags, d);
1012
1106
  }
1107
+ const agentId = task.assigned_to || task.agent_id || null;
1108
+ if (input.status !== undefined && input.status !== task.status)
1109
+ logTaskChange(id, "update", "status", task.status, input.status, agentId, d);
1110
+ if (input.priority !== undefined && input.priority !== task.priority)
1111
+ logTaskChange(id, "update", "priority", task.priority, input.priority, agentId, d);
1112
+ if (input.title !== undefined && input.title !== task.title)
1113
+ logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
1114
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
1115
+ logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
1116
+ if (input.approved_by !== undefined)
1117
+ logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
1013
1118
  return getTask(id, d);
1014
1119
  }
1015
1120
  function deleteTask(id, db) {
@@ -1031,6 +1136,7 @@ function startTask(id, agentId, db) {
1031
1136
  throw new LockError(id, current.locked_by);
1032
1137
  }
1033
1138
  }
1139
+ logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
1034
1140
  return getTask(id, d);
1035
1141
  }
1036
1142
  function completeTask(id, agentId, db) {
@@ -1045,6 +1151,7 @@ function completeTask(id, agentId, db) {
1045
1151
  const timestamp = now();
1046
1152
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
1047
1153
  WHERE id = ?`, [timestamp, timestamp, id]);
1154
+ logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
1048
1155
  return getTask(id, d);
1049
1156
  }
1050
1157
  function lockTask(id, agentId, db) {
@@ -1435,23 +1542,6 @@ function deleteSession(id, db) {
1435
1542
  const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
1436
1543
  return result.changes > 0;
1437
1544
  }
1438
- // src/db/audit.ts
1439
- function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
1440
- const d = db || getDatabase();
1441
- const id = uuid();
1442
- const timestamp = now();
1443
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
1444
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
1445
- return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
1446
- }
1447
- function getTaskHistory(taskId, db) {
1448
- const d = db || getDatabase();
1449
- return d.query("SELECT * FROM task_history WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
1450
- }
1451
- function getRecentActivity(limit = 50, db) {
1452
- const d = db || getDatabase();
1453
- return d.query("SELECT * FROM task_history ORDER BY created_at DESC LIMIT ?").all(limit);
1454
- }
1455
1545
  // src/db/webhooks.ts
1456
1546
  function rowToWebhook(row) {
1457
1547
  return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };