@hasna/todos 0.11.65 → 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 (55) hide show
  1. package/README.md +78 -49
  2. package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/query-commands.d.ts.map +1 -1
  6. package/dist/cli/commands/storage-commands.d.ts +5 -0
  7. package/dist/cli/commands/storage-commands.d.ts.map +1 -1
  8. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  9. package/dist/cli/index.js +1034 -231
  10. package/dist/contracts.js +409 -129
  11. package/dist/db/agents.d.ts.map +1 -1
  12. package/dist/db/audit.d.ts.map +1 -1
  13. package/dist/db/migrations.d.ts.map +1 -1
  14. package/dist/db/plans.d.ts.map +1 -1
  15. package/dist/db/projects.d.ts.map +1 -1
  16. package/dist/db/schema.d.ts.map +1 -1
  17. package/dist/db/storage-tombstones.d.ts +26 -0
  18. package/dist/db/storage-tombstones.d.ts.map +1 -0
  19. package/dist/db/task-crud.d.ts.map +1 -1
  20. package/dist/db/task-lifecycle.d.ts.map +1 -1
  21. package/dist/db/task-lists.d.ts.map +1 -1
  22. package/dist/db/task-runs.d.ts.map +1 -1
  23. package/dist/db/templates.d.ts.map +1 -1
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +928 -219
  27. package/dist/lib/approval-gates.d.ts.map +1 -1
  28. package/dist/lib/event-emission-safety.d.ts +9 -0
  29. package/dist/lib/event-emission-safety.d.ts.map +1 -0
  30. package/dist/lib/event-hooks.d.ts +1 -0
  31. package/dist/lib/event-hooks.d.ts.map +1 -1
  32. package/dist/lib/feature-manifest.d.ts.map +1 -1
  33. package/dist/lib/project-bootstrap.d.ts +1 -0
  34. package/dist/lib/project-bootstrap.d.ts.map +1 -1
  35. package/dist/lib/review-queues.d.ts.map +1 -1
  36. package/dist/lib/shared-events.d.ts +1 -0
  37. package/dist/lib/shared-events.d.ts.map +1 -1
  38. package/dist/lib/task-route-contract.d.ts +33 -0
  39. package/dist/lib/task-route-contract.d.ts.map +1 -0
  40. package/dist/lib/task-routing.d.ts +55 -0
  41. package/dist/lib/task-routing.d.ts.map +1 -0
  42. package/dist/mcp/index.js +459 -162
  43. package/dist/registry.js +409 -129
  44. package/dist/release-provenance.json +3 -3
  45. package/dist/server/index.js +482 -185
  46. package/dist/storage/interfaces.d.ts +20 -0
  47. package/dist/storage/interfaces.d.ts.map +1 -1
  48. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  49. package/dist/storage/postgres-sync.d.ts +6 -1
  50. package/dist/storage/postgres-sync.d.ts.map +1 -1
  51. package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
  52. package/dist/storage.js +677 -152
  53. package/dist/types/index.d.ts +22 -0
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/package.json +1 -1
package/dist/mcp/index.js CHANGED
@@ -1183,6 +1183,22 @@ var init_migrations = __esm(() => {
1183
1183
  CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
1184
1184
  CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
1185
1185
  INSERT OR IGNORE INTO _migrations (id) VALUES (62);
1186
+ `,
1187
+ `
1188
+ CREATE TABLE IF NOT EXISTS storage_tombstones (
1189
+ id TEXT PRIMARY KEY,
1190
+ object_type TEXT NOT NULL,
1191
+ object_id TEXT NOT NULL,
1192
+ deleted_at TEXT NOT NULL,
1193
+ updated_at TEXT NOT NULL,
1194
+ source_machine_id TEXT,
1195
+ payload TEXT,
1196
+ version INTEGER,
1197
+ UNIQUE(object_type, object_id)
1198
+ );
1199
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
1200
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
1201
+ INSERT OR IGNORE INTO _migrations (id) VALUES (63);
1186
1202
  `
1187
1203
  ];
1188
1204
  });
@@ -1704,6 +1720,20 @@ function ensureSchema(db) {
1704
1720
  )`);
1705
1721
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
1706
1722
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
1723
+ ensureTable("storage_tombstones", `
1724
+ CREATE TABLE storage_tombstones (
1725
+ id TEXT PRIMARY KEY,
1726
+ object_type TEXT NOT NULL,
1727
+ object_id TEXT NOT NULL,
1728
+ deleted_at TEXT NOT NULL,
1729
+ updated_at TEXT NOT NULL,
1730
+ source_machine_id TEXT,
1731
+ payload TEXT,
1732
+ version INTEGER,
1733
+ UNIQUE(object_type, object_id)
1734
+ )`);
1735
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
1736
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
1707
1737
  ensureTable("machines", `
1708
1738
  CREATE TABLE machines (
1709
1739
  id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
@@ -2765,6 +2795,65 @@ var init_database = __esm(() => {
2765
2795
  ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
2766
2796
  });
2767
2797
 
2798
+ // src/db/storage-tombstones.ts
2799
+ function recordStorageTombstone(input, db) {
2800
+ const d = db ?? getDatabase();
2801
+ const deletedAt = input.deleted_at ?? now();
2802
+ const machineId = input.source_machine_id ?? currentStorageMachineId(d);
2803
+ d.run(`INSERT INTO storage_tombstones (
2804
+ id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
2805
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2806
+ ON CONFLICT(object_type, object_id) DO UPDATE SET
2807
+ deleted_at = excluded.deleted_at,
2808
+ updated_at = excluded.updated_at,
2809
+ source_machine_id = excluded.source_machine_id,
2810
+ payload = excluded.payload,
2811
+ version = excluded.version
2812
+ WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
2813
+ uuid(),
2814
+ input.object_type,
2815
+ input.object_id,
2816
+ deletedAt,
2817
+ deletedAt,
2818
+ machineId,
2819
+ input.payload ? JSON.stringify(input.payload) : null,
2820
+ input.version ?? null
2821
+ ]);
2822
+ return getStorageTombstone(input.object_type, input.object_id, d);
2823
+ }
2824
+ function getStorageTombstone(objectType, objectId, db) {
2825
+ const d = db ?? getDatabase();
2826
+ const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
2827
+ return row ? rowToStorageTombstone(row) : null;
2828
+ }
2829
+ function rowToStorageTombstone(row) {
2830
+ return {
2831
+ ...row,
2832
+ payload: parsePayload(row.payload)
2833
+ };
2834
+ }
2835
+ function parsePayload(value) {
2836
+ if (!value)
2837
+ return null;
2838
+ try {
2839
+ const parsed = JSON.parse(value);
2840
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2841
+ } catch {
2842
+ return null;
2843
+ }
2844
+ }
2845
+ function currentStorageMachineId(db) {
2846
+ try {
2847
+ return getMachineId(db);
2848
+ } catch {
2849
+ return null;
2850
+ }
2851
+ }
2852
+ var init_storage_tombstones = __esm(() => {
2853
+ init_database();
2854
+ init_machines();
2855
+ });
2856
+
2768
2857
  // src/db/agent-names.ts
2769
2858
  function normalizeAgentNameInput(name) {
2770
2859
  return name.trim().toLowerCase();
@@ -3173,6 +3262,7 @@ function rowToAgent(row) {
3173
3262
  }
3174
3263
  function registerAgent(input, db) {
3175
3264
  const d = db || getDatabase();
3265
+ const machineId = currentStorageMachineId(d);
3176
3266
  const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
3177
3267
  const normalizedName = validateAgentName(input.name, existingNames);
3178
3268
  const existing = getAgentByName(normalizedName, d);
@@ -3213,14 +3303,18 @@ function registerAgent(input, db) {
3213
3303
  updates.push("active_project_id = ?");
3214
3304
  params.push(input.project_id);
3215
3305
  }
3306
+ if (!existing.machine_id && machineId) {
3307
+ updates.push("machine_id = ?");
3308
+ params.push(machineId);
3309
+ }
3216
3310
  params.push(existing.id);
3217
3311
  d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
3218
3312
  return getAgent(existing.id, d);
3219
3313
  }
3220
3314
  const id = shortUuid();
3221
3315
  const timestamp = now();
3222
- 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)
3223
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
3316
+ 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)
3317
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
3224
3318
  id,
