@hasna/todos 0.11.66 → 0.11.67

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 (44) 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/storage-commands.d.ts +5 -0
  5. package/dist/cli/commands/storage-commands.d.ts.map +1 -1
  6. package/dist/cli/index.js +667 -169
  7. package/dist/contracts.js +301 -71
  8. package/dist/db/agents.d.ts.map +1 -1
  9. package/dist/db/audit.d.ts.map +1 -1
  10. package/dist/db/migrations.d.ts.map +1 -1
  11. package/dist/db/plans.d.ts.map +1 -1
  12. package/dist/db/projects.d.ts.map +1 -1
  13. package/dist/db/schema.d.ts.map +1 -1
  14. package/dist/db/storage-tombstones.d.ts +26 -0
  15. package/dist/db/storage-tombstones.d.ts.map +1 -0
  16. package/dist/db/task-crud.d.ts.map +1 -1
  17. package/dist/db/task-lifecycle.d.ts.map +1 -1
  18. package/dist/db/task-lists.d.ts.map +1 -1
  19. package/dist/db/task-runs.d.ts.map +1 -1
  20. package/dist/db/templates.d.ts.map +1 -1
  21. package/dist/index.js +628 -160
  22. package/dist/lib/approval-gates.d.ts.map +1 -1
  23. package/dist/lib/event-emission-safety.d.ts +9 -0
  24. package/dist/lib/event-emission-safety.d.ts.map +1 -0
  25. package/dist/lib/event-hooks.d.ts +1 -0
  26. package/dist/lib/event-hooks.d.ts.map +1 -1
  27. package/dist/lib/feature-manifest.d.ts.map +1 -1
  28. package/dist/lib/review-queues.d.ts.map +1 -1
  29. package/dist/lib/shared-events.d.ts +1 -0
  30. package/dist/lib/shared-events.d.ts.map +1 -1
  31. package/dist/mcp/index.js +343 -104
  32. package/dist/registry.js +301 -71
  33. package/dist/release-provenance.json +3 -3
  34. package/dist/server/index.js +366 -127
  35. package/dist/storage/interfaces.d.ts +20 -0
  36. package/dist/storage/interfaces.d.ts.map +1 -1
  37. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  38. package/dist/storage/postgres-sync.d.ts +6 -1
  39. package/dist/storage/postgres-sync.d.ts.map +1 -1
  40. package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
  41. package/dist/storage.js +569 -94
  42. package/dist/types/index.d.ts +22 -0
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1011
1011
  this._exitCallback = (err) => {
1012
1012
  if (err.code !== "commander.executeSubCommandAsync") {
1013
1013
  throw err;
1014
- } else {}
1014
+ }
1015
1015
  };
1016
1016
  }
1017
1017
  return this;
@@ -3266,6 +3266,22 @@ var init_migrations = __esm(() => {
3266
3266
  CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
3267
3267
  CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
3268
3268
  INSERT OR IGNORE INTO _migrations (id) VALUES (62);
3269
+ `,
3270
+ `
3271
+ CREATE TABLE IF NOT EXISTS storage_tombstones (
3272
+ id TEXT PRIMARY KEY,
3273
+ object_type TEXT NOT NULL,
3274
+ object_id TEXT NOT NULL,
3275
+ deleted_at TEXT NOT NULL,
3276
+ updated_at TEXT NOT NULL,
3277
+ source_machine_id TEXT,
3278
+ payload TEXT,
3279
+ version INTEGER,
3280
+ UNIQUE(object_type, object_id)
3281
+ );
3282
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
3283
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
3284
+ INSERT OR IGNORE INTO _migrations (id) VALUES (63);
3269
3285
  `
3270
3286
  ];
3271
3287
  });
@@ -3787,6 +3803,20 @@ function ensureSchema(db) {
3787
3803
  )`);
3788
3804
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
3789
3805
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
3806
+ ensureTable("storage_tombstones", `
3807
+ CREATE TABLE storage_tombstones (
3808
+ id TEXT PRIMARY KEY,
3809
+ object_type TEXT NOT NULL,
3810
+ object_id TEXT NOT NULL,
3811
+ deleted_at TEXT NOT NULL,
3812
+ updated_at TEXT NOT NULL,
3813
+ source_machine_id TEXT,
3814
+ payload TEXT,
3815
+ version INTEGER,
3816
+ UNIQUE(object_type, object_id)
3817
+ )`);
3818
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
3819
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
3790
3820
  ensureTable("machines", `
3791
3821
  CREATE TABLE machines (
3792
3822
  id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
@@ -4981,6 +5011,80 @@ var init_types = __esm(() => {
4981
5011
  };
4982
5012
  });
4983
5013
 
5014
+ // src/db/storage-tombstones.ts
5015
+ function recordStorageTombstone(input, db) {
5016
+ const d = db ?? getDatabase();
5017
+ const deletedAt = input.deleted_at ?? now();
5018
+ const machineId = input.source_machine_id ?? currentStorageMachineId(d);
5019
+ d.run(`INSERT INTO storage_tombstones (
5020
+ id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
5021
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
5022
+ ON CONFLICT(object_type, object_id) DO UPDATE SET
5023
+ deleted_at = excluded.deleted_at,
5024
+ updated_at = excluded.updated_at,
5025
+ source_machine_id = excluded.source_machine_id,
5026
+ payload = excluded.payload,
5027
+ version = excluded.version
5028
+ WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
5029
+ uuid(),
5030
+ input.object_type,
5031
+ input.object_id,
5032
+ deletedAt,
5033
+ deletedAt,
5034
+ machineId,
5035
+ input.payload ? JSON.stringify(input.payload) : null,
5036
+ input.version ?? null
5037
+ ]);
5038
+ return getStorageTombstone(input.object_type, input.object_id, d);
5039
+ }
5040
+ function getStorageTombstone(objectType, objectId, db) {
5041
+ const d = db ?? getDatabase();
5042
+ const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
5043
+ return row ? rowToStorageTombstone(row) : null;
5044
+ }
5045
+ function listStorageTombstones(db) {
5046
+ const d = db ?? getDatabase();
5047
+ return d.query("SELECT * FROM storage_tombstones ORDER BY updated_at ASC, object_type ASC, object_id ASC").all().map(rowToStorageTombstone);
5048
+ }
5049
+ function shouldApplyStorageTombstone(tombstone, existingUpdatedAt) {
5050
+ const tombstoneClock = Date.parse(tombstone.updated_at || tombstone.deleted_at);
5051
+ if (!existingUpdatedAt)
5052
+ return true;
5053
+ const existingClock = Date.parse(existingUpdatedAt);
5054
+ if (Number.isNaN(tombstoneClock))
5055
+ return true;
5056
+ if (Number.isNaN(existingClock))
5057
+ return true;
5058
+ return tombstoneClock >= existingClock;
5059
+ }
5060
+ function rowToStorageTombstone(row) {
5061
+ return {
5062
+ ...row,
5063
+ payload: parsePayload(row.payload)
5064
+ };
5065
+ }
5066
+ function parsePayload(value) {
5067
+ if (!value)
5068
+ return null;
5069
+ try {
5070
+ const parsed = JSON.parse(value);
5071
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
5072
+ } catch {
5073
+ return null;
5074
+ }
5075
+ }
5076
+ function currentStorageMachineId(db) {
5077
+ try {
5078
+ return getMachineId(db);
5079
+ } catch {
5080
+ return null;
5081
+ }
5082
+ }
5083
+ var init_storage_tombstones = __esm(() => {
5084
+ init_database();
5085
+ init_machines();
5086
+ });
5087
+
4984
5088
  // src/db/projects.ts
4985
5089
  var exports_projects = {};
