@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/registry.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,
@@ -6068,6 +6098,80 @@ init_config();
6068
6098
  init_types();
6069
6099
  init_database();
6070
6100
  init_machines();
6101
+
6102
+ // src/db/storage-tombstones.ts
6103
+ init_database();
6104
+ init_machines();
6105
+ function recordStorageTombstone(input, db) {
6106
+ const d = db ?? getDatabase();
6107
+ const deletedAt = input.deleted_at ?? now();
6108
+ const machineId = input.source_machine_id ?? currentStorageMachineId(d);
6109
+ d.run(`INSERT INTO storage_tombstones (
6110
+ id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
6111
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
6112
+ ON CONFLICT(object_type, object_id) DO UPDATE SET
6113
+ deleted_at = excluded.deleted_at,
6114
+ updated_at = excluded.updated_at,
6115
+ source_machine_id = excluded.source_machine_id,
6116
+ payload = excluded.payload,
6117
+ version = excluded.version
6118
+ WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
6119
+ uuid(),
6120
+ input.object_type,
6121
+ input.object_id,
6122
+ deletedAt,
6123
+ deletedAt,
6124
+ machineId,
6125
+ input.payload ? JSON.stringify(input.payload) : null,
6126
+ input.version ?? null
6127
+ ]);
6128
+ return getStorageTombstone(input.object_type, input.object_id, d);
6129
+ }
6130
+ function getStorageTombstone(objectType, objectId, db) {
6131
+ const d = db ?? getDatabase();
6132
+ const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
6133
+ return row ? rowToStorageTombstone(row) : null;
6134
+ }
6135
+ function listStorageTombstones(db) {
6136
+ const d = db ?? getDatabase();
6137
+ return d.query("SELECT * FROM storage_tombstones ORDER BY updated_at ASC, object_type ASC, object_id ASC").all().map(rowToStorageTombstone);
6138
+ }
6139
+ function shouldApplyStorageTombstone(tombstone, existingUpdatedAt) {
6140
+ const tombstoneClock = Date.parse(tombstone.updated_at || tombstone.deleted_at);
6141
+ if (!existingUpdatedAt)
6142
+ return true;
6143
+ const existingClock = Date.parse(existingUpdatedAt);
6144
+ if (Number.isNaN(tombstoneClock))
6145
+ return true;
6146
+ if (Number.isNaN(existingClock))
6147
+ return true;
6148
+ return tombstoneClock >= existingClock;
6149
+ }
6150
+ function rowToStorageTombstone(row) {
6151
+ return {
6152
+ ...row,
6153
+ payload: parsePayload(row.payload)
6154
+ };
6155
+ }
6156
+ function parsePayload(value) {
6157
+ if (!value)
6158
+ return null;
6159
+ try {
6160
+ const parsed = JSON.parse(value);
6161
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6162
+ } catch {
6163
+ return null;
6164
+ }
6165
+ }
6166
+ function currentStorageMachineId(db) {
6167
+ try {
6168
+ return getMachineId(db);
6169
+ } catch {
6170
+ return null;
6171
+ }
6172
+ }
6173
+
6174
+ // src/db/projects.ts
6071
6175
  function slugify(name) {
6072
6176
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
6073
6177
  }
@@ -6097,8 +6201,9 @@ function createProject(input, db) {
6097
6201
  const timestamp = now();
6098
6202
  const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
6099
6203
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
6100
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
6101
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
6204
+ const machineId = currentStorageMachineId(d);
6205
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
6206
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
6102
6207
  return getProject(id, d);
6103
6208
  }
6104
6209
  function getProject(id, db) {
@@ -6177,6 +6282,14 @@ function renameProject(id, input, db) {
6177
6282
  }
6178
6283
  function deleteProject(id, db) {
6179
6284
  const d = db || getDatabase();
6285
+ const project = getProject(id, d);
6286
+ if (!project)
6287
+ return false;
6288
+ recordStorageTombstone({
6289
+ object_type: "projects",
6290
+ object_id: id,
6291
+ payload: project
6292
+ }, d);
6180
6293
  const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
6181
6294
  return result.changes > 0;
6182
6295
  }
@@ -6281,6 +6394,14 @@ function listMachineLocalPaths(projectId, db) {
6281
6394
  function removeMachineLocalPath(projectId, machineId, db) {
6282
6395
  const d = db || getDatabase();
6283
6396
  const mid = machineId ?? getMachineId(d);
6397
+ const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
6398
+ if (!existing)
6399
+ return false;
6400
+ recordStorageTombstone({
6401
+ object_type: "project_machine_paths",
6402
+ object_id: existing.id,
6403
+ payload: existing
6404
+ }, d);
6284
6405
  const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
6285
6406
  return result.changes > 0;
6286
6407
  }
@@ -6329,20 +6450,69 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
6329
6450
  }
6330
6451
  }
6331
6452
 
6453
+ // src/lib/event-emission-safety.ts
6454
+ init_database();
6455
+ init_sync_utils();
6456
+ import { tmpdir } from "os";
6457
+ import { resolve as resolve3, sep } from "path";
6458
+ function envFlag(name) {
6459
+ const value = process.env[name];
6460
+ return value === "1" || value === "true" || value === "yes";
6461
+ }
6462
+ function isUnder(parent, child) {
6463
+ const normalizedParent = resolve3(parent);
6464
+ const normalizedChild = resolve3(child);
6465
+ return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
6466
+ }
6467
+ function databasePathFromDatabase(db) {
6468
+ const filename = db?.filename;
6469
+ return typeof filename === "string" && filename.trim() ? filename : undefined;
6470
+ }
6471
+ function isEphemeralTodosDatabase(dbPath) {
6472
+ const resolvedPath = dbPath ?? getDatabasePath();
6473
+ if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
6474
+ return true;
6475
+ return isUnder(tmpdir(), resolvedPath);
6476
+ }
6477
+ function hasExplicitSharedEventsStore() {
6478
+ return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
6479
+ }
6480
+ function usesIsolatedTodosHome() {
6481
+ return isUnder(tmpdir(), getTodosGlobalDir());
6482
+ }
6483
+ function shouldEmitSharedTaskEvents(dbPath) {
6484
+ if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
6485
+ return false;
6486
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
6487
+ return true;
6488
+ if (!isEphemeralTodosDatabase(dbPath))
6489
+ return true;
6490
+ return hasExplicitSharedEventsStore();
6491
+ }
6492
+ function shouldDeliverLocalLifecycleHooks(dbPath) {
6493
+ if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
6494
+ return false;
6495
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
6496
+ return true;
6497
+ if (!isEphemeralTodosDatabase(dbPath))
6498
+ return true;
6499
+ return usesIsolatedTodosHome();
6500
+ }
6501
+
6332
6502
  // src/lib/event-hooks.ts
6333
6503
  init_redaction();
6334
6504
  import { createHash, randomUUID } from "crypto";
6335
6505
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
6336
- import { dirname as dirname4, resolve as resolve5 } from "path";
6506
+ import { dirname as dirname4, resolve as resolve6 } from "path";
6337
6507
  import { createConnection } from "net";
6338
6508
 
6339
6509
  // src/lib/runner-sandbox.ts
6340
6510
  init_config();
6341
- import { relative as relative2, resolve as resolve4 } from "path";
6511
+ import { relative as relative2, resolve as resolve5 } from "path";
6342
6512
 
6343
6513
  // src/lib/workspace-trust.ts
6344
6514
  init_config();
6345
- import { relative, resolve as resolve3 } from "path";
6515
+ import { relative, resolve as resolve4 } from "path";
6346
6516
  var DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
6347
6517
  var DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
6348
6518
  var PRESET_DEFAULTS = {
@@ -6388,7 +6558,7 @@ var PRESET_DEFAULTS = {
6388
6558
  }
6389
6559
  };
6390
6560
  function normalizePath(path) {
6391
- return resolve3(path);
6561
+ return resolve4(path);
6392
6562
  }
6393
6563
  function unique2(values) {
6394
6564
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -6525,7 +6695,7 @@ function checkWorkspacePermission(input = {}) {
6525
6695
  var DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
6526
6696
  var DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
6527
6697
  function normalizePath2(path) {
6528
- return resolve4(path);
6698
+ return resolve5(path);
6529
6699
  }
6530
6700
  function unique3(values) {
6531
6701
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -6871,7 +7041,7 @@ async function deliverHook(hook, envelope) {
6871
7041
  if (hook.target === "stdout") {
6872
7042
  output = line.trim();
6873
7043
  } else if (hook.target === "file") {
6874
- const filePath = resolve5(hook.file_path);
7044
+ const filePath = resolve6(hook.file_path);
6875
7045
  mkdirSync3(dirname4(filePath), { recursive: true });
6876
7046
  appendFileSync(filePath, line);
6877
7047
  } else if (hook.target === "socket") {
@@ -6946,6 +7116,8 @@ async function emitLocalEventHooks(input) {
6946
7116
  return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
6947
7117
  }
6948
7118
  function emitLocalEventHooksQuiet(input) {
7119
+ if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
7120
+ return;
6949
7121
  emitLocalEventHooks(input).catch(() => {});
6950
7122
  }
6951
7123
  async function testLocalEventHook(name, input) {
@@ -7262,7 +7434,7 @@ async function dispatchCommand(event, channel) {
7262
7434
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
7263
7435
  HASNA_EVENT_JSON: eventJson
7264
7436
  };
7265
- return new Promise((resolve6) => {
7437
+ return new Promise((resolve7) => {
7266
7438
  const child = spawn(channel.command.command, channel.command.args ?? [], {
7267
7439
  cwd: channel.command.cwd,
7268
7440
  env,
@@ -7280,7 +7452,7 @@ async function dispatchCommand(event, channel) {
7280
7452
  });
7281
7453
  child.on("error", (error) => {
7282
7454
  clearTimeout(timeout);
7283
- resolve6({
7455
+ resolve7({
7284
7456
  attempt: 1,
7285
7457
  status: "failed",
7286
7458
  startedAt,
@@ -7293,7 +7465,7 @@ async function dispatchCommand(event, channel) {
7293
7465
  child.on("close", (code, signal) => {
7294
7466
  clearTimeout(timeout);
7295
7467
  const success = code === 0;
7296
- resolve6({
7468
+ resolve7({
7297
7469
  attempt: 1,
7298
7470
  status: success ? "success" : "failed",
7299
7471
  startedAt,
@@ -7561,14 +7733,15 @@ function createTaskList(input, db) {
7561
7733
  const id = uuid();
7562
7734
  const timestamp = now();
7563
7735
  const slug = input.slug || slugify(input.name);
7736
+ const machineId = currentStorageMachineId(d);
7564
7737
  if (!input.project_id) {
7565
7738
  const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
7566
7739
  if (existing) {
7567
7740
  throw new Error(`Standalone task list with slug "${slug}" already exists`);
7568
7741
  }
7569
7742
  }
7570
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
7571
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
7743
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
7744
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
7572
7745
  return getTaskList(id, d);
7573
7746
  }
7574
7747
  function getTaskList(id, db) {
@@ -7618,6 +7791,14 @@ function updateTaskList(id, input, db) {
7618
7791
  }
7619
7792
  function deleteTaskList(id, db) {
7620
7793
  const d = db || getDatabase();
7794
+ const list = getTaskList(id, d);
7795
+ if (!list)
7796
+ return false;
7797
+ recordStorageTombstone({
7798
+ object_type: "task_lists",
7799
+ object_id: id,
7800
+ payload: list
7801
+ }, d);
7621
7802
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
7622
7803
  }
7623
7804
  function ensureTaskList(name, slug, projectId, db) {
@@ -7834,6 +8015,8 @@ function readMachineLocalPath(project) {
7834
8015
  }
7835
8016
  }
7836
8017
  async function emitSharedTaskEvent(input) {
8018
+ if (!shouldEmitSharedTaskEvents(input.databasePath))
8019
+ return;
7837
8020
  const data = taskEventData(input.task, input.data);
7838
8021
  await new EventsClient().emit({
7839
8022
  source: SOURCE,
@@ -7858,8 +8041,9 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
7858
8041
  const d = db || getDatabase();
7859
8042
  const id = uuid();
7860
8043
  const timestamp = now();
7861
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
7862
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
8044
+ const machineId = currentStorageMachineId(d);
8045
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
8046
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
7863
8047
  try {
7864
8048
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
7865
8049
  logActivity2({
@@ -7872,7 +8056,7 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
7872
8056
  actor_id: agentId ?? undefined
7873
8057
  }, d);
7874
8058
  } catch {}
7875
- return { id, task_id: taskId, action, field: field2 || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
8059
+ 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 };
7876
8060
  }
7877
8061
  function getTaskHistory(taskId, db) {
7878
8062
  const d = db || getDatabase();
@@ -8221,13 +8405,14 @@ function createTask(input, db) {
8221
8405
  const d = db || getDatabase();
8222
8406
  const timestamp = now();
8223
8407
  const tags = input.tags || [];
8408
+ const machineId = currentStorageMachineId(d);
8224
8409
  const assignedBy = input.assigned_by || input.agent_id;
8225
8410
  const assignedFromProject = input.assigned_from_project || null;
8226
8411
  let id = uuid();
8227
8412
  for (let attempt = 0;attempt < 3; attempt++) {
8228
8413
  try {
8229
- 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)
8230
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8414
+ 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)
8415
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8231
8416
  id,
8232
8417
  null,
8233
8418
  input.project_id || null,
@@ -8264,7 +8449,8 @@ function createTask(input, db) {
8264
8449
  input.spawned_from_session || null,
8265
8450
  assignedBy || null,
8266
8451
  assignedFromProject || null,
8267
- input.task_type || null
8452
+ input.task_type || null,
8453
+ machineId
8268
8454
  ]);
8269
8455
  break;
8270
8456
  } catch (e) {
@@ -8280,9 +8466,10 @@ function createTask(input, db) {
8280
8466
  }
8281
8467
  const task = getTask(id, d);
8282
8468
  const payload = taskEventData(task);
8469
+ const databasePath = databasePathFromDatabase(d);
8283
8470
  dispatchWebhook2("task.created", payload, d).catch(() => {});
8284
- emitLocalEventHooksQuiet({ type: "task.created", payload });
8285
- emitSharedTaskEventQuiet({ type: "task.created", task });
8471
+ emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
8472
+ emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
8286
8473
  return task;
8287
8474
  }
8288
8475
  function getTask(id, db) {
@@ -8700,29 +8887,39 @@ function updateTask(id, input, db) {
8700
8887
  approved_by: input.approved_by ?? task.approved_by,
8701
8888
  approved_at: input.approved_by ? timestamp : task.approved_at
8702
8889
  };
8890
+ const databasePath = databasePathFromDatabase(d);
8703
8891
  if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
8704
8892
  const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
8705
8893
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
8706
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
8707
- emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
8894
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
8895
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
8708
8896
  }
8709
8897
  if (input.status !== undefined && input.status !== task.status) {
8710
8898
  const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
8711
8899
  dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
8712
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
8713
- emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
8900
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
8901
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
8714
8902
  }
8715
8903
  if (input.approved_by !== undefined) {
8716
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
8904
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
8717
8905
  }
8718
8906
  const updatePayload = taskEventData(updatedTask);
8719
8907
  dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
8720
- emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
8721
- emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
8908
+ emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
8909
+ emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
8722
8910
  return updatedTask;
8723
8911
  }
8724
8912
  function deleteTask(id, db) {
8725
8913
  const d = db || getDatabase();
8914
+ const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
8915
+ if (!row)
8916
+ return false;
8917
+ recordStorageTombstone({
8918
+ object_type: "tasks",
8919
+ object_id: id,
8920
+ payload: rowToTask(row),
8921
+ version: row.version
8922
+ }, d);
8726
8923
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
8727
8924
  return result.changes > 0;
8728
8925
  }
@@ -8854,8 +9051,9 @@ function resolveTemplateId(id, d) {
8854
9051
  function createTemplate(input, db) {
8855
9052
  const d = db || getDatabase();
8856
9053
  const id = uuid();
8857
- d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
8858
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
9054
+ const machineId = currentStorageMachineId(d);
9055
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
9056
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8859
9057
  id,
8860
9058
  input.name,
8861
9059
  input.title_pattern,
@@ -8866,7 +9064,8 @@ function createTemplate(input, db) {
8866
9064
  input.project_id || null,
8867
9065
  input.plan_id || null,
8868
9066
  JSON.stringify(input.metadata || {}),
8869
- now()
9067
+ now(),
9068
+ machineId
8870
9069
  ]);
8871
9070
  if (input.tasks && input.tasks.length > 0) {
8872
9071
  addTemplateTasks(id, input.tasks, d);
@@ -8890,6 +9089,15 @@ function deleteTemplate(id, db) {
8890
9089
  const resolved = resolveTemplateId(id, d);
8891
9090
  if (!resolved)
8892
9091
  return false;
9092
+ const template = getTemplate(resolved, d);
9093
+ if (!template)
9094
+ return false;
9095
+ recordStorageTombstone({
9096
+ object_type: "templates",
9097
+ object_id: resolved,
9098
+ payload: template,
9099
+ version: template.version
9100
+ }, d);
8893
9101
  return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
8894
9102
  }
8895
9103
  function updateTemplate(id, updates, db) {
@@ -9419,6 +9627,7 @@ function getBlockingDeps(id, db) {
9419
9627
  }
9420
9628
  function startTask(id, agentId, db) {
9421
9629
  const d = db || getDatabase();
9630
+ const databasePath = databasePathFromDatabase(d);
9422
9631
  const task = getTask(id, d);
9423
9632
  if (!task)
9424
9633
  throw new TaskNotFoundError(id);
@@ -9433,7 +9642,8 @@ function startTask(id, agentId, db) {
9433
9642
  agent_id: agentId,
9434
9643
  title: task.title,
9435
9644
  blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
9436
- }
9645
+ },
9646
+ databasePath
9437
9647
  });
9438
9648
  throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
9439
9649
  }
@@ -9455,12 +9665,13 @@ function startTask(id, agentId, db) {
9455
9665
  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 };
9456
9666
  const payload = taskEventData(startedTask, { agent_id: agentId });
9457
9667
  dispatchWebhook2("task.started", payload, d).catch(() => {});
9458
- emitLocalEventHooksQuiet({ type: "task.started", payload });
9459
- emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
9668
+ emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
9669
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
9460
9670
  return startedTask;
9461
9671
  }
9462
9672
  function completeTask(id, agentId, db, options) {
9463
9673
  const d = db || getDatabase();
9674
+ const databasePath = databasePathFromDatabase(d);
9464
9675
  const task = getTask(id, d);
9465
9676
  if (!task)
9466
9677
  throw new TaskNotFoundError(id);
@@ -9506,8 +9717,8 @@ function completeTask(id, agentId, db, options) {
9506
9717
  };
9507
9718
  const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
9508
9719
  dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
9509
- emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
9510
- emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
9720
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
9721
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
9511
9722
  let spawnedTask = null;
9512
9723
  if (task.recurrence_rule && !options?.skip_recurrence) {
9513
9724
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -9551,9 +9762,9 @@ function completeTask(id, agentId, db, options) {
9551
9762
  const depTask = getTask(dep.id, d);
9552
9763
  const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
9553
9764
  dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
9554
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
9765
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
9555
9766
  if (depTask)
9556
- emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
9767
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
9557
9768
  }
9558
9769
  }
9559
9770
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -9723,6 +9934,7 @@ function getTasksChangedSince(since, filters, db) {
9723
9934
  }
9724
9935
  function failTask(id, agentId, reason, options, db) {
9725
9936
  const d = db || getDatabase();
9937
+ const databasePath = databasePathFromDatabase(d);
9726
9938
  const task = getTask(id, d);
9727
9939
  if (!task)
9728
9940
  throw new TaskNotFoundError(id);
@@ -9751,8 +9963,8 @@ function failTask(id, agentId, reason, options, db) {
9751
9963
  logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
9752
9964
  const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
9753
9965
  dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
9754
- emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
9755
- emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
9966
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
9967
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
9756
9968
  let retryTask;
9757
9969
  if (options?.retry) {
9758
9970
  const retryCount = (task.retry_count || 0) + 1;
@@ -9812,6 +10024,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
9812
10024
  }
9813
10025
  function stealTask(agentId, opts, db) {
9814
10026
  const d = db || getDatabase();
10027
+ const databasePath = databasePathFromDatabase(d);
9815
10028
  const staleMinutes = opts?.stale_minutes ?? 30;
9816
10029
  const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
9817
10030
  if (staleTasks.length === 0)
@@ -9830,8 +10043,8 @@ function stealTask(agentId, opts, db) {
9830
10043
  const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
9831
10044
  const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
9832
10045
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
9833
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
9834
- emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
10046
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
10047
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
9835
10048
  return stolenTask;
9836
10049
  }
9837
10050
  function claimOrSteal(agentId, filters, db) {
@@ -10422,8 +10635,9 @@ function createPlan(input, db) {
10422
10635
  const d = db || getDatabase();
10423
10636
  const id = uuid();
10424
10637
  const timestamp = now();
10425
- d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
10426
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10638
+ const machineId = currentStorageMachineId(d);
10639
+ d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
10640
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10427
10641
  id,
10428
10642
  input.project_id || null,
10429
10643
  input.task_list_id || null,
@@ -10432,7 +10646,8 @@ function createPlan(input, db) {
10432
10646
  input.description || null,
10433
10647
  input.status || "active",
10434
10648
  timestamp,
10435
- timestamp
10649
+ timestamp,
10650
+ machineId
10436
10651
  ]);
10437
10652
  return getPlan(id, d);
10438
10653
  }
@@ -10480,12 +10695,21 @@ function updatePlan(id, input, db) {
10480
10695
  const updated = getPlan(id, d);
10481
10696
  emitLocalEventHooksQuiet({
10482
10697
  type: "plan.updated",
10483
- payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
10698
+ payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
10699
+ databasePath: databasePathFromDatabase(d)
10484
10700
  });
10485
10701
  return updated;
10486
10702
  }
10487
10703
  function deletePlan(id, db) {
10488
10704
  const d = db || getDatabase();
10705
+ const plan = getPlan(id, d);
10706
+ if (!plan)
10707
+ return false;
10708
+ recordStorageTombstone({
10709
+ object_type: "plans",
10710
+ object_id: id,
10711
+ payload: plan
10712
+ }, d);
10489
10713
  const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
10490
10714
  return result.changes > 0;
10491
10715
  }
@@ -10892,20 +11116,20 @@ init_database();
10892
11116
  init_redaction();
10893
11117
  import { createHash as createHash2 } from "crypto";
10894
11118
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
10895
- import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
10896
- import { tmpdir } from "os";
11119
+ import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
11120
+ import { tmpdir as tmpdir2 } from "os";
10897
11121
  function isInMemoryDb2(path) {
10898
11122
  return path === ":memory:" || path.startsWith("file::memory:");
10899
11123
  }
10900
11124
  function artifactStoreRoot() {
10901
11125
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
10902
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11126
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
10903
11127
  if (process.env["TODOS_ARTIFACTS_DIR"])
10904
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
11128
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
10905
11129
  const dbPath = getDatabasePath();
10906
11130
  if (isInMemoryDb2(dbPath))
10907
- return join6(tmpdir(), "hasna-todos-artifacts");
10908
- return join6(dirname5(resolve6(dbPath)), "artifacts");
11131
+ return join6(tmpdir2(), "hasna-todos-artifacts");
11132
+ return join6(dirname5(resolve7(dbPath)), "artifacts");
10909
11133
  }
10910
11134
  function artifactStorePath(relativePath) {
10911
11135
  const normalized = relativePath.replace(/\\/g, "/");
@@ -10952,7 +11176,7 @@ function mediaTypeFor(path, textLike) {
10952
11176
  return "application/octet-stream";
10953
11177
  }
10954
11178
  function storeArtifactContent(input) {
10955
- const sourcePath = resolve6(input.path);
11179
+ const sourcePath = resolve7(input.path);
10956
11180
  if (!existsSync8(sourcePath))
10957
11181
  return null;
10958
11182
  const sourceStat = statSync2(sourcePath);
@@ -11108,19 +11332,19 @@ function importStoredArtifactContent(content) {
11108
11332
  var DEFAULT_DELETED_RETENTION_DAYS = 30;
11109
11333
  function getArtifactStoreRoot(dbPath) {
11110
11334
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11111
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11335
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11112
11336
  if (process.env["TODOS_ARTIFACTS_DIR"])
11113
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
11337
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11114
11338
  const path = dbPath ?? getDatabasePath();
11115
11339
  if (isInMemoryDb2(path))
11116
- return join6(tmpdir(), "hasna-todos-artifacts");
11117
- return join6(dirname5(resolve6(path)), "artifacts");
11340
+ return join6(tmpdir2(), "hasna-todos-artifacts");
11341
+ return join6(dirname5(resolve7(path)), "artifacts");
11118
11342
  }
11119
11343
  function computeContentHash(path) {
11120
- return sha256(readFileSync4(resolve6(path)));
11344
+ return sha256(readFileSync4(resolve7(path)));
11121
11345
  }
11122
11346
  function storeArtifactFile(input) {
11123
- const sourcePath = resolve6(input.sourcePath);
11347
+ const sourcePath = resolve7(input.sourcePath);
11124
11348
  if (!existsSync8(sourcePath)) {
11125
11349
  throw new Error(`Source file not found: ${input.sourcePath}`);
11126
11350
  }
@@ -11169,7 +11393,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
11169
11393
  };
11170
11394
  }
11171
11395
  function writeArtifactExportManifest(manifest, outputPath) {
11172
- const destination = resolve6(outputPath);
11396
+ const destination = resolve7(outputPath);
11173
11397
  mkdirSync4(dirname5(destination), { recursive: true });
11174
11398
  writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
11175
11399
  `);
@@ -11556,7 +11780,11 @@ function startTaskRun(input, db) {
11556
11780
  }, d);