3225
3319
  normalizedName,
3226
3320
  input.description || null,
@@ -3236,7 +3330,8 @@ function registerAgent(input, db) {
3236
3330
  timestamp,
3237
3331
  input.session_id || null,
3238
3332
  input.working_dir || null,
3239
- input.project_id && input.session_id ? input.project_id : null
3333
+ input.project_id && input.session_id ? input.project_id : null,
3334
+ machineId
3240
3335
  ]);
3241
3336
  return getAgent(id, d);
3242
3337
  }
@@ -3419,6 +3514,7 @@ function getCapableAgents(capabilities, opts, db) {
3419
3514
  }
3420
3515
  var init_agents = __esm(() => {
3421
3516
  init_database();
3517
+ init_storage_tombstones();
3422
3518
  init_agent_names();
3423
3519
  });
3424
3520
 
@@ -7767,8 +7863,9 @@ function createProject(input, db) {
7767
7863
  const timestamp = now();
7768
7864
  const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
7769
7865
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
7770
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
7771
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
7866
+ const machineId = currentStorageMachineId(d);
7867
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
7868
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
7772
7869
  return getProject(id, d);
7773
7870
  }
7774
7871
  function getProject(id, db) {
@@ -7847,6 +7944,14 @@ function renameProject(id, input, db) {
7847
7944
  }
7848
7945
  function deleteProject(id, db) {
7849
7946
  const d = db || getDatabase();
7947
+ const project = getProject(id, d);
7948
+ if (!project)
7949
+ return false;
7950
+ recordStorageTombstone({
7951
+ object_type: "projects",
7952
+ object_id: id,
7953
+ payload: project
7954
+ }, d);
7850
7955
  const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
7851
7956
  return result.changes > 0;
7852
7957
  }
@@ -7951,6 +8056,14 @@ function listMachineLocalPaths(projectId, db) {
7951
8056
  function removeMachineLocalPath(projectId, machineId, db) {
7952
8057
  const d = db || getDatabase();
7953
8058
  const mid = machineId ?? getMachineId(d);
8059
+ const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
8060
+ if (!existing)
8061
+ return false;
8062
+ recordStorageTombstone({
8063
+ object_type: "project_machine_paths",
8064
+ object_id: existing.id,
8065
+ payload: existing
8066
+ }, d);
7954
8067
  const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
7955
8068
  return result.changes > 0;
7956
8069
  }
@@ -7958,6 +8071,7 @@ var init_projects = __esm(() => {
7958
8071
  init_types();
7959
8072
  init_database();
7960
8073
  init_machines();
8074
+ init_storage_tombstones();
7961
8075
  });
7962
8076
 
7963
8077
  // src/lib/completion-guard.ts
@@ -8009,6 +8123,57 @@ var init_completion_guard = __esm(() => {
8009
8123
  init_projects();
8010
8124
  });
8011
8125
 
8126
+ // src/lib/event-emission-safety.ts
8127
+ import { tmpdir } from "os";
8128
+ import { resolve as resolve3, sep } from "path";
8129
+ function envFlag(name) {
8130
+ const value = process.env[name];
8131
+ return value === "1" || value === "true" || value === "yes";
8132
+ }
8133
+ function isUnder(parent, child) {
8134
+ const normalizedParent = resolve3(parent);
8135
+ const normalizedChild = resolve3(child);
8136
+ return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
8137
+ }
8138
+ function databasePathFromDatabase(db) {
8139
+ const filename = db?.filename;
8140
+ return typeof filename === "string" && filename.trim() ? filename : undefined;
8141
+ }
8142
+ function isEphemeralTodosDatabase(dbPath) {
8143
+ const resolvedPath = dbPath ?? getDatabasePath();
8144
+ if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
8145
+ return true;
8146
+ return isUnder(tmpdir(), resolvedPath);
8147
+ }
8148
+ function hasExplicitSharedEventsStore() {
8149
+ return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
8150
+ }
8151
+ function usesIsolatedTodosHome() {
8152
+ return isUnder(tmpdir(), getTodosGlobalDir());
8153
+ }
8154
+ function shouldEmitSharedTaskEvents(dbPath) {
8155
+ if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
8156
+ return false;
8157
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
8158
+ return true;
8159
+ if (!isEphemeralTodosDatabase(dbPath))
8160
+ return true;
8161
+ return hasExplicitSharedEventsStore();
8162
+ }
8163
+ function shouldDeliverLocalLifecycleHooks(dbPath) {
8164
+ if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
8165
+ return false;
8166
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
8167
+ return true;
8168
+ if (!isEphemeralTodosDatabase(dbPath))
8169
+ return true;
8170
+ return usesIsolatedTodosHome();
8171
+ }
8172
+ var init_event_emission_safety = __esm(() => {
8173
+ init_database();
8174
+ init_sync_utils();
8175
+ });
8176
+
8012
8177
  // src/lib/redaction.ts
8013
8178
  function unique(values) {
8014
8179
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -8109,9 +8274,9 @@ var init_redaction = __esm(() => {
8109
8274
  });
8110
8275
 
8111
8276
  // src/lib/workspace-trust.ts
8112
- import { relative, resolve as resolve3 } from "path";
8277
+ import { relative, resolve as resolve4 } from "path";
8113
8278
  function normalizePath(path) {
8114
- return resolve3(path);
8279
+ return resolve4(path);
8115
8280
  }
8116
8281
  function unique2(values) {
8117
8282
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -8293,9 +8458,9 @@ var init_workspace_trust = __esm(() => {
8293
8458
  });
8294
8459
 
8295
8460
  // src/lib/runner-sandbox.ts
8296
- import { relative as relative2, resolve as resolve4 } from "path";
8461
+ import { relative as relative2, resolve as resolve5 } from "path";
8297
8462
  function normalizePath2(path) {
8298
- return resolve4(path);
8463
+ return resolve5(path);
8299
8464
  }
8300
8465
  function unique3(values) {
8301
8466
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -8485,7 +8650,7 @@ var init_runner_sandbox = __esm(() => {
8485
8650
  // src/lib/event-hooks.ts
8486
8651
  import { createHash, randomUUID } from "crypto";
8487
8652
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
8488
- import { dirname as dirname3, resolve as resolve5 } from "path";
8653
+ import { dirname as dirname3, resolve as resolve6 } from "path";
8489
8654
  import { createConnection } from "net";
8490
8655
  function safeName(name) {
8491
8656
  const trimmed = name.trim();
@@ -8623,7 +8788,7 @@ async function deliverHook(hook, envelope) {
8623
8788
  if (hook.target === "stdout") {
8624
8789
  output = line.trim();
8625
8790
  } else if (hook.target === "file") {
8626
- const filePath = resolve5(hook.file_path);
8791
+ const filePath = resolve6(hook.file_path);
8627
8792
  mkdirSync3(dirname3(filePath), { recursive: true });
8628
8793
  appendFileSync(filePath, line);
8629
8794
  } else if (hook.target === "socket") {
@@ -8698,6 +8863,8 @@ async function emitLocalEventHooks(input) {
8698
8863
  return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
8699
8864
  }
8700
8865
  function emitLocalEventHooksQuiet(input) {
8866
+ if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
8867
+ return;
8701
8868
  emitLocalEventHooks(input).catch(() => {});
8702
8869
  }
8703
8870
  async function testLocalEventHook(name, input) {
@@ -8711,6 +8878,7 @@ var init_event_hooks = __esm(() => {
8711
8878
  init_redaction();
8712
8879
  init_runner_sandbox();
8713
8880
  init_config();
8881
+ init_event_emission_safety();
8714
8882
  LOCAL_EVENT_TYPES = [
8715
8883
  "task.created",
8716
8884
  "task.assigned",
@@ -9043,7 +9211,7 @@ async function dispatchCommand(event, channel) {
9043
9211
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
9044
9212
  HASNA_EVENT_JSON: eventJson
9045
9213
  };
9046
- return new Promise((resolve6) => {
9214
+ return new Promise((resolve7) => {
9047
9215
  const child = spawn(channel.command.command, channel.command.args ?? [], {
9048
9216
  cwd: channel.command.cwd,
9049
9217
  env,
@@ -9061,7 +9229,7 @@ async function dispatchCommand(event, channel) {
9061
9229
  });
9062
9230
  child.on("error", (error) => {
9063
9231
  clearTimeout(timeout);
9064
- resolve6({
9232
+ resolve7({
9065
9233
  attempt: 1,
9066
9234
  status: "failed",
9067
9235
  startedAt,
@@ -9074,7 +9242,7 @@ async function dispatchCommand(event, channel) {
9074
9242
  child.on("close", (code, signal) => {
9075
9243
  clearTimeout(timeout);
9076
9244
  const success = code === 0;
9077
- resolve6({
9245
+ resolve7({
9078
9246
  attempt: 1,
9079
9247
  status: success ? "success" : "failed",
9080
9248
  startedAt,
@@ -9341,14 +9509,15 @@ function createTaskList(input, db) {
9341
9509
  const id = uuid();
9342
9510
  const timestamp = now();
9343
9511
  const slug = input.slug || slugify(input.name);
9512
+ const machineId = currentStorageMachineId(d);
9344
9513
  if (!input.project_id) {
9345
9514
  const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
9346
9515
  if (existing) {
9347
9516
  throw new Error(`Standalone task list with slug "${slug}" already exists`);
9348
9517
  }
9349
9518
  }
9350
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
9351
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
9519
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
9520
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
9352
9521
  return getTaskList(id, d);
9353
9522
  }
9354
9523
  function getTaskList(id, db) {
@@ -9398,6 +9567,14 @@ function updateTaskList(id, input, db) {
9398
9567
  }
9399
9568
  function deleteTaskList(id, db) {
9400
9569
  const d = db || getDatabase();
9570
+ const list = getTaskList(id, d);
9571
+ if (!list)
9572
+ return false;
9573
+ recordStorageTombstone({
9574
+ object_type: "task_lists",
9575
+ object_id: id,
9576
+ payload: list
9577
+ }, d);
9401
9578
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
9402
9579
  }
9403
9580
  function ensureTaskList(name, slug, projectId, db) {
@@ -9411,40 +9588,10 @@ var init_task_lists = __esm(() => {
9411
9588
  init_types();
9412
9589
  init_database();
9413
9590
  init_projects();
9591
+ init_storage_tombstones();
9414
9592
  });
9415
9593
 
9416
- // src/lib/shared-events.ts
9417
- function taskEventData(task, extra = {}) {
9418
- return {
9419
- id: task.id,
9420
- task_id: task.id,
9421
- short_id: task.short_id,
9422
- title: task.title,
9423
- description: task.description,
9424
- status: task.status,
9425
- priority: task.priority,
9426
- project_id: task.project_id,
9427
- parent_id: task.parent_id,
9428
- plan_id: task.plan_id,
9429
- task_list_id: task.task_list_id,
9430
- agent_id: task.agent_id,
9431
- assigned_to: task.assigned_to,
9432
- session_id: task.session_id,
9433
- working_dir: task.working_dir,
9434
- tags: task.tags,
9435
- metadata: task.metadata,
9436
- version: task.version,
9437
- created_at: task.created_at,
9438
- updated_at: task.updated_at,
9439
- started_at: task.started_at,
9440
- completed_at: task.completed_at,
9441
- due_at: task.due_at,
9442
- requires_approval: task.requires_approval,
9443
- approved_by: task.approved_by,
9444
- approved_at: task.approved_at,
9445
- ...extra
9446
- };
9447
- }
9594
+ // src/lib/task-route-contract.ts
9448
9595
  function booleanField(value) {
9449
9596
  if (typeof value === "boolean")
9450
9597
  return value;
@@ -9466,21 +9613,31 @@ function booleanField(value) {
9466
9613
  function objectField(value) {
9467
9614
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
9468
9615
  }
9469
- function firstBoolean(records, keys) {
9616
+ function collectBooleans(records, keys) {
9617
+ const values = [];
9470
9618
  for (const record of records) {
9619
+ if (!record)
9620
+ continue;
9471
9621
  for (const key of keys) {
9472
9622
  const value = booleanField(record[key]);
9473
9623
  if (value !== undefined)
9474
- return value;
9624
+ values.push(value);
9475
9625
  }
9476
9626
  }
9477
- return;
9627
+ return values;
9478
9628
  }
9479
- function routingAutomationMetadata(task) {
9480
- const automation = objectField(task.metadata.automation);
9481
- const records = [task.metadata];
9482
- if (automation)
9483
- records.push(automation);
9629
+ function mergedBoolean(records, keys, trueWins) {
9630
+ const values = collectBooleans(records, keys);
9631
+ if (values.length === 0)
9632
+ return;
9633
+ if (trueWins)
9634
+ return values.some(Boolean);
9635
+ return values.includes(false) ? false : true;
9636
+ }
9637
+ function routingAutomationMetadata(task, taskList) {
9638
+ const taskAutomation = objectField(task.metadata.automation);
9639
+ const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
9640
+ const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
9484
9641
  const result = {};
9485
9642
  const aliases = [
9486
9643
  ["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
@@ -9491,7 +9648,7 @@ function routingAutomationMetadata(task) {
9491
9648
  ["approval_required", ["approval_required", "approvalRequired"]]
9492
9649
  ];
9493
9650
  for (const [canonical, keys] of aliases) {
9494
- const value = firstBoolean(records, keys);
9651
+ const value = mergedBoolean(records, keys, canonical !== "allowed");
9495
9652
  if (value !== undefined)
9496
9653
  result[canonical] = value;
9497
9654
  }
@@ -9499,23 +9656,88 @@ function routingAutomationMetadata(task) {
9499
9656
  result.requires_approval = true;
9500
9657
  return Object.keys(result).length > 0 ? result : undefined;
9501
9658
  }
9659
+ function routeEnabledForTask(task, taskList) {
9660
+ const explicit = booleanField(task.metadata.route_enabled);
9661
+ if (explicit !== undefined)
9662
+ return explicit;
9663
+ if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
9664
+ return true;
9665
+ const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
9666
+ if (taskListDefault !== undefined)
9667
+ return taskListDefault;
9668
+ return;
9669
+ }
9670
+ function stringField(value) {
9671
+ return typeof value === "string" && value.trim() ? value : undefined;
9672
+ }
9673
+ function workflowPointersFromMetadata(metadata) {
9674
+ const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
9675
+ return {
9676
+ current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
9677
+ current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
9678
+ latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
9679
+ latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
9680
+ workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
9681
+ };
9682
+ }
9683
+ function classifyProjectKind(path) {
9684
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
9685
+ }
9686
+ function isWorktreePath(path) {
9687
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
9688
+ }
9689
+ function inferRootProjectId(project) {
9690
+ return isWorktreePath(project.path) ? null : project.id;
9691
+ }
9692
+ var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
9693
+
9694
+ // src/lib/shared-events.ts
9695
+ function taskEventData(task, extra = {}) {
9696
+ return {
9697
+ id: task.id,
9698
+ task_id: task.id,
9699
+ short_id: task.short_id,
9700
+ title: task.title,
9701
+ description: task.description,
9702
+ status: task.status,
9703
+ priority: task.priority,
9704
+ project_id: task.project_id,
9705
+ parent_id: task.parent_id,
9706
+ plan_id: task.plan_id,
9707
+ task_list_id: task.task_list_id,
9708
+ agent_id: task.agent_id,
9709
+ assigned_to: task.assigned_to,
9710
+ session_id: task.session_id,
9711
+ working_dir: task.working_dir,
9712
+ tags: task.tags,
9713
+ metadata: task.metadata,
9714
+ version: task.version,
9715
+ created_at: task.created_at,
9716
+ updated_at: task.updated_at,
9717
+ started_at: task.started_at,
9718
+ completed_at: task.completed_at,
9719
+ due_at: task.due_at,
9720
+ requires_approval: task.requires_approval,
9721
+ approved_by: task.approved_by,
9722
+ approved_at: task.approved_at,
9723
+ ...extra
9724
+ };
9725
+ }
9502
9726
  function taskEventMetadata(task) {
9503
9727
  const metadata = {
9504
9728
  package: "@hasna/todos",
9505
9729
  todos_event_schema_version: 1,
9730
+ route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
9506
9731
  task_id: task.id,
9507
9732
  task_short_id: task.short_id,
9508
9733
  project_id: task.project_id,
9509
9734
  task_list_id: task.task_list_id,
9510
9735
  working_dir: task.working_dir
9511
9736
  };
9512
- const routeEnabled = booleanField(task.metadata.route_enabled);
9513
- if (routeEnabled !== undefined) {
9514
- metadata.route_enabled = routeEnabled;
9515
- }
9516
- const automation = routingAutomationMetadata(task);
9517
- if (automation) {
9518
- metadata.automation = automation;
9737
+ const pointers = workflowPointersFromMetadata(task.metadata);
9738
+ for (const [key, value] of Object.entries(pointers)) {
9739
+ if (value)
9740
+ metadata[key] = value;
9519
9741
  }
9520
9742
  try {
9521
9743
  const project = task.project_id ? getProject(task.project_id) : null;
@@ -9544,18 +9766,20 @@ function taskEventMetadata(task) {
9544
9766
  metadata.task_list_project_id = taskList.project_id;
9545
9767
  metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
9546
9768
  }
9769
+ const routeEnabled = routeEnabledForTask(task, taskList);
9770
+ if (routeEnabled !== undefined) {
9771
+ metadata.route_enabled = routeEnabled;
9772
+ }
9773
+ const automation = routingAutomationMetadata(task, taskList);
9774
+ if (automation) {
9775
+ metadata.automation = automation;
9776
+ metadata.route_blocked_by_no_auto = automation.no_auto === true;
9777
+ metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
9778
+ metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
9779
+ }
9547
9780
  } catch {}
9548
9781
  return metadata;
9549
9782
  }
9550
- function classifyProjectKind(path) {
9551
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
9552
- }
9553
- function isWorktreePath(path) {
9554
- return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
9555
- }
9556
- function inferRootProjectId(project) {
9557
- return isWorktreePath(project.path) ? null : project.id;
9558
- }
9559
9783
  function readMachineLocalPath(project) {
9560
9784
  const machineId = process.env["TODOS_MACHINE_ID"];
9561
9785
  if (!machineId)
@@ -9568,6 +9792,8 @@ function readMachineLocalPath(project) {
9568
9792
  }
9569
9793
  }
9570
9794
  async function emitSharedTaskEvent(input) {
9795
+ if (!shouldEmitSharedTaskEvents(input.databasePath))
9796
+ return;
9571
9797
  const data = taskEventData(input.task, input.data);
9572
9798
  await new EventsClient().emit({
9573
9799
  source: SOURCE,
@@ -9591,6 +9817,7 @@ var init_shared_events = __esm(() => {
9591
9817
  init_database();
9592
9818
  init_projects();
9593
9819
  init_task_lists();
9820
+ init_event_emission_safety();
9594
9821
  });
9595
9822
 
9596
9823
  // src/lib/secret-redaction.ts
@@ -9839,8 +10066,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
9839
10066
  const d = db || getDatabase();
9840
10067
  const id = uuid();
9841
10068
  const timestamp = now();
9842
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
9843
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
10069
+ const machineId = currentStorageMachineId(d);
10070
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
10071
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
9844
10072
  try {
9845
10073
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
9846
10074
  logActivity2({
@@ -9853,7 +10081,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
9853
10081
  actor_id: agentId ?? undefined
9854
10082
  }, d);
9855
10083
  } catch {}
9856
- return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
10084
+ 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 };
9857
10085
  }
9858
10086
  function getTaskHistory(taskId, db) {
9859
10087
  const d = db || getDatabase();
@@ -9865,6 +10093,7 @@ function getRecentActivity(limit = 50, db) {
9865
10093
  }
9866
10094
  var init_audit = __esm(() => {
9867
10095
  init_database();
10096
+ init_storage_tombstones();
9868
10097
  });
9869
10098
 
9870
10099
  // src/db/webhooks.ts
@@ -10130,13 +10359,14 @@ function createTask(input, db) {
10130
10359
  const d = db || getDatabase();
10131
10360
  const timestamp = now();
10132
10361
  const tags = input.tags || [];
10362
+ const machineId = currentStorageMachineId(d);
10133
10363
  const assignedBy = input.assigned_by || input.agent_id;
10134
10364
  const assignedFromProject = input.assigned_from_project || null;
10135
10365
  let id = uuid();
10136
10366
  for (let attempt = 0;attempt < 3; attempt++) {
10137
10367
  try {
10138
- 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)
10139
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10368
+ 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)
10369
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10140
10370
  id,
10141
10371
  null,
10142
10372
  input.project_id || null,
@@ -10173,7 +10403,8 @@ function createTask(input, db) {
10173
10403
  input.spawned_from_session || null,
10174
10404
  assignedBy || null,
10175
10405
  assignedFromProject || null,
10176
- input.task_type || null
10406
+ input.task_type || null,
10407
+ machineId
10177
10408
  ]);
10178
10409
  break;
10179
10410
  } catch (e) {
@@ -10189,9 +10420,10 @@ function createTask(input, db) {
10189
10420
  }
10190
10421
  const task = getTask(id, d);
10191
10422
  const payload = taskEventData(task);
10423
+ const databasePath = databasePathFromDatabase(d);
10192
10424
  dispatchWebhook2("task.created", payload, d).catch(() => {});
10193
- emitLocalEventHooksQuiet({ type: "task.created", payload });
10194
- emitSharedTaskEventQuiet({ type: "task.created", task });
10425
+ emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
10426
+ emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
10195
10427
  return task;
10196
10428
  }
10197
10429
  function getTask(id, db) {
@@ -10609,29 +10841,39 @@ function updateTask(id, input, db) {
10609
10841
  approved_by: input.approved_by ?? task.approved_by,
10610
10842
  approved_at: input.approved_by ? timestamp : task.approved_at
10611
10843
  };
10844
+ const databasePath = databasePathFromDatabase(d);
10612
10845
  if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
10613
10846
  const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
10614
10847
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
10615
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
10616
- emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
10848
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
10849
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
10617
10850
  }
10618
10851
  if (input.status !== undefined && input.status !== task.status) {
10619
10852
  const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
10620
10853
  dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
10621
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
10622
- emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
10854
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
10855
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
10623
10856
  }
10624
10857
  if (input.approved_by !== undefined) {
10625
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
10858
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
10626
10859
  }
10627
10860
  const updatePayload = taskEventData(updatedTask);
10628
10861
  dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
10629
- emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
10630
- emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
10862
+ emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
10863
+ emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
10631
10864
  return updatedTask;
10632
10865
  }
10633
10866
  function deleteTask(id, db) {
10634
10867
  const d = db || getDatabase();
10868
+ const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
10869
+ if (!row)
10870
+ return false;
10871
+ recordStorageTombstone({
10872
+ object_type: "tasks",
10873
+ object_id: id,
10874
+ payload: rowToTask(row),
10875
+ version: row.version
10876
+ }, d);
10635
10877
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
10636
10878
  return result.changes > 0;
10637
10879
  }
@@ -10639,11 +10881,13 @@ var init_task_crud = __esm(() => {
10639
10881
  init_types();
10640
10882
  init_database();
10641
10883
  init_completion_guard();
10884
+ init_event_emission_safety();
10642
10885
  init_event_hooks();
10643
10886
  init_shared_events();
10644
10887
  init_audit();
10645
10888
  init_webhooks();
10646
10889
  init_checklists();
10890
+ init_storage_tombstones();
10647
10891
  });
10648
10892
 
10649
10893
  // src/lib/recurrence.ts
@@ -10792,8 +11036,9 @@ function resolveTemplateId(id, d) {
10792
11036
  function createTemplate(input, db) {
10793
11037
  const d = db || getDatabase();
10794
11038
  const id = uuid();
10795
- d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
10796
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
11039
+ const machineId = currentStorageMachineId(d);
11040
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
11041
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10797
11042
  id,
10798
11043
  input.name,
10799
11044
  input.title_pattern,
@@ -10804,7 +11049,8 @@ function createTemplate(input, db) {
10804
11049
  input.project_id || null,
10805
11050
  input.plan_id || null,
10806
11051
  JSON.stringify(input.metadata || {}),
10807
- now()
11052
+ now(),
11053
+ machineId
10808
11054
  ]);
10809
11055
  if (input.tasks && input.tasks.length > 0) {
10810
11056
  addTemplateTasks(id, input.tasks, d);
@@ -10828,6 +11074,15 @@ function deleteTemplate(id, db) {
10828
11074
  const resolved = resolveTemplateId(id, d);
10829
11075
  if (!resolved)
10830
11076
  return false;
11077
+ const template = getTemplate(resolved, d);
11078
+ if (!template)
11079
+ return false;
11080
+ recordStorageTombstone({
11081
+ object_type: "templates",
11082
+ object_id: resolved,
11083
+ payload: template,
11084
+ version: template.version
11085
+ }, d);
10831
11086
  return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
10832
11087
  }
10833
11088
  function updateTemplate(id, updates, db) {
@@ -11197,6 +11452,7 @@ function previewTemplate(templateId, variables, db) {
11197
11452
  var init_templates = __esm(() => {
11198
11453
  init_database();
11199
11454
  init_tasks();
11455
+ init_storage_tombstones();
11200
11456
  });
11201
11457
 
11202
11458
  // src/db/task-graph.ts
@@ -11363,6 +11619,7 @@ function getBlockingDeps(id, db) {
11363
11619
  }
11364
11620
  function startTask(id, agentId, db) {
11365
11621
  const d = db || getDatabase();
11622
+ const databasePath = databasePathFromDatabase(d);
11366
11623
  const task = getTask(id, d);
11367
11624
  if (!task)
11368
11625
  throw new TaskNotFoundError(id);
@@ -11377,7 +11634,8 @@ function startTask(id, agentId, db) {
11377
11634
  agent_id: agentId,
11378
11635
  title: task.title,
11379
11636
  blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
11380
- }
11637
+ },
11638
+ databasePath
11381
11639
  });
11382
11640
  throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
11383
11641
  }
@@ -11399,12 +11657,13 @@ function startTask(id, agentId, db) {
11399
11657
  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 };
11400
11658
  const payload = taskEventData(startedTask, { agent_id: agentId });
11401
11659
  dispatchWebhook2("task.started", payload, d).catch(() => {});
11402
- emitLocalEventHooksQuiet({ type: "task.started", payload });
11403
- emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
11660
+ emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
11661
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
11404
11662
  return startedTask;
11405
11663
  }
11406
11664
  function completeTask(id, agentId, db, options) {
11407
11665
  const d = db || getDatabase();
11666
+ const databasePath = databasePathFromDatabase(d);
11408
11667
  const task = getTask(id, d);
11409
11668
  if (!task)
11410
11669
  throw new TaskNotFoundError(id);
@@ -11450,8 +11709,8 @@ function completeTask(id, agentId, db, options) {
11450
11709
  };
11451
11710
  const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
11452
11711
  dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
11453
- emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
11454
- emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
11712
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
11713
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
11455
11714
  let spawnedTask = null;
11456
11715
  if (task.recurrence_rule && !options?.skip_recurrence) {
11457
11716
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -11495,9 +11754,9 @@ function completeTask(id, agentId, db, options) {
11495
11754
  const depTask = getTask(dep.id, d);
11496
11755
  const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
11497
11756
  dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
11498
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
11757
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
11499
11758
  if (depTask)
11500
- emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
11759
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
11501
11760
  }
11502
11761
  }
11503
11762
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -11667,6 +11926,7 @@ function getTasksChangedSince(since, filters, db) {
11667
11926
  }
11668
11927
  function failTask(id, agentId, reason, options, db) {
11669
11928
  const d = db || getDatabase();
11929
+ const databasePath = databasePathFromDatabase(d);
11670
11930
  const task = getTask(id, d);
11671
11931
  if (!task)
11672
11932
  throw new TaskNotFoundError(id);
@@ -11695,8 +11955,8 @@ function failTask(id, agentId, reason, options, db) {
11695
11955
  logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
11696
11956
  const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
11697
11957
  dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
11698
- emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
11699
- emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
11958
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
11959
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
11700
11960
  let retryTask;
11701
11961
  if (options?.retry) {
11702
11962
  const retryCount = (task.retry_count || 0) + 1;
@@ -11756,6 +12016,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
11756
12016
  }
11757
12017
  function stealTask(agentId, opts, db) {
11758
12018
  const d = db || getDatabase();
12019
+ const databasePath = databasePathFromDatabase(d);
11759
12020
  const staleMinutes = opts?.stale_minutes ?? 30;
11760
12021
  const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
11761
12022
  if (staleTasks.length === 0)
@@ -11774,8 +12035,8 @@ function stealTask(agentId, opts, db) {
11774
12035
  const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
11775
12036
  const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
11776
12037
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
11777
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
11778
- emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
12038
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
12039
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
11779
12040
  return stolenTask;
11780
12041
  }
11781
12042
  function claimOrSteal(agentId, filters, db) {
@@ -11823,6 +12084,7 @@ var init_task_lifecycle = __esm(() => {
11823
12084
  init_types();
11824
12085
  init_database();
11825
12086
  init_completion_guard();
12087
+ init_event_emission_safety();
11826
12088
  init_event_hooks();
11827
12089
  init_shared_events();
11828
12090
  init_audit();
@@ -12530,8 +12792,9 @@ function createPlan(input, db) {
12530
12792
  const d = db || getDatabase();
12531
12793
  const id = uuid();
12532
12794
  const timestamp = now();
12533
- d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
12534
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
12795
+ const machineId = currentStorageMachineId(d);
12796
+ d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
12797
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
12535
12798
  id,
12536
12799
  input.project_id || null,
12537
12800
  input.task_list_id || null,
@@ -12540,7 +12803,8 @@ function createPlan(input, db) {
12540
12803
  input.description || null,
12541
12804
  input.status || "active",
12542
12805
  timestamp,
12543
- timestamp
12806
+ timestamp,
12807
+ machineId
12544
12808
  ]);
12545
12809
  return getPlan(id, d);
12546
12810
  }
@@ -12588,19 +12852,30 @@ function updatePlan(id, input, db) {
12588
12852
  const updated = getPlan(id, d);
12589
12853
  emitLocalEventHooksQuiet({
12590
12854
  type: "plan.updated",
12591
- payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
12855
+ payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
12856
+ databasePath: databasePathFromDatabase(d)
12592
12857
  });
12593
12858
  return updated;
12594
12859
  }
12595
12860
  function deletePlan(id, db) {
12596
12861
  const d = db || getDatabase();
12862
+ const plan = getPlan(id, d);
12863
+ if (!plan)
12864
+ return false;
12865
+ recordStorageTombstone({
12866
+ object_type: "plans",
12867
+ object_id: id,
12868
+ payload: plan
12869
+ }, d);
12597
12870
  const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
12598
12871
  return result.changes > 0;
12599
12872
  }
12600
12873
  var init_plans = __esm(() => {
12601
12874
  init_types();
12875
+ init_event_emission_safety();
12602
12876
  init_event_hooks();
12603
12877
  init_database();
12878
+ init_storage_tombstones();
12604
12879
  });
12605
12880
 
12606
12881
  // src/db/boards.ts
@@ -13007,20 +13282,20 @@ var init_boards = __esm(() => {
13007
13282
  // src/lib/artifact-store.ts
13008
13283
  import { createHash as createHash2 } from "crypto";
13009
13284
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
13010
- import { basename, dirname as dirname4, join as join5, resolve as resolve6 } from "path";
13011
- import { tmpdir } from "os";
13285
+ import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
13286
+ import { tmpdir as tmpdir2 } from "os";
13012
13287
  function isInMemoryDb2(path) {
13013
13288
  return path === ":memory:" || path.startsWith("file::memory:");
13014
13289
  }
13015
13290
  function artifactStoreRoot() {
13016
13291
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
13017
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
13292
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
13018
13293
  if (process.env["TODOS_ARTIFACTS_DIR"])
13019
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
13294
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
13020
13295
  const dbPath = getDatabasePath();
13021
13296
  if (isInMemoryDb2(dbPath))
13022
- return join5(tmpdir(), "hasna-todos-artifacts");
13023
- return join5(dirname4(resolve6(dbPath)), "artifacts");
13297
+ return join5(tmpdir2(), "hasna-todos-artifacts");
13298
+ return join5(dirname4(resolve7(dbPath)), "artifacts");
13024
13299
  }
13025
13300
  function artifactStorePath(relativePath) {
13026
13301
  const normalized = relativePath.replace(/\\/g, "/");
@@ -13067,7 +13342,7 @@ function mediaTypeFor(path, textLike) {
13067
13342
  return "application/octet-stream";
13068
13343
  }
13069
13344
  function storeArtifactContent(input) {
13070
- const sourcePath = resolve6(input.path);
13345
+ const sourcePath = resolve7(input.path);
13071
13346
  if (!existsSync6(sourcePath))
13072
13347
  return null;
13073
13348
  const sourceStat = statSync2(sourcePath);
@@ -13764,7 +14039,11 @@ function startTaskRun(input, db) {
13764
14039
  }, d);
13765
14040
  }
13766
14041
  const run = getTaskRun(id, d);
13767
- emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
14042
+ emitLocalEventHooksQuiet({
14043
+ type: "run.started",
14044
+ payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
14045
+ databasePath: databasePathFromDatabase(d)
14046
+ });
13768
14047
  return run;
13769
14048
  }
13770
14049
  function beginTaskRunTransaction(input, db) {
@@ -14033,7 +14312,8 @@ function finishTaskRun(input, db) {
14033
14312
  const updated = getTaskRun(run.id, d);
14034
14313
  emitLocalEventHooksQuiet({
14035
14314
  type: `run.${input.status}`,
14036
- payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
14315
+ payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
14316
+ databasePath: databasePathFromDatabase(d)
14037
14317
  });
14038
14318
  return updated;
14039
14319
  }
@@ -14117,6 +14397,7 @@ function getTaskRunLedger(runId, db) {
14117
14397
  var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
14118
14398
  var init_task_runs = __esm(() => {
14119
14399
  init_artifact_store();
14400
+ init_event_emission_safety();
14120
14401
  init_event_hooks();
14121
14402
  init_redaction();
14122
14403
  init_types();
@@ -15885,7 +16166,7 @@ var init_task_crud2 = __esm(() => {
15885
16166
 
15886
16167
  // src/lib/project-bootstrap.ts
15887
16168
  import { existsSync as existsSync7, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
15888
- import { basename as basename2, dirname as dirname5, resolve as resolve7 } from "path";
16169
+ import { basename as basename2, dirname as dirname5, resolve as resolve8 } from "path";
15889
16170
  function safeStat(path) {
15890
16171
  try {
15891
16172
  return statSync3(path);
@@ -15894,7 +16175,7 @@ function safeStat(path) {
15894
16175
  }
15895
16176
  }
15896
16177
  function canonicalPath(input) {
15897
- const resolved = resolve7(input);
16178
+ const resolved = resolve8(input);
15898
16179
  const stats = safeStat(resolved);
15899
16180
  if (stats?.isFile())
15900
16181
  return dirname5(resolved);
@@ -15903,7 +16184,7 @@ function canonicalPath(input) {
15903
16184
  function findUp(start, marker) {
15904
16185
  let current = canonicalPath(start);
15905
16186
  while (true) {
15906
- if (existsSync7(resolve7(current, marker)))
16187
+ if (existsSync7(resolve8(current, marker)))
15907
16188
  return current;
15908
16189
  const parent = dirname5(current);
15909
16190
  if (parent === current)
@@ -15914,7 +16195,7 @@ function findUp(start, marker) {
15914
16195
  function readPackageJson(path) {
15915
16196
  if (!path)
15916
16197
  return null;
15917
- const file = resolve7(path, "package.json");
16198
+ const file = resolve8(path, "package.json");
15918
16199
  if (!existsSync7(file))
15919
16200
  return null;
15920
16201
  try {
@@ -15937,7 +16218,7 @@ function workspaceMarker(root, rootPackage) {
15937
16218
  if (rootPackage?.workspaces)
15938
16219
  markers.push("package.json#workspaces");
15939
16220
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
15940
- if (existsSync7(resolve7(root, marker)))
16221
+ if (existsSync7(resolve8(root, marker)))
15941
16222
  markers.push(marker);
15942
16223
  }
15943
16224
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -15999,7 +16280,19 @@ function bootstrapProject(options = {}, db) {
15999
16280
  }
16000
16281
  setMachineLocalPath(project.id, discovery.projectPath, d);
16001
16282
  const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
16002
- const taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
16283
+ let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
16284
+ if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
16285
+ taskList = updateTaskList(taskList.id, {
16286
+ metadata: {
16287
+ ...taskList.metadata,
16288
+ route_enabled: true,
16289
+ automation: {
16290
+ ...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
16291
+ no_auto: false
16292
+ }
16293
+ }
16294
+ }, d);
16295
+ }
16003
16296
  const createdSources = [];
16004
16297
  for (const source of [
16005
16298
  addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
@@ -16469,7 +16762,7 @@ var init_retention_cleanup = __esm(() => {
16469
16762
 
16470
16763
  // src/lib/mention-resolver.ts
16471
16764
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
16472
- import { basename as basename3, isAbsolute, join as join6, relative as relative3, resolve as resolve8, sep } from "path";
16765
+ import { basename as basename3, isAbsolute, join as join6, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
16473
16766
  function blankResolution(parsed) {
16474
16767
  return {
16475
16768
  input: parsed.input,
@@ -16492,11 +16785,11 @@ function backlink(kind, key, label, target = key) {
16492
16785
  return { kind, key, label, target };
16493
16786
  }
16494
16787
  function normalizeWorkspace(workspace) {
16495
- return resolve8(workspace || process.cwd());
16788
+ return resolve9(workspace || process.cwd());
16496
16789
  }
16497
16790
  function isInside(root, absolutePath) {
16498
16791
  const rel = relative3(root, absolutePath);
16499
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep}`) && !isAbsolute(rel);
16792
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
16500
16793
  }
16501
16794
  function normalizeRelativePath(value) {
16502
16795
  const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
@@ -16560,7 +16853,7 @@ function resolveFile(parsed, workspace) {
16560
16853
  resolution.warnings.push("path is empty or escapes the workspace");
16561
16854
  return resolution;
16562
16855
  }
16563
- const absolutePath = resolve8(workspace, relPath);
16856
+ const absolutePath = resolve9(workspace, relPath);
16564
16857
  if (!isInside(workspace, absolutePath)) {
16565
16858
  resolution.path = relPath;
16566
16859
  resolution.warnings.push("path escapes the workspace");
@@ -16905,9 +17198,9 @@ var init_mention_resolver = __esm(() => {
16905
17198
  });
16906
17199
 
16907
17200
  // src/lib/policy-packs.ts
16908
- import { relative as relative4, resolve as resolve9 } from "path";
17201
+ import { relative as relative4, resolve as resolve10 } from "path";
16909
17202
  function normalizePath3(path) {
16910
- return resolve9(path);
17203
+ return resolve10(path);
16911
17204
  }
16912
17205
  function unique4(values) {
16913
17206
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -16962,7 +17255,7 @@ function commandMatches(commands, pattern) {
16962
17255
  }
16963
17256
  function pathMatches(paths, pattern, root) {
16964
17257
  return paths.filter((path) => {
16965
- const candidate = path.startsWith("/") ? path : resolve9(root, path);
17258
+ const candidate = path.startsWith("/") ? path : resolve10(root, path);
16966
17259
  if (!isPathInside3(root, candidate))
16967
17260
  return matchesPattern3(path, pattern);
16968
17261
  return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
@@ -17309,7 +17602,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
17309
17602
  if (action === "approved" || action === "rejected" || action === "expired") {
17310
17603
  emitLocalEventHooksQuiet({
17311
17604
  type: "approval.decided",
17312
- payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
17605
+ payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
17606
+ databasePath: databasePathFromDatabase(db)
17313
17607
  });
17314
17608
  }
17315
17609
  }
@@ -17449,6 +17743,7 @@ var init_approval_gates = __esm(() => {
17449
17743
  init_task_runs();
17450
17744
  init_tasks();
17451
17745
  init_types();
17746
+ init_event_emission_safety();
17452
17747
  init_event_hooks();
17453
17748
  });
17454
17749
 
@@ -20015,7 +20310,7 @@ function canonicalize(value) {
20015
20310
  function hash(value) {
20016
20311
  return createHash4("sha256").update(value).digest("hex");
20017
20312
  }
20018
- function parsePayload(value) {
20313
+ function parsePayload2(value) {
20019
20314
  if (!value)
20020
20315
  return {};
20021
20316
  try {
@@ -20114,7 +20409,7 @@ function taskScopedRows(db, scope) {
20114
20409
  FROM handoffs h
20115
20410
  WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
20116
20411
  `).all(scope.project_id ?? null).filter((row) => {
20117
- const payload = parsePayload(row.payload_json);
20412
+ const payload = parsePayload2(row.payload_json);
20118
20413
  const taskRefs = parseStringArray(payload["task_ids"]);
20119
20414
  const runRefs = parseStringArray(payload["run_ids"]);
20120
20415
  if (scope.project_id && row.project_id !== scope.project_id)
@@ -20145,7 +20440,7 @@ function toLedgerEntries(rows) {
20145
20440
  });
20146
20441
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
20147
20442
  return ordered.map((row, index) => {
20148
- const payload = parsePayload(row.payload_json);
20443
+ const payload = parsePayload2(row.payload_json);
20149
20444
  const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
20150
20445
  const chainHash = hash(`${previous}
20151
20446
  ${payloadHash}`);
@@ -20287,7 +20582,7 @@ var init_audit_ledger = __esm(() => {
20287
20582
 
20288
20583
  // src/lib/release-compatibility.ts
20289
20584
  import { readFileSync as readFileSync5 } from "fs";
20290
- import { join as join7, resolve as resolve10 } from "path";
20585
+ import { join as join7, resolve as resolve11 } from "path";
20291
20586
  import { Database as Database2 } from "bun:sqlite";
20292
20587
  function pass(id, message, details) {
20293
20588
  return { id, status: "passed", message, details };
@@ -20395,7 +20690,7 @@ function checkChangelog() {
20395
20690
  ];
20396
20691
  }
20397
20692
  function createReleaseCompatibilityReport(options = {}) {
20398
- const root = resolve10(options.root ?? process.cwd());
20693
+ const root = resolve11(options.root ?? process.cwd());
20399
20694
  const packageJson = readPackageJson2(root);
20400
20695
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
20401
20696
  const checks = [
@@ -26775,7 +27070,7 @@ function classifyLog(text) {
26775
27070
  async function sleep2(ms) {
26776
27071
  if (ms <= 0)
26777
27072
  return;
26778
- await new Promise((resolve11) => setTimeout(resolve11, ms));
27073
+ await new Promise((resolve12) => setTimeout(resolve12, ms));
26779
27074
  }
26780
27075
  async function runCommandProvider(provider, input) {
26781
27076
  const commandTemplate = input.command || provider.command;
@@ -29528,7 +29823,7 @@ var init_local_bridge = __esm(() => {
29528
29823
  // src/lib/local-backups.ts
29529
29824
  import { createHash as createHash5 } from "crypto";
29530
29825
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
29531
- import { dirname as dirname8, resolve as resolve11 } from "path";
29826
+ import { dirname as dirname8, resolve as resolve12 } from "path";
29532
29827
  import { mkdirSync as mkdirSync6 } from "fs";
29533
29828
  function stableJson(value) {
29534
29829
  if (value === null || typeof value !== "object")
@@ -29630,14 +29925,14 @@ function createLocalBackup(options = {}, db) {
29630
29925
  return backup;
29631
29926
  }
29632
29927
  function writeLocalBackupFile(backup, outputPath) {
29633
- const path = resolve11(outputPath);
29928
+ const path = resolve12(outputPath);
29634
29929
  mkdirSync6(dirname8(path), { recursive: true });
29635
29930
  writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
29636
29931
  `);
29637
29932
  return path;
29638
29933
  }
29639
29934
  function readLocalBackupFile(path) {
29640
- return JSON.parse(readFileSync8(resolve11(path), "utf-8"));
29935
+ return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
29641
29936
  }
29642
29937
  function verifyLocalBackup(value, options = {}, db) {
29643
29938
  const verifiedAt = options.verified_at ?? now();
@@ -31087,7 +31382,7 @@ __export(exports_local_extensions, {
31087
31382
  });
31088
31383
  import { createHash as createHash8, createVerify } from "crypto";
31089
31384
  import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
31090
- import { basename as basename5, join as join10, resolve as resolve12 } from "path";
31385
+ import { basename as basename5, join as join10, resolve as resolve13 } from "path";
31091
31386
  function isObject2(value) {
31092
31387
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
31093
31388
  }
@@ -31345,7 +31640,7 @@ function verifyExtensionSignature(input) {
31345
31640
  return verifier.verify(input.public_key, decodeSignature(input.signature));
31346
31641
  }
31347
31642
  function inspectExtensionSource(source3) {
31348
- const resolved = resolve12(source3);
31643
+ const resolved = resolve13(source3);
31349
31644
  if (!existsSync13(resolved))
31350
31645
  throw new Error(`extension source not found: ${source3}`);
31351
31646
  const stat = statSync6(resolved);
@@ -31443,7 +31738,7 @@ function testExtensionCompatibility(sourceOrManifest) {
31443
31738
  function projectExtensionSources(projectPath) {
31444
31739
  if (!projectPath)
31445
31740
  return [];
31446
- const root = resolve12(projectPath);
31741
+ const root = resolve13(projectPath);
31447
31742
  const candidates = [
31448
31743
  join10(root, "todos.extension.json"),
31449
31744
  join10(root, ".todos", "todos.extension.json")
@@ -31462,7 +31757,7 @@ function projectExtensionSources(projectPath) {
31462
31757
  }
31463
31758
  function discoverLocalExtensions(options = {}) {
31464
31759
  const config = loadConfig();
31465
- const projectPath = options.project_path ? resolve12(options.project_path) : null;
31760
+ const projectPath = options.project_path ? resolve13(options.project_path) : null;
31466
31761
  const configuredSources = [
31467
31762
  ...config.extension_sources || [],
31468
31763
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -31470,7 +31765,7 @@ function discoverLocalExtensions(options = {}) {
31470
31765
  const sources = Array.from(new Set([
31471
31766
  ...configuredSources,
31472
31767
  ...projectExtensionSources(projectPath || undefined)
31473
- ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve12(projectPath, source3) : resolve12(source3));
31768
+ ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
31474
31769
  const warnings = [];
31475
31770
  const discovered = [];
31476
31771
  for (const source3 of sources) {
@@ -34420,7 +34715,8 @@ function writeQueue(task2, queue, actor, action, db) {
34420
34715
  logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
34421
34716
  emitLocalEventHooksQuiet({
34422
34717
  type: `review.${action}`,
34423
- payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
34718
+ payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
34719
+ databasePath: databasePathFromDatabase(d)
34424
34720
  });
34425
34721
  return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
34426
34722
  }
@@ -34724,6 +35020,7 @@ var init_review_queues = __esm(() => {
34724
35020
  init_database();
34725
35021
  init_tasks();
34726
35022
  init_config();
35023
+ init_event_emission_safety();
34727
35024
  init_event_hooks();
34728
35025
  init_task_contracts();
34729
35026
  STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
@@ -35879,7 +36176,7 @@ __export(exports_extract, {
35879
36176
  });
35880
36177
  import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
35881
36178
  import { createHash as createHash10 } from "crypto";
35882
- import { relative as relative5, resolve as resolve13, join as join11 } from "path";
36179
+ import { relative as relative5, resolve as resolve14, join as join11 } from "path";
35883
36180
  function stableHash(value) {
35884
36181
  return createHash10("sha256").update(value).digest("hex");
35885
36182
  }
@@ -35887,7 +36184,7 @@ function normalizePathForMatch(value) {
35887
36184
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
35888
36185
  }
35889
36186
  function readGitignorePatterns(basePath) {
35890
- const root = statSync7(basePath).isFile() ? resolve13(basePath, "..") : basePath;
36187
+ const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
35891
36188
  const gitignorePath = join11(root, ".gitignore");
35892
36189
  if (!existsSync14(gitignorePath))
35893
36190
  return [];
@@ -36023,7 +36320,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
36023
36320
  return files.sort();
36024
36321
  }
36025
36322
  function buildCodebaseIndex(options) {
36026
- const basePath = resolve13(options.path);
36323
+ const basePath = resolve14(options.path);
36027
36324
  const tags = options.patterns || [...EXTRACT_TAGS];
36028
36325
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
36029
36326
  const excludes = options.exclude || [];
@@ -36034,7 +36331,7 @@ function buildCodebaseIndex(options) {
36034
36331
  const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
36035
36332
  try {
36036
36333
  const source3 = readFileSync10(fullPath, "utf-8");
36037
- const relPath = statSync7(basePath).isFile() ? relative5(resolve13(basePath, ".."), fullPath) : file;
36334
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
36038
36335
  indexed.push({
36039
36336
  file: relPath,
36040
36337
  checksum: stableHash(source3).slice(0, 24),
@@ -36054,7 +36351,7 @@ function buildCodebaseIndex(options) {
36054
36351
  };
36055
36352
  }
36056
36353
  function extractTodos(options, db) {
36057
- const basePath = resolve13(options.path);
36354
+ const basePath = resolve14(options.path);
36058
36355
  const tags = options.patterns || [...EXTRACT_TAGS];
36059
36356
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
36060
36357
  const excludes = options.exclude || [];
@@ -36065,7 +36362,7 @@ function extractTodos(options, db) {
36065
36362
  const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
36066
36363
  try {
36067
36364
  const source3 = readFileSync10(fullPath, "utf-8");
36068
- const relPath = statSync7(basePath).isFile() ? relative5(resolve13(basePath, ".."), fullPath) : file;
36365
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
36069
36366
  const comments = extractFromSource(source3, relPath, tags);
36070
36367
  allComments.push(...comments);
36071
36368
  } catch {}
@@ -36159,7 +36456,7 @@ async function watchSourceTodos(options, onRun) {
36159
36456
  const interval = Math.max(100, options.interval_ms || 2000);
36160
36457
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
36161
36458
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
36162
- const root = resolve13(options.path);
36459
+ const root = resolve14(options.path);
36163
36460
  const runs = [];
36164
36461
  let previous = new Map;
36165
36462
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -37542,8 +37839,8 @@ __export(exports_environment_snapshots, {
37542
37839
  import { createHash as createHash11 } from "crypto";
37543
37840
  import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
37544
37841
  import { hostname as hostname2, platform, arch } from "os";
37545
- import { dirname as dirname9, join as join13, resolve as resolve14 } from "path";
37546
- import { tmpdir as tmpdir2 } from "os";
37842
+ import { dirname as dirname9, join as join13, resolve as resolve15 } from "path";
37843
+ import { tmpdir as tmpdir3 } from "os";
37547
37844
  function sha2566(value) {
37548
37845
  return createHash11("sha256").update(value).digest("hex");
37549
37846
  }
@@ -37656,15 +37953,15 @@ function commandEnv(env, includeValues) {
37656
37953
  function defaultSnapshotDir() {
37657
37954
  const dbPath = getDatabasePath();
37658
37955
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
37659
- return join13(tmpdir2(), "hasna-todos", "environment-snapshots");
37660
- return join13(dirname9(resolve14(dbPath)), "environment-snapshots");
37956
+ return join13(tmpdir3(), "hasna-todos", "environment-snapshots");
37957
+ return join13(dirname9(resolve15(dbPath)), "environment-snapshots");
37661
37958
  }
37662
37959
  function snapshotWithId(snapshot) {
37663
37960
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
37664
37961
  return { id: `env_${digest}`, ...snapshot };
37665
37962
  }
37666
37963
  function captureEnvironmentSnapshot(input = {}) {
37667
- const root = resolve14(input.root || process.cwd());
37964
+ const root = resolve15(input.root || process.cwd());
37668
37965
  const env = input.env || process.env;
37669
37966
  const warnings = [];
37670
37967
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -37704,13 +38001,13 @@ function captureEnvironmentSnapshot(input = {}) {
37704
38001
  });
37705
38002
  }
37706
38003
  function writeEnvironmentSnapshot(snapshot, outputPath) {
37707
- const path = outputPath ? resolve14(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
38004
+ const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
37708
38005
  ensureDir2(dirname9(path));
37709
38006
  writeJsonFile(path, snapshot);
37710
38007
  return path;
37711
38008
  }
37712
38009
  function readEnvironmentSnapshot(path) {
37713
- const snapshot = readJsonFile(resolve14(path));
38010
+ const snapshot = readJsonFile(resolve15(path));
37714
38011
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
37715
38012
  throw new Error(`Invalid environment snapshot: ${path}`);
37716
38013
  }
@@ -38256,7 +38553,7 @@ var init_headless_boundaries = __esm(() => {
38256
38553
  });
38257
38554
 
38258
38555
  // src/server/routes.ts
38259
- import { join as join14, resolve as resolve15, sep as sep2 } from "path";
38556
+ import { join as join14, resolve as resolve16, sep as sep3 } from "path";
38260
38557
  function parseFieldsParam(url) {
38261
38558
  const fieldsParam = url.searchParams.get("fields");
38262
38559
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -38994,9 +39291,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
38994
39291
  return null;
38995
39292
  if (path !== "/") {
38996
39293
  const filePath = join14(ctx.dashboardDir, path);
38997
- const resolvedFile = resolve15(filePath);
38998
- const resolvedBase = resolve15(ctx.dashboardDir);
38999
- if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
39294
+ const resolvedFile = resolve16(filePath);
39295
+ const resolvedBase = resolve16(ctx.dashboardDir);
39296
+ if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
39000
39297
  return json2({ error: "Forbidden" }, 403);
39001
39298
  }
39002
39299
  const res2 = serveStaticFile2(filePath);