4986
5090
  __export(exports_projects, {
@@ -5032,8 +5136,9 @@ function createProject(input, db) {
5032
5136
  const timestamp = now();
5033
5137
  const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
5034
5138
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
5035
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
5036
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
5139
+ const machineId = currentStorageMachineId(d);
5140
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
5141
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
5037
5142
  return getProject(id, d);
5038
5143
  }
5039
5144
  function getProject(id, db) {
@@ -5112,6 +5217,14 @@ function renameProject(id, input, db) {
5112
5217
  }
5113
5218
  function deleteProject(id, db) {
5114
5219
  const d = db || getDatabase();
5220
+ const project = getProject(id, d);
5221
+ if (!project)
5222
+ return false;
5223
+ recordStorageTombstone({
5224
+ object_type: "projects",
5225
+ object_id: id,
5226
+ payload: project
5227
+ }, d);
5115
5228
  const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
5116
5229
  return result.changes > 0;
5117
5230
  }
@@ -5216,6 +5329,14 @@ function listMachineLocalPaths(projectId, db) {
5216
5329
  function removeMachineLocalPath(projectId, machineId, db) {
5217
5330
  const d = db || getDatabase();
5218
5331
  const mid = machineId ?? getMachineId(d);
5332
+ const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
5333
+ if (!existing)
5334
+ return false;
5335
+ recordStorageTombstone({
5336
+ object_type: "project_machine_paths",
5337
+ object_id: existing.id,
5338
+ payload: existing
5339
+ }, d);
5219
5340
  const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
5220
5341
  return result.changes > 0;
5221
5342
  }
@@ -5223,6 +5344,7 @@ var init_projects = __esm(() => {
5223
5344
  init_types();
5224
5345
  init_database();
5225
5346
  init_machines();
5347
+ init_storage_tombstones();
5226
5348
  });
5227
5349
 
5228
5350
  // src/lib/sync-utils.ts
@@ -5467,6 +5589,57 @@ var init_completion_guard = __esm(() => {
5467
5589
  init_projects();
5468
5590
  });
5469
5591
 
5592
+ // src/lib/event-emission-safety.ts
5593
+ import { tmpdir } from "os";
5594
+ import { resolve as resolve3, sep } from "path";
5595
+ function envFlag(name) {
5596
+ const value = process.env[name];
5597
+ return value === "1" || value === "true" || value === "yes";
5598
+ }
5599
+ function isUnder(parent, child) {
5600
+ const normalizedParent = resolve3(parent);
5601
+ const normalizedChild = resolve3(child);
5602
+ return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
5603
+ }
5604
+ function databasePathFromDatabase(db) {
5605
+ const filename = db?.filename;
5606
+ return typeof filename === "string" && filename.trim() ? filename : undefined;
5607
+ }
5608
+ function isEphemeralTodosDatabase(dbPath) {
5609
+ const resolvedPath = dbPath ?? getDatabasePath();
5610
+ if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
5611
+ return true;
5612
+ return isUnder(tmpdir(), resolvedPath);
5613
+ }
5614
+ function hasExplicitSharedEventsStore() {
5615
+ return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
5616
+ }
5617
+ function usesIsolatedTodosHome() {
5618
+ return isUnder(tmpdir(), getTodosGlobalDir());
5619
+ }
5620
+ function shouldEmitSharedTaskEvents(dbPath) {
5621
+ if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
5622
+ return false;
5623
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
5624
+ return true;
5625
+ if (!isEphemeralTodosDatabase(dbPath))
5626
+ return true;
5627
+ return hasExplicitSharedEventsStore();
5628
+ }
5629
+ function shouldDeliverLocalLifecycleHooks(dbPath) {
5630
+ if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
5631
+ return false;
5632
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
5633
+ return true;
5634
+ if (!isEphemeralTodosDatabase(dbPath))
5635
+ return true;
5636
+ return usesIsolatedTodosHome();
5637
+ }
5638
+ var init_event_emission_safety = __esm(() => {
5639
+ init_database();
5640
+ init_sync_utils();
5641
+ });
5642
+
5470
5643
  // src/lib/redaction.ts
5471
5644
  var exports_redaction = {};
5472
5645
  __export(exports_redaction, {
@@ -5587,9 +5760,9 @@ __export(exports_workspace_trust, {
5587
5760
  getWorkspaceTrustStatus: () => getWorkspaceTrustStatus,
5588
5761
  checkWorkspacePermission: () => checkWorkspacePermission
5589
5762
  });
5590
- import { relative, resolve as resolve3 } from "path";
5763
+ import { relative, resolve as resolve4 } from "path";
5591
5764
  function normalizePath(path) {
5592
- return resolve3(path);
5765
+ return resolve4(path);
5593
5766
  }
5594
5767
  function unique2(values) {
5595
5768
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -5780,9 +5953,9 @@ __export(exports_runner_sandbox, {
5780
5953
  explainRunnerSandbox: () => explainRunnerSandbox,
5781
5954
  checkRunnerSandbox: () => checkRunnerSandbox
5782
5955
  });
5783
- import { relative as relative2, resolve as resolve4 } from "path";
5956
+ import { relative as relative2, resolve as resolve5 } from "path";
5784
5957
  function normalizePath2(path) {
5785
- return resolve4(path);
5958
+ return resolve5(path);
5786
5959
  }
5787
5960
  function unique3(values) {
5788
5961
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -5986,7 +6159,7 @@ __export(exports_event_hooks, {
5986
6159
  });
5987
6160
  import { createHash, randomUUID } from "crypto";
5988
6161
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
5989
- import { dirname as dirname4, resolve as resolve5 } from "path";
6162
+ import { dirname as dirname4, resolve as resolve6 } from "path";
5990
6163
  import { createConnection } from "net";
5991
6164
  function safeName(name) {
5992
6165
  const trimmed = name.trim();
@@ -6124,7 +6297,7 @@ async function deliverHook(hook, envelope) {
6124
6297
  if (hook.target === "stdout") {
6125
6298
  output = line.trim();
6126
6299
  } else if (hook.target === "file") {
6127
- const filePath = resolve5(hook.file_path);
6300
+ const filePath = resolve6(hook.file_path);
6128
6301
  mkdirSync3(dirname4(filePath), { recursive: true });
6129
6302
  appendFileSync(filePath, line);
6130
6303
  } else if (hook.target === "socket") {
@@ -6199,6 +6372,8 @@ async function emitLocalEventHooks(input) {
6199
6372
  return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
6200
6373
  }
6201
6374
  function emitLocalEventHooksQuiet(input) {
6375
+ if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
6376
+ return;
6202
6377
  emitLocalEventHooks(input).catch(() => {});
6203
6378
  }
6204
6379
  async function testLocalEventHook(name, input) {
@@ -6212,6 +6387,7 @@ var init_event_hooks = __esm(() => {
6212
6387
  init_redaction();
6213
6388
  init_runner_sandbox();
6214
6389
  init_config();
6390
+ init_event_emission_safety();
6215
6391
  LOCAL_EVENT_TYPES = [
6216
6392
  "task.created",
6217
6393
  "task.assigned",
@@ -6544,7 +6720,7 @@ async function dispatchCommand(event, channel) {
6544
6720
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
6545
6721
  HASNA_EVENT_JSON: eventJson
6546
6722
  };
6547
- return new Promise((resolve6) => {
6723
+ return new Promise((resolve7) => {
6548
6724
  const child = spawn(channel.command.command, channel.command.args ?? [], {
6549
6725
  cwd: channel.command.cwd,
6550
6726
  env,
@@ -6562,7 +6738,7 @@ async function dispatchCommand(event, channel) {
6562
6738
  });
6563
6739
  child.on("error", (error) => {
6564
6740
  clearTimeout(timeout);
6565
- resolve6({
6741
+ resolve7({
6566
6742
  attempt: 1,
6567
6743
  status: "failed",
6568
6744
  startedAt,
@@ -6575,7 +6751,7 @@ async function dispatchCommand(event, channel) {
6575
6751
  child.on("close", (code, signal) => {
6576
6752
  clearTimeout(timeout);
6577
6753
  const success = code === 0;
6578
- resolve6({
6754
+ resolve7({
6579
6755
  attempt: 1,
6580
6756
  status: success ? "success" : "failed",
6581
6757
  startedAt,
@@ -6842,14 +7018,15 @@ function createTaskList(input, db) {
6842
7018
  const id = uuid();
6843
7019
  const timestamp = now();
6844
7020
  const slug = input.slug || slugify(input.name);
7021
+ const machineId = currentStorageMachineId(d);
6845
7022
  if (!input.project_id) {
6846
7023
  const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
6847
7024
  if (existing) {
6848
7025
  throw new Error(`Standalone task list with slug "${slug}" already exists`);
6849
7026
  }
6850
7027
  }
6851
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
6852
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
7028
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
7029
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
6853
7030
  return getTaskList(id, d);
6854
7031
  }
6855
7032
  function getTaskList(id, db) {
@@ -6899,6 +7076,14 @@ function updateTaskList(id, input, db) {
6899
7076
  }
6900
7077
  function deleteTaskList(id, db) {
6901
7078
  const d = db || getDatabase();
7079
+ const list = getTaskList(id, d);
7080
+ if (!list)
7081
+ return false;
7082
+ recordStorageTombstone({
7083
+ object_type: "task_lists",
7084
+ object_id: id,
7085
+ payload: list
7086
+ }, d);
6902
7087
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
6903
7088
  }
6904
7089
  function ensureTaskList(name, slug, projectId, db) {
@@ -6912,6 +7097,7 @@ var init_task_lists = __esm(() => {
6912
7097
  init_types();
6913
7098
  init_database();
6914
7099
  init_projects();
7100
+ init_storage_tombstones();
6915
7101
  });
6916
7102
 
6917
7103
  // src/lib/task-route-contract.ts
@@ -7118,6 +7304,8 @@ function readMachineLocalPath(project) {
7118
7304
  }
7119
7305
  }
7120
7306
  async function emitSharedTaskEvent(input) {
7307
+ if (!shouldEmitSharedTaskEvents(input.databasePath))
7308
+ return;
7121
7309
  const data = taskEventData(input.task, input.data);
7122
7310
  await new EventsClient().emit({
7123
7311
  source: SOURCE,
@@ -7141,6 +7329,7 @@ var init_shared_events = __esm(() => {
7141
7329
  init_database();
7142
7330
  init_projects();
7143
7331
  init_task_lists();
7332
+ init_event_emission_safety();
7144
7333
  });
7145
7334
 
7146
7335
  // src/lib/secret-redaction.ts
@@ -7396,8 +7585,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
7396
7585
  const d = db || getDatabase();
7397
7586
  const id = uuid();
7398
7587
  const timestamp = now();
7399
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
7400
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
7588
+ const machineId = currentStorageMachineId(d);
7589
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
7590
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
7401
7591
  try {
7402
7592
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
7403
7593
  logActivity2({
@@ -7410,7 +7600,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
7410
7600
  actor_id: agentId ?? undefined
7411
7601
  }, d);
7412
7602
  } catch {}
7413
- return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
7603
+ return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp, machine_id: machineId };
7414
7604
  }
7415
7605
  function getTaskHistory(taskId, db) {
7416
7606
  const d = db || getDatabase();
@@ -7448,6 +7638,7 @@ function getRecap(hours = 8, projectId, db) {
7448
7638
  }
7449
7639
  var init_audit = __esm(() => {
7450
7640
  init_database();
7641
+ init_storage_tombstones();
7451
7642
  });
7452
7643
 
7453
7644
  // src/db/webhooks.ts
@@ -7713,13 +7904,14 @@ function createTask(input, db) {
7713
7904
  const d = db || getDatabase();
7714
7905
  const timestamp = now();
7715
7906
  const tags = input.tags || [];
7907
+ const machineId = currentStorageMachineId(d);
7716
7908
  const assignedBy = input.assigned_by || input.agent_id;
7717
7909
  const assignedFromProject = input.assigned_from_project || null;
7718
7910
  let id = uuid();
7719
7911
  for (let attempt = 0;attempt < 3; attempt++) {
7720
7912
  try {
7721
- 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)
7722
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
7913
+ 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)
7914
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
7723
7915
  id,
7724
7916
  null,
7725
7917
  input.project_id || null,
@@ -7756,7 +7948,8 @@ function createTask(input, db) {
7756
7948
  input.spawned_from_session || null,
7757
7949
  assignedBy || null,
7758
7950
  assignedFromProject || null,
7759
- input.task_type || null
7951
+ input.task_type || null,
7952
+ machineId
7760
7953
  ]);
7761
7954
  break;
7762
7955
  } catch (e) {
@@ -7772,9 +7965,10 @@ function createTask(input, db) {
7772
7965
  }
7773
7966
  const task = getTask(id, d);
7774
7967
  const payload = taskEventData(task);
7968
+ const databasePath = databasePathFromDatabase(d);
7775
7969
  dispatchWebhook2("task.created", payload, d).catch(() => {});
7776
- emitLocalEventHooksQuiet({ type: "task.created", payload });
7777
- emitSharedTaskEventQuiet({ type: "task.created", task });
7970
+ emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
7971
+ emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
7778
7972
  return task;
7779
7973
  }
7780
7974
  function getTask(id, db) {
@@ -8192,29 +8386,39 @@ function updateTask(id, input, db) {
8192
8386
  approved_by: input.approved_by ?? task.approved_by,
8193
8387
  approved_at: input.approved_by ? timestamp : task.approved_at
8194
8388
  };
8389
+ const databasePath = databasePathFromDatabase(d);
8195
8390
  if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
8196
8391
  const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
8197
8392
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
8198
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
8199
- emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
8393
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
8394
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
8200
8395
  }
8201
8396
  if (input.status !== undefined && input.status !== task.status) {
8202
8397
  const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
8203
8398
  dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
8204
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
8205
- emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
8399
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
8400
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
8206
8401
  }
8207
8402
  if (input.approved_by !== undefined) {
8208
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
8403
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
8209
8404
  }
8210
8405
  const updatePayload = taskEventData(updatedTask);
8211
8406
  dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
8212
- emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
8213
- emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
8407
+ emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
8408
+ emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
8214
8409
  return updatedTask;
8215
8410
  }
8216
8411
  function deleteTask(id, db) {
8217
8412
  const d = db || getDatabase();
8413
+ const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
8414
+ if (!row)
8415
+ return false;
8416
+ recordStorageTombstone({
8417
+ object_type: "tasks",
8418
+ object_id: id,
8419
+ payload: rowToTask(row),
8420
+ version: row.version
8421
+ }, d);
8218
8422
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
8219
8423
  return result.changes > 0;
8220
8424
  }
@@ -8222,11 +8426,13 @@ var init_task_crud = __esm(() => {
8222
8426
  init_types();
8223
8427
  init_database();
8224
8428
  init_completion_guard();
8429
+ init_event_emission_safety();
8225
8430
  init_event_hooks();
8226
8431
  init_shared_events();
8227
8432
  init_audit();
8228
8433
  init_webhooks();
8229
8434
  init_checklists();
8435
+ init_storage_tombstones();
8230
8436
  });
8231
8437
 
8232
8438
  // src/lib/recurrence.ts
@@ -8375,8 +8581,9 @@ function resolveTemplateId(id, d) {
8375
8581
  function createTemplate(input, db) {
8376
8582
  const d = db || getDatabase();
8377
8583
  const id = uuid();
8378
- d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
8379
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8584
+ const machineId = currentStorageMachineId(d);
8585
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
8586
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8380
8587
  id,
8381
8588
  input.name,
8382
8589
  input.title_pattern,
@@ -8387,7 +8594,8 @@ function createTemplate(input, db) {
8387
8594
  input.project_id || null,
8388
8595
  input.plan_id || null,
8389
8596
  JSON.stringify(input.metadata || {}),
8390
- now()
8597
+ now(),
8598
+ machineId
8391
8599
  ]);
8392
8600
  if (input.tasks && input.tasks.length > 0) {
8393
8601
  addTemplateTasks(id, input.tasks, d);
@@ -8411,6 +8619,15 @@ function deleteTemplate(id, db) {
8411
8619
  const resolved = resolveTemplateId(id, d);
8412
8620
  if (!resolved)
8413
8621
  return false;
8622
+ const template = getTemplate(resolved, d);
8623
+ if (!template)
8624
+ return false;
8625
+ recordStorageTombstone({
8626
+ object_type: "templates",
8627
+ object_id: resolved,
8628
+ payload: template,
8629
+ version: template.version
8630
+ }, d);
8414
8631
  return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
8415
8632
  }
8416
8633
  function updateTemplate(id, updates, db) {
@@ -8780,6 +8997,7 @@ function previewTemplate(templateId, variables, db) {
8780
8997
  var init_templates = __esm(() => {
8781
8998
  init_database();
8782
8999
  init_tasks();
9000
+ init_storage_tombstones();
8783
9001
  });
8784
9002
 
8785
9003
  // src/db/task-graph.ts
@@ -8946,6 +9164,7 @@ function getBlockingDeps(id, db) {
8946
9164
  }
8947
9165
  function startTask(id, agentId, db) {
8948
9166
  const d = db || getDatabase();
9167
+ const databasePath = databasePathFromDatabase(d);
8949
9168
  const task = getTask(id, d);
8950
9169
  if (!task)
8951
9170
  throw new TaskNotFoundError(id);
@@ -8960,7 +9179,8 @@ function startTask(id, agentId, db) {
8960
9179
  agent_id: agentId,
8961
9180
  title: task.title,
8962
9181
  blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
8963
- }
9182
+ },
9183
+ databasePath
8964
9184
  });
8965
9185
  throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
8966
9186
  }
@@ -8982,12 +9202,13 @@ function startTask(id, agentId, db) {
8982
9202
  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 };
8983
9203
  const payload = taskEventData(startedTask, { agent_id: agentId });
8984
9204
  dispatchWebhook2("task.started", payload, d).catch(() => {});
8985
- emitLocalEventHooksQuiet({ type: "task.started", payload });
8986
- emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
9205
+ emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
9206
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
8987
9207
  return startedTask;
8988
9208
  }
8989
9209
  function completeTask(id, agentId, db, options) {
8990
9210
  const d = db || getDatabase();
9211
+ const databasePath = databasePathFromDatabase(d);
8991
9212
  const task = getTask(id, d);
8992
9213
  if (!task)
8993
9214
  throw new TaskNotFoundError(id);
@@ -9033,8 +9254,8 @@ function completeTask(id, agentId, db, options) {
9033
9254
  };
9034
9255
  const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
9035
9256
  dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
9036
- emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
9037
- emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
9257
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
9258
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
9038
9259
  let spawnedTask = null;
9039
9260
  if (task.recurrence_rule && !options?.skip_recurrence) {
9040
9261
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -9078,9 +9299,9 @@ function completeTask(id, agentId, db, options) {
9078
9299
  const depTask = getTask(dep.id, d);
9079
9300
  const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
9080
9301
  dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
9081
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
9302
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
9082
9303
  if (depTask)
9083
- emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
9304
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
9084
9305
  }
9085
9306
  }
9086
9307
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -9250,6 +9471,7 @@ function getTasksChangedSince(since, filters, db) {
9250
9471
  }
9251
9472
  function failTask(id, agentId, reason, options, db) {
9252
9473
  const d = db || getDatabase();
9474
+ const databasePath = databasePathFromDatabase(d);
9253
9475
  const task = getTask(id, d);
9254
9476
  if (!task)
9255
9477
  throw new TaskNotFoundError(id);
@@ -9278,8 +9500,8 @@ function failTask(id, agentId, reason, options, db) {
9278
9500
  logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
9279
9501
  const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
9280
9502
  dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
9281
- emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
9282
- emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
9503
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
9504
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
9283
9505
  let retryTask;
9284
9506
  if (options?.retry) {
9285
9507
  const retryCount = (task.retry_count || 0) + 1;
@@ -9339,6 +9561,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
9339
9561
  }
9340
9562
  function stealTask(agentId, opts, db) {
9341
9563
  const d = db || getDatabase();
9564
+ const databasePath = databasePathFromDatabase(d);
9342
9565
  const staleMinutes = opts?.stale_minutes ?? 30;
9343
9566
  const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
9344
9567
  if (staleTasks.length === 0)
@@ -9357,8 +9580,8 @@ function stealTask(agentId, opts, db) {
9357
9580
  const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
9358
9581
  const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
9359
9582
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
9360
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
9361
- emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
9583
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
9584
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
9362
9585
  return stolenTask;
9363
9586
  }
9364
9587
  function claimOrSteal(agentId, filters, db) {
@@ -9406,6 +9629,7 @@ var init_task_lifecycle = __esm(() => {
9406
9629
  init_types();
9407
9630
  init_database();
9408
9631
  init_completion_guard();
9632
+ init_event_emission_safety();
9409
9633
  init_event_hooks();
9410
9634
  init_shared_events();
9411
9635
  init_audit();
@@ -10113,8 +10337,9 @@ function createPlan(input, db) {
10113
10337
  const d = db || getDatabase();
10114
10338
  const id = uuid();
10115
10339
  const timestamp = now();
10116
- d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
10117
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10340
+ const machineId = currentStorageMachineId(d);
10341
+ d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
10342
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10118
10343
  id,
10119
10344
  input.project_id || null,
10120
10345
  input.task_list_id || null,
@@ -10123,7 +10348,8 @@ function createPlan(input, db) {
10123
10348
  input.description || null,
10124
10349
  input.status || "active",
10125
10350
  timestamp,
10126
- timestamp
10351
+ timestamp,
10352
+ machineId
10127
10353
  ]);
10128
10354
  return getPlan(id, d);
10129
10355
  }
@@ -10171,19 +10397,30 @@ function updatePlan(id, input, db) {
10171
10397
  const updated = getPlan(id, d);
10172
10398
  emitLocalEventHooksQuiet({
10173
10399
  type: "plan.updated",
10174
- payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
10400
+ payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
10401
+ databasePath: databasePathFromDatabase(d)
10175
10402
  });
10176
10403
  return updated;
10177
10404
  }
10178
10405
  function deletePlan(id, db) {
10179
10406
  const d = db || getDatabase();
10407
+ const plan = getPlan(id, d);
10408
+ if (!plan)
10409
+ return false;
10410
+ recordStorageTombstone({
10411
+ object_type: "plans",
10412
+ object_id: id,
10413
+ payload: plan
10414
+ }, d);
10180
10415
  const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
10181
10416
  return result.changes > 0;
10182
10417
  }
10183
10418
  var init_plans = __esm(() => {
10184
10419
  init_types();
10420
+ init_event_emission_safety();
10185
10421
  init_event_hooks();
10186
10422
  init_database();
10423
+ init_storage_tombstones();
10187
10424
  });
10188
10425
 
10189
10426
  // src/db/boards.ts
@@ -10590,20 +10827,20 @@ var init_boards = __esm(() => {
10590
10827
  // src/lib/artifact-store.ts
10591
10828
  import { createHash as createHash2 } from "crypto";
10592
10829
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
10593
- import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
10594
- import { tmpdir } from "os";
10830
+ import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
10831
+ import { tmpdir as tmpdir2 } from "os";
10595
10832
  function isInMemoryDb2(path) {
10596
10833
  return path === ":memory:" || path.startsWith("file::memory:");
10597
10834
  }
10598
10835
  function artifactStoreRoot() {
10599
10836
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
10600
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
10837
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
10601
10838
  if (process.env["TODOS_ARTIFACTS_DIR"])
10602
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
10839
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
10603
10840
  const dbPath = getDatabasePath();
10604
10841
  if (isInMemoryDb2(dbPath))
10605
- return join6(tmpdir(), "hasna-todos-artifacts");
10606
- return join6(dirname5(resolve6(dbPath)), "artifacts");
10842
+ return join6(tmpdir2(), "hasna-todos-artifacts");
10843
+ return join6(dirname5(resolve7(dbPath)), "artifacts");
10607
10844
  }
10608
10845
  function artifactStorePath(relativePath) {
10609
10846
  const normalized = relativePath.replace(/\\/g, "/");
@@ -10650,7 +10887,7 @@ function mediaTypeFor(path, textLike) {
10650
10887
  return "application/octet-stream";
10651
10888
  }
10652
10889
  function storeArtifactContent(input) {
10653
- const sourcePath = resolve6(input.path);
10890
+ const sourcePath = resolve7(input.path);
10654
10891
  if (!existsSync7(sourcePath))
10655
10892
  return null;
10656
10893
  const sourceStat = statSync2(sourcePath);
@@ -11367,7 +11604,11 @@ function startTaskRun(input, db) {
11367
11604
  }, d);
11368
11605
  }
11369
11606
  const run = getTaskRun(id, d);
11370
- emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
11607
+ emitLocalEventHooksQuiet({
11608
+ type: "run.started",
11609
+ payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
11610
+ databasePath: databasePathFromDatabase(d)
11611
+ });
11371
11612
  return run;
11372
11613
  }
11373
11614
  function beginTaskRunTransaction(input, db) {
@@ -11636,7 +11877,8 @@ function finishTaskRun(input, db) {
11636
11877
  const updated = getTaskRun(run.id, d);
11637
11878
  emitLocalEventHooksQuiet({
11638
11879
  type: `run.${input.status}`,
11639
- payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
11880
+ payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
11881
+ databasePath: databasePathFromDatabase(d)
11640
11882
  });
11641
11883
  return updated;
11642
11884
  }
@@ -11720,6 +11962,7 @@ function getTaskRunLedger(runId, db) {
11720
11962
  var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
11721
11963
  var init_task_runs = __esm(() => {
11722
11964
  init_artifact_store();
11965
+ init_event_emission_safety();
11723
11966
  init_event_hooks();
11724
11967
  init_redaction();
11725
11968
  init_types();
@@ -12217,7 +12460,7 @@ __export(exports_helpers, {
12217
12460
  });
12218
12461
  import chalk from "chalk";
12219
12462
  import { execSync } from "child_process";
12220
- import { resolve as resolve7 } from "path";
12463
+ import { resolve as resolve8 } from "path";
12221
12464
  function handleError(e) {
12222
12465
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
12223
12466
  process.exit(1);
@@ -12246,7 +12489,7 @@ function detectGitRoot() {
12246
12489
  }
12247
12490
  function resolveExplicitProject(input) {
12248
12491
  const db = getDatabase();
12249
- const byPath = getProjectByPath(resolve7(input), db);
12492
+ const byPath = getProjectByPath(resolve8(input), db);
12250
12493
  if (byPath)
12251
12494
  return byPath;
12252
12495
  const id = resolvePartialId(db, "projects", input);
@@ -12515,6 +12758,8 @@ function pointerPatch(previous, input, key) {
12515
12758
  if (!Object.prototype.hasOwnProperty.call(input, key))
12516
12759
  return previous;
12517
12760
  const value = input[key];
12761
+ if (value === undefined)
12762
+ return previous;
12518
12763
  return typeof value === "string" && value.trim() ? value : undefined;
12519
12764
  }
12520
12765
  var init_task_routing = __esm(() => {
@@ -12531,7 +12776,7 @@ __export(exports_task_commands, {
12531
12776
  registerTaskCommands: () => registerTaskCommands
12532
12777
  });
12533
12778
  import chalk2 from "chalk";
12534
- import { basename as basename3, resolve as resolve8 } from "path";
12779
+ import { basename as basename3, resolve as resolve9 } from "path";
12535
12780
  function resolveProjectIdOrSlug(input) {
12536
12781
  const db = getDatabase();
12537
12782
  const byId = getProject(input, db);
@@ -12541,7 +12786,7 @@ function resolveProjectIdOrSlug(input) {
12541
12786
  if (row)
12542
12787
  return row.id;
12543
12788
  if (isPathLike(input)) {
12544
- const projectPath = resolve8(input);
12789
+ const projectPath = resolve9(input);
12545
12790
  const byPath = getProjectByPath(projectPath, db);
12546
12791
  return (byPath ?? ensureProject(basename3(projectPath), projectPath, db)).id;
12547
12792
  }
@@ -12701,7 +12946,7 @@ function registerTaskCommands(program2) {
12701
12946
  task_list_id: taskListId,
12702
12947
  tags: parseTags(opts.tags),
12703
12948
  metadata: buildExpectationMetadata(opts),
12704
- working_dir: opts.workingDir ? resolve8(opts.workingDir) : process.cwd(),
12949
+ working_dir: opts.workingDir ? resolve9(opts.workingDir) : process.cwd(),
12705
12950
  project_id: projectId,
12706
12951
  assigned_to: opts.assign,
12707
12952
  agent_id: globalOpts.agent,
@@ -15272,7 +15517,7 @@ __export(exports_project_bootstrap, {
15272
15517
  bootstrapProject: () => bootstrapProject
15273
15518
  });
15274
15519
  import { existsSync as existsSync10, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
15275
- import { basename as basename4, dirname as dirname6, resolve as resolve9 } from "path";
15520
+ import { basename as basename4, dirname as dirname6, resolve as resolve10 } from "path";
15276
15521
  function safeStat(path) {
15277
15522
  try {
15278
15523
  return statSync3(path);
@@ -15281,7 +15526,7 @@ function safeStat(path) {
15281
15526
  }
15282
15527
  }
15283
15528
  function canonicalPath(input) {
15284
- const resolved = resolve9(input);
15529
+ const resolved = resolve10(input);
15285
15530
  const stats = safeStat(resolved);
15286
15531
  if (stats?.isFile())
15287
15532
  return dirname6(resolved);
@@ -15290,7 +15535,7 @@ function canonicalPath(input) {
15290
15535
  function findUp(start, marker) {
15291
15536
  let current = canonicalPath(start);
15292
15537
  while (true) {
15293
- if (existsSync10(resolve9(current, marker)))
15538
+ if (existsSync10(resolve10(current, marker)))
15294
15539
  return current;
15295
15540
  const parent = dirname6(current);
15296
15541
  if (parent === current)
@@ -15301,7 +15546,7 @@ function findUp(start, marker) {
15301
15546
  function readPackageJson(path) {
15302
15547
  if (!path)
15303
15548
  return null;
15304
- const file = resolve9(path, "package.json");
15549
+ const file = resolve10(path, "package.json");
15305
15550
  if (!existsSync10(file))
15306
15551
  return null;
15307
15552
  try {
@@ -15324,7 +15569,7 @@ function workspaceMarker(root, rootPackage) {
15324
15569
  if (rootPackage?.workspaces)
15325
15570
  markers.push("package.json#workspaces");
15326
15571
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
15327
- if (existsSync10(resolve9(root, marker)))
15572
+ if (existsSync10(resolve10(root, marker)))
15328
15573
  markers.push(marker);
15329
15574
  }
15330
15575
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -15460,7 +15705,7 @@ __export(exports_extract, {
15460
15705
  });
15461
15706
  import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
15462
15707
  import { createHash as createHash3 } from "crypto";
15463
- import { relative as relative3, resolve as resolve10, join as join10 } from "path";
15708
+ import { relative as relative3, resolve as resolve11, join as join10 } from "path";
15464
15709
  function stableHash(value) {
15465
15710
  return createHash3("sha256").update(value).digest("hex");
15466
15711
  }
@@ -15468,7 +15713,7 @@ function normalizePathForMatch(value) {
15468
15713
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
15469
15714
  }
15470
15715
  function readGitignorePatterns(basePath) {
15471
- const root = statSync4(basePath).isFile() ? resolve10(basePath, "..") : basePath;
15716
+ const root = statSync4(basePath).isFile() ? resolve11(basePath, "..") : basePath;
15472
15717
  const gitignorePath = join10(root, ".gitignore");
15473
15718
  if (!existsSync11(gitignorePath))
15474
15719
  return [];
@@ -15604,7 +15849,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
15604
15849
  return files.sort();
15605
15850
  }
15606
15851
  function buildCodebaseIndex(options) {
15607
- const basePath = resolve10(options.path);
15852
+ const basePath = resolve11(options.path);
15608
15853
  const tags = options.patterns || [...EXTRACT_TAGS];
15609
15854
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
15610
15855
  const excludes = options.exclude || [];
@@ -15615,7 +15860,7 @@ function buildCodebaseIndex(options) {
15615
15860
  const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
15616
15861
  try {
15617
15862
  const source = readFileSync6(fullPath, "utf-8");
15618
- const relPath = statSync4(basePath).isFile() ? relative3(resolve10(basePath, ".."), fullPath) : file;
15863
+ const relPath = statSync4(basePath).isFile() ? relative3(resolve11(basePath, ".."), fullPath) : file;
15619
15864
  indexed.push({
15620
15865
  file: relPath,
15621
15866
  checksum: stableHash(source).slice(0, 24),
@@ -15635,7 +15880,7 @@ function buildCodebaseIndex(options) {
15635
15880
  };
15636
15881
  }
15637
15882
  function extractTodos(options, db) {
15638
- const basePath = resolve10(options.path);
15883
+ const basePath = resolve11(options.path);
15639
15884
  const tags = options.patterns || [...EXTRACT_TAGS];
15640
15885
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
15641
15886
  const excludes = options.exclude || [];
@@ -15646,7 +15891,7 @@ function extractTodos(options, db) {
15646
15891
  const fullPath = statSync4(basePath).isFile() ? basePath : join10(basePath, file);
15647
15892
  try {
15648
15893
  const source = readFileSync6(fullPath, "utf-8");
15649
- const relPath = statSync4(basePath).isFile() ? relative3(resolve10(basePath, ".."), fullPath) : file;
15894
+ const relPath = statSync4(basePath).isFile() ? relative3(resolve11(basePath, ".."), fullPath) : file;
15650
15895
  const comments = extractFromSource(source, relPath, tags);
15651
15896
  allComments.push(...comments);
15652
15897
  } catch {}
@@ -15740,7 +15985,7 @@ async function watchSourceTodos(options, onRun) {
15740
15985
  const interval = Math.max(100, options.interval_ms || 2000);
15741
15986
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
15742
15987
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
15743
- const root = resolve10(options.path);
15988
+ const root = resolve11(options.path);
15744
15989
  const runs = [];
15745
15990
  let previous = new Map;
15746
15991
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -16911,7 +17156,7 @@ __export(exports_project_commands, {
16911
17156
  registerProjectCommands: () => registerProjectCommands
16912
17157
  });
16913
17158
  import chalk4 from "chalk";
16914
- import { basename as basename5, resolve as resolve11 } from "path";
17159
+ import { basename as basename5, resolve as resolve12 } from "path";
16915
17160
  function collectOption(value, previous = []) {
16916
17161
  return [...previous, value];
16917
17162
  }
@@ -17253,7 +17498,7 @@ function registerProjectCommands(program2) {
17253
17498
  program2.command("projects").description("List and manage projects").option("--add <path>", "Register a project by path").option("--name <name>", "Project name (with --add)").option("--task-list-id <id>", "Custom task list ID (with --add)").action(async (opts) => {
17254
17499
  const globalOpts = program2.opts();
17255
17500
  if (opts.add) {
17256
- const projectPath = resolve11(opts.add);
17501
+ const projectPath = resolve12(opts.add);
17257
17502
  const name = opts.name || basename5(projectPath);
17258
17503
  const existing = getProjectByPath(projectPath);
17259
17504
  let project;
@@ -17337,7 +17582,7 @@ function registerProjectCommands(program2) {
17337
17582
  console.error(chalk4.red(`Project not found: ${projectId}`));
17338
17583
  process.exit(1);
17339
17584
  }
17340
- const entry = setMachineLocalPath2(resolved, resolve11(projectPath));
17585
+ const entry = setMachineLocalPath2(resolved, resolve12(projectPath));
17341
17586
  if (useJson) {
17342
17587
  output(entry, true);
17343
17588
  } else {
@@ -17404,7 +17649,7 @@ function registerProjectCommands(program2) {
17404
17649
  const patterns = opts.pattern ? opts.pattern.split(",").map((t) => t.trim().toUpperCase()) : undefined;
17405
17650
  const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
17406
17651
  const result = extractTodos2({
17407
- path: resolve11(scanPath),
17652
+ path: resolve12(scanPath),
17408
17653
  patterns,
17409
17654
  project_id: projectId,
17410
17655
  task_list_id: taskListId,
@@ -17465,7 +17710,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
17465
17710
  const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
17466
17711
  const maxRuns = opts.maxRuns ? parseInt(opts.maxRuns, 10) : 1;
17467
17712
  const result = await watchSourceTodos2({
17468
- path: resolve11(scanPath),
17713
+ path: resolve12(scanPath),
17469
17714
  patterns,
17470
17715
  project_id: projectId,
17471
17716
  task_list_id: taskListId,
@@ -17502,7 +17747,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
17502
17747
  const writeOutput = async (content) => {
17503
17748
  if (opts.output) {
17504
17749
  const { writeFileSync: writeFileSync5 } = await import("fs");
17505
- writeFileSync5(resolve11(opts.output), content.endsWith(`
17750
+ writeFileSync5(resolve12(opts.output), content.endsWith(`
17506
17751
  `) ? content : `${content}
17507
17752
  `);
17508
17753
  } else {
@@ -17517,12 +17762,12 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
17517
17762
  const exported = opts.encrypt ? createEncryptedBridgeBundle2(bundle, { profile: opts.encryptionProfile }) : bundle;
17518
17763
  const json = JSON.stringify(exported, null, 2);
17519
17764
  await writeOutput(json);
17520
- emitLocalEventHooksQuiet3({ type: "export.finished", payload: { format: "bridge", encrypted: Boolean(opts.encrypt), project_id: projectId, output: opts.output ? resolve11(opts.output) : null, stats: bundle.stats } });
17765
+ emitLocalEventHooksQuiet3({ type: "export.finished", payload: { format: "bridge", encrypted: Boolean(opts.encrypt), project_id: projectId, output: opts.output ? resolve12(opts.output) : null, stats: bundle.stats } });
17521
17766
  if (!opts.encrypt && !opts.allowPlaintextSensitive) {
17522
17767
  console.error(chalk4.yellow("Warning: bridge exports are plaintext JSON. Use --encrypt for sensitive metadata, evidence, and artifact bundles."));
17523
17768
  }
17524
17769
  if (opts.output && !globalOpts.json) {
17525
- console.log(chalk4.green(`${opts.encrypt ? "Encrypted bridge export" : "Bridge export"} written to ${resolve11(opts.output)}`));
17770
+ console.log(chalk4.green(`${opts.encrypt ? "Encrypted bridge export" : "Bridge export"} written to ${resolve12(opts.output)}`));
17526
17771
  }
17527
17772
  return;
17528
17773
  }
@@ -17535,7 +17780,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
17535
17780
  await writeOutput(JSON.stringify(tasks, null, 2));
17536
17781
  }
17537
17782
  const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
17538
- emitLocalEventHooksQuiet2({ type: "export.finished", payload: { format: opts.format, project_id: projectId, output: opts.output ? resolve11(opts.output) : null, count: exportedCount } });
17783
+ emitLocalEventHooksQuiet2({ type: "export.finished", payload: { format: opts.format, project_id: projectId, output: opts.output ? resolve12(opts.output) : null, count: exportedCount } });
17539
17784
  });
17540
17785
  program2.command("bridge-import <file>").description("Dry-run or apply a local hasna/todos bridge export bundle").option("--apply", "Apply the import. Defaults to dry-run.").option("--decrypt", "Decrypt an encrypted bridge export before importing").option("--resolve-conflicts", "Safely merge existing local tasks by filling blank fields, unioning tags, and recording unresolved divergences").action(async (file, opts) => {
17541
17786
  const globalOpts = program2.opts();
@@ -17543,13 +17788,13 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
17543
17788
  const { readFileSync: readFileSync7 } = await import("fs");
17544
17789
  const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
17545
17790
  const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
17546
- const parsed = JSON.parse(readFileSync7(resolve11(file), "utf-8"));
17791
+ const parsed = JSON.parse(readFileSync7(resolve12(file), "utf-8"));
17547
17792
  const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
17548
17793
  throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
17549
17794
  })() : parsed;
17550
17795
  const result = importLocalBridgeBundle2(bundle, { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
17551
17796
  const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
17552
- emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve11(file), dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, conflicts: result.conflicts.length, issues: result.issues.length } });
17797
+ emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve12(file), dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, conflicts: result.conflicts.length, issues: result.issues.length } });
17553
17798
  if (globalOpts.json) {
17554
17799
  output(result, true);
17555
17800
  return;
@@ -17579,9 +17824,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
17579
17824
  try {
17580
17825
  const { readFileSync: readFileSync7 } = await import("fs");
17581
17826
  const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
17582
- const result = importTodosMarkdown2(readFileSync7(resolve11(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
17827
+ const result = importTodosMarkdown2(readFileSync7(resolve12(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
17583
17828
  const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
17584
- emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve11(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
17829
+ emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve12(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
17585
17830
  if (globalOpts.json) {
17586
17831
  output(result, true);
17587
17832
  return;
@@ -18076,6 +18321,7 @@ function rowToAgent(row) {
18076
18321
  }
18077
18322
  function registerAgent(input, db) {
18078
18323
  const d = db || getDatabase();
18324
+ const machineId = currentStorageMachineId(d);
18079
18325
  const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
18080
18326
  const normalizedName = validateAgentName(input.name, existingNames);
18081
18327
  const existing = getAgentByName(normalizedName, d);
@@ -18116,14 +18362,18 @@ function registerAgent(input, db) {
18116
18362
  updates.push("active_project_id = ?");
18117
18363
  params.push(input.project_id);
18118
18364
  }
18365
+ if (!existing.machine_id && machineId) {
18366
+ updates.push("machine_id = ?");
18367
+ params.push(machineId);
18368
+ }
18119
18369
  params.push(existing.id);
18120
18370
  d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
18121
18371
  return getAgent(existing.id, d);
18122
18372
  }
18123
18373
  const id = shortUuid();
18124
18374
  const timestamp = now();
18125
- 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)
18126
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
18375
+ 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)
18376
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
18127
18377
  id,
18128
18378
  normalizedName,
18129
18379
  input.description || null,
@@ -18139,7 +18389,8 @@ function registerAgent(input, db) {
18139
18389
  timestamp,
18140
18390
  input.session_id || null,
18141
18391
  input.working_dir || null,
18142
- input.project_id && input.session_id ? input.project_id : null
18392
+ input.project_id && input.session_id ? input.project_id : null,
18393
+ machineId
18143
18394
  ]);
18144
18395
  return getAgent(id, d);
18145
18396
  }
@@ -18322,6 +18573,7 @@ function getCapableAgents(capabilities, opts, db) {
18322
18573
  }
18323
18574
  var init_agents = __esm(() => {
18324
18575
  init_database();
18576
+ init_storage_tombstones();
18325
18577
  init_agent_names();
18326
18578
  });
18327
18579
 
@@ -19584,7 +19836,7 @@ __export(exports_local_extensions, {
19584
19836
  });
19585
19837
  import { createHash as createHash5, createVerify } from "crypto";
19586
19838
  import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
19587
- import { basename as basename6, join as join11, resolve as resolve12 } from "path";
19839
+ import { basename as basename6, join as join11, resolve as resolve13 } from "path";
19588
19840
  function isObject(value) {
19589
19841
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
19590
19842
  }
@@ -19842,7 +20094,7 @@ function verifyExtensionSignature(input) {
19842
20094
  return verifier.verify(input.public_key, decodeSignature(input.signature));
19843
20095
  }
19844
20096
  function inspectExtensionSource(source2) {
19845
- const resolved = resolve12(source2);
20097
+ const resolved = resolve13(source2);
19846
20098
  if (!existsSync13(resolved))
19847
20099
  throw new Error(`extension source not found: ${source2}`);
19848
20100
  const stat = statSync5(resolved);
@@ -19940,7 +20192,7 @@ function testExtensionCompatibility(sourceOrManifest) {
19940
20192
  function projectExtensionSources(projectPath) {
19941
20193
  if (!projectPath)
19942
20194
  return [];
19943
- const root = resolve12(projectPath);
20195
+ const root = resolve13(projectPath);
19944
20196
  const candidates = [
19945
20197
  join11(root, "todos.extension.json"),
19946
20198
  join11(root, ".todos", "todos.extension.json")
@@ -19959,7 +20211,7 @@ function projectExtensionSources(projectPath) {
19959
20211
  }
19960
20212
  function discoverLocalExtensions(options = {}) {
19961
20213
  const config = loadConfig();
19962
- const projectPath = options.project_path ? resolve12(options.project_path) : null;
20214
+ const projectPath = options.project_path ? resolve13(options.project_path) : null;
19963
20215
  const configuredSources = [
19964
20216
  ...config.extension_sources || [],
19965
20217
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -19967,7 +20219,7 @@ function discoverLocalExtensions(options = {}) {
19967
20219
  const sources = Array.from(new Set([
19968
20220
  ...configuredSources,
19969
20221
  ...projectExtensionSources(projectPath || undefined)
19970
- ])).map((source2) => projectPath && !source2.startsWith("/") ? resolve12(projectPath, source2) : resolve12(source2));
20222
+ ])).map((source2) => projectPath && !source2.startsWith("/") ? resolve13(projectPath, source2) : resolve13(source2));
19971
20223
  const warnings = [];
19972
20224
  const discovered = [];
19973
20225
  for (const source2 of sources) {
@@ -20227,9 +20479,9 @@ __export(exports_policy_packs, {
20227
20479
  getPolicyPack: () => getPolicyPack,
20228
20480
  explainPolicyPack: () => explainPolicyPack
20229
20481
  });
20230
- import { relative as relative4, resolve as resolve13 } from "path";
20482
+ import { relative as relative4, resolve as resolve14 } from "path";
20231
20483
  function normalizePath3(path) {
20232
- return resolve13(path);
20484
+ return resolve14(path);
20233
20485
  }
20234
20486
  function unique5(values) {
20235
20487
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -20284,7 +20536,7 @@ function commandMatches(commands, pattern) {
20284
20536
  }
20285
20537
  function pathMatches(paths, pattern, root) {
20286
20538
  return paths.filter((path) => {
20287
- const candidate = path.startsWith("/") ? path : resolve13(root, path);
20539
+ const candidate = path.startsWith("/") ? path : resolve14(root, path);
20288
20540
  if (!isPathInside3(root, candidate))
20289
20541
  return matchesPattern3(path, pattern);
20290
20542
  return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
@@ -20641,7 +20893,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
20641
20893
  if (action === "approved" || action === "rejected" || action === "expired") {
20642
20894
  emitLocalEventHooksQuiet({
20643
20895
  type: "approval.decided",
20644
- payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
20896
+ payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
20897
+ databasePath: databasePathFromDatabase(db)
20645
20898
  });
20646
20899
  }
20647
20900
  }
@@ -20787,6 +21040,7 @@ var init_approval_gates = __esm(() => {
20787
21040
  init_task_runs();
20788
21041
  init_tasks();
20789
21042
  init_types();
21043
+ init_event_emission_safety();
20790
21044
  init_event_hooks();
20791
21045
  });
20792
21046
 
@@ -21638,7 +21892,7 @@ var init_doctor = __esm(() => {
21638
21892
  });
21639
21893
 
21640
21894
  // src/server/routes.ts
21641
- import { join as join13, resolve as resolve14, sep } from "path";
21895
+ import { join as join13, resolve as resolve15, sep as sep2 } from "path";
21642
21896
  function parseFieldsParam(url) {
21643
21897
  const fieldsParam = url.searchParams.get("fields");
21644
21898
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -22376,9 +22630,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
22376
22630
  return null;
22377
22631
  if (path !== "/") {
22378
22632
  const filePath = join13(ctx.dashboardDir, path);
22379
- const resolvedFile = resolve14(filePath);
22380
- const resolvedBase = resolve14(ctx.dashboardDir);
22381
- if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
22633
+ const resolvedFile = resolve15(filePath);
22634
+ const resolvedBase = resolve15(ctx.dashboardDir);
22635
+ if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
22382
22636
  return json2({ error: "Forbidden" }, 403);
22383
22637
  }
22384
22638
  const res2 = serveStaticFile2(filePath);
@@ -27381,7 +27635,7 @@ __export(exports_mention_resolver, {
27381
27635
  resolveMentions: () => resolveMentions
27382
27636
  });
27383
27637
  import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync7 } from "fs";
27384
- import { basename as basename8, isAbsolute, join as join14, relative as relative5, resolve as resolve15, sep as sep2 } from "path";
27638
+ import { basename as basename8, isAbsolute, join as join14, relative as relative5, resolve as resolve16, sep as sep3 } from "path";
27385
27639
  function blankResolution(parsed) {
27386
27640
  return {
27387
27641
  input: parsed.input,
@@ -27404,11 +27658,11 @@ function backlink(kind, key, label, target = key) {
27404
27658
  return { kind, key, label, target };
27405
27659
  }
27406
27660
  function normalizeWorkspace(workspace) {
27407
- return resolve15(workspace || process.cwd());
27661
+ return resolve16(workspace || process.cwd());
27408
27662
  }
27409
27663
  function isInside(root, absolutePath) {
27410
27664
  const rel = relative5(root, absolutePath);
27411
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
27665
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep3}`) && !isAbsolute(rel);
27412
27666
  }
27413
27667
  function normalizeRelativePath(value) {
27414
27668
  const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
@@ -27472,7 +27726,7 @@ function resolveFile(parsed, workspace) {
27472
27726
  resolution.warnings.push("path is empty or escapes the workspace");
27473
27727
  return resolution;
27474
27728
  }
27475
- const absolutePath = resolve15(workspace, relPath);
27729
+ const absolutePath = resolve16(workspace, relPath);
27476
27730
  if (!isInside(workspace, absolutePath)) {
27477
27731
  resolution.path = relPath;
27478
27732
  resolution.warnings.push("path escapes the workspace");
@@ -29999,7 +30253,7 @@ function canonicalize(value) {
29999
30253
  function hash(value) {
30000
30254
  return createHash7("sha256").update(value).digest("hex");
30001
30255
  }
30002
- function parsePayload(value) {
30256
+ function parsePayload2(value) {
30003
30257
  if (!value)
30004
30258
  return {};
30005
30259
  try {
@@ -30098,7 +30352,7 @@ function taskScopedRows(db, scope) {
30098
30352
  FROM handoffs h
30099
30353
  WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
30100
30354
  `).all(scope.project_id ?? null).filter((row) => {
30101
- const payload = parsePayload(row.payload_json);
30355
+ const payload = parsePayload2(row.payload_json);
30102
30356
  const taskRefs = parseStringArray(payload["task_ids"]);
30103
30357
  const runRefs = parseStringArray(payload["run_ids"]);
30104
30358
  if (scope.project_id && row.project_id !== scope.project_id)
@@ -30129,7 +30383,7 @@ function toLedgerEntries(rows) {
30129
30383
  });
30130
30384
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
30131
30385
  return ordered.map((row, index) => {
30132
- const payload = parsePayload(row.payload_json);
30386
+ const payload = parsePayload2(row.payload_json);
30133
30387
  const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
30134
30388
  const chainHash = hash(`${previous}
30135
30389
  ${payloadHash}`);
@@ -30277,7 +30531,7 @@ __export(exports_release_compatibility, {
30277
30531
  LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
30278
30532
  });
30279
30533
  import { readFileSync as readFileSync9 } from "fs";
30280
- import { join as join15, resolve as resolve16 } from "path";
30534
+ import { join as join15, resolve as resolve17 } from "path";
30281
30535
  import { Database as Database2 } from "bun:sqlite";
30282
30536
  function pass(id, message, details) {
30283
30537
  return { id, status: "passed", message, details };
@@ -30385,7 +30639,7 @@ function checkChangelog() {
30385
30639
  ];
30386
30640
  }
30387
30641
  function createReleaseCompatibilityReport(options = {}) {
30388
- const root = resolve16(options.root ?? process.cwd());
30642
+ const root = resolve17(options.root ?? process.cwd());
30389
30643
  const packageJson = readPackageJson2(root);
30390
30644
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
30391
30645
  const checks = [
@@ -36065,7 +36319,7 @@ function classifyLog(text) {
36065
36319
  async function sleep3(ms) {
36066
36320
  if (ms <= 0)
36067
36321
  return;
36068
- await new Promise((resolve17) => setTimeout(resolve17, ms));
36322
+ await new Promise((resolve18) => setTimeout(resolve18, ms));
36069
36323
  }
36070
36324
  async function runCommandProvider(provider, input) {
36071
36325
  const commandTemplate = input.command || provider.command;
@@ -38415,7 +38669,7 @@ __export(exports_local_backups, {
38415
38669
  });
38416
38670
  import { createHash as createHash8 } from "crypto";
38417
38671
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
38418
- import { dirname as dirname8, resolve as resolve17 } from "path";
38672
+ import { dirname as dirname8, resolve as resolve18 } from "path";
38419
38673
  import { mkdirSync as mkdirSync7 } from "fs";
38420
38674
  function stableJson(value) {
38421
38675
  if (value === null || typeof value !== "object")
@@ -38517,14 +38771,14 @@ function createLocalBackup(options = {}, db) {
38517
38771
  return backup;
38518
38772
  }
38519
38773
  function writeLocalBackupFile(backup, outputPath) {
38520
- const path = resolve17(outputPath);
38774
+ const path = resolve18(outputPath);
38521
38775
  mkdirSync7(dirname8(path), { recursive: true });
38522
38776
  writeFileSync5(path, `${JSON.stringify(backup, null, 2)}
38523
38777
  `);
38524
38778
  return path;
38525
38779
  }
38526
38780
  function readLocalBackupFile(path) {
38527
- return JSON.parse(readFileSync11(resolve17(path), "utf-8"));
38781
+ return JSON.parse(readFileSync11(resolve18(path), "utf-8"));
38528
38782
  }
38529
38783
  function verifyLocalBackup(value, options = {}, db) {
38530
38784
  const verifiedAt = options.verified_at ?? now();
@@ -42820,7 +43074,8 @@ function writeQueue(task2, queue, actor, action, db) {
42820
43074
  logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
42821
43075
  emitLocalEventHooksQuiet({
42822
43076
  type: `review.${action}`,
42823
- payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
43077
+ payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
43078
+ databasePath: databasePathFromDatabase(d)
42824
43079
  });
42825
43080
  return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
42826
43081
  }
@@ -43124,6 +43379,7 @@ var init_review_queues = __esm(() => {
43124
43379
  init_database();
43125
43380
  init_tasks();
43126
43381
  init_config();
43382
+ init_event_emission_safety();
43127
43383
  init_event_hooks();
43128
43384
  init_task_contracts();
43129
43385
  STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
@@ -45222,8 +45478,8 @@ __export(exports_environment_snapshots, {
45222
45478
  import { createHash as createHash12 } from "crypto";
45223
45479
  import { existsSync as existsSync17, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
45224
45480
  import { hostname as hostname2, platform, arch } from "os";
45225
- import { dirname as dirname9, join as join17, resolve as resolve18 } from "path";
45226
- import { tmpdir as tmpdir2 } from "os";
45481
+ import { dirname as dirname9, join as join17, resolve as resolve19 } from "path";
45482
+ import { tmpdir as tmpdir3 } from "os";
45227
45483
  function sha2566(value) {
45228
45484
  return createHash12("sha256").update(value).digest("hex");
45229
45485
  }
@@ -45336,15 +45592,15 @@ function commandEnv(env, includeValues) {
45336
45592
  function defaultSnapshotDir() {
45337
45593
  const dbPath = getDatabasePath();
45338
45594
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
45339
- return join17(tmpdir2(), "hasna-todos", "environment-snapshots");
45340
- return join17(dirname9(resolve18(dbPath)), "environment-snapshots");
45595
+ return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
45596
+ return join17(dirname9(resolve19(dbPath)), "environment-snapshots");
45341
45597
  }
45342
45598
  function snapshotWithId(snapshot) {
45343
45599
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
45344
45600
  return { id: `env_${digest}`, ...snapshot };
45345
45601
  }
45346
45602
  function captureEnvironmentSnapshot(input = {}) {
45347
- const root = resolve18(input.root || process.cwd());
45603
+ const root = resolve19(input.root || process.cwd());
45348
45604
  const env = input.env || process.env;
45349
45605
  const warnings = [];
45350
45606
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -45384,13 +45640,13 @@ function captureEnvironmentSnapshot(input = {}) {
45384
45640
  });
45385
45641
  }
45386
45642
  function writeEnvironmentSnapshot(snapshot, outputPath) {
45387
- const path = outputPath ? resolve18(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
45643
+ const path = outputPath ? resolve19(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
45388
45644
  ensureDir2(dirname9(path));
45389
45645
  writeJsonFile(path, snapshot);
45390
45646
  return path;
45391
45647
  }
45392
45648
  function readEnvironmentSnapshot(path) {
45393
- const snapshot = readJsonFile(resolve18(path));
45649
+ const snapshot = readJsonFile(resolve19(path));
45394
45650
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
45395
45651
  throw new Error(`Invalid environment snapshot: ${path}`);
45396
45652
  }
@@ -53241,7 +53497,7 @@ __export(exports_machines, {
53241
53497
  import chalk10 from "chalk";
53242
53498
  import { execSync as execSync4 } from "child_process";
53243
53499
  import { readFileSync as readFileSync17, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
53244
- import { tmpdir as tmpdir3 } from "os";
53500
+ import { tmpdir as tmpdir4 } from "os";
53245
53501
  import { join as join21 } from "path";
53246
53502
  function getOrCreateLocalMachineName() {
53247
53503
  return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
@@ -53280,7 +53536,7 @@ function remoteTempPath(sshAddress) {
53280
53536
  }
53281
53537
  function readRemoteBridgeBundle(sshAddress) {
53282
53538
  const remotePath = remoteTempPath(sshAddress);
53283
- const localPath = join21(tmpdir3(), `todos-bridge-pull-${uuid()}.json`);
53539
+ const localPath = join21(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
53284
53540
  try {
53285
53541
  runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
53286
53542
  scpFromRemote(sshAddress, remotePath, localPath);
@@ -53295,7 +53551,7 @@ function readRemoteBridgeBundle(sshAddress) {
53295
53551
  }
53296
53552
  }
53297
53553
  function writeLocalBridgeBundle() {
53298
- const localPath = join21(tmpdir3(), `todos-bridge-push-${uuid()}.json`);
53554
+ const localPath = join21(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
53299
53555
  writeFileSync10(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
53300
53556
  return localPath;
53301
53557
  }
@@ -54359,7 +54615,7 @@ __export(exports_onboarding_commands, {
54359
54615
  registerOnboardingCommands: () => registerOnboardingCommands
54360
54616
  });
54361
54617
  import chalk17 from "chalk";
54362
- import { resolve as resolve19 } from "path";
54618
+ import { resolve as resolve20 } from "path";
54363
54619
  function registerOnboardingCommands(program2) {
54364
54620
  program2.command("onboarding").alias("demo-fixtures").description("List, show, write, or import bundled local onboarding fixtures").option("--show <name>", "Show one fixture bridge bundle as JSON").option("--write <dir>", "Write all bundled fixture bridge bundles to a directory").option("--import <name>", "Dry-run or apply an onboarding fixture import").option("--apply", "Apply an onboarding fixture import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge existing local tasks while preserving divergent fields").action(async (opts) => {
54365
54621
  const globalOpts = program2.opts();
@@ -54375,7 +54631,7 @@ function registerOnboardingCommands(program2) {
54375
54631
  return;
54376
54632
  }
54377
54633
  if (opts.write) {
54378
- const result = writeOnboardingFixtureFiles2(resolve19(opts.write));
54634
+ const result = writeOnboardingFixtureFiles2(resolve20(opts.write));
54379
54635
  if (globalOpts.json) {
54380
54636
  output(result, true);
54381
54637
  return;
@@ -57955,7 +58211,7 @@ __export(exports_sdk_fixture_commands, {
57955
58211
  registerSdkFixtureCommands: () => registerSdkFixtureCommands
57956
58212
  });
57957
58213
  import chalk19 from "chalk";
57958
- import { resolve as resolve20 } from "path";
58214
+ import { resolve as resolve21 } from "path";
57959
58215
  function registerSdkFixtureCommands(program2) {
57960
58216
  program2.command("sdk-fixtures").description("List, show, or write local SDK integration fixtures").option("--show", "Print the full fixture pack JSON").option("--write <dir>", "Write fixture pack, bridge fixture, contract snapshots, and example index to a directory").action(async (opts) => {
57961
58217
  const globalOpts = program2.opts();
@@ -57966,7 +58222,7 @@ function registerSdkFixtureCommands(program2) {
57966
58222
  writeSdkIntegrationFixtures: writeSdkIntegrationFixtures2
57967
58223
  } = await Promise.resolve().then(() => (init_sdk_integration_fixtures(), exports_sdk_integration_fixtures));
57968
58224
  if (opts.write) {
57969
- const result = writeSdkIntegrationFixtures2(resolve20(opts.write));
58225
+ const result = writeSdkIntegrationFixtures2(resolve21(opts.write));
57970
58226
  if (globalOpts.json) {
57971
58227
  console.log(JSON.stringify(result));
57972
58228
  return;
@@ -58788,7 +59044,7 @@ __export(exports_local_backup_commands, {
58788
59044
  registerLocalBackupCommands: () => registerLocalBackupCommands
58789
59045
  });
58790
59046
  import chalk26 from "chalk";
58791
- import { resolve as resolve21 } from "path";
59047
+ import { resolve as resolve22 } from "path";
58792
59048
  function globalOptions6(program2) {
58793
59049
  const command = program2;
58794
59050
  return command.optsWithGlobals?.() ?? program2.opts();
@@ -58810,10 +59066,10 @@ function registerLocalBackupCommands(program2) {
58810
59066
  const projectId = opts.projectId ?? autoProject(globalOpts);
58811
59067
  const backupBundle = createLocalBackup2({
58812
59068
  project_id: projectId,
58813
- output_path: opts.output ? resolve21(opts.output) : undefined
59069
+ output_path: opts.output ? resolve22(opts.output) : undefined
58814
59070
  });
58815
59071
  const result = {
58816
- output_path: opts.output ? resolve21(opts.output) : null,
59072
+ output_path: opts.output ? resolve22(opts.output) : null,
58817
59073
  backup: backupBundle
58818
59074
  };
58819
59075
  if (opts.json || globalOpts.json) {
@@ -59072,11 +59328,13 @@ function exportSqliteTodosStorageSnapshot(db) {
59072
59328
  source: "sqlite",
59073
59329
  tasks: listTasks({ include_archived: true }, d),
59074
59330
  projects: listProjects(d),
59331
+ projectMachinePaths: listProjectMachinePaths(d),
59075
59332
  plans: listPlans(undefined, d),
59076
59333
  agents: listAgents({ include_archived: true }, d),
59077
59334
  taskLists: listTaskLists(undefined, d),
59078
59335
  templates: listTemplates(d),
59079
- auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d)
59336
+ auditHistory: getRecentActivity(Number.MAX_SAFE_INTEGER, d),
59337
+ tombstones: listStorageTombstones(d)
59080
59338
  };
59081
59339
  }
59082
59340
  function importSqliteTodosStorageSnapshot(snapshot, db) {
@@ -59084,13 +59342,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
59084
59342
  const result = {
59085
59343
  inserted: 0,
59086
59344
  updated: 0,
59345
+ deleted: 0,
59087
59346
  skipped: 0,
59088
59347
  errors: []
59089
59348
  };
59090
- const applyRows = (table, columns, rows, updateClockColumn, afterUpsert) => {
59349
+ const applyRows = (objectType2, table, columns, rows, updateClockColumn, afterUpsert) => {
59091
59350
  for (const row of rows) {
59092
59351
  try {
59093
59352
  const record = asRecord2(row);
59353
+ const tombstone = typeof record["id"] === "string" ? getStorageTombstone(objectType2, record["id"], d) : null;
59354
+ if (tombstone && shouldApplyStorageTombstone(tombstone, rowClock(record, updateClockColumn))) {
59355
+ result.skipped += 1;
59356
+ continue;
59357
+ }
59094
59358
  const state = upsertById(d, table, columns, record, updateClockColumn);
59095
59359
  if (state === "inserted")
59096
59360
  result.inserted += 1;
@@ -59104,17 +59368,19 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
59104
59368
  }
59105
59369
  }
59106
59370
  };
59107
- applyRows("projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
59108
- applyRows("agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
59109
- applyRows("task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
59110
- applyRows("plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
59111
- applyRows("task_templates", TEMPLATE_COLUMNS, snapshot.templates);
59112
- applyRows("tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
59371
+ applyRows("projects", "projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
59372
+ applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
59373
+ applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
59374
+ applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
59375
+ applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
59376
+ applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
59377
+ applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks2(snapshot.tasks), "updated_at", (row, changed) => {
59113
59378
  if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
59114
59379
  replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
59115
59380
  }
59116
59381
  });
59117
- applyRows("task_history", AUDIT_COLUMNS, snapshot.auditHistory);
59382
+ applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
59383
+ applyTombstones(d, snapshot.tombstones ?? [], result);
59118
59384
  return result;
59119
59385
  }
59120
59386
  function upsertById(db, table, columns, row, updateClockColumn) {
@@ -59169,7 +59435,86 @@ function sortedTasks2(tasks) {
59169
59435
  visit(task2);
59170
59436
  return result;
59171
59437
  }
59172
- var PROJECT_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
59438
+ function applyTombstones(db, tombstones, result) {
59439
+ for (const tombstone of tombstones) {
59440
+ try {
59441
+ recordStorageTombstone({
59442
+ object_type: tombstone.object_type,
59443
+ object_id: tombstone.object_id,
59444
+ deleted_at: tombstone.deleted_at,
59445
+ source_machine_id: tombstone.source_machine_id ?? null,
59446
+ payload: tombstone.payload ?? null,
59447
+ version: tombstone.version ?? null
59448
+ }, db);
59449
+ const table = tableForTombstone(tombstone.object_type);
59450
+ const existing = existingClock(db, table, tombstone.object_id);
59451
+ if (!shouldApplyStorageTombstone(tombstone, existing)) {
59452
+ result.skipped += 1;
59453
+ continue;
59454
+ }
59455
+ const deletedTags = table === "tasks" ? db.run("DELETE FROM task_tags WHERE task_id = ?", [tombstone.object_id]).changes : 0;
59456
+ const deleted = db.run(`DELETE FROM ${table} WHERE id = ?`, [tombstone.object_id]).changes;
59457
+ if (deleted > 0 || deletedTags > 0)
59458
+ result.deleted = (result.deleted ?? 0) + 1;
59459
+ else
59460
+ result.skipped += 1;
59461
+ } catch (error) {
59462
+ result.errors.push(error instanceof Error ? error.message : String(error));
59463
+ }
59464
+ }
59465
+ }
59466
+ function tableForTombstone(objectType2) {
59467
+ if (objectType2 === "tasks")
59468
+ return "tasks";
59469
+ if (objectType2 === "projects")
59470
+ return "projects";
59471
+ if (objectType2 === "project_machine_paths")
59472
+ return "project_machine_paths";
59473
+ if (objectType2 === "plans")
59474
+ return "plans";
59475
+ if (objectType2 === "agents")
59476
+ return "agents";
59477
+ if (objectType2 === "task_lists")
59478
+ return "task_lists";
59479
+ if (objectType2 === "templates")
59480
+ return "task_templates";
59481
+ return "task_history";
59482
+ }
59483
+ function listRows(db, table, columns) {
59484
+ return db.query(`SELECT ${columns.join(", ")} FROM ${table} ORDER BY id`).all();
59485
+ }
59486
+ function listProjectMachinePaths(db) {
59487
+ return listRows(db, "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS).map((row) => ({
59488
+ id: String(row.id),
59489
+ project_id: String(row.project_id),
59490
+ machine_id: String(row.machine_id),
59491
+ path: String(row.path),
59492
+ created_at: String(row.created_at),
59493
+ updated_at: String(row.updated_at)
59494
+ }));
59495
+ }
59496
+ function existingClock(db, table, id) {
59497
+ const clockColumns = clockColumnsForTable(table);
59498
+ const row = db.query(`SELECT ${clockColumns.join(", ")} FROM ${table} WHERE id = ?`).get(id);
59499
+ return row?.updated_at ?? row?.last_seen_at ?? row?.created_at ?? null;
59500
+ }
59501
+ function rowClock(row, updateClockColumn) {
59502
+ const value = updateClockColumn ? row[updateClockColumn] : null;
59503
+ return stringClock(value) ?? stringClock(row["updated_at"]) ?? stringClock(row["last_seen_at"]) ?? stringClock(row["created_at"]);
59504
+ }
59505
+ function stringClock(value) {
59506
+ return typeof value === "string" && value ? value : null;
59507
+ }
59508
+ function clockColumnsForTable(table) {
59509
+ if (table === "agents")
59510
+ return ["last_seen_at", "created_at"];
59511
+ if (table === "task_templates")
59512
+ return ["created_at"];
59513
+ if (table === "task_history")
59514
+ return ["created_at"];
59515
+ return ["updated_at", "created_at"];
59516
+ }
59517
+ var PROJECT_COLUMNS, PROJECT_MACHINE_PATH_COLUMNS, TASK_LIST_COLUMNS, PLAN_COLUMNS, AGENT_COLUMNS, TEMPLATE_COLUMNS, TASK_COLUMNS, AUDIT_COLUMNS, JSON_COLUMNS, BOOLEAN_COLUMNS;
59173
59518
  var init_sqlite_snapshot = __esm(() => {
59174
59519
  init_database();
59175
59520
  init_agents();
@@ -59179,6 +59524,7 @@ var init_sqlite_snapshot = __esm(() => {
59179
59524
  init_task_lists();
59180
59525
  init_tasks();
59181
59526
  init_templates();
59527
+ init_storage_tombstones();
59182
59528
  PROJECT_COLUMNS = [
59183
59529
  "id",
59184
59530
  "name",
@@ -59192,6 +59538,14 @@ var init_sqlite_snapshot = __esm(() => {
59192
59538
  "machine_id",
59193
59539
  "synced_at"
59194
59540
  ];
59541
+ PROJECT_MACHINE_PATH_COLUMNS = [
59542
+ "id",
59543
+ "project_id",
59544
+ "machine_id",
59545
+ "path",
59546
+ "created_at",
59547
+ "updated_at"
59548
+ ];
59195
59549
  TASK_LIST_COLUMNS = [
59196
59550
  "id",
59197
59551
  "project_id",
@@ -59501,7 +59855,7 @@ class PostgresTodosSyncStore {
59501
59855
  }
59502
59856
  async pullSnapshot(options = {}) {
59503
59857
  const params = [this.service];
59504
- const filters = ["service = $1", "deleted_at IS NULL"];
59858
+ const filters = ["service = $1"];
59505
59859
  if (options.since) {
59506
59860
  params.push(options.since);
59507
59861
  filters.push(`updated_at > $${params.length}::timestamptz`);
@@ -59510,7 +59864,7 @@ class PostgresTodosSyncStore {
59510
59864
  params.push(options.objectTypes);
59511
59865
  filters.push(`object_type = ANY($${params.length}::text[])`);
59512
59866
  }
59513
- const response = await this.client.query(`SELECT object_type, payload FROM ${this.tableName}
59867
+ const response = await this.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version FROM ${this.tableName}
59514
59868
  WHERE ${filters.join(" AND ")}
59515
59869
  ORDER BY updated_at ASC, object_type ASC, object_id ASC`, params);
59516
59870
  return rowsToSnapshot(response.rows);
@@ -59534,11 +59888,20 @@ function snapshotEntries(snapshot) {
59534
59888
  return [
59535
59889
  ...snapshot.tasks.map((payload) => entry("tasks", payload, snapshot.exportedAt)),
59536
59890
  ...snapshot.projects.map((payload) => entry("projects", payload, snapshot.exportedAt)),
59891
+ ...(snapshot.projectMachinePaths ?? []).map((payload) => entry("project_machine_paths", payload, snapshot.exportedAt)),
59537
59892
  ...snapshot.plans.map((payload) => entry("plans", payload, snapshot.exportedAt)),
59538
59893
  ...snapshot.agents.map((payload) => entry("agents", payload, snapshot.exportedAt)),
59539
59894
  ...snapshot.taskLists.map((payload) => entry("task_lists", payload, snapshot.exportedAt)),
59540
59895
  ...snapshot.templates.map((payload) => entry("templates", payload, snapshot.exportedAt)),
59541
- ...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt))
59896
+ ...snapshot.auditHistory.map((payload) => entry("audit_history", payload, snapshot.exportedAt)),
59897
+ ...(snapshot.tombstones ?? []).map((tombstone) => ({
59898
+ type: tombstone.object_type,
59899
+ id: tombstone.object_id,
59900
+ payload: tombstone.payload ?? { id: tombstone.object_id, deleted_at: tombstone.deleted_at },
59901
+ updatedAt: tombstone.updated_at || tombstone.deleted_at,
59902
+ deletedAt: tombstone.deleted_at,
59903
+ version: tombstone.version ?? null
59904
+ }))
59542
59905
  ];
59543
59906
  }
59544
59907
  function entry(type, payload, fallbackUpdatedAt) {
@@ -59560,19 +59923,38 @@ function rowsToSnapshot(rows) {
59560
59923
  source: "postgres",
59561
59924
  tasks: [],
59562
59925
  projects: [],
59926
+ projectMachinePaths: [],
59563
59927
  plans: [],
59564
59928
  agents: [],
59565
59929
  taskLists: [],
59566
59930
  templates: [],
59567
- auditHistory: []
59931
+ auditHistory: [],
59932
+ tombstones: []
59568
59933
  };
59569
59934
  for (const row of rows) {
59570
59935
  const payload = payloadRecord(row.payload);
59936
+ const deletedAt = stringValue(row.deleted_at);
59937
+ if (deletedAt) {
59938
+ snapshot.tombstones ??= [];
59939
+ snapshot.tombstones.push({
59940
+ object_type: row.object_type,
59941
+ object_id: stringValue(row.object_id) ?? stringValue(payload["id"]) ?? "",
59942
+ deleted_at: deletedAt,
59943
+ updated_at: stringValue(row.updated_at) ?? deletedAt,
59944
+ source_machine_id: stringValue(row.source_machine_id),
59945
+ payload,
59946
+ version: numberValue2(row.version)
59947
+ });
59948
+ continue;
59949
+ }
59571
59950
  if (row.object_type === "tasks")
59572
59951
  snapshot.tasks.push(payload);
59573
59952
  else if (row.object_type === "projects")
59574
59953
  snapshot.projects.push(payload);
59575
- else if (row.object_type === "plans")
59954
+ else if (row.object_type === "project_machine_paths") {
59955
+ snapshot.projectMachinePaths ??= [];
59956
+ snapshot.projectMachinePaths.push(payload);
59957
+ } else if (row.object_type === "plans")
59576
59958
  snapshot.plans.push(payload);
59577
59959
  else if (row.object_type === "agents")
59578
59960
  snapshot.agents.push(payload);
@@ -59593,6 +59975,8 @@ function payloadRecord(value) {
59593
59975
  throw new Error("Postgres sync payload must be a JSON object");
59594
59976
  }
59595
59977
  function stringValue(value) {
59978
+ if (value instanceof Date)
59979
+ return value.toISOString();
59596
59980
  return typeof value === "string" && value ? value : null;
59597
59981
  }
59598
59982
  function numberValue2(value) {
@@ -59751,6 +60135,9 @@ class PostgresJsonRecordStore {
59751
60135
  this.tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
59752
60136
  this.cursorTableName = options.cursorTableName ?? DEFAULT_TODOS_POSTGRES_CURSOR_TABLE;
59753
60137
  }
60138
+ machineId(context) {
60139
+ return context?.requestId ?? this.sourceMachineId ?? null;
60140
+ }
59754
60141
  async ensureSchema() {
59755
60142
  this.schemaReady ??= (async () => {
59756
60143
  for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
@@ -59782,6 +60169,25 @@ class PostgresJsonRecordStore {
59782
60169
  updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
59783
60170
  }));
59784
60171
  }
60172
+ async listTombstones() {
60173
+ await this.ensureSchema();
60174
+ const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
60175
+ FROM ${this.tableName}
60176
+ WHERE service = $1 AND deleted_at IS NOT NULL
60177
+ ORDER BY updated_at ASC, object_type ASC, object_id ASC`, [this.service]);
60178
+ return result.rows.map((row) => {
60179
+ const deletedAt = stringValue2(row.deleted_at) ?? stringValue2(row.updated_at) ?? new Date().toISOString();
60180
+ return {
60181
+ object_type: row.object_type,
60182
+ object_id: row.object_id,
60183
+ deleted_at: deletedAt,
60184
+ updated_at: stringValue2(row.updated_at) ?? deletedAt,
60185
+ source_machine_id: stringValue2(row.source_machine_id),
60186
+ payload: payloadRecord2(row.payload),
60187
+ version: numberValue3(row.version)
60188
+ };
60189
+ });
60190
+ }
59785
60191
  async upsert(type, value, context = {}) {
59786
60192
  await this.ensureSchema();
59787
60193
  const updatedAt = stringValue2(value.updated_at) ?? stringValue2(value.created_at) ?? new Date().toISOString();
@@ -59794,7 +60200,8 @@ class PostgresJsonRecordStore {
59794
60200
  updated_at = EXCLUDED.updated_at,
59795
60201
  deleted_at = NULL,
59796
60202
  source_machine_id = EXCLUDED.source_machine_id,
59797
- version = EXCLUDED.version`, [
60203
+ version = EXCLUDED.version
60204
+ WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
59798
60205
  this.service,
59799
60206
  type,
59800
60207
  value.id,
@@ -59811,13 +60218,58 @@ class PostgresJsonRecordStore {
59811
60218
  if (!existing)
59812
60219
  return false;
59813
60220
  const timestamp3 = new Date().toISOString();
59814
- await this.options.client.query(`UPDATE ${this.tableName}
59815
- SET deleted_at = $4::timestamptz,
59816
- updated_at = $4::timestamptz,
59817
- source_machine_id = $5
59818
- WHERE service = $1 AND object_type = $2 AND object_id = $3`, [this.service, type, id, timestamp3, context.requestId ?? this.sourceMachineId ?? null]);
60221
+ return this.tombstone({
60222
+ object_type: type,
60223
+ object_id: id,
60224
+ deleted_at: timestamp3,
60225
+ updated_at: timestamp3,
60226
+ payload: existing,
60227
+ version: numberValue3(existing["version"])
60228
+ }, context);
60229
+ }
60230
+ async tombstone(tombstone, context = {}) {
60231
+ await this.ensureSchema();
60232
+ const deletedAt = stringValue2(tombstone.deleted_at) ?? new Date().toISOString();
60233
+ const updatedAt = stringValue2(tombstone.updated_at) ?? deletedAt;
60234
+ const existing = await this.clock(tombstone.object_type, tombstone.object_id);
60235
+ if (existing && compareClock(existing.updatedAt, updatedAt) > 0)
60236
+ return false;
60237
+ await this.options.client.query(`INSERT INTO ${this.tableName} (
60238
+ service, object_type, object_id, payload, updated_at,
60239
+ deleted_at, source_machine_id, version
60240
+ ) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, $6::timestamptz, $7, $8)
60241
+ ON CONFLICT (service, object_type, object_id) DO UPDATE SET
60242
+ payload = EXCLUDED.payload,
60243
+ updated_at = EXCLUDED.updated_at,
60244
+ deleted_at = EXCLUDED.deleted_at,
60245
+ source_machine_id = EXCLUDED.source_machine_id,
60246
+ version = EXCLUDED.version
60247
+ WHERE ${this.tableName}.updated_at IS NULL OR ${this.tableName}.updated_at <= EXCLUDED.updated_at`, [
60248
+ this.service,
60249
+ tombstone.object_type,
60250
+ tombstone.object_id,
60251
+ JSON.stringify(tombstone.payload ?? { id: tombstone.object_id, deleted_at: deletedAt }),
60252
+ updatedAt,
60253
+ deletedAt,
60254
+ tombstone.source_machine_id ?? context.requestId ?? this.sourceMachineId ?? null,
60255
+ tombstone.version ?? null
60256
+ ]);
59819
60257
  return true;
59820
60258
  }
60259
+ async clock(type, id) {
60260
+ await this.ensureSchema();
60261
+ const result = await this.options.client.query(`SELECT object_type, object_id, updated_at, deleted_at
60262
+ FROM ${this.tableName}
60263
+ WHERE service = $1 AND object_type = $2 AND object_id = $3
60264
+ LIMIT 1`, [this.service, type, id]);
60265
+ const row = result.rows[0];
60266
+ if (!row)
60267
+ return null;
60268
+ return {
60269
+ updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString(),
60270
+ deletedAt: stringValue2(row.deleted_at)
60271
+ };
60272
+ }
59821
60273
  async getCursor(name) {
59822
60274
  await this.ensureSchema();
59823
60275
  const result = await this.options.client.query(`SELECT value FROM ${this.cursorTableName} WHERE service = $1 AND cursor_name = $2`, [this.service, name]);
@@ -59886,7 +60338,10 @@ async function createTask3(input, store, context) {
59886
60338
  runner_started_at: null,
59887
60339
  runner_completed_at: null,
59888
60340
  current_step: null,
59889
- total_steps: null
60341
+ total_steps: null,
60342
+ machine_id: store.machineId(context),
60343
+ synced_at: null,
60344
+ archived_at: null
59890
60345
  };
59891
60346
  await store.upsert("tasks", task2, context);
59892
60347
  await logTaskChange2(task2.id, "created", "status", null, task2.status, task2.assigned_by ?? task2.agent_id, store, context);
@@ -60011,7 +60466,9 @@ async function createProject2(input, store, context) {
60011
60466
  task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
60012
60467
  task_counter: 0,
60013
60468
  created_at: timestamp3,
60014
- updated_at: timestamp3
60469
+ updated_at: timestamp3,
60470
+ machine_id: store.machineId(context),
60471
+ synced_at: null
60015
60472
  };
60016
60473
  return store.upsert("projects", project, context);
60017
60474
  }
@@ -60031,7 +60488,9 @@ async function createPlan2(input, store, context) {
60031
60488
  description: input.description ?? null,
60032
60489
  status: input.status ?? "active",
60033
60490
  created_at: timestamp3,
60034
- updated_at: timestamp3
60491
+ updated_at: timestamp3,
60492
+ machine_id: store.machineId(context),
60493
+ synced_at: null
60035
60494
  }, context);
60036
60495
  }
60037
60496
  async function updatePlan2(id, input, store) {
@@ -60061,7 +60520,9 @@ async function registerAgent2(input, store, context) {
60061
60520
  last_seen_at: timestamp3,
60062
60521
  session_id: input.session_id ?? context?.sessionId ?? existing?.session_id ?? null,
60063
60522
  working_dir: input.working_dir ?? existing?.working_dir ?? null,
60064
- active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null
60523
+ active_project_id: input.project_id ?? context?.projectId ?? existing?.active_project_id ?? null,
60524
+ machine_id: existing?.machine_id ?? store.machineId(context),
60525
+ synced_at: existing?.synced_at ?? null
60065
60526
  };
60066
60527
  return store.upsert("agents", agent, context);
60067
60528
  }
@@ -60088,7 +60549,9 @@ async function createTaskList2(input, store, context) {
60088
60549
  description: input.description ?? null,
60089
60550
  metadata: input.metadata ?? {},
60090
60551
  created_at: timestamp3,
60091
- updated_at: timestamp3
60552
+ updated_at: timestamp3,
60553
+ machine_id: store.machineId(context),
60554
+ synced_at: null
60092
60555
  }, context);
60093
60556
  }
60094
60557
  async function updateTaskList2(id, input, store) {
@@ -60114,7 +60577,9 @@ async function createTemplate2(input, store, context) {
60114
60577
  project_id: input.project_id ?? context?.projectId ?? null,
60115
60578
  plan_id: input.plan_id ?? null,
60116
60579
  metadata: input.metadata ?? {},
60117
- created_at: timestamp3
60580
+ created_at: timestamp3,
60581
+ machine_id: store.machineId(context),
60582
+ synced_at: null
60118
60583
  }, context);
60119
60584
  }
60120
60585
  async function updateTemplate2(id, input, store) {
@@ -60139,7 +60604,8 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
60139
60604
  old_value: oldValue ?? null,
60140
60605
  new_value: newValue ?? null,
60141
60606
  agent_id: agentId ?? context?.agentId ?? null,
60142
- created_at: new Date().toISOString()
60607
+ created_at: new Date().toISOString(),
60608
+ machine_id: store.machineId(context)
60143
60609
  };
60144
60610
  return store.upsert("audit_history", entry2, context);
60145
60611
  }
@@ -60162,18 +60628,21 @@ async function exportSnapshot(store) {
60162
60628
  source: "postgres",
60163
60629
  tasks: await store.list("tasks"),
60164
60630
  projects: await store.list("projects"),
60631
+ projectMachinePaths: await store.list("project_machine_paths"),
60165
60632
  plans: await store.list("plans"),
60166
60633
  agents: await store.list("agents"),
60167
60634
  taskLists: await store.list("task_lists"),
60168
60635
  templates: await store.list("templates"),
60169
- auditHistory: await store.list("audit_history")
60636
+ auditHistory: await store.list("audit_history"),
60637
+ tombstones: await store.listTombstones()
60170
60638
  };
60171
60639
  }
60172
60640
  async function importSnapshot(snapshot, store, context) {
60173
- const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
60641
+ const result = { inserted: 0, updated: 0, deleted: 0, skipped: 0, errors: [] };
60174
60642
  const entries = [
60175
60643
  ...snapshot.tasks.map((row) => ["tasks", row]),
60176
60644
  ...snapshot.projects.map((row) => ["projects", row]),
60645
+ ...(snapshot.projectMachinePaths ?? []).map((row) => ["project_machine_paths", row]),
60177
60646
  ...snapshot.plans.map((row) => ["plans", row]),
60178
60647
  ...snapshot.agents.map((row) => ["agents", row]),
60179
60648
  ...snapshot.taskLists.map((row) => ["task_lists", row]),
@@ -60192,6 +60661,25 @@ async function importSnapshot(snapshot, store, context) {
60192
60661
  result.errors.push(error instanceof Error ? error.message : String(error));
60193
60662
  }
60194
60663
  }
60664
+ for (const tombstone of snapshot.tombstones ?? []) {
60665
+ try {
60666
+ const deleted = await store.tombstone({
60667
+ object_type: tombstone.object_type,
60668
+ object_id: tombstone.object_id,
60669
+ deleted_at: tombstone.deleted_at,
60670
+ updated_at: tombstone.updated_at,
60671
+ source_machine_id: tombstone.source_machine_id ?? null,
60672
+ payload: tombstone.payload ?? null,
60673
+ version: tombstone.version ?? null
60674
+ }, context);
60675
+ if (deleted)
60676
+ result.deleted = (result.deleted ?? 0) + 1;
60677
+ else
60678
+ result.skipped += 1;
60679
+ } catch (error) {
60680
+ result.errors.push(error instanceof Error ? error.message : String(error));
60681
+ }
60682
+ }
60195
60683
  return result;
60196
60684
  }
60197
60685
  async function requireRecord(type, id, store) {
@@ -60274,8 +60762,17 @@ function payloadRecord2(value) {
60274
60762
  throw new Error("Postgres storage payload must be a JSON object");
60275
60763
  }
60276
60764
  function stringValue2(value) {
60765
+ if (value instanceof Date)
60766
+ return value.toISOString();
60277
60767
  return typeof value === "string" && value ? value : null;
60278
60768
  }
60769
+ function compareClock(left, right) {
60770
+ const leftClock = Date.parse(left);
60771
+ const rightClock = Date.parse(right);
60772
+ if (Number.isNaN(leftClock) || Number.isNaN(rightClock))
60773
+ return left.localeCompare(right);
60774
+ return leftClock - rightClock;
60775
+ }
60279
60776
  function numberValue3(value) {
60280
60777
  return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
60281
60778
  }
@@ -60949,6 +61446,7 @@ var init_native_storage_status = __esm(() => {
60949
61446
  // src/cli/commands/storage-commands.ts
60950
61447
  var exports_storage_commands = {};
60951
61448
  __export(exports_storage_commands, {
61449
+ s3CredentialsFromEnv: () => s3CredentialsFromEnv,
60952
61450
  registerStorageCommands: () => registerStorageCommands
60953
61451
  });
60954
61452
  import chalk27 from "chalk";
@@ -61027,13 +61525,13 @@ function artifactFilter(opts) {
61027
61525
  ...opts.includeAlreadySynced ? { includeAlreadySynced: true } : {}
61028
61526
  };
61029
61527
  }
61030
- function s3CredentialsFromEnv() {
61031
- const { TODOS_STORAGE_ENV: TODOS_STORAGE_ENV2 } = (init_storage(), __toCommonJS(exports_storage));
61032
- const accessKeyId = process.env[TODOS_STORAGE_ENV2.s3AccessKeyId]?.trim();
61033
- const secretAccessKey = process.env[TODOS_STORAGE_ENV2.s3SecretAccessKey]?.trim();
61034
- const sessionToken = process.env[TODOS_STORAGE_ENV2.s3SessionToken]?.trim();
61528
+ function s3CredentialsFromEnv(env = process.env) {
61529
+ const { TODOS_STORAGE_ENV: TODOS_STORAGE_ENV2, TODOS_STORAGE_FALLBACK_ENV: TODOS_STORAGE_FALLBACK_ENV2 } = (init_storage(), __toCommonJS(exports_storage));
61530
+ const accessKeyId = (env[TODOS_STORAGE_ENV2.s3AccessKeyId] ?? env[TODOS_STORAGE_FALLBACK_ENV2.s3AccessKeyId])?.trim();
61531
+ const secretAccessKey = (env[TODOS_STORAGE_ENV2.s3SecretAccessKey] ?? env[TODOS_STORAGE_FALLBACK_ENV2.s3SecretAccessKey])?.trim();
61532
+ const sessionToken = (env[TODOS_STORAGE_ENV2.s3SessionToken] ?? env[TODOS_STORAGE_FALLBACK_ENV2.s3SessionToken])?.trim();
61035
61533
  if (!accessKeyId || !secretAccessKey) {
61036
- throw new Error(`${TODOS_STORAGE_ENV2.s3AccessKeyId} and ${TODOS_STORAGE_ENV2.s3SecretAccessKey} are required for --apply`);
61534
+ throw new Error(`${TODOS_STORAGE_ENV2.s3AccessKeyId}/${TODOS_STORAGE_FALLBACK_ENV2.s3AccessKeyId} and ${TODOS_STORAGE_ENV2.s3SecretAccessKey}/${TODOS_STORAGE_FALLBACK_ENV2.s3SecretAccessKey} are required for --apply`);
61037
61535
  }
61038
61536
  return {
61039
61537
  accessKeyId,