@hasna/todos 0.11.66 → 0.11.68

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.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/storage-commands.d.ts +5 -0
  6. package/dist/cli/commands/storage-commands.d.ts.map +1 -1
  7. package/dist/cli/index.js +8811 -2900
  8. package/dist/contracts.js +301 -71
  9. package/dist/db/agents.d.ts.map +1 -1
  10. package/dist/db/audit.d.ts.map +1 -1
  11. package/dist/db/migrations.d.ts.map +1 -1
  12. package/dist/db/plans.d.ts.map +1 -1
  13. package/dist/db/projects.d.ts.map +1 -1
  14. package/dist/db/schema.d.ts.map +1 -1
  15. package/dist/db/storage-tombstones.d.ts +26 -0
  16. package/dist/db/storage-tombstones.d.ts.map +1 -0
  17. package/dist/db/task-crud.d.ts.map +1 -1
  18. package/dist/db/task-lifecycle.d.ts.map +1 -1
  19. package/dist/db/task-lists.d.ts.map +1 -1
  20. package/dist/db/task-runs.d.ts.map +1 -1
  21. package/dist/db/templates.d.ts.map +1 -1
  22. package/dist/index.js +628 -160
  23. package/dist/lib/approval-gates.d.ts.map +1 -1
  24. package/dist/lib/event-emission-safety.d.ts +9 -0
  25. package/dist/lib/event-emission-safety.d.ts.map +1 -0
  26. package/dist/lib/event-hooks.d.ts +1 -0
  27. package/dist/lib/event-hooks.d.ts.map +1 -1
  28. package/dist/lib/feature-manifest.d.ts.map +1 -1
  29. package/dist/lib/project-panel.d.ts +8 -0
  30. package/dist/lib/project-panel.d.ts.map +1 -0
  31. package/dist/lib/review-queues.d.ts.map +1 -1
  32. package/dist/lib/shared-events.d.ts +1 -0
  33. package/dist/lib/shared-events.d.ts.map +1 -1
  34. package/dist/mcp/index.js +343 -104
  35. package/dist/registry.js +301 -71
  36. package/dist/release-provenance.json +3 -3
  37. package/dist/server/index.js +366 -127
  38. package/dist/storage/interfaces.d.ts +20 -0
  39. package/dist/storage/interfaces.d.ts.map +1 -1
  40. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  41. package/dist/storage/postgres-sync.d.ts +6 -1
  42. package/dist/storage/postgres-sync.d.ts.map +1 -1
  43. package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
  44. package/dist/storage.js +569 -94
  45. package/dist/types/index.d.ts +22 -0
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1182,6 +1182,22 @@ var init_migrations = __esm(() => {
1182
1182
  CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
1183
1183
  CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
1184
1184
  INSERT OR IGNORE INTO _migrations (id) VALUES (62);
1185
+ `,
1186
+ `
1187
+ CREATE TABLE IF NOT EXISTS storage_tombstones (
1188
+ id TEXT PRIMARY KEY,
1189
+ object_type TEXT NOT NULL,
1190
+ object_id TEXT NOT NULL,
1191
+ deleted_at TEXT NOT NULL,
1192
+ updated_at TEXT NOT NULL,
1193
+ source_machine_id TEXT,
1194
+ payload TEXT,
1195
+ version INTEGER,
1196
+ UNIQUE(object_type, object_id)
1197
+ );
1198
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
1199
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
1200
+ INSERT OR IGNORE INTO _migrations (id) VALUES (63);
1185
1201
  `
1186
1202
  ];
1187
1203
  });
@@ -1703,6 +1719,20 @@ function ensureSchema(db) {
1703
1719
  )`);
1704
1720
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
1705
1721
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
1722
+ ensureTable("storage_tombstones", `
1723
+ CREATE TABLE storage_tombstones (
1724
+ id TEXT PRIMARY KEY,
1725
+ object_type TEXT NOT NULL,
1726
+ object_id TEXT NOT NULL,
1727
+ deleted_at TEXT NOT NULL,
1728
+ updated_at TEXT NOT NULL,
1729
+ source_machine_id TEXT,
1730
+ payload TEXT,
1731
+ version INTEGER,
1732
+ UNIQUE(object_type, object_id)
1733
+ )`);
1734
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
1735
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
1706
1736
  ensureTable("machines", `
1707
1737
  CREATE TABLE machines (
1708
1738
  id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
@@ -6173,6 +6203,80 @@ init_config();
6173
6203
  init_types();
6174
6204
  init_database();
6175
6205
  init_machines();
6206
+
6207
+ // src/db/storage-tombstones.ts
6208
+ init_database();
6209
+ init_machines();
6210
+ function recordStorageTombstone(input, db) {
6211
+ const d = db ?? getDatabase();
6212
+ const deletedAt = input.deleted_at ?? now();
6213
+ const machineId = input.source_machine_id ?? currentStorageMachineId(d);
6214
+ d.run(`INSERT INTO storage_tombstones (
6215
+ id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
6216
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
6217
+ ON CONFLICT(object_type, object_id) DO UPDATE SET
6218
+ deleted_at = excluded.deleted_at,
6219
+ updated_at = excluded.updated_at,
6220
+ source_machine_id = excluded.source_machine_id,
6221
+ payload = excluded.payload,
6222
+ version = excluded.version
6223
+ WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
6224
+ uuid(),
6225
+ input.object_type,
6226
+ input.object_id,
6227
+ deletedAt,
6228
+ deletedAt,
6229
+ machineId,
6230
+ input.payload ? JSON.stringify(input.payload) : null,
6231
+ input.version ?? null
6232
+ ]);
6233
+ return getStorageTombstone(input.object_type, input.object_id, d);
6234
+ }
6235
+ function getStorageTombstone(objectType, objectId, db) {
6236
+ const d = db ?? getDatabase();
6237
+ const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
6238
+ return row ? rowToStorageTombstone(row) : null;
6239
+ }
6240
+ function listStorageTombstones(db) {
6241
+ const d = db ?? getDatabase();
6242
+ return d.query("SELECT * FROM storage_tombstones ORDER BY updated_at ASC, object_type ASC, object_id ASC").all().map(rowToStorageTombstone);
6243
+ }
6244
+ function shouldApplyStorageTombstone(tombstone, existingUpdatedAt) {
6245
+ const tombstoneClock = Date.parse(tombstone.updated_at || tombstone.deleted_at);
6246
+ if (!existingUpdatedAt)
6247
+ return true;
6248
+ const existingClock = Date.parse(existingUpdatedAt);
6249
+ if (Number.isNaN(tombstoneClock))
6250
+ return true;
6251
+ if (Number.isNaN(existingClock))
6252
+ return true;
6253
+ return tombstoneClock >= existingClock;
6254
+ }
6255
+ function rowToStorageTombstone(row) {
6256
+ return {
6257
+ ...row,
6258
+ payload: parsePayload(row.payload)
6259
+ };
6260
+ }
6261
+ function parsePayload(value) {
6262
+ if (!value)
6263
+ return null;
6264
+ try {
6265
+ const parsed = JSON.parse(value);
6266
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6267
+ } catch {
6268
+ return null;
6269
+ }
6270
+ }
6271
+ function currentStorageMachineId(db) {
6272
+ try {
6273
+ return getMachineId(db);
6274
+ } catch {
6275
+ return null;
6276
+ }
6277
+ }
6278
+
6279
+ // src/db/projects.ts
6176
6280
  function slugify(name) {
6177
6281
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
6178
6282
  }
@@ -6202,8 +6306,9 @@ function createProject(input, db) {
6202
6306
  const timestamp = now();
6203
6307
  const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
6204
6308
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
6205
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
6206
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
6309
+ const machineId = currentStorageMachineId(d);
6310
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
6311
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
6207
6312
  return getProject(id, d);
6208
6313
  }
6209
6314
  function getProject(id, db) {
@@ -6282,6 +6387,14 @@ function renameProject(id, input, db) {
6282
6387
  }
6283
6388
  function deleteProject(id, db) {
6284
6389
  const d = db || getDatabase();
6390
+ const project = getProject(id, d);
6391
+ if (!project)
6392
+ return false;
6393
+ recordStorageTombstone({
6394
+ object_type: "projects",
6395
+ object_id: id,
6396
+ payload: project
6397
+ }, d);
6285
6398
  const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
6286
6399
  return result.changes > 0;
6287
6400
  }
@@ -6386,6 +6499,14 @@ function listMachineLocalPaths(projectId, db) {
6386
6499
  function removeMachineLocalPath(projectId, machineId, db) {
6387
6500
  const d = db || getDatabase();
6388
6501
  const mid = machineId ?? getMachineId(d);
6502
+ const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
6503
+ if (!existing)
6504
+ return false;
6505
+ recordStorageTombstone({
6506
+ object_type: "project_machine_paths",
6507
+ object_id: existing.id,
6508
+ payload: existing
6509
+ }, d);
6389
6510
  const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
6390
6511
  return result.changes > 0;
6391
6512
  }
@@ -6434,20 +6555,69 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
6434
6555
  }
6435
6556
  }
6436
6557
 
6558
+ // src/lib/event-emission-safety.ts
6559
+ init_database();
6560
+ init_sync_utils();
6561
+ import { tmpdir } from "os";
6562
+ import { resolve as resolve3, sep } from "path";
6563
+ function envFlag(name) {
6564
+ const value = process.env[name];
6565
+ return value === "1" || value === "true" || value === "yes";
6566
+ }
6567
+ function isUnder(parent, child) {
6568
+ const normalizedParent = resolve3(parent);
6569
+ const normalizedChild = resolve3(child);
6570
+ return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
6571
+ }
6572
+ function databasePathFromDatabase(db) {
6573
+ const filename = db?.filename;
6574
+ return typeof filename === "string" && filename.trim() ? filename : undefined;
6575
+ }
6576
+ function isEphemeralTodosDatabase(dbPath) {
6577
+ const resolvedPath = dbPath ?? getDatabasePath();
6578
+ if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
6579
+ return true;
6580
+ return isUnder(tmpdir(), resolvedPath);
6581
+ }
6582
+ function hasExplicitSharedEventsStore() {
6583
+ return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
6584
+ }
6585
+ function usesIsolatedTodosHome() {
6586
+ return isUnder(tmpdir(), getTodosGlobalDir());
6587
+ }
6588
+ function shouldEmitSharedTaskEvents(dbPath) {
6589
+ if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
6590
+ return false;
6591
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
6592
+ return true;
6593
+ if (!isEphemeralTodosDatabase(dbPath))
6594
+ return true;
6595
+ return hasExplicitSharedEventsStore();
6596
+ }
6597
+ function shouldDeliverLocalLifecycleHooks(dbPath) {
6598
+ if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
6599
+ return false;
6600
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
6601
+ return true;
6602
+ if (!isEphemeralTodosDatabase(dbPath))
6603
+ return true;
6604
+ return usesIsolatedTodosHome();
6605
+ }
6606
+
6437
6607
  // src/lib/event-hooks.ts