11557
11781
  }
11558
11782
  const run = getTaskRun(id, d);
11559
- emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
11783
+ emitLocalEventHooksQuiet({
11784
+ type: "run.started",
11785
+ payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
11786
+ databasePath: databasePathFromDatabase(d)
11787
+ });
11560
11788
  return run;
11561
11789
  }
11562
11790
  function beginTaskRunTransaction(input, db) {
@@ -11825,7 +12053,8 @@ function finishTaskRun(input, db) {
11825
12053
  const updated = getTaskRun(run.id, d);
11826
12054
  emitLocalEventHooksQuiet({
11827
12055
  type: `run.${input.status}`,
11828
- payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
12056
+ payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
12057
+ databasePath: databasePathFromDatabase(d)
11829
12058
  });
11830
12059
  return updated;
11831
12060
  }
@@ -13159,7 +13388,7 @@ function importOnboardingFixture(options = {}) {
13159
13388
  init_database();
13160
13389
  import { createHash as createHash3 } from "crypto";
13161
13390
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
13162
- import { dirname as dirname6, resolve as resolve7 } from "path";
13391
+ import { dirname as dirname6, resolve as resolve8 } from "path";
13163
13392
  import { mkdirSync as mkdirSync6 } from "fs";
13164
13393
  var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
13165
13394
  var TODOS_LOCAL_BACKUP_SCHEMA_VERSION = 1;
@@ -13266,14 +13495,14 @@ function createLocalBackup(options = {}, db) {
13266
13495
  return backup;
13267
13496
  }
13268
13497
  function writeLocalBackupFile(backup, outputPath) {
13269
- const path = resolve7(outputPath);
13498
+ const path = resolve8(outputPath);
13270
13499
  mkdirSync6(dirname6(path), { recursive: true });
13271
13500
  writeFileSync4(path, `${JSON.stringify(backup, null, 2)}
13272
13501
  `);
13273
13502
  return path;
13274
13503
  }
13275
13504
  function readLocalBackupFile(path) {
13276
- return JSON.parse(readFileSync5(resolve7(path), "utf-8"));
13505
+ return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
13277
13506
  }
13278
13507
  function verifyLocalBackup(value, options = {}, db) {
13279
13508
  const verifiedAt = options.verified_at ?? now();
@@ -16241,7 +16470,8 @@ function writeQueue(task2, queue, actor, action, db) {
16241
16470
  logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
16242
16471
  emitLocalEventHooksQuiet({
16243
16472
  type: `review.${action}`,
16244
- payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
16473
+ payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
16474
+ databasePath: databasePathFromDatabase(d)
16245
16475
  });
16246
16476
  return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
16247
16477
  }
@@ -16922,7 +17152,7 @@ function canonicalize(value) {
16922
17152
  function hash(value) {
16923
17153
  return createHash5("sha256").update(value).digest("hex");
16924
17154
  }
16925
- function parsePayload(value) {
17155
+ function parsePayload2(value) {
16926
17156
  if (!value)
16927
17157
  return {};
16928
17158
  try {
@@ -17021,7 +17251,7 @@ function taskScopedRows(db, scope) {
17021
17251
  FROM handoffs h
17022
17252
  WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
17023
17253
  `).all(scope.project_id ?? null).filter((row) => {
17024
- const payload = parsePayload(row.payload_json);
17254
+ const payload = parsePayload2(row.payload_json);
17025
17255
  const taskRefs = parseStringArray(payload["task_ids"]);
17026
17256
  const runRefs = parseStringArray(payload["run_ids"]);
17027
17257
  if (scope.project_id && row.project_id !== scope.project_id)
@@ -17052,7 +17282,7 @@ function toLedgerEntries(rows) {
17052
17282
  });
17053
17283
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
17054
17284
  return ordered.map((row, index) => {
17055
- const payload = parsePayload(row.payload_json);
17285
+ const payload = parsePayload2(row.payload_json);
17056
17286
  const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
17057
17287
  const chainHash = hash(`${previous}
17058
17288
  ${payloadHash}`);
@@ -17187,7 +17417,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
17187
17417
  init_migrations();
17188
17418
  init_schema();
17189
17419
  import { readFileSync as readFileSync6 } from "fs";
17190
- import { join as join9, resolve as resolve8 } from "path";
17420
+ import { join as join9, resolve as resolve9 } from "path";
17191
17421
  import { Database as Database2 } from "bun:sqlite";
17192
17422
  var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
17193
17423
  var EXPECTED_PACKAGE_NAME = "@hasna/todos";
@@ -17329,7 +17559,7 @@ function checkChangelog() {
17329
17559
  ];
17330
17560
  }
17331
17561
  function createReleaseCompatibilityReport(options = {}) {
17332
- const root = resolve8(options.root ?? process.cwd());
17562
+ const root = resolve9(options.root ?? process.cwd());
17333
17563
  const packageJson = readPackageJson(root);
17334
17564
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
17335
17565
  const checks = [