6438
6608
  init_redaction();
6439
6609
  import { createHash, randomUUID } from "crypto";
6440
6610
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
6441
- import { dirname as dirname4, resolve as resolve5 } from "path";
6611
+ import { dirname as dirname4, resolve as resolve6 } from "path";
6442
6612
  import { createConnection } from "net";
6443
6613
 
6444
6614
  // src/lib/runner-sandbox.ts
6445
6615
  init_config();
6446
- import { relative as relative2, resolve as resolve4 } from "path";
6616
+ import { relative as relative2, resolve as resolve5 } from "path";
6447
6617
 
6448
6618
  // src/lib/workspace-trust.ts
6449
6619
  init_config();
6450
- import { relative, resolve as resolve3 } from "path";
6620
+ import { relative, resolve as resolve4 } from "path";
6451
6621
  var DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
6452
6622
  var DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
6453
6623
  var PRESET_DEFAULTS = {
@@ -6493,7 +6663,7 @@ var PRESET_DEFAULTS = {
6493
6663
  }
6494
6664
  };
6495
6665
  function normalizePath(path) {
6496
- return resolve3(path);
6666
+ return resolve4(path);
6497
6667
  }
6498
6668
  function unique2(values) {
6499
6669
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -6630,7 +6800,7 @@ function checkWorkspacePermission(input = {}) {
6630
6800
  var DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
6631
6801
  var DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
6632
6802
  function normalizePath2(path) {
6633
- return resolve4(path);
6803
+ return resolve5(path);
6634
6804
  }
6635
6805
  function unique3(values) {
6636
6806
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -6976,7 +7146,7 @@ async function deliverHook(hook, envelope) {
6976
7146
  if (hook.target === "stdout") {
6977
7147
  output = line.trim();
6978
7148
  } else if (hook.target === "file") {
6979
- const filePath = resolve5(hook.file_path);
7149
+ const filePath = resolve6(hook.file_path);
6980
7150
  mkdirSync3(dirname4(filePath), { recursive: true });
6981
7151
  appendFileSync(filePath, line);
6982
7152
  } else if (hook.target === "socket") {
@@ -7051,6 +7221,8 @@ async function emitLocalEventHooks(input) {
7051
7221
  return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
7052
7222
  }
7053
7223
  function emitLocalEventHooksQuiet(input) {
7224
+ if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
7225
+ return;
7054
7226
  emitLocalEventHooks(input).catch(() => {});
7055
7227
  }
7056
7228
  async function testLocalEventHook(name, input) {
@@ -7367,7 +7539,7 @@ async function dispatchCommand(event, channel) {
7367
7539
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
7368
7540
  HASNA_EVENT_JSON: eventJson
7369
7541
  };
7370
- return new Promise((resolve6) => {
7542
+ return new Promise((resolve7) => {
7371
7543
  const child = spawn(channel.command.command, channel.command.args ?? [], {
7372
7544
  cwd: channel.command.cwd,
7373
7545
  env,
@@ -7385,7 +7557,7 @@ async function dispatchCommand(event, channel) {
7385
7557
  });
7386
7558
  child.on("error", (error) => {
7387
7559
  clearTimeout(timeout);
7388
- resolve6({
7560
+ resolve7({
7389
7561
  attempt: 1,
7390
7562
  status: "failed",
7391
7563
  startedAt,
@@ -7398,7 +7570,7 @@ async function dispatchCommand(event, channel) {
7398
7570
  child.on("close", (code, signal) => {
7399
7571
  clearTimeout(timeout);
7400
7572
  const success = code === 0;
7401
- resolve6({
7573
+ resolve7({
7402
7574
  attempt: 1,
7403
7575
  status: success ? "success" : "failed",
7404
7576
  startedAt,
@@ -7666,14 +7838,15 @@ function createTaskList(input, db) {
7666
7838
  const id = uuid();
7667
7839
  const timestamp = now();
7668
7840
  const slug = input.slug || slugify(input.name);
7841
+ const machineId = currentStorageMachineId(d);
7669
7842
  if (!input.project_id) {
7670
7843
  const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
7671
7844
  if (existing) {
7672
7845
  throw new Error(`Standalone task list with slug "${slug}" already exists`);
7673
7846
  }
7674
7847
  }
7675
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
7676
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
7848
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
7849
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
7677
7850
  return getTaskList(id, d);
7678
7851
  }
7679
7852
  function getTaskList(id, db) {
@@ -7723,6 +7896,14 @@ function updateTaskList(id, input, db) {
7723
7896
  }
7724
7897
  function deleteTaskList(id, db) {
7725
7898
  const d = db || getDatabase();
7899
+ const list = getTaskList(id, d);
7900
+ if (!list)
7901
+ return false;
7902
+ recordStorageTombstone({
7903
+ object_type: "task_lists",
7904
+ object_id: id,
7905
+ payload: list
7906
+ }, d);
7726
7907
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
7727
7908
  }
7728
7909
  function ensureTaskList(name, slug, projectId, db) {
@@ -7939,6 +8120,8 @@ function readMachineLocalPath(project) {
7939
8120
  }
7940
8121
  }
7941
8122
  async function emitSharedTaskEvent(input) {
8123
+ if (!shouldEmitSharedTaskEvents(input.databasePath))
8124
+ return;
7942
8125
  const data = taskEventData(input.task, input.data);
7943
8126
  await new EventsClient().emit({
7944
8127
  source: SOURCE,
@@ -7963,8 +8146,9 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
7963
8146
  const d = db || getDatabase();
7964
8147
  const id = uuid();
7965
8148
  const timestamp = now();
7966
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
7967
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
8149
+ const machineId = currentStorageMachineId(d);
8150
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
8151
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
7968
8152
  try {
7969
8153
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
7970
8154
  logActivity2({
@@ -7977,7 +8161,7 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
7977
8161
  actor_id: agentId ?? undefined
7978
8162
  }, d);
7979
8163
  } catch {}
7980
- return { id, task_id: taskId, action, field: field2 || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
8164
+ return { id, task_id: taskId, action, field: field2 || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp, machine_id: machineId };
7981
8165
  }
7982
8166
  function getTaskHistory(taskId, db) {
7983
8167
  const d = db || getDatabase();
@@ -8326,13 +8510,14 @@ function createTask(input, db) {
8326
8510
  const d = db || getDatabase();
8327
8511
  const timestamp = now();
8328
8512
  const tags = input.tags || [];
8513
+ const machineId = currentStorageMachineId(d);
8329
8514
  const assignedBy = input.assigned_by || input.agent_id;
8330
8515
  const assignedFromProject = input.assigned_from_project || null;
8331
8516
  let id = uuid();
8332
8517
  for (let attempt = 0;attempt < 3; attempt++) {
8333
8518
  try {
8334
- d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
8335
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8519
+ d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type, machine_id)
8520
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8336
8521
  id,
8337
8522
  null,
8338
8523
  input.project_id || null,
@@ -8369,7 +8554,8 @@ function createTask(input, db) {
8369
8554
  input.spawned_from_session || null,
8370
8555
  assignedBy || null,
8371
8556
  assignedFromProject || null,
8372
- input.task_type || null
8557
+ input.task_type || null,
8558
+ machineId
8373
8559
  ]);
8374
8560
  break;
8375
8561
  } catch (e) {
@@ -8385,9 +8571,10 @@ function createTask(input, db) {
8385
8571
  }
8386
8572
  const task = getTask(id, d);
8387
8573
  const payload = taskEventData(task);
8574
+ const databasePath = databasePathFromDatabase(d);
8388
8575
  dispatchWebhook2("task.created", payload, d).catch(() => {});
8389
- emitLocalEventHooksQuiet({ type: "task.created", payload });
8390
- emitSharedTaskEventQuiet({ type: "task.created", task });
8576
+ emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
8577
+ emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
8391
8578
  return task;
8392
8579
  }
8393
8580
  function getTask(id, db) {
@@ -8805,29 +8992,39 @@ function updateTask(id, input, db) {
8805
8992
  approved_by: input.approved_by ?? task.approved_by,
8806
8993
  approved_at: input.approved_by ? timestamp : task.approved_at
8807
8994
  };
8995
+ const databasePath = databasePathFromDatabase(d);
8808
8996
  if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
8809
8997
  const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
8810
8998
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
8811
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
8812
- emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
8999
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
9000
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
8813
9001
  }
8814
9002
  if (input.status !== undefined && input.status !== task.status) {
8815
9003
  const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
8816
9004
  dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
8817
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
8818
- emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
9005
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
9006
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
8819
9007
  }
8820
9008
  if (input.approved_by !== undefined) {
8821
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
9009
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
8822
9010
  }
8823
9011
  const updatePayload = taskEventData(updatedTask);
8824
9012
  dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
8825
- emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
8826
- emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
9013
+ emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
9014
+ emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
8827
9015
  return updatedTask;
8828
9016
  }
8829
9017
  function deleteTask(id, db) {
8830
9018
  const d = db || getDatabase();
9019
+ const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
9020
+ if (!row)
9021
+ return false;
9022
+ recordStorageTombstone({
9023
+ object_type: "tasks",
9024
+ object_id: id,
9025
+ payload: rowToTask(row),
9026
+ version: row.version
9027
+ }, d);
8831
9028
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
8832
9029
  return result.changes > 0;
8833
9030
  }
@@ -8959,8 +9156,9 @@ function resolveTemplateId(id, d) {
8959
9156
  function createTemplate(input, db) {
8960
9157
  const d = db || getDatabase();
8961
9158
  const id = uuid();
8962
- d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
8963
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
9159
+ const machineId = currentStorageMachineId(d);
9160
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
9161
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8964
9162
  id,
8965
9163
  input.name,
8966
9164
  input.title_pattern,
@@ -8971,7 +9169,8 @@ function createTemplate(input, db) {
8971
9169
  input.project_id || null,
8972
9170
  input.plan_id || null,
8973
9171
  JSON.stringify(input.metadata || {}),
8974
- now()
9172
+ now(),
9173
+ machineId
8975
9174
  ]);
8976
9175
  if (input.tasks && input.tasks.length > 0) {
8977
9176
  addTemplateTasks(id, input.tasks, d);
@@ -8995,6 +9194,15 @@ function deleteTemplate(id, db) {
8995
9194
  const resolved = resolveTemplateId(id, d);
8996
9195
  if (!resolved)
8997
9196
  return false;
9197
+ const template = getTemplate(resolved, d);
9198
+ if (!template)
9199
+ return false;
9200
+ recordStorageTombstone({
9201
+ object_type: "templates",
9202
+ object_id: resolved,
9203
+ payload: template,
9204
+ version: template.version
9205
+ }, d);
8998
9206
  return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
8999
9207
  }
9000
9208
  function updateTemplate(id, updates, db) {
@@ -9524,6 +9732,7 @@ function getBlockingDeps(id, db) {
9524
9732
  }
9525
9733
  function startTask(id, agentId, db) {
9526
9734
  const d = db || getDatabase();
9735
+ const databasePath = databasePathFromDatabase(d);
9527
9736
  const task = getTask(id, d);
9528
9737
  if (!task)
9529
9738
  throw new TaskNotFoundError(id);
@@ -9538,7 +9747,8 @@ function startTask(id, agentId, db) {
9538
9747
  agent_id: agentId,
9539
9748
  title: task.title,
9540
9749
  blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
9541
- }
9750
+ },
9751
+ databasePath
9542
9752
  });
9543
9753
  throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
9544
9754
  }
@@ -9560,12 +9770,13 @@ function startTask(id, agentId, db) {
9560
9770
  const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, started_at: task.started_at || timestamp, version: task.version + 1, updated_at: timestamp };
9561
9771
  const payload = taskEventData(startedTask, { agent_id: agentId });
9562
9772
  dispatchWebhook2("task.started", payload, d).catch(() => {});
9563
- emitLocalEventHooksQuiet({ type: "task.started", payload });
9564
- emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
9773
+ emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
9774
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
9565
9775
  return startedTask;
9566
9776
  }
9567
9777
  function completeTask(id, agentId, db, options) {
9568
9778
  const d = db || getDatabase();
9779
+ const databasePath = databasePathFromDatabase(d);
9569
9780
  const task = getTask(id, d);
9570
9781
  if (!task)
9571
9782
  throw new TaskNotFoundError(id);
@@ -9611,8 +9822,8 @@ function completeTask(id, agentId, db, options) {
9611
9822
  };
9612
9823
  const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
9613
9824
  dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
9614
- emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
9615
- emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
9825
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
9826
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
9616
9827
  let spawnedTask = null;
9617
9828
  if (task.recurrence_rule && !options?.skip_recurrence) {
9618
9829
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -9656,9 +9867,9 @@ function completeTask(id, agentId, db, options) {
9656
9867
  const depTask = getTask(dep.id, d);
9657
9868
  const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
9658
9869
  dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
9659
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
9870
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
9660
9871
  if (depTask)
9661
- emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
9872
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
9662
9873
  }
9663
9874
  }
9664
9875
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -9828,6 +10039,7 @@ function getTasksChangedSince(since, filters, db) {
9828
10039
  }
9829
10040
  function failTask(id, agentId, reason, options, db) {
9830
10041
  const d = db || getDatabase();
10042
+ const databasePath = databasePathFromDatabase(d);
9831
10043
  const task = getTask(id, d);
9832
10044
  if (!task)
9833
10045
  throw new TaskNotFoundError(id);
@@ -9856,8 +10068,8 @@ function failTask(id, agentId, reason, options, db) {
9856
10068
  logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
9857
10069
  const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
9858
10070
  dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
9859
- emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
9860
- emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
10071
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
10072
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
9861
10073
  let retryTask;
9862
10074
  if (options?.retry) {
9863
10075
  const retryCount = (task.retry_count || 0) + 1;
@@ -9917,6 +10129,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
9917
10129
  }
9918
10130
  function stealTask(agentId, opts, db) {
9919
10131
  const d = db || getDatabase();
10132
+ const databasePath = databasePathFromDatabase(d);
9920
10133
  const staleMinutes = opts?.stale_minutes ?? 30;
9921
10134
  const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
9922
10135
  if (staleTasks.length === 0)
@@ -9935,8 +10148,8 @@ function stealTask(agentId, opts, db) {
9935
10148
  const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
9936
10149
  const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
9937
10150
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
9938
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
9939
- emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
10151
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
10152
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
9940
10153
  return stolenTask;
9941
10154
  }
9942
10155
  function claimOrSteal(agentId, filters, db) {
@@ -10527,8 +10740,9 @@ function createPlan(input, db) {
10527
10740
  const d = db || getDatabase();
10528
10741
  const id = uuid();
10529
10742
  const timestamp = now();
10530
- d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
10531
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10743
+ const machineId = currentStorageMachineId(d);
10744
+ d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
10745
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10532
10746
  id,
10533
10747
  input.project_id || null,
10534
10748
  input.task_list_id || null,
@@ -10537,7 +10751,8 @@ function createPlan(input, db) {
10537
10751
  input.description || null,
10538
10752
  input.status || "active",
10539
10753
  timestamp,
10540
- timestamp
10754
+ timestamp,
10755
+ machineId
10541
10756
  ]);
10542
10757
  return getPlan(id, d);
10543
10758
  }
@@ -10585,12 +10800,21 @@ function updatePlan(id, input, db) {
10585
10800
  const updated = getPlan(id, d);
10586
10801
  emitLocalEventHooksQuiet({
10587
10802
  type: "plan.updated",
10588
- payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
10803
+ payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
10804
+ databasePath: databasePathFromDatabase(d)
10589
10805
  });
10590
10806
  return updated;
10591
10807
  }
10592
10808
  function deletePlan(id, db) {
10593
10809
  const d = db || getDatabase();
10810
+ const plan = getPlan(id, d);
10811
+ if (!plan)
10812
+ return false;
10813
+ recordStorageTombstone({
10814
+ object_type: "plans",
10815
+ object_id: id,
10816
+ payload: plan
10817
+ }, d);
10594
10818
  const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
10595
10819
  return result.changes > 0;
10596
10820
  }
@@ -10997,20 +11221,20 @@ init_database();
10997
11221
  init_redaction();
10998
11222
  import { createHash as createHash2 } from "crypto";
10999
11223
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11000
- import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
11001
- import { tmpdir } from "os";
11224
+ import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
11225
+ import { tmpdir as tmpdir2 } from "os";
11002
11226
  function isInMemoryDb2(path) {
11003
11227
  return path === ":memory:" || path.startsWith("file::memory:");
11004
11228
  }
11005
11229
  function artifactStoreRoot() {
11006
11230
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11007
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11231
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11008
11232
  if (process.env["TODOS_ARTIFACTS_DIR"])
11009
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
11233
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11010
11234
  const dbPath = getDatabasePath();
11011
11235
  if (isInMemoryDb2(dbPath))
11012
- return join6(tmpdir(), "hasna-todos-artifacts");
11013
- return join6(dirname5(resolve6(dbPath)), "artifacts");
11236
+ return join6(tmpdir2(), "hasna-todos-artifacts");
11237
+ return join6(dirname5(resolve7(dbPath)), "artifacts");
11014
11238
  }
11015
11239
  function artifactStorePath(relativePath) {
11016
11240
  const normalized = relativePath.replace(/\\/g, "/");
@@ -11057,7 +11281,7 @@ function mediaTypeFor(path, textLike) {
11057
11281
  return "application/octet-stream";
11058
11282
  }
11059
11283
  function storeArtifactContent(input) {
11060
- const sourcePath = resolve6(input.path);
11284
+ const sourcePath = resolve7(input.path);
11061
11285
  if (!existsSync8(sourcePath))
11062
11286
  return null;
11063
11287
  const sourceStat = statSync2(sourcePath);
@@ -11213,19 +11437,19 @@ function importStoredArtifactContent(content) {
11213
11437
  var DEFAULT_DELETED_RETENTION_DAYS = 30;
11214
11438
  function getArtifactStoreRoot(dbPath) {
11215
11439
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11216
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11440
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11217
11441
  if (process.env["TODOS_ARTIFACTS_DIR"])
11218
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
11442
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11219
11443
  const path = dbPath ?? getDatabasePath();
11220
11444
  if (isInMemoryDb2(path))
11221
- return join6(tmpdir(), "hasna-todos-artifacts");
11222
- return join6(dirname5(resolve6(path)), "artifacts");
11445
+ return join6(tmpdir2(), "hasna-todos-artifacts");
11446
+ return join6(dirname5(resolve7(path)), "artifacts");
11223
11447
  }
11224
11448
  function computeContentHash(path) {
11225
- return sha256(readFileSync4(resolve6(path)));
11449
+ return sha256(readFileSync4(resolve7(path)));
11226
11450
  }
11227
11451
  function storeArtifactFile(input) {
11228
- const sourcePath = resolve6(input.sourcePath);
11452
+ const sourcePath = resolve7(input.sourcePath);
11229
11453
  if (!existsSync8(sourcePath)) {
11230
11454
  throw new Error(`Source file not found: ${input.sourcePath}`);
11231
11455
  }
@@ -11274,7 +11498,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
11274
11498
  };
11275
11499
  }
11276
11500
  function writeArtifactExportManifest(manifest, outputPath) {
11277
- const destination = resolve6(outputPath);
11501
+ const destination = resolve7(outputPath);
11278
11502
  mkdirSync4(dirname5(destination), { recursive: true });
11279
11503
  writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
11280
11504
  `);
@@ -11661,7 +11885,11 @@ function startTaskRun(input, db) {
11661
11885
  }, d);
11662
11886
  }
11663
11887
  const run = getTaskRun(id, d);
11664
- emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
11888
+ emitLocalEventHooksQuiet({
11889
+ type: "run.started",
11890
+ payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
11891
+ databasePath: databasePathFromDatabase(d)
11892
+ });
11665
11893
  return run;
11666
11894
  }
11667
11895
  function beginTaskRunTransaction(input, db) {
@@ -11930,7 +12158,8 @@ function finishTaskRun(input, db) {
11930
12158
  const updated = getTaskRun(run.id, d);
11931
12159
  emitLocalEventHooksQuiet({
11932
12160
  type: `run.${input.status}`,
11933
- payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
12161
+ payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
12162
+ databasePath: databasePathFromDatabase(d)
11934
12163
  });
11935
12164
  return updated;
11936
12165
  }
@@ -13264,7 +13493,7 @@ function importOnboardingFixture(options = {}) {
13264
13493
  init_database();
13265
13494
  import { createHash as createHash3 } from "crypto";
13266
13495
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
13267
- import { dirname as dirname6, resolve as resolve7 } from "path";
13496
+ import { dirname as dirname6, resolve as resolve8 } from "path";
13268
13497
  import { mkdirSync as mkdirSync6 } from "fs";
13269
13498
  var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
13270
13499
  var TODOS_LOCAL_BACKUP_SCHEMA_VERSION = 1;
@@ -13371,14 +13600,14 @@ function createLocalBackup(options = {}, db) {
13371
13600
  return backup;
13372
13601
  }
13373
13602
  function writeLocalBackupFile(backup, outputPath) {
13374
- const path = resolve7(outputPath);
13603
+ const path = resolve8(outputPath);
13375
13604
  mkdirSync6(dirname6(path), { recursive: true });
13376
13605
  writeFileSync4(path, `${JSON.stringify(backup, null, 2)}
13377
13606
  `);
13378
13607
  return path;
13379
13608
  }
13380
13609
  function readLocalBackupFile(path) {
13381
- return JSON.parse(readFileSync5(resolve7(path), "utf-8"));
13610
+ return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
13382
13611
  }
13383
13612
  function verifyLocalBackup(value, options = {}, db) {
13384
13613
  const verifiedAt = options.verified_at ?? now();
@@ -16346,7 +16575,8 @@ function writeQueue(task2, queue, actor, action, db) {
16346
16575
  logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
16347
16576
  emitLocalEventHooksQuiet({
16348
16577
  type: `review.${action}`,
16349
- payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
16578
+ payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
16579
+ databasePath: databasePathFromDatabase(d)
16350
16580
  });
16351
16581
  return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
16352
16582
  }
@@ -17027,7 +17257,7 @@ function canonicalize(value) {
17027
17257
  function hash(value) {
17028
17258
  return createHash5("sha256").update(value).digest("hex");
17029
17259
  }
17030
- function parsePayload(value) {
17260
+ function parsePayload2(value) {
17031
17261
  if (!value)
17032
17262
  return {};
17033
17263
  try {
@@ -17126,7 +17356,7 @@ function taskScopedRows(db, scope) {
17126
17356
  FROM handoffs h
17127
17357
  WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
17128
17358
  `).all(scope.project_id ?? null).filter((row) => {
17129
- const payload = parsePayload(row.payload_json);
17359
+ const payload = parsePayload2(row.payload_json);
17130
17360
  const taskRefs = parseStringArray(payload["task_ids"]);
17131
17361
  const runRefs = parseStringArray(payload["run_ids"]);
17132
17362
  if (scope.project_id && row.project_id !== scope.project_id)
@@ -17157,7 +17387,7 @@ function toLedgerEntries(rows) {
17157
17387
  });
17158
17388
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
17159
17389
  return ordered.map((row, index) => {
17160
- const payload = parsePayload(row.payload_json);
17390
+ const payload = parsePayload2(row.payload_json);
17161
17391
  const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
17162
17392
  const chainHash = hash(`${previous}
17163
17393
  ${payloadHash}`);
@@ -17292,7 +17522,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
17292
17522
  init_migrations();
17293
17523
  init_schema();
17294
17524
  import { readFileSync as readFileSync6 } from "fs";
17295
- import { join as join9, resolve as resolve8 } from "path";
17525
+ import { join as join9, resolve as resolve9 } from "path";
17296
17526
  import { Database as Database2 } from "bun:sqlite";
17297
17527
  var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
17298
17528
  var EXPECTED_PACKAGE_NAME = "@hasna/todos";
@@ -17434,7 +17664,7 @@ function checkChangelog() {
17434
17664
  ];
17435
17665
  }
17436
17666
  function createReleaseCompatibilityReport(options = {}) {
17437
- const root = resolve8(options.root ?? process.cwd());
17667
+ const root = resolve9(options.root ?? process.cwd());
17438
17668
  const packageJson = readPackageJson(root);
17439
17669
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
17440
17670
  const checks = [
@@ -21762,6 +21992,7 @@ function rowToAgent(row) {
21762
21992
  }
21763
21993
  function registerAgent(input, db) {
21764
21994
  const d = db || getDatabase();
21995
+ const machineId = currentStorageMachineId(d);
21765
21996
  const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
21766
21997
  const normalizedName = validateAgentName(input.name, existingNames);
21767
21998
  const existing = getAgentByName(normalizedName, d);
@@ -21802,14 +22033,18 @@ function registerAgent(input, db) {
21802
22033
  updates.push("active_project_id = ?");
21803
22034
  params.push(input.project_id);
21804
22035
  }
22036
+ if (!existing.machine_id && machineId) {
22037
+ updates.push("machine_id = ?");
22038
+ params.push(machineId);
22039
+ }
21805
22040
  params.push(existing.id);
21806
22041
  d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
21807
22042
  return getAgent(existing.id, d);
21808
22043
  }
21809
22044
  const id = shortUuid();
21810
22045
  const timestamp2 = now();
21811
- d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir, active_project_id)
21812
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
22046
+ d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir, active_project_id, machine_id)
22047
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
21813
22048
  id,
21814
22049
  normalizedName,
21815
22050
  input.description || null,
@@ -21825,7 +22060,8 @@ function registerAgent(input, db) {
21825
22060
  timestamp2,
21826
22061
  input.session_id || null,
21827
22062
  input.working_dir || null,
21828
- input.project_id && input.session_id ? input.project_id : null
22063
+ input.project_id && input.session_id ? input.project_id : null,
22064
+ machineId
21829
22065
  ]);
21830
22066
  return getAgent(id, d);
21831
22067
  }
@@ -22025,6 +22261,14 @@ var PROJECT_COLUMNS = [
22025
22261
  "machine_id",
22026
22262
  "synced_at"
22027
22263
  ];
22264
+ var PROJECT_MACHINE_PATH_COLUMNS = [
22265
+ "id",
22266
+ "project_id",
22267
+ "machine_id",
22268
+ "path",
22269
+ "created_at",
22270
+ "updated_at"
22271
+ ];
22028
22272
  var TASK_LIST_COLUMNS = [
22029
22273
  "id",
22030
22274
  "project_id",
@@ -22164,11 +22408,13 @@ function exportSqliteTodosStorageSnapshot(db) {
22164
22408
  source: "sqlite",
22165
22409
  tasks: listTasks({ include_archived: true }, d),
22166
22410
  projects: listProjects(d),
22411
+ projectMachinePaths: listProjectMachinePaths(d),
22167
22412
  plans: listPlans(undefined, d),
22168
22413
  agents: listAgents({ include_archived: true }, d),
22169
22414
  taskLists: listTaskLists(undefined, d),
22170
22415
  templates: listTemplates(d),
22171
- auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d)
22416
+ auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
22417
+ tombstones: listStorageTombstones(d)
22172
22418
  };
22173
22419
  }
22174
22420
  function importSqliteTodosStorageSnapshot(snapshot, db) {
@@ -22176,13 +22422,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
22176
22422
  const result = {
22177
22423
  inserted: 0,
22178
22424
  updated: 0,
22425
+ deleted: 0,
22179
22426
  skipped: 0,
22180
22427
  errors: []
22181
22428
  };
22182
- const applyRows = (table, columns, rows, updateClockColumn, afterUpsert) => {
22429
+ const applyRows = (objectType, table, columns, rows, updateClockColumn, afterUpsert) => {
22183
22430
  for (const row of rows) {
22184
22431
  try {
22185
22432
  const record = asRecord2(row);
22433
+ const tombstone = typeof record["id"] === "string" ? getStorageTombstone(objectType, record["id"], d) : null;
22434
+ if (tombstone && shouldApplyStorageTombstone(tombstone, rowClock(record, updateClockColumn))) {
22435
+ result.skipped += 1;
22436
+ continue;
22437
+ }
22186
22438
  const state = upsertById(d, table, columns, record, updateClockColumn);
22187
22439
  if (state === "inserted")
22188
22440
  result.inserted += 1;
@@ -22196,17 +22448,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
22196
22448
  }
22197
22449
  }
22198
22450
  };
22199
- applyRows("projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
22200
- applyRows("agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
22201
- applyRows("task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
22202
- applyRows("plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
22203
- applyRows("task_templates", TEMPLATE_COLUMNS, snapshot.templates);
22204
- applyRows("tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
22451
+ applyRows("projects", "projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
22452
+ applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
22453
+ applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
22454
+ applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
22455
+ applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
22456
+ applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
22457
+ applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
22205
22458
  if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
22206
22459
  replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
22207
22460
  }
22208
22461
  });
22209
- applyRows("task_history", AUDIT_COLUMNS, snapshot.auditHistory);
22462
+ applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
22463
+ applyTombstones(d, snapshot.tombstones ?? [], result);
22210
22464
  return result;
22211
22465
  }
22212
22466
  function upsertById(db, table, columns, row, updateClockColumn) {
@@ -22261,6 +22515,85 @@ function sortedTasks2(tasks) {
22261
22515
  visit(task2);
22262
22516
  return result;
22263
22517
  }
22518
+ function applyTombstones(db, tombstones, result) {
22519
+ for (const tombstone of tombstones) {
22520
+ try {
22521
+ recordStorageTombstone({
22522
+ object_type: tombstone.object_type,
22523
+ object_id: tombstone.object_id,
22524
+ deleted_at: tombstone.deleted_at,
22525
+ source_machine_id: tombstone.source_machine_id ?? null,
22526
+ payload: tombstone.payload ?? null,
22527
+ version: tombstone.version ?? null
22528
+ }, db);
22529
+ const table = tableForTombstone(tombstone.object_type);
22530
+ const existing = existingClock(db, table, tombstone.object_id);
22531
+ if (!shouldApplyStorageTombstone(tombstone, existing)) {
22532
+ result.skipped += 1;
22533
+ continue;
22534
+ }
22535
+ const deletedTags = table === "tasks" ? db.run("DELETE FROM task_tags WHERE task_id = ?", [tombstone.object_id]).changes : 0;
22536
+ const deleted = db.run(`DELETE FROM ${table} WHERE id = ?`, [tombstone.object_id]).changes;
22537
+ if (deleted > 0 || deletedTags > 0)
22538
+ result.deleted = (result.deleted ?? 0) + 1;
22539
+ else
22540
+ result.skipped += 1;
22541
+ } catch (error) {
22542
+ result.errors.push(error instanceof Error ? error.message : String(error));
22543
+ }
22544
+ }
22545
+ }
22546
+ function tableForTombstone(objectType) {
22547
+ if (objectType === "tasks")
22548
+ return "tasks";
22549
+ if (objectType === "projects")
22550
+ return "projects";
22551
+ if (objectType === "project_machine_paths")
22552
+ return "project_machine_paths";
22553
+ if (objectType === "plans")
22554
+ return "plans";
22555
+ if (objectType === "agents")
22556
+ return "agents";
22557
+ if (objectType === "task_lists")
22558
+ return "task_lists";
22559
+ if (objectType === "templates")
22560
+ return "task_templates";
22561
+ return "task_history";
22562
+ }
22563
+ function listRows(db, table, columns) {
22564
+ return db.query(`SELECT ${columns.join(", ")} FROM ${table} ORDER BY id`).all();
22565
+ }
22566
+ function listProjectMachinePaths(db) {
22567
+ return listRows(db, "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS).map((row) => ({
22568
+ id: String(row.id),
22569
+ project_id: String(row.project_id),
22570
+ machine_id: String(row.machine_id),
22571
+ path: String(row.path),
22572
+ created_at: String(row.created_at),
22573
+ updated_at: String(row.updated_at)
22574
+ }));
22575
+ }
22576
+ function existingClock(db, table, id) {
22577
+ const clockColumns = clockColumnsForTable(table);
22578
+ const row = db.query(`SELECT ${clockColumns.join(", ")} FROM ${table} WHERE id = ?`).get(id);
22579
+ return row?.updated_at ?? row?.last_seen_at ?? row?.created_at ?? null;
22580
+ }
22581
+ function rowClock(row, updateClockColumn) {
22582
+ const value = updateClockColumn ? row[updateClockColumn] : null;
22583
+ return stringClock(value) ?? stringClock(row["updated_at"]) ?? stringClock(row["last_seen_at"]) ?? stringClock(row["created_at"]);
22584
+ }
22585
+ function stringClock(value) {
22586
+ return typeof value === "string" && value ? value : null;
22587
+ }
22588
+ function clockColumnsForTable(table) {
22589
+ if (table === "agents")
22590
+ return ["last_seen_at", "created_at"];
22591
+ if (table === "task_templates")
22592
+ return ["created_at"];
22593
+ if (table === "task_history")
22594
+ return ["created_at"];
22595
+ return ["updated_at", "created_at"];
22596
+ }
22264
22597
 
22265
22598
  // src/storage/local-sqlite.ts
22266
22599
  function createLocalSqliteTodosStorageAdapter(options = {}) {
@@ -22427,7 +22760,7 @@ class PostgresTodosSyncStore {
22427
22760
  }
22428
22761
  async pullSnapshot(options = {}) {
22429
22762
  const params = [this.service];
22430
- const filters = ["service = $1", "deleted_at IS NULL"];
22763
+ const filters = ["service = $1"];
22431
22764
  if (options.since) {
22432
22765
  params.push(options.since);
22433
22766
  filters.push(`updated_at > $${params.length}::timestamptz`);
@@ -22436,7 +22769,7 @@ class PostgresTodosSyncStore {
22436
22769
  params.push(options.objectTypes);
22437
22770
  filters.push(`object_type = ANY($${params.length}::text[])`);
22438
22771
  }
22439
- const response = await this.client.query(`SELECT object_type, payload FROM ${this.tableName}
22772
+ const response = await this.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version FROM ${this.tableName}
22440
22773
  WHERE ${filters.join(" AND ")}
22441
22774
  ORDER BY updated_at ASC, object_type ASC, object_id ASC`, params);
22442
22775
  return rowsToSnapshot(response.rows);
@@ -22460,11 +22793,20 @@ function snapshotEntries(snapshot) {
22460
22793
  return [
22461
22794
  ...snapshot.tasks.map((payload) => entry("tasks", payload, snapshot.exportedAt)),
22462
22795
  ...snapshot.projects.map((payload) => entry("projects", payload, snapshot.exportedAt)),
22796
+ ...(snapshot.projectMachinePaths ?? []).map((payload) => entry("project_machine_paths", payload, snapshot.exportedAt)),
22463
22797
  ...snapshot.plans.map((payload) => entry("plans", payload, snapshot.exportedAt)),
22464
22798
  ...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
22465
22799
  ...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
22466
22800
  ...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
22467
- ...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt))
22801
+ ...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
22802
+ ...(snapshot.tombstones ?? []).map((tombstone) => ({
22803
+ type: tombstone.object_type,
22804
+ id: tombstone.object_id,
22805
+ payload: tombstone.payload ?? { id: tombstone.object_id, deleted_at: tombstone.deleted_at },
22806
+ updatedAt: tombstone.updated_at || tombstone.deleted_at,
22807
+ deletedAt: tombstone.deleted_at,
22808
+ version: tombstone.version ?? null
22809
+ }))
22468
22810
  ];
22469
22811
  }
22470
22812
  function entry(type, payload, fallbackUpdatedAt) {
@@ -22486,19 +22828,38 @@ function rowsToSnapshot(rows) {
22486
22828
  source: "postgres",
22487
22829
  tasks: [],
22488
22830
  projects: [],
22831
+ projectMachinePaths: [],
22489
22832
  plans: [],
22490
22833
  agents: [],
22491
22834
  taskLists: [],
22492
22835
  templates: [],
22493
- auditHistory: []
22836
+ auditHistory: [],
22837
+ tombstones: []
22494
22838
  };
22495
22839
  for (const row of rows) {
22496
22840
  const payload = payloadRecord(row.payload);
22841
+ const deletedAt = stringValue(row.deleted_at);
22842
+ if (deletedAt) {
22843
+ snapshot.tombstones ??= [];
22844
+ snapshot.tombstones.push({
22845
+ object_type: row.object_type,
22846
+ object_id: stringValue(row.object_id) ?? stringValue(payload["id"]) ?? "",
22847
+ deleted_at: deletedAt,
22848
+ updated_at: stringValue(row.updated_at) ?? deletedAt,
22849
+ source_machine_id: stringValue(row.source_machine_id),
22850
+ payload,
22851
+ version: numberValue2(row.version)
22852
+ });
22853
+ continue;
22854
+ }
22497
22855
  if (row.object_type === "tasks")
22498
22856
  snapshot.tasks.push(payload);
22499
22857
  else if (row.object_type === "projects")
22500
22858
  snapshot.projects.push(payload);
22501
- else if (row.object_type === "plans")
22859
+ else if (row.object_type === "project_machine_paths") {
22860
+ snapshot.projectMachinePaths ??= [];
22861
+ snapshot.projectMachinePaths.push(payload);
22862
+ } else if (row.object_type === "plans")
22502
22863
  snapshot.plans.push(payload);
22503
22864
  else if (row.object_type === "agents")
22504
22865
  snapshot.agents.push(payload);
@@ -22519,6 +22880,8 @@ function payloadRecord(value) {
22519
22880
  throw new Error("Postgres sync payload must be a JSON object");
22520
22881
  }
22521
22882
  function stringValue(value) {
22883
+ if (value instanceof Date)
22884
+ return value.toISOString();
22522
22885
  return typeof value === "string" && value ? value : null;
22523
22886
  }
22524
22887
  function numberValue2(value) {
@@ -22673,6 +23036,9 @@ class PostgresJsonRecordStore {
22673
23036
  this.tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
22674
23037
  this.cursorTableName = options.cursorTableName ?? DEFAULT_TODOS_POSTGRES_CURSOR_TABLE;
22675
23038
  }
23039
+ machineId(context) {
23040
+ return context?.requestId ?? this.sourceMachineId ?? null;
23041
+ }
22676
23042
  async ensureSchema() {
22677
23043
  this.schemaReady ??= (async () => {
22678
23044
  for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
@@ -22704,6 +23070,25 @@ class PostgresJsonRecordStore {
22704
23070
  updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
22705
23071
  }));
22706
23072
  }
23073
+ async listTombstones() {
23074
+ await this.ensureSchema();
23075
+ const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
23076
+ FROM ${this.tableName}
23077
+ WHERE service = $1 AND deleted_at IS NOT NULL
23078
+ ORDER BY updated_at ASC, object_type ASC, object_id ASC`, [this.service]);
23079
+ return result.rows.map((row) => {
23080
+ const deletedAt = stringValue2(row.deleted_at) ?? stringValue2(row.updated_at) ?? new Date().toISOString();
23081
+ return {
23082
+ object_type: row.object_type,
23083
+ object_id: row.object_id,
23084
+ deleted_at: deletedAt,
23085
+ updated_at: stringValue2(row.updated_at) ?? deletedAt,
23086
+ source_machine_id: stringValue2(row.source_machine_id),
23087
+ payload: payloadRecord2(row.payload),
23088
+ version: numberValue3(row.version)
23089
+ };
23090
+ });
23091
+ }
22707
23092
  async upsert(type, value, context = {}) {
22708
23093
  await this.ensureSchema();
22709
23094
  const updatedAt = stringValue2(value.updated_at) ?? stringValue2(value.created_at) ?? new Date().toISOString();
@@ -22716,7 +23101,8 @@ class PostgresJsonRecordStore {
22716
23101
  updated_at = EXCLUDED.updated_at,
22717
23102
  deleted_at = NULL,
22718
23103
  source_machine_id = EXCLUDED.source_machine_id,
22719
- version = EXCLUDED.version`, [
23104
+ version = EXCLUDED.version
23105
+ WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
22720
23106
  this.service,
22721
23107
  type,
22722
23108
  value.id,
@@ -22733,13 +23119,58 @@ class PostgresJsonRecordStore {
22733
23119
  if (!existing)
22734
23120
  return false;
22735
23121
  const timestamp2 = new Date().toISOString();
22736
- await this.options.client.query(`UPDATE ${this.tableName}
22737
- SET deleted_at = $4::timestamptz,
22738
- updated_at = $4::timestamptz,
22739
- source_machine_id = $5
22740
- WHERE service = $1 AND object_type = $2 AND object_id = $3`, [this.service, type, id, timestamp2, context.requestId ?? this.sourceMachineId ?? null]);
23122
+ return this.tombstone({
23123
+ object_type: type,
23124
+ object_id: id,
23125
+ deleted_at: timestamp2,
23126
+ updated_at: timestamp2,
23127
+ payload: existing,
23128
+ version: numberValue3(existing["version"])
23129
+ }, context);
23130
+ }
23131
+ async tombstone(tombstone, context = {}) {
23132
+ await this.ensureSchema();
23133
+ const deletedAt = stringValue2(tombstone.deleted_at) ?? new Date().toISOString();
23134
+ const updatedAt = stringValue2(tombstone.updated_at) ?? deletedAt;
23135
+ const existing = await this.clock(tombstone.object_type, tombstone.object_id);
23136
+ if (existing && compareClock(existing.updatedAt, updatedAt) > 0)
23137
+ return false;
23138
+ await this.options.client.query(`INSERT INTO ${this.tableName} (
23139
+ service, object_type, object_id, payload, updated_at,
23140
+ deleted_at, source_machine_id, version
23141
+ ) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, $6::timestamptz, $7, $8)
23142
+ ON CONFLICT (service, object_type, object_id) DO UPDATE SET
23143
+ payload = EXCLUDED.payload,
23144
+ updated_at = EXCLUDED.updated_at,
23145
+ deleted_at = EXCLUDED.deleted_at,
23146
+ source_machine_id = EXCLUDED.source_machine_id,
23147
+ version = EXCLUDED.version
23148
+ WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
23149
+ this.service,
23150
+ tombstone.object_type,
23151
+ tombstone.object_id,
23152
+ JSON.stringify(tombstone.payload ?? { id: tombstone.object_id, deleted_at: deletedAt }),
23153
+ updatedAt,
23154
+ deletedAt,
23155
+ tombstone.source_machine_id ?? context.requestId ?? this.sourceMachineId ?? null,
23156
+ tombstone.version ?? null
23157
+ ]);
22741
23158
  return true;
22742
23159
  }
23160
+ async clock(type, id) {
23161
+ await this.ensureSchema();
23162
+ const result = await this.options.client.query(`SELECT object_type, object_id, updated_at, deleted_at
23163
+ FROM ${this.tableName}
23164
+ WHERE service = $1 AND object_type = $2 AND object_id = $3
23165
+ LIMIT 1`, [this.service, type, id]);
23166
+ const row = result.rows[0];
23167
+ if (!row)
23168
+ return null;
23169
+ return {
23170
+ updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString(),
23171
+ deletedAt: stringValue2(row.deleted_at)
23172
+ };
23173
+ }
22743
23174
  async getCursor(name) {
22744
23175
  await this.ensureSchema();
22745
23176
  const result = await this.options.client.query(`SELECT value FROM ${this.cursorTableName} WHERE service = $1 AND cursor_name = $2`, [this.service, name]);
@@ -22808,7 +23239,10 @@ async function createTask2(input, store, context) {
22808
23239
  runner_started_at: null,
22809
23240
  runner_completed_at: null,
22810
23241
  current_step: null,
22811
- total_steps: null
23242
+ total_steps: null,
23243
+ machine_id: store.machineId(context),
23244
+ synced_at: null,
23245
+ archived_at: null
22812
23246
  };
22813
23247
  await store.upsert("tasks", task2, context);
22814
23248
  await logTaskChange2(task2.id, "created", "status", null, task2.status, task2.assigned_by ?? task2.agent_id, store, context);
@@ -22933,7 +23367,9 @@ async function createProject2(input, store, context) {
22933
23367
  task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
22934
23368
  task_counter: 0,
22935
23369
  created_at: timestamp2,
22936
- updated_at: timestamp2
23370
+ updated_at: timestamp2,
23371
+ machine_id: store.machineId(context),
23372
+ synced_at: null
22937
23373
  };
22938
23374
  return store.upsert("projects", project, context);
22939
23375
  }
@@ -22953,7 +23389,9 @@ async function createPlan2(input, store, context) {
22953
23389
  description: input.description ?? null,
22954
23390
  status: input.status ?? "active",
22955
23391
  created_at: timestamp2,
22956
- updated_at: timestamp2
23392
+ updated_at: timestamp2,
23393
+ machine_id: store.machineId(context),
23394
+ synced_at: null
22957
23395
  }, context);
22958
23396
  }
22959
23397
  async function updatePlan2(id, input, store) {
@@ -22983,7 +23421,9 @@ async function registerAgent2(input, store, context) {
22983
23421
  last_seen_at: timestamp2,
22984
23422
  session_id: input.session_id ?? context?.sessionId ?? existing?.session_id ?? null,
22985
23423
  working_dir: input.working_dir ?? existing?.working_dir ?? null,
22986
- active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null
23424
+ active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null,
23425
+ machine_id: existing?.machine_id ?? store.machineId(context),
23426
+ synced_at: existing?.synced_at ?? null
22987
23427
  };
22988
23428
  return store.upsert("agents", agent, context);
22989
23429
  }
@@ -23010,7 +23450,9 @@ async function createTaskList2(input, store, context) {
23010
23450
  description: input.description ?? null,
23011
23451
  metadata: input.metadata ?? {},
23012
23452
  created_at: timestamp2,
23013
- updated_at: timestamp2
23453
+ updated_at: timestamp2,
23454
+ machine_id: store.machineId(context),
23455
+ synced_at: null
23014
23456
  }, context);
23015
23457
  }
23016
23458
  async function updateTaskList2(id, input, store) {
@@ -23036,7 +23478,9 @@ async function createTemplate2(input, store, context) {
23036
23478
  project_id: input.project_id ?? context?.projectId ?? null,
23037
23479
  plan_id: input.plan_id ?? null,
23038
23480
  metadata: input.metadata ?? {},
23039
- created_at: timestamp2
23481
+ created_at: timestamp2,
23482
+ machine_id: store.machineId(context),
23483
+ synced_at: null
23040
23484
  }, context);
23041
23485
  }
23042
23486
  async function updateTemplate2(id, input, store) {
@@ -23061,7 +23505,8 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
23061
23505
  old_value: oldValue ?? null,
23062
23506
  new_value: newValue ?? null,
23063
23507
  agent_id: agentId ?? context?.agentId ?? null,
23064
- created_at: new Date().toISOString()
23508
+ created_at: new Date().toISOString(),
23509
+ machine_id: store.machineId(context)
23065
23510
  };
23066
23511
  return store.upsert("audit_history", entry2, context);
23067
23512
  }
@@ -23084,18 +23529,21 @@ async function exportSnapshot(store) {
23084
23529
  source: "postgres",
23085
23530
  tasks: await store.list("tasks"),
23086
23531
  projects: await store.list("projects"),
23532
+ projectMachinePaths: await store.list("project_machine_paths"),
23087
23533
  plans: await store.list("plans"),
23088
23534
  agents: await store.list("agents"),
23089
23535
  taskLists: await store.list("task_lists"),
23090
23536
  templates: await store.list("templates"),
23091
- auditHistory: await store.list("audit_history")
23537
+ auditHistory: await store.list("audit_history"),
23538
+ tombstones: await store.listTombstones()
23092
23539
  };
23093
23540
  }
23094
23541
  async function importSnapshot(snapshot, store, context) {
23095
- const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
23542
+ const result = { inserted: 0, updated: 0, deleted: 0, skipped: 0, errors: [] };
23096
23543
  const entries = [
23097
23544
  ...snapshot.tasks.map((row) => ["tasks", row]),
23098
23545
  ...snapshot.projects.map((row) => ["projects", row]),
23546
+ ...(snapshot.projectMachinePaths ?? []).map((row) => ["project_machine_paths", row]),
23099
23547
  ...snapshot.plans.map((row) => ["plans", row]),
23100
23548
  ...snapshot.agents.map((row) => ["agents", row]),
23101
23549
  ...snapshot.taskLists.map((row) => ["task_lists", row]),
@@ -23114,6 +23562,25 @@ async function importSnapshot(snapshot, store, context) {
23114
23562
  result.errors.push(error instanceof Error ? error.message : String(error));
23115
23563
  }
23116
23564
  }
23565
+ for (const tombstone of snapshot.tombstones ?? []) {
23566
+ try {
23567
+ const deleted = await store.tombstone({
23568
+ object_type: tombstone.object_type,
23569
+ object_id: tombstone.object_id,
23570
+ deleted_at: tombstone.deleted_at,
23571
+ updated_at: tombstone.updated_at,
23572
+ source_machine_id: tombstone.source_machine_id ?? null,
23573
+ payload: tombstone.payload ?? null,
23574
+ version: tombstone.version ?? null
23575
+ }, context);
23576
+ if (deleted)
23577
+ result.deleted = (result.deleted ?? 0) + 1;
23578
+ else
23579
+ result.skipped += 1;
23580
+ } catch (error) {
23581
+ result.errors.push(error instanceof Error ? error.message : String(error));
23582
+ }
23583
+ }
23117
23584
  return result;
23118
23585
  }
23119
23586
  async function requireRecord(type, id, store) {
@@ -23196,8 +23663,17 @@ function payloadRecord2(value) {
23196
23663
  throw new Error("Postgres storage payload must be a JSON object");
23197
23664
  }
23198
23665
  function stringValue2(value) {
23666
+ if (value instanceof Date)
23667
+ return value.toISOString();
23199
23668
  return typeof value === "string" && value ? value : null;
23200
23669
  }
23670
+ function compareClock(left, right) {
23671
+ const leftClock = Date.parse(left);
23672
+ const rightClock = Date.parse(right);
23673
+ if (Number.isNaN(leftClock) || Number.isNaN(rightClock))
23674
+ return left.localeCompare(right);
23675
+ return leftClock - rightClock;
23676
+ }
23201
23677
  function numberValue3(value) {
23202
23678
  return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
23203
23679
  }
@@ -24132,7 +24608,7 @@ class TodosClient {
24132
24608
  return this._fetchWithRetry(path, { method: "DELETE" });
24133
24609
  }
24134
24610
  _sleep(ms) {
24135
- return new Promise((resolve9) => setTimeout(resolve9, ms));
24611
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
24136
24612
  }
24137
24613
  async getHealth() {
24138
24614
  return this._get("/api/health");
@@ -25514,7 +25990,7 @@ function renderRetrospectiveMarkdown(record) {
25514
25990
  // src/lib/project-bootstrap.ts
25515
25991
  init_database();
25516
25992
  import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
25517
- import { basename as basename2, dirname as dirname7, resolve as resolve9 } from "path";
25993
+ import { basename as basename2, dirname as dirname7, resolve as resolve10 } from "path";
25518
25994
  function safeStat(path) {
25519
25995
  try {
25520
25996
  return statSync3(path);
@@ -25523,7 +25999,7 @@ function safeStat(path) {
25523
25999
  }
25524
26000
  }
25525
26001
  function canonicalPath(input) {
25526
- const resolved = resolve9(input);
26002
+ const resolved = resolve10(input);
25527
26003
  const stats2 = safeStat(resolved);
25528
26004
  if (stats2?.isFile())
25529
26005
  return dirname7(resolved);
@@ -25532,7 +26008,7 @@ function canonicalPath(input) {
25532
26008
  function findUp(start, marker) {
25533
26009
  let current = canonicalPath(start);
25534
26010
  while (true) {
25535
- if (existsSync9(resolve9(current, marker)))
26011
+ if (existsSync9(resolve10(current, marker)))
25536
26012
  return current;
25537
26013
  const parent = dirname7(current);
25538
26014
  if (parent === current)
@@ -25543,7 +26019,7 @@ function findUp(start, marker) {
25543
26019
  function readPackageJson2(path) {
25544
26020
  if (!path)
25545
26021
  return null;
25546
- const file = resolve9(path, "package.json");
26022
+ const file = resolve10(path, "package.json");
25547
26023
  if (!existsSync9(file))
25548
26024
  return null;
25549
26025
  try {
@@ -25566,7 +26042,7 @@ function workspaceMarker(root, rootPackage) {
25566
26042
  if (rootPackage?.workspaces)
25567
26043
  markers.push("package.json#workspaces");
25568
26044
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
25569
- if (existsSync9(resolve9(root, marker)))
26045
+ if (existsSync9(resolve10(root, marker)))
25570
26046
  markers.push(marker);
25571
26047
  }
25572
26048
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -25848,6 +26324,8 @@ function pointerPatch(previous, input, key) {
25848
26324
  if (!Object.prototype.hasOwnProperty.call(input, key))
25849
26325
  return previous;
25850
26326
  const value = input[key];
26327
+ if (value === undefined)
26328
+ return previous;
25851
26329
  return typeof value === "string" && value.trim() ? value : undefined;
25852
26330
  }
25853
26331
  // src/db/api-keys.ts
@@ -26915,7 +27393,7 @@ function classifyLog(text) {
26915
27393
  async function sleep2(ms) {
26916
27394
  if (ms <= 0)
26917
27395
  return;
26918
- await new Promise((resolve10) => setTimeout(resolve10, ms));
27396
+ await new Promise((resolve11) => setTimeout(resolve11, ms));
26919
27397
  }
26920
27398
  async function runCommandProvider(provider, input) {
26921
27399
  const commandTemplate = input.command || provider.command;
@@ -27271,11 +27749,11 @@ function writeVerificationExport(bundle, path) {
27271
27749
  writeFileSync7(path, JSON.stringify(bundle, null, 2), "utf8");
27272
27750
  }
27273
27751
  // src/lib/policy-packs.ts
27274
- import { relative as relative3, resolve as resolve10 } from "path";
27752
+ import { relative as relative3, resolve as resolve11 } from "path";
27275
27753
  init_database();
27276
27754
  init_config();
27277
27755
  function normalizePath3(path) {
27278
- return resolve10(path);
27756
+ return resolve11(path);
27279
27757
  }
27280
27758
  function unique4(values) {
27281
27759
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -27330,7 +27808,7 @@ function commandMatches(commands, pattern) {
27330
27808
  }
27331
27809
  function pathMatches(paths, pattern, root) {
27332
27810
  return paths.filter((path) => {
27333
- const candidate = path.startsWith("/") ? path : resolve10(root, path);
27811
+ const candidate = path.startsWith("/") ? path : resolve11(root, path);
27334
27812
  if (!isPathInside3(root, candidate))
27335
27813
  return matchesPattern3(path, pattern);
27336
27814
  return matchesPattern3(path, pattern) || matchesPattern3(relative3(root, candidate), pattern);
@@ -28042,7 +28520,7 @@ function resetAgentAdapterCache() {}
28042
28520
  // src/lib/git-traceability.ts
28043
28521
  import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
28044
28522
  import { spawnSync as spawnSync2 } from "child_process";
28045
- import { resolve as resolve11 } from "path";
28523
+ import { resolve as resolve12 } from "path";
28046
28524
  var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
28047
28525
  function runGit(args, cwd) {
28048
28526
  const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
@@ -28085,7 +28563,7 @@ function inspectGitCommit(sha, cwd) {
28085
28563
  };
28086
28564
  }
28087
28565
  function loadCiSnapshot(path) {
28088
- const target = path ? resolve11(path) : resolve11(process.cwd(), ".todos", "ci-snapshot.json");
28566
+ const target = path ? resolve12(path) : resolve12(process.cwd(), ".todos", "ci-snapshot.json");
28089
28567
  if (!existsSync13(target))
28090
28568
  return null;
28091
28569
  try {
@@ -28183,7 +28661,7 @@ function formatTraceabilityReport(report) {
28183
28661
  }
28184
28662
  // src/lib/mention-resolver.ts
28185
28663
  import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
28186
- import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as resolve12, sep } from "path";
28664
+ import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as resolve13, sep as sep2 } from "path";
28187
28665
  init_database();
28188
28666
  var PREFIXES = {
28189
28667
  file: "file",
@@ -28259,11 +28737,11 @@ function backlink(kind, key, label, target = key) {
28259
28737
  return { kind, key, label, target };
28260
28738
  }
28261
28739
  function normalizeWorkspace(workspace) {
28262
- return resolve12(workspace || process.cwd());
28740
+ return resolve13(workspace || process.cwd());
28263
28741
  }
28264
28742
  function isInside(root, absolutePath) {
28265
28743
  const rel = relative4(root, absolutePath);
28266
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep}`) && !isAbsolute(rel);
28744
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
28267
28745
  }
28268
28746
  function normalizeRelativePath(value) {
28269
28747
  const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
@@ -28327,7 +28805,7 @@ function resolveFile(parsed, workspace) {
28327
28805
  resolution.warnings.push("path is empty or escapes the workspace");
28328
28806
  return resolution;
28329
28807
  }
28330
- const absolutePath = resolve12(workspace, relPath);
28808
+ const absolutePath = resolve13(workspace, relPath);
28331
28809
  if (!isInside(workspace, absolutePath)) {
28332
28810
  resolution.path = relPath;
28333
28811
  resolution.warnings.push("path escapes the workspace");
@@ -29263,7 +29741,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
29263
29741
  if (action === "approved" || action === "rejected" || action === "expired") {
29264
29742
  emitLocalEventHooksQuiet({
29265
29743
  type: "approval.decided",
29266
- payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
29744
+ payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
29745
+ databasePath: databasePathFromDatabase(db)
29267
29746
  });
29268
29747
  }
29269
29748
  }
@@ -31913,14 +32392,14 @@ function renderReleaseNotesMarkdown(document) {
31913
32392
  init_database();
31914
32393
  init_migrations();
31915
32394
  import { existsSync as existsSync19, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
31916
- import { dirname as dirname11, join as join15, resolve as resolve13 } from "path";
32395
+ import { dirname as dirname11, join as join15, resolve as resolve14 } from "path";
31917
32396
  import { Database as Database3 } from "bun:sqlite";
31918
32397
  var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
31919
32398
  function resolveDbPath(dbPath) {
31920
32399
  if (dbPath)
31921
- return resolve13(dbPath);
32400
+ return resolve14(dbPath);
31922
32401
  if (process.env["TODOS_DB_PATH"] && process.env["TODOS_DB_PATH"] !== ":memory:") {
31923
- return resolve13(process.env["TODOS_DB_PATH"]);
32402
+ return resolve14(process.env["TODOS_DB_PATH"]);
31924
32403
  }
31925
32404
  const db = getDatabase();
31926
32405
  const filename = db.filename;
@@ -31959,7 +32438,7 @@ function restoreDatabase(backupPath, targetPath) {
31959
32438
  if (!integrity.ok) {
31960
32439
  throw new Error(`Backup failed integrity check: ${integrity.errors.join("; ")}`);
31961
32440
  }
31962
- const target = targetPath ? resolve13(targetPath) : resolveDbPath();
32441
+ const target = targetPath ? resolve14(targetPath) : resolveDbPath();
31963
32442
  mkdirSync12(dirname11(target), { recursive: true });
31964
32443
  const staging = `${target}.restore.tmp`;
31965
32444
  copyFileSync(backupPath, staging);
@@ -31978,7 +32457,7 @@ function restoreDatabase(backupPath, targetPath) {
31978
32457
  };
31979
32458
  }
31980
32459
  function checkDatabaseIntegrity(dbPath) {
31981
- const path = dbPath ? resolve13(dbPath) : resolveDbPath();
32460
+ const path = dbPath ? resolve14(dbPath) : resolveDbPath();
31982
32461
  const errors = [];
31983
32462
  if (!existsSync19(path)) {
31984
32463
  return {
@@ -32043,7 +32522,7 @@ function checkDatabaseIntegrity(dbPath) {
32043
32522
  };
32044
32523
  }
32045
32524
  function compactDatabase(dbPath) {
32046
- const path = dbPath ? resolve13(dbPath) : resolveDbPath();
32525
+ const path = dbPath ? resolve14(dbPath) : resolveDbPath();
32047
32526
  const before = statSync6(path).size;
32048
32527
  const db = new Database3(path);
32049
32528
  db.exec("VACUUM");
@@ -32053,7 +32532,7 @@ function compactDatabase(dbPath) {
32053
32532
  return { path, bytes_before: before, bytes_after: after };
32054
32533
  }
32055
32534
  function migrationDryRun(dbPath) {
32056
- const path = dbPath ? resolve13(dbPath) : resolveDbPath();
32535
+ const path = dbPath ? resolve14(dbPath) : resolveDbPath();
32057
32536
  const db = new Database3(path, { readonly: true });
32058
32537
  let current = 0;
32059
32538
  try {
@@ -32077,7 +32556,7 @@ function migrationDryRun(dbPath) {
32077
32556
  };
32078
32557
  }
32079
32558
  function defaultBackupPath(dbPath) {
32080
- const base = dbPath ? dirname11(resolve13(dbPath)) : dirname11(resolveDbPath());
32559
+ const base = dbPath ? dirname11(resolve14(dbPath)) : dirname11(resolveDbPath());
32081
32560
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
32082
32561
  return join15(base, "backups", `todos-${stamp}.db`);
32083
32562
  }
@@ -35741,8 +36220,8 @@ todos machines topology # full diagnostic report
35741
36220
  import { createHash as createHash12 } from "crypto";
35742
36221
  import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
35743
36222
  import { hostname as hostname3, platform, arch } from "os";
35744
- import { dirname as dirname15, join as join17, resolve as resolve14 } from "path";
35745
- import { tmpdir as tmpdir2 } from "os";
36223
+ import { dirname as dirname15, join as join17, resolve as resolve15 } from "path";
36224
+ import { tmpdir as tmpdir3 } from "os";
35746
36225
  init_database();
35747
36226
  init_redaction();
35748
36227
  init_sync_utils();
@@ -35874,15 +36353,15 @@ function commandEnv(env, includeValues) {
35874
36353
  function defaultSnapshotDir() {
35875
36354
  const dbPath = getDatabasePath();
35876
36355
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
35877
- return join17(tmpdir2(), "hasna-todos", "environment-snapshots");
35878
- return join17(dirname15(resolve14(dbPath)), "environment-snapshots");
36356
+ return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
36357
+ return join17(dirname15(resolve15(dbPath)), "environment-snapshots");
35879
36358
  }
35880
36359
  function snapshotWithId(snapshot) {
35881
36360
  const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
35882
36361
  return { id: `env_${digest}`, ...snapshot };
35883
36362
  }
35884
36363
  function captureEnvironmentSnapshot(input = {}) {
35885
- const root = resolve14(input.root || process.cwd());
36364
+ const root = resolve15(input.root || process.cwd());
35886
36365
  const env = input.env || process.env;
35887
36366
  const warnings = [];
35888
36367
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -35922,13 +36401,13 @@ function captureEnvironmentSnapshot(input = {}) {
35922
36401
  });
35923
36402
  }
35924
36403
  function writeEnvironmentSnapshot(snapshot, outputPath) {
35925
- const path = outputPath ? resolve14(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
36404
+ const path = outputPath ? resolve15(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
35926
36405
  ensureDir2(dirname15(path));
35927
36406
  writeJsonFile(path, snapshot);
35928
36407
  return path;
35929
36408
  }
35930
36409
  function readEnvironmentSnapshot(path) {
35931
- const snapshot = readJsonFile(resolve14(path));
36410
+ const snapshot = readJsonFile(resolve15(path));
35932
36411
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
35933
36412
  throw new Error(`Invalid environment snapshot: ${path}`);
35934
36413
  }
@@ -37667,7 +38146,7 @@ function listLinkedTemplates(db, cwd) {
37667
38146
  init_database();
37668
38147
  import { mkdtempSync } from "fs";
37669
38148
  import { join as join21 } from "path";
37670
- import { tmpdir as tmpdir3 } from "os";
38149
+ import { tmpdir as tmpdir4 } from "os";
37671
38150
  var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
37672
38151
  var DEMO_DEFAULT_AGENT = "demoagent";
37673
38152
  var DEMO_DEFAULT_PROJECT = "Agent Workflow Demo";
@@ -37682,7 +38161,7 @@ function setupEphemeralDemoDb(options = {}) {
37682
38161
  if (options.db_path) {
37683
38162
  db_path = options.db_path;
37684
38163
  } else if (options.persist) {
37685
- db_path = join21(mkdtempSync(join21(tmpdir3(), "todos-demo-")), "todos.db");
38164
+ db_path = join21(mkdtempSync(join21(tmpdir4(), "todos-demo-")), "todos.db");
37686
38165
  } else {
37687
38166
  db_path = ":memory:";
37688
38167
  }
@@ -38304,11 +38783,6 @@ var ALL_MCP_TOOLS = [
38304
38783
  "template_history",
38305
38784
  "todos_inbox",
38306
38785
  "todos_retro",
38307
- "todos_storage_conflicts",
38308
- "todos_storage_feedback",
38309
- "todos_storage_pull",
38310
- "todos_storage_push",
38311
- "todos_storage_status",
38312
38786
  "trust_workspace",
38313
38787
  "unarchive_agent",
38314
38788
  "unarchive_task",
@@ -38445,12 +38919,6 @@ var MCP_GROUP_DEFS = [
38445
38919
  description: "Sandbox, trust, secrets, crypto, and verification.",
38446
38920
  match: (t) => /sandbox|trust|secret|crypto|verification|policy_pack|redact|scan_/.test(t)
38447
38921
  },
38448
- {
38449
- id: "storage",
38450
- name: "Storage bridge",
38451
- description: "Optional storage sync tools (admin profile).",
38452
- match: (t) => t.startsWith("todos_storage_") || t === "sync_all" || t === "migrate_pg"
38453
- },
38454
38922
  {
38455
38923
  id: "workflow",
38456
38924
  name: "Workflow utilities",
@@ -40541,7 +41009,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
40541
41009
  // src/lib/extract.ts
40542
41010
  import { existsSync as existsSync25, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
40543
41011
  import { createHash as createHash14 } from "crypto";
40544
- import { relative as relative6, resolve as resolve15, join as join24 } from "path";
41012
+ import { relative as relative6, resolve as resolve16, join as join24 } from "path";
40545
41013
  var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
40546
41014
  var DEFAULT_EXTENSIONS = new Set([
40547
41015
  ".ts",
@@ -40611,7 +41079,7 @@ function normalizePathForMatch(value) {
40611
41079
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
40612
41080
  }
40613
41081
  function readGitignorePatterns(basePath) {
40614
- const root = statSync8(basePath).isFile() ? resolve15(basePath, "..") : basePath;
41082
+ const root = statSync8(basePath).isFile() ? resolve16(basePath, "..") : basePath;
40615
41083
  const gitignorePath = join24(root, ".gitignore");
40616
41084
  if (!existsSync25(gitignorePath))
40617
41085
  return [];
@@ -40747,7 +41215,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
40747
41215
  return files.sort();
40748
41216
  }
40749
41217
  function buildCodebaseIndex(options) {
40750
- const basePath = resolve15(options.path);
41218
+ const basePath = resolve16(options.path);
40751
41219
  const tags = options.patterns || [...EXTRACT_TAGS];
40752
41220
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
40753
41221
  const excludes = options.exclude || [];
@@ -40758,7 +41226,7 @@ function buildCodebaseIndex(options) {
40758
41226
  const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
40759
41227
  try {
40760
41228
  const source9 = readFileSync23(fullPath, "utf-8");
40761
- const relPath = statSync8(basePath).isFile() ? relative6(resolve15(basePath, ".."), fullPath) : file;
41229
+ const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
40762
41230
  indexed.push({
40763
41231
  file: relPath,
40764
41232
  checksum: stableHash(source9).slice(0, 24),
@@ -40778,7 +41246,7 @@ function buildCodebaseIndex(options) {
40778
41246
  };
40779
41247
  }
40780
41248
  function extractTodos(options, db) {
40781
- const basePath = resolve15(options.path);
41249
+ const basePath = resolve16(options.path);
40782
41250
  const tags = options.patterns || [...EXTRACT_TAGS];
40783
41251
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
40784
41252
  const excludes = options.exclude || [];
@@ -40789,7 +41257,7 @@ function extractTodos(options, db) {
40789
41257
  const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
40790
41258
  try {
40791
41259
  const source9 = readFileSync23(fullPath, "utf-8");
40792
- const relPath = statSync8(basePath).isFile() ? relative6(resolve15(basePath, ".."), fullPath) : file;
41260
+ const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
40793
41261
  const comments = extractFromSource(source9, relPath, tags);
40794
41262
  allComments.push(...comments);
40795
41263
  } catch {}
@@ -40883,7 +41351,7 @@ async function watchSourceTodos(options, onRun) {
40883
41351
  const interval = Math.max(100, options.interval_ms || 2000);
40884
41352
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
40885
41353
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
40886
- const root = resolve15(options.path);
41354
+ const root = resolve16(options.path);
40887
41355
  const runs = [];
40888
41356
  let previous = new Map;
40889
41357
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -41756,7 +42224,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
41756
42224
  init_config();
41757
42225
  import { createHash as createHash16, createVerify } from "crypto";
41758
42226
  import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
41759
- import { basename as basename5, join as join25, resolve as resolve16 } from "path";
42227
+ import { basename as basename5, join as join25, resolve as resolve17 } from "path";
41760
42228
  init_redaction();
41761
42229
  function isObject2(value) {
41762
42230
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -42036,7 +42504,7 @@ function verifyExtensionSignature(input) {
42036
42504
  return verifier.verify(input.public_key, decodeSignature(input.signature));
42037
42505
  }
42038
42506
  function inspectExtensionSource(source9) {
42039
- const resolved = resolve16(source9);
42507
+ const resolved = resolve17(source9);
42040
42508
  if (!existsSync26(resolved))
42041
42509
  throw new Error(`extension source not found: ${source9}`);
42042
42510
  const stat = statSync9(resolved);
@@ -42134,7 +42602,7 @@ function testExtensionCompatibility(sourceOrManifest) {
42134
42602
  function projectExtensionSources(projectPath) {
42135
42603
  if (!projectPath)
42136
42604
  return [];
42137
- const root = resolve16(projectPath);
42605
+ const root = resolve17(projectPath);
42138
42606
  const candidates = [
42139
42607
  join25(root, "todos.extension.json"),
42140
42608
  join25(root, ".todos", "todos.extension.json")
@@ -42153,7 +42621,7 @@ function projectExtensionSources(projectPath) {
42153
42621
  }
42154
42622
  function discoverLocalExtensions(options = {}) {
42155
42623
  const config = loadConfig();
42156
- const projectPath = options.project_path ? resolve16(options.project_path) : null;
42624
+ const projectPath = options.project_path ? resolve17(options.project_path) : null;
42157
42625
  const configuredSources = [
42158
42626
  ...config.extension_sources || [],
42159
42627
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -42161,7 +42629,7 @@ function discoverLocalExtensions(options = {}) {
42161
42629
  const sources = Array.from(new Set([
42162
42630
  ...configuredSources,
42163
42631
  ...projectExtensionSources(projectPath || undefined)
42164
- ])).map((source9) => projectPath && !source9.startsWith("/") ? resolve16(projectPath, source9) : resolve16(source9));
42632
+ ])).map((source9) => projectPath && !source9.startsWith("/") ? resolve17(projectPath, source9) : resolve17(source9));
42165
42633
  const warnings = [];
42166
42634
  const discovered = [];
42167
42635
  for (const source9 of sources) {