@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
@@ -1232,6 +1232,22 @@ var init_migrations = __esm(() => {
1232
1232
  CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
1233
1233
  CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
1234
1234
  INSERT OR IGNORE INTO _migrations (id) VALUES (62);
1235
+ `,
1236
+ `
1237
+ CREATE TABLE IF NOT EXISTS storage_tombstones (
1238
+ id TEXT PRIMARY KEY,
1239
+ object_type TEXT NOT NULL,
1240
+ object_id TEXT NOT NULL,
1241
+ deleted_at TEXT NOT NULL,
1242
+ updated_at TEXT NOT NULL,
1243
+ source_machine_id TEXT,
1244
+ payload TEXT,
1245
+ version INTEGER,
1246
+ UNIQUE(object_type, object_id)
1247
+ );
1248
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
1249
+ CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
1250
+ INSERT OR IGNORE INTO _migrations (id) VALUES (63);
1235
1251
  `
1236
1252
  ];
1237
1253
  });
@@ -1753,6 +1769,20 @@ function ensureSchema(db) {
1753
1769
  )`);
1754
1770
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
1755
1771
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
1772
+ ensureTable("storage_tombstones", `
1773
+ CREATE TABLE storage_tombstones (
1774
+ id TEXT PRIMARY KEY,
1775
+ object_type TEXT NOT NULL,
1776
+ object_id TEXT NOT NULL,
1777
+ deleted_at TEXT NOT NULL,
1778
+ updated_at TEXT NOT NULL,
1779
+ source_machine_id TEXT,
1780
+ payload TEXT,
1781
+ version INTEGER,
1782
+ UNIQUE(object_type, object_id)
1783
+ )`);
1784
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id)");
1785
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at)");
1756
1786
  ensureTable("machines", `
1757
1787
  CREATE TABLE machines (
1758
1788
  id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
@@ -3077,6 +3107,65 @@ var init_config = __esm(() => {
3077
3107
  };
3078
3108
  });
3079
3109
 
3110
+ // src/db/storage-tombstones.ts
3111
+ function recordStorageTombstone(input, db) {
3112
+ const d = db ?? getDatabase();
3113
+ const deletedAt = input.deleted_at ?? now();
3114
+ const machineId = input.source_machine_id ?? currentStorageMachineId(d);
3115
+ d.run(`INSERT INTO storage_tombstones (
3116
+ id, object_type, object_id, deleted_at, updated_at, source_machine_id, payload, version
3117
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3118
+ ON CONFLICT(object_type, object_id) DO UPDATE SET
3119
+ deleted_at = excluded.deleted_at,
3120
+ updated_at = excluded.updated_at,
3121
+ source_machine_id = excluded.source_machine_id,
3122
+ payload = excluded.payload,
3123
+ version = excluded.version
3124
+ WHERE storage_tombstones.updated_at IS NULL OR storage_tombstones.updated_at <= excluded.updated_at`, [
3125
+ uuid(),
3126
+ input.object_type,
3127
+ input.object_id,
3128
+ deletedAt,
3129
+ deletedAt,
3130
+ machineId,
3131
+ input.payload ? JSON.stringify(input.payload) : null,
3132
+ input.version ?? null
3133
+ ]);
3134
+ return getStorageTombstone(input.object_type, input.object_id, d);
3135
+ }
3136
+ function getStorageTombstone(objectType, objectId, db) {
3137
+ const d = db ?? getDatabase();
3138
+ const row = d.query("SELECT * FROM storage_tombstones WHERE object_type = ? AND object_id = ?").get(objectType, objectId);
3139
+ return row ? rowToStorageTombstone(row) : null;
3140
+ }
3141
+ function rowToStorageTombstone(row) {
3142
+ return {
3143
+ ...row,
3144
+ payload: parsePayload(row.payload)
3145
+ };
3146
+ }
3147
+ function parsePayload(value) {
3148
+ if (!value)
3149
+ return null;
3150
+ try {
3151
+ const parsed = JSON.parse(value);
3152
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
3153
+ } catch {
3154
+ return null;
3155
+ }
3156
+ }
3157
+ function currentStorageMachineId(db) {
3158
+ try {
3159
+ return getMachineId(db);
3160
+ } catch {
3161
+ return null;
3162
+ }
3163
+ }
3164
+ var init_storage_tombstones = __esm(() => {
3165
+ init_database();
3166
+ init_machines();
3167
+ });
3168
+
3080
3169
  // src/db/projects.ts
3081
3170
  var exports_projects = {};
3082
3171
  __export(exports_projects, {
@@ -3128,8 +3217,9 @@ function createProject(input, db) {
3128
3217
  const timestamp = now();
3129
3218
  const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
3130
3219
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
3131
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
3132
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
3220
+ const machineId = currentStorageMachineId(d);
3221
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
3222
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
3133
3223
  return getProject(id, d);
3134
3224
  }
3135
3225
  function getProject(id, db) {
@@ -3208,6 +3298,14 @@ function renameProject(id, input, db) {
3208
3298
  }
3209
3299
  function deleteProject(id, db) {
3210
3300
  const d = db || getDatabase();
3301
+ const project = getProject(id, d);
3302
+ if (!project)
3303
+ return false;
3304
+ recordStorageTombstone({
3305
+ object_type: "projects",
3306
+ object_id: id,
3307
+ payload: project
3308
+ }, d);
3211
3309
  const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
3212
3310
  return result.changes > 0;
3213
3311
  }
@@ -3312,6 +3410,14 @@ function listMachineLocalPaths(projectId, db) {
3312
3410
  function removeMachineLocalPath(projectId, machineId, db) {
3313
3411
  const d = db || getDatabase();
3314
3412
  const mid = machineId ?? getMachineId(d);
3413
+ const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, mid);
3414
+ if (!existing)
3415
+ return false;
3416
+ recordStorageTombstone({
3417
+ object_type: "project_machine_paths",
3418
+ object_id: existing.id,
3419
+ payload: existing
3420
+ }, d);
3315
3421
  const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
3316
3422
  return result.changes > 0;
3317
3423
  }
@@ -3319,6 +3425,7 @@ var init_projects = __esm(() => {
3319
3425
  init_types();
3320
3426
  init_database();
3321
3427
  init_machines();
3428
+ init_storage_tombstones();
3322
3429
  });
3323
3430
 
3324
3431
  // src/lib/completion-guard.ts
@@ -3370,6 +3477,57 @@ var init_completion_guard = __esm(() => {
3370
3477
  init_projects();
3371
3478
  });
3372
3479
 
3480
+ // src/lib/event-emission-safety.ts
3481
+ import { tmpdir } from "os";
3482
+ import { resolve as resolve3, sep } from "path";
3483
+ function envFlag(name) {
3484
+ const value = process.env[name];
3485
+ return value === "1" || value === "true" || value === "yes";
3486
+ }
3487
+ function isUnder(parent, child) {
3488
+ const normalizedParent = resolve3(parent);
3489
+ const normalizedChild = resolve3(child);
3490
+ return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
3491
+ }
3492
+ function databasePathFromDatabase(db) {
3493
+ const filename = db?.filename;
3494
+ return typeof filename === "string" && filename.trim() ? filename : undefined;
3495
+ }
3496
+ function isEphemeralTodosDatabase(dbPath) {
3497
+ const resolvedPath = dbPath ?? getDatabasePath();
3498
+ if (resolvedPath === ":memory:" || resolvedPath.startsWith("file::memory:"))
3499
+ return true;
3500
+ return isUnder(tmpdir(), resolvedPath);
3501
+ }
3502
+ function hasExplicitSharedEventsStore() {
3503
+ return Boolean(process.env["HASNA_EVENTS_DIR"] || process.env["HASNA_EVENTS_HOME"]);
3504
+ }
3505
+ function usesIsolatedTodosHome() {
3506
+ return isUnder(tmpdir(), getTodosGlobalDir());
3507
+ }
3508
+ function shouldEmitSharedTaskEvents(dbPath) {
3509
+ if (envFlag("TODOS_DISABLE_SHARED_EVENTS"))
3510
+ return false;
3511
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_SHARED_EVENTS"))
3512
+ return true;
3513
+ if (!isEphemeralTodosDatabase(dbPath))
3514
+ return true;
3515
+ return hasExplicitSharedEventsStore();
3516
+ }
3517
+ function shouldDeliverLocalLifecycleHooks(dbPath) {
3518
+ if (envFlag("TODOS_DISABLE_LOCAL_EVENT_HOOKS"))
3519
+ return false;
3520
+ if (envFlag("TODOS_ALLOW_EPHEMERAL_LOCAL_EVENT_HOOKS"))
3521
+ return true;
3522
+ if (!isEphemeralTodosDatabase(dbPath))
3523
+ return true;
3524
+ return usesIsolatedTodosHome();
3525
+ }
3526
+ var init_event_emission_safety = __esm(() => {
3527
+ init_database();
3528
+ init_sync_utils();
3529
+ });
3530
+
3373
3531
  // src/lib/redaction.ts
3374
3532
  function unique(values) {
3375
3533
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -3470,9 +3628,9 @@ var init_redaction = __esm(() => {
3470
3628
  });
3471
3629
 
3472
3630
  // src/lib/workspace-trust.ts
3473
- import { relative, resolve as resolve3 } from "path";
3631
+ import { relative, resolve as resolve4 } from "path";
3474
3632
  function normalizePath(path) {
3475
- return resolve3(path);
3633
+ return resolve4(path);
3476
3634
  }
3477
3635
  function unique2(values) {
3478
3636
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -3654,9 +3812,9 @@ var init_workspace_trust = __esm(() => {
3654
3812
  });
3655
3813
 
3656
3814
  // src/lib/runner-sandbox.ts
3657
- import { relative as relative2, resolve as resolve4 } from "path";
3815
+ import { relative as relative2, resolve as resolve5 } from "path";
3658
3816
  function normalizePath2(path) {
3659
- return resolve4(path);
3817
+ return resolve5(path);
3660
3818
  }
3661
3819
  function unique3(values) {
3662
3820
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -3846,7 +4004,7 @@ var init_runner_sandbox = __esm(() => {
3846
4004
  // src/lib/event-hooks.ts
3847
4005
  import { createHash as createHash2, randomUUID } from "crypto";
3848
4006
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
3849
- import { dirname as dirname4, resolve as resolve5 } from "path";
4007
+ import { dirname as dirname4, resolve as resolve6 } from "path";
3850
4008
  import { createConnection } from "net";
3851
4009
  function safeName(name) {
3852
4010
  const trimmed = name.trim();
@@ -3984,7 +4142,7 @@ async function deliverHook(hook, envelope) {
3984
4142
  if (hook.target === "stdout") {
3985
4143
  output = line.trim();
3986
4144
  } else if (hook.target === "file") {
3987
- const filePath = resolve5(hook.file_path);
4145
+ const filePath = resolve6(hook.file_path);
3988
4146
  mkdirSync3(dirname4(filePath), { recursive: true });
3989
4147
  appendFileSync(filePath, line);
3990
4148
  } else if (hook.target === "socket") {
@@ -4059,6 +4217,8 @@ async function emitLocalEventHooks(input) {
4059
4217
  return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
4060
4218
  }
4061
4219
  function emitLocalEventHooksQuiet(input) {
4220
+ if (!shouldDeliverLocalLifecycleHooks(input.databasePath))
4221
+ return;
4062
4222
  emitLocalEventHooks(input).catch(() => {});
4063
4223
  }
4064
4224
  async function testLocalEventHook(name, input) {
@@ -4072,6 +4232,7 @@ var init_event_hooks = __esm(() => {
4072
4232
  init_redaction();
4073
4233
  init_runner_sandbox();
4074
4234
  init_config();
4235
+ init_event_emission_safety();
4075
4236
  LOCAL_EVENT_TYPES = [
4076
4237
  "task.created",
4077
4238
  "task.assigned",
@@ -4404,7 +4565,7 @@ async function dispatchCommand(event, channel) {
4404
4565
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
4405
4566
  HASNA_EVENT_JSON: eventJson
4406
4567
  };
4407
- return new Promise((resolve6) => {
4568
+ return new Promise((resolve7) => {
4408
4569
  const child = spawn(channel.command.command, channel.command.args ?? [], {
4409
4570
  cwd: channel.command.cwd,
4410
4571
  env,
@@ -4422,7 +4583,7 @@ async function dispatchCommand(event, channel) {
4422
4583
  });
4423
4584
  child.on("error", (error) => {
4424
4585
  clearTimeout(timeout);
4425
- resolve6({
4586
+ resolve7({
4426
4587
  attempt: 1,
4427
4588
  status: "failed",
4428
4589
  startedAt,
@@ -4435,7 +4596,7 @@ async function dispatchCommand(event, channel) {
4435
4596
  child.on("close", (code, signal) => {
4436
4597
  clearTimeout(timeout);
4437
4598
  const success = code === 0;
4438
- resolve6({
4599
+ resolve7({
4439
4600
  attempt: 1,
4440
4601
  status: success ? "success" : "failed",
4441
4602
  startedAt,
@@ -4702,14 +4863,15 @@ function createTaskList(input, db) {
4702
4863
  const id = uuid();
4703
4864
  const timestamp = now();
4704
4865
  const slug = input.slug || slugify(input.name);
4866
+ const machineId = currentStorageMachineId(d);
4705
4867
  if (!input.project_id) {
4706
4868
  const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
4707
4869
  if (existing) {
4708
4870
  throw new Error(`Standalone task list with slug "${slug}" already exists`);
4709
4871
  }
4710
4872
  }
4711
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
4712
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
4873
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
4874
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
4713
4875
  return getTaskList(id, d);
4714
4876
  }
4715
4877
  function getTaskList(id, db) {
@@ -4759,6 +4921,14 @@ function updateTaskList(id, input, db) {
4759
4921
  }
4760
4922
  function deleteTaskList(id, db) {
4761
4923
  const d = db || getDatabase();
4924
+ const list = getTaskList(id, d);
4925
+ if (!list)
4926
+ return false;
4927
+ recordStorageTombstone({
4928
+ object_type: "task_lists",
4929
+ object_id: id,
4930
+ payload: list
4931
+ }, d);
4762
4932
  return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
4763
4933
  }
4764
4934
  function ensureTaskList(name, slug, projectId, db) {
@@ -4772,40 +4942,10 @@ var init_task_lists = __esm(() => {
4772
4942
  init_types();
4773
4943
  init_database();
4774
4944
  init_projects();
4945
+ init_storage_tombstones();
4775
4946
  });
4776
4947
 
4777
- // src/lib/shared-events.ts
4778
- function taskEventData(task, extra = {}) {
4779
- return {
4780
- id: task.id,
4781
- task_id: task.id,
4782
- short_id: task.short_id,
4783
- title: task.title,
4784
- description: task.description,
4785
- status: task.status,
4786
- priority: task.priority,
4787
- project_id: task.project_id,
4788
- parent_id: task.parent_id,
4789
- plan_id: task.plan_id,
4790
- task_list_id: task.task_list_id,
4791
- agent_id: task.agent_id,
4792
- assigned_to: task.assigned_to,
4793
- session_id: task.session_id,
4794
- working_dir: task.working_dir,
4795
- tags: task.tags,
4796
- metadata: task.metadata,
4797
- version: task.version,
4798
- created_at: task.created_at,
4799
- updated_at: task.updated_at,
4800
- started_at: task.started_at,
4801
- completed_at: task.completed_at,
4802
- due_at: task.due_at,
4803
- requires_approval: task.requires_approval,
4804
- approved_by: task.approved_by,
4805
- approved_at: task.approved_at,
4806
- ...extra
4807
- };
4808
- }
4948
+ // src/lib/task-route-contract.ts
4809
4949
  function booleanField(value) {
4810
4950
  if (typeof value === "boolean")
4811
4951
  return value;
@@ -4827,21 +4967,31 @@ function booleanField(value) {
4827
4967
  function objectField(value) {
4828
4968
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
4829
4969
  }
4830
- function firstBoolean(records, keys) {
4970
+ function collectBooleans(records, keys) {
4971
+ const values = [];
4831
4972
  for (const record of records) {
4973
+ if (!record)
4974
+ continue;
4832
4975
  for (const key of keys) {
4833
4976
  const value = booleanField(record[key]);
4834
4977
  if (value !== undefined)
4835
- return value;
4978
+ values.push(value);
4836
4979
  }
4837
4980
  }
4838
- return;
4981
+ return values;
4839
4982
  }
4840
- function routingAutomationMetadata(task) {
4841
- const automation = objectField(task.metadata.automation);
4842
- const records = [task.metadata];
4843
- if (automation)
4844
- records.push(automation);
4983
+ function mergedBoolean(records, keys, trueWins) {
4984
+ const values = collectBooleans(records, keys);
4985
+ if (values.length === 0)
4986
+ return;
4987
+ if (trueWins)
4988
+ return values.some(Boolean);
4989
+ return values.includes(false) ? false : true;
4990
+ }
4991
+ function routingAutomationMetadata(task, taskList) {
4992
+ const taskAutomation = objectField(task.metadata.automation);
4993
+ const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
4994
+ const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
4845
4995
  const result = {};
4846
4996
  const aliases = [
4847
4997
  ["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
@@ -4852,7 +5002,7 @@ function routingAutomationMetadata(task) {
4852
5002
  ["approval_required", ["approval_required", "approvalRequired"]]
4853
5003
  ];
4854
5004
  for (const [canonical, keys] of aliases) {
4855
- const value = firstBoolean(records, keys);
5005
+ const value = mergedBoolean(records, keys, canonical !== "allowed");
4856
5006
  if (value !== undefined)
4857
5007
  result[canonical] = value;
4858
5008
  }
@@ -4860,23 +5010,88 @@ function routingAutomationMetadata(task) {
4860
5010
  result.requires_approval = true;
4861
5011
  return Object.keys(result).length > 0 ? result : undefined;
4862
5012
  }
5013
+ function routeEnabledForTask(task, taskList) {
5014
+ const explicit = booleanField(task.metadata.route_enabled);
5015
+ if (explicit !== undefined)
5016
+ return explicit;
5017
+ if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
5018
+ return true;
5019
+ const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
5020
+ if (taskListDefault !== undefined)
5021
+ return taskListDefault;
5022
+ return;
5023
+ }
5024
+ function stringField(value) {
5025
+ return typeof value === "string" && value.trim() ? value : undefined;
5026
+ }
5027
+ function workflowPointersFromMetadata(metadata) {
5028
+ const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
5029
+ return {
5030
+ current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
5031
+ current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
5032
+ latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
5033
+ latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
5034
+ workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
5035
+ };
5036
+ }
5037
+ function classifyProjectKind(path) {
5038
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
5039
+ }
5040
+ function isWorktreePath(path) {
5041
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
5042
+ }
5043
+ function inferRootProjectId(project) {
5044
+ return isWorktreePath(project.path) ? null : project.id;
5045
+ }
5046
+ var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
5047
+
5048
+ // src/lib/shared-events.ts
5049
+ function taskEventData(task, extra = {}) {
5050
+ return {
5051
+ id: task.id,
5052
+ task_id: task.id,
5053
+ short_id: task.short_id,
5054
+ title: task.title,
5055
+ description: task.description,
5056
+ status: task.status,
5057
+ priority: task.priority,
5058
+ project_id: task.project_id,
5059
+ parent_id: task.parent_id,
5060
+ plan_id: task.plan_id,
5061
+ task_list_id: task.task_list_id,
5062
+ agent_id: task.agent_id,
5063
+ assigned_to: task.assigned_to,
5064
+ session_id: task.session_id,
5065
+ working_dir: task.working_dir,
5066
+ tags: task.tags,
5067
+ metadata: task.metadata,
5068
+ version: task.version,
5069
+ created_at: task.created_at,
5070
+ updated_at: task.updated_at,
5071
+ started_at: task.started_at,
5072
+ completed_at: task.completed_at,
5073
+ due_at: task.due_at,
5074
+ requires_approval: task.requires_approval,
5075
+ approved_by: task.approved_by,
5076
+ approved_at: task.approved_at,
5077
+ ...extra
5078
+ };
5079
+ }
4863
5080
  function taskEventMetadata(task) {
4864
5081
  const metadata = {
4865
5082
  package: "@hasna/todos",
4866
5083
  todos_event_schema_version: 1,
5084
+ route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
4867
5085
  task_id: task.id,
4868
5086
  task_short_id: task.short_id,
4869
5087
  project_id: task.project_id,
4870
5088
  task_list_id: task.task_list_id,
4871
5089
  working_dir: task.working_dir
4872
5090
  };
4873
- const routeEnabled = booleanField(task.metadata.route_enabled);
4874
- if (routeEnabled !== undefined) {
4875
- metadata.route_enabled = routeEnabled;
4876
- }
4877
- const automation = routingAutomationMetadata(task);
4878
- if (automation) {
4879
- metadata.automation = automation;
5091
+ const pointers = workflowPointersFromMetadata(task.metadata);
5092
+ for (const [key, value] of Object.entries(pointers)) {
5093
+ if (value)
5094
+ metadata[key] = value;
4880
5095
  }
4881
5096
  try {
4882
5097
  const project = task.project_id ? getProject(task.project_id) : null;
@@ -4905,18 +5120,20 @@ function taskEventMetadata(task) {
4905
5120
  metadata.task_list_project_id = taskList.project_id;
4906
5121
  metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
4907
5122
  }
5123
+ const routeEnabled = routeEnabledForTask(task, taskList);
5124
+ if (routeEnabled !== undefined) {
5125
+ metadata.route_enabled = routeEnabled;
5126
+ }
5127
+ const automation = routingAutomationMetadata(task, taskList);
5128
+ if (automation) {
5129
+ metadata.automation = automation;
5130
+ metadata.route_blocked_by_no_auto = automation.no_auto === true;
5131
+ metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
5132
+ metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
5133
+ }
4908
5134
  } catch {}
4909
5135
  return metadata;
4910
5136
  }
4911
- function classifyProjectKind(path) {
4912
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
4913
- }
4914
- function isWorktreePath(path) {
4915
- return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
4916
- }
4917
- function inferRootProjectId(project) {
4918
- return isWorktreePath(project.path) ? null : project.id;
4919
- }
4920
5137
  function readMachineLocalPath(project) {
4921
5138
  const machineId = process.env["TODOS_MACHINE_ID"];
4922
5139
  if (!machineId)
@@ -4929,6 +5146,8 @@ function readMachineLocalPath(project) {
4929
5146
  }
4930
5147
  }
4931
5148
  async function emitSharedTaskEvent(input) {
5149
+ if (!shouldEmitSharedTaskEvents(input.databasePath))
5150
+ return;
4932
5151
  const data = taskEventData(input.task, input.data);
4933
5152
  await new EventsClient().emit({
4934
5153
  source: SOURCE,
@@ -4952,6 +5171,7 @@ var init_shared_events = __esm(() => {
4952
5171
  init_database();
4953
5172
  init_projects();
4954
5173
  init_task_lists();
5174
+ init_event_emission_safety();
4955
5175
  });
4956
5176
 
4957
5177
  // src/lib/secret-redaction.ts
@@ -5200,8 +5420,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
5200
5420
  const d = db || getDatabase();
5201
5421
  const id = uuid();
5202
5422
  const timestamp = now();
5203
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
5204
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
5423
+ const machineId = currentStorageMachineId(d);
5424
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
5425
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp, machineId]);
5205
5426
  try {
5206
5427
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
5207
5428
  logActivity2({
@@ -5214,7 +5435,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
5214
5435
  actor_id: agentId ?? undefined
5215
5436
  }, d);
5216
5437
  } catch {}
5217
- return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
5438
+ 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 };
5218
5439
  }
5219
5440
  function getTaskHistory(taskId, db) {
5220
5441
  const d = db || getDatabase();
@@ -5226,6 +5447,7 @@ function getRecentActivity(limit = 50, db) {
5226
5447
  }
5227
5448
  var init_audit = __esm(() => {
5228
5449
  init_database();
5450
+ init_storage_tombstones();
5229
5451
  });
5230
5452
 
5231
5453
  // src/db/webhooks.ts
@@ -5491,13 +5713,14 @@ function createTask(input, db) {
5491
5713
  const d = db || getDatabase();
5492
5714
  const timestamp = now();
5493
5715
  const tags = input.tags || [];
5716
+ const machineId = currentStorageMachineId(d);
5494
5717
  const assignedBy = input.assigned_by || input.agent_id;
5495
5718
  const assignedFromProject = input.assigned_from_project || null;
5496
5719
  let id = uuid();
5497
5720
  for (let attempt = 0;attempt < 3; attempt++) {
5498
5721
  try {
5499
- 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)
5500
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
5722
+ 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)
5723
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
5501
5724
  id,
5502
5725
  null,
5503
5726
  input.project_id || null,
@@ -5534,7 +5757,8 @@ function createTask(input, db) {
5534
5757
  input.spawned_from_session || null,
5535
5758
  assignedBy || null,
5536
5759
  assignedFromProject || null,
5537
- input.task_type || null
5760
+ input.task_type || null,
5761
+ machineId
5538
5762
  ]);
5539
5763
  break;
5540
5764
  } catch (e) {
@@ -5550,9 +5774,10 @@ function createTask(input, db) {
5550
5774
  }
5551
5775
  const task = getTask(id, d);
5552
5776
  const payload = taskEventData(task);
5777
+ const databasePath = databasePathFromDatabase(d);
5553
5778
  dispatchWebhook2("task.created", payload, d).catch(() => {});
5554
- emitLocalEventHooksQuiet({ type: "task.created", payload });
5555
- emitSharedTaskEventQuiet({ type: "task.created", task });
5779
+ emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
5780
+ emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
5556
5781
  return task;
5557
5782
  }
5558
5783
  function getTask(id, db) {
@@ -5970,29 +6195,39 @@ function updateTask(id, input, db) {
5970
6195
  approved_by: input.approved_by ?? task.approved_by,
5971
6196
  approved_at: input.approved_by ? timestamp : task.approved_at
5972
6197
  };
6198
+ const databasePath = databasePathFromDatabase(d);
5973
6199
  if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
5974
6200
  const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
5975
6201
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
5976
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
5977
- emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
6202
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
6203
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to }, databasePath });
5978
6204
  }
5979
6205
  if (input.status !== undefined && input.status !== task.status) {
5980
6206
  const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
5981
6207
  dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
5982
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
5983
- emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
6208
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload, databasePath });
6209
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status }, databasePath });
5984
6210
  }
5985
6211
  if (input.approved_by !== undefined) {
5986
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
6212
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title }, databasePath });
5987
6213
  }
5988
6214
  const updatePayload = taskEventData(updatedTask);
5989
6215
  dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
5990
- emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
5991
- emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
6216
+ emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
6217
+ emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
5992
6218
  return updatedTask;
5993
6219
  }
5994
6220
  function deleteTask(id, db) {
5995
6221
  const d = db || getDatabase();
6222
+ const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
6223
+ if (!row)
6224
+ return false;
6225
+ recordStorageTombstone({
6226
+ object_type: "tasks",
6227
+ object_id: id,
6228
+ payload: rowToTask(row),
6229
+ version: row.version
6230
+ }, d);
5996
6231
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
5997
6232
  return result.changes > 0;
5998
6233
  }
@@ -6000,11 +6235,13 @@ var init_task_crud = __esm(() => {
6000
6235
  init_types();
6001
6236
  init_database();
6002
6237
  init_completion_guard();
6238
+ init_event_emission_safety();
6003
6239
  init_event_hooks();
6004
6240
  init_shared_events();
6005
6241
  init_audit();
6006
6242
  init_webhooks();
6007
6243
  init_checklists();
6244
+ init_storage_tombstones();
6008
6245
  });
6009
6246
 
6010
6247
  // src/lib/recurrence.ts
@@ -6153,8 +6390,9 @@ function resolveTemplateId(id, d) {
6153
6390
  function createTemplate(input, db) {
6154
6391
  const d = db || getDatabase();
6155
6392
  const id = uuid();
6156
- d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
6157
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
6393
+ const machineId = currentStorageMachineId(d);
6394
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at, machine_id)
6395
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
6158
6396
  id,
6159
6397
  input.name,
6160
6398
  input.title_pattern,
@@ -6165,7 +6403,8 @@ function createTemplate(input, db) {
6165
6403
  input.project_id || null,
6166
6404
  input.plan_id || null,
6167
6405
  JSON.stringify(input.metadata || {}),
6168
- now()
6406
+ now(),
6407
+ machineId
6169
6408
  ]);
6170
6409
  if (input.tasks && input.tasks.length > 0) {
6171
6410
  addTemplateTasks(id, input.tasks, d);
@@ -6189,6 +6428,15 @@ function deleteTemplate(id, db) {
6189
6428
  const resolved = resolveTemplateId(id, d);
6190
6429
  if (!resolved)
6191
6430
  return false;
6431
+ const template = getTemplate(resolved, d);
6432
+ if (!template)
6433
+ return false;
6434
+ recordStorageTombstone({
6435
+ object_type: "templates",
6436
+ object_id: resolved,
6437
+ payload: template,
6438
+ version: template.version
6439
+ }, d);
6192
6440
  return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
6193
6441
  }
6194
6442
  function updateTemplate(id, updates, db) {
@@ -6558,6 +6806,7 @@ function previewTemplate(templateId, variables, db) {
6558
6806
  var init_templates = __esm(() => {
6559
6807
  init_database();
6560
6808
  init_tasks();
6809
+ init_storage_tombstones();
6561
6810
  });
6562
6811
 
6563
6812
  // src/db/task-graph.ts
@@ -6724,6 +6973,7 @@ function getBlockingDeps(id, db) {
6724
6973
  }
6725
6974
  function startTask(id, agentId, db) {
6726
6975
  const d = db || getDatabase();
6976
+ const databasePath = databasePathFromDatabase(d);
6727
6977
  const task = getTask(id, d);
6728
6978
  if (!task)
6729
6979
  throw new TaskNotFoundError(id);
@@ -6738,7 +6988,8 @@ function startTask(id, agentId, db) {
6738
6988
  agent_id: agentId,
6739
6989
  title: task.title,
6740
6990
  blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
6741
- }
6991
+ },
6992
+ databasePath
6742
6993
  });
6743
6994
  throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
6744
6995
  }
@@ -6760,12 +7011,13 @@ function startTask(id, agentId, db) {
6760
7011
  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 };
6761
7012
  const payload = taskEventData(startedTask, { agent_id: agentId });
6762
7013
  dispatchWebhook2("task.started", payload, d).catch(() => {});
6763
- emitLocalEventHooksQuiet({ type: "task.started", payload });
6764
- emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
7014
+ emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
7015
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId }, databasePath });
6765
7016
  return startedTask;
6766
7017
  }
6767
7018
  function completeTask(id, agentId, db, options) {
6768
7019
  const d = db || getDatabase();
7020
+ const databasePath = databasePathFromDatabase(d);
6769
7021
  const task = getTask(id, d);
6770
7022
  if (!task)
6771
7023
  throw new TaskNotFoundError(id);
@@ -6811,8 +7063,8 @@ function completeTask(id, agentId, db, options) {
6811
7063
  };
6812
7064
  const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
6813
7065
  dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
6814
- emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
6815
- emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
7066
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
7067
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp }, databasePath });
6816
7068
  let spawnedTask = null;
6817
7069
  if (task.recurrence_rule && !options?.skip_recurrence) {
6818
7070
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -6856,9 +7108,9 @@ function completeTask(id, agentId, db, options) {
6856
7108
  const depTask = getTask(dep.id, d);
6857
7109
  const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
6858
7110
  dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
6859
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
7111
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
6860
7112
  if (depTask)
6861
- emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
7113
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
6862
7114
  }
6863
7115
  }
6864
7116
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -7028,6 +7280,7 @@ function getTasksChangedSince(since, filters, db) {
7028
7280
  }
7029
7281
  function failTask(id, agentId, reason, options, db) {
7030
7282
  const d = db || getDatabase();
7283
+ const databasePath = databasePathFromDatabase(d);
7031
7284
  const task = getTask(id, d);
7032
7285
  if (!task)
7033
7286
  throw new TaskNotFoundError(id);
@@ -7056,8 +7309,8 @@ function failTask(id, agentId, reason, options, db) {
7056
7309
  logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
7057
7310
  const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
7058
7311
  dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
7059
- emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
7060
- emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
7312
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
7313
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning", databasePath });
7061
7314
  let retryTask;
7062
7315
  if (options?.retry) {
7063
7316
  const retryCount = (task.retry_count || 0) + 1;
@@ -7117,6 +7370,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
7117
7370
  }
7118
7371
  function stealTask(agentId, opts, db) {
7119
7372
  const d = db || getDatabase();
7373
+ const databasePath = databasePathFromDatabase(d);
7120
7374
  const staleMinutes = opts?.stale_minutes ?? 30;
7121
7375
  const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
7122
7376
  if (staleTasks.length === 0)
@@ -7135,8 +7389,8 @@ function stealTask(agentId, opts, db) {
7135
7389
  const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
7136
7390
  const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
7137
7391
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
7138
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
7139
- emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
7392
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
7393
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to }, databasePath });
7140
7394
  return stolenTask;
7141
7395
  }
7142
7396
  function claimOrSteal(agentId, filters, db) {
@@ -7184,6 +7438,7 @@ var init_task_lifecycle = __esm(() => {
7184
7438
  init_types();
7185
7439
  init_database();
7186
7440
  init_completion_guard();
7441
+ init_event_emission_safety();
7187
7442
  init_event_hooks();
7188
7443
  init_shared_events();
7189
7444
  init_audit();
@@ -7891,8 +8146,9 @@ function createPlan(input, db) {
7891
8146
  const d = db || getDatabase();
7892
8147
  const id = uuid();
7893
8148
  const timestamp = now();
7894
- d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
7895
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
8149
+ const machineId = currentStorageMachineId(d);
8150
+ d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
8151
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
7896
8152
  id,
7897
8153
  input.project_id || null,
7898
8154
  input.task_list_id || null,
@@ -7901,7 +8157,8 @@ function createPlan(input, db) {
7901
8157
  input.description || null,
7902
8158
  input.status || "active",
7903
8159
  timestamp,
7904
- timestamp
8160
+ timestamp,
8161
+ machineId
7905
8162
  ]);
7906
8163
  return getPlan(id, d);
7907
8164
  }
@@ -7949,19 +8206,30 @@ function updatePlan(id, input, db) {
7949
8206
  const updated = getPlan(id, d);
7950
8207
  emitLocalEventHooksQuiet({
7951
8208
  type: "plan.updated",
7952
- payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
8209
+ payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id },
8210
+ databasePath: databasePathFromDatabase(d)
7953
8211
  });
7954
8212
  return updated;
7955
8213
  }
7956
8214
  function deletePlan(id, db) {
7957
8215
  const d = db || getDatabase();
8216
+ const plan = getPlan(id, d);
8217
+ if (!plan)
8218
+ return false;
8219
+ recordStorageTombstone({
8220
+ object_type: "plans",
8221
+ object_id: id,
8222
+ payload: plan
8223
+ }, d);
7958
8224
  const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
7959
8225
  return result.changes > 0;
7960
8226
  }
7961
8227
  var init_plans = __esm(() => {
7962
8228
  init_types();
8229
+ init_event_emission_safety();
7963
8230
  init_event_hooks();
7964
8231
  init_database();
8232
+ init_storage_tombstones();
7965
8233
  });
7966
8234
 
7967
8235
  // src/db/boards.ts
@@ -8368,20 +8636,20 @@ var init_boards = __esm(() => {
8368
8636
  // src/lib/artifact-store.ts
8369
8637
  import { createHash as createHash3 } from "crypto";
8370
8638
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
8371
- import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
8372
- import { tmpdir } from "os";
8639
+ import { basename, dirname as dirname5, join as join6, resolve as resolve7 } from "path";
8640
+ import { tmpdir as tmpdir2 } from "os";
8373
8641
  function isInMemoryDb2(path) {
8374
8642
  return path === ":memory:" || path.startsWith("file::memory:");
8375
8643
  }
8376
8644
  function artifactStoreRoot() {
8377
8645
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
8378
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
8646
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
8379
8647
  if (process.env["TODOS_ARTIFACTS_DIR"])
8380
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
8648
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
8381
8649
  const dbPath = getDatabasePath();
8382
8650
  if (isInMemoryDb2(dbPath))
8383
- return join6(tmpdir(), "hasna-todos-artifacts");
8384
- return join6(dirname5(resolve6(dbPath)), "artifacts");
8651
+ return join6(tmpdir2(), "hasna-todos-artifacts");
8652
+ return join6(dirname5(resolve7(dbPath)), "artifacts");
8385
8653
  }
8386
8654
  function artifactStorePath(relativePath) {
8387
8655
  const normalized = relativePath.replace(/\\/g, "/");
@@ -8428,7 +8696,7 @@ function mediaTypeFor(path, textLike) {
8428
8696
  return "application/octet-stream";
8429
8697
  }
8430
8698
  function storeArtifactContent(input) {
8431
- const sourcePath = resolve6(input.path);
8699
+ const sourcePath = resolve7(input.path);
8432
8700
  if (!existsSync7(sourcePath))
8433
8701
  return null;
8434
8702
  const sourceStat = statSync2(sourcePath);
@@ -9125,7 +9393,11 @@ function startTaskRun(input, db) {
9125
9393
  }, d);
9126
9394
  }
9127
9395
  const run = getTaskRun(id, d);
9128
- emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
9396
+ emitLocalEventHooksQuiet({
9397
+ type: "run.started",
9398
+ payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title },
9399
+ databasePath: databasePathFromDatabase(d)
9400
+ });
9129
9401
  return run;
9130
9402
  }
9131
9403
  function beginTaskRunTransaction(input, db) {
@@ -9394,7 +9666,8 @@ function finishTaskRun(input, db) {
9394
9666
  const updated = getTaskRun(run.id, d);
9395
9667
  emitLocalEventHooksQuiet({
9396
9668
  type: `run.${input.status}`,
9397
- payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp }
9669
+ payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp },
9670
+ databasePath: databasePathFromDatabase(d)
9398
9671
  });
9399
9672
  return updated;
9400
9673
  }
@@ -9478,6 +9751,7 @@ function getTaskRunLedger(runId, db) {
9478
9751
  var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
9479
9752
  var init_task_runs = __esm(() => {
9480
9753
  init_artifact_store();
9754
+ init_event_emission_safety();
9481
9755
  init_event_hooks();
9482
9756
  init_redaction();
9483
9757
  init_types();
@@ -10364,6 +10638,7 @@ function rowToAgent(row) {
10364
10638
  }
10365
10639
  function registerAgent(input, db) {
10366
10640
  const d = db || getDatabase();
10641
+ const machineId = currentStorageMachineId(d);
10367
10642
  const existingNames = d.query("SELECT name FROM agents").all().map((row) => row.name);
10368
10643
  const normalizedName = validateAgentName(input.name, existingNames);
10369
10644
  const existing = getAgentByName(normalizedName, d);
@@ -10404,14 +10679,18 @@ function registerAgent(input, db) {
10404
10679
  updates.push("active_project_id = ?");
10405
10680
  params.push(input.project_id);
10406
10681
  }
10682
+ if (!existing.machine_id && machineId) {
10683
+ updates.push("machine_id = ?");
10684
+ params.push(machineId);
10685
+ }
10407
10686
  params.push(existing.id);
10408
10687
  d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
10409
10688
  return getAgent(existing.id, d);
10410
10689
  }
10411
10690
  const id = shortUuid();
10412
10691
  const timestamp = now();
10413
- 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)
10414
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10692
+ 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)
10693
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10415
10694
  id,
10416
10695
  normalizedName,
10417
10696
  input.description || null,
@@ -10427,7 +10706,8 @@ function registerAgent(input, db) {
10427
10706
  timestamp,
10428
10707
  input.session_id || null,
10429
10708
  input.working_dir || null,
10430
- input.project_id && input.session_id ? input.project_id : null
10709
+ input.project_id && input.session_id ? input.project_id : null,
10710
+ machineId
10431
10711
  ]);
10432
10712
  return getAgent(id, d);
10433
10713
  }
@@ -10610,6 +10890,7 @@ function getCapableAgents(capabilities, opts, db) {
10610
10890
  }
10611
10891
  var init_agents = __esm(() => {
10612
10892
  init_database();
10893
+ init_storage_tombstones();
10613
10894
  init_agent_names();
10614
10895
  });
10615
10896
 
@@ -11145,7 +11426,7 @@ var init_doctor = __esm(() => {
11145
11426
  });
11146
11427
 
11147
11428
  // src/server/routes.ts
11148
- import { join as join8, resolve as resolve7, sep } from "path";
11429
+ import { join as join8, resolve as resolve8, sep as sep2 } from "path";
11149
11430
  function parseFieldsParam(url) {
11150
11431
  const fieldsParam = url.searchParams.get("fields");
11151
11432
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -11883,9 +12164,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
11883
12164
  return null;
11884
12165
  if (path !== "/") {
11885
12166
  const filePath = join8(ctx.dashboardDir, path);
11886
- const resolvedFile = resolve7(filePath);
11887
- const resolvedBase = resolve7(ctx.dashboardDir);
11888
- if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
12167
+ const resolvedFile = resolve8(filePath);
12168
+ const resolvedBase = resolve8(ctx.dashboardDir);
12169
+ if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
11889
12170
  return json2({ error: "Forbidden" }, 403);
11890
12171
  }
11891
12172
  const res2 = serveStaticFile2(filePath);
@@ -15284,7 +15565,7 @@ class JSONSchemaGenerator {
15284
15565
  if (val === undefined) {
15285
15566
  if (this.unrepresentable === "throw") {
15286
15567
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
15287
- } else {}
15568
+ }
15288
15569
  } else if (typeof val === "bigint") {
15289
15570
  if (this.unrepresentable === "throw") {
15290
15571
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -17650,9 +17931,9 @@ data:
17650
17931
  const initRequest = messages.find((m) => isInitializeRequest(m));
17651
17932
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
17652
17933
  if (this._enableJsonResponse) {
17653
- return new Promise((resolve8) => {
17934
+ return new Promise((resolve9) => {
17654
17935
  this._streamMapping.set(streamId, {
17655
- resolveJson: resolve8,
17936
+ resolveJson: resolve9,
17656
17937
  cleanup: () => {
17657
17938
  this._streamMapping.delete(streamId);
17658
17939
  }
@@ -23952,7 +24233,7 @@ class Protocol {
23952
24233
  return;
23953
24234
  }
23954
24235
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
23955
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
24236
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
23956
24237
  options?.signal?.throwIfAborted();
23957
24238
  }
23958
24239
  } catch (error2) {
@@ -23964,7 +24245,7 @@ class Protocol {
23964
24245
  }
23965
24246
  request(request, resultSchema, options) {
23966
24247
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
23967
- return new Promise((resolve8, reject) => {
24248
+ return new Promise((resolve9, reject) => {
23968
24249
  const earlyReject = (error2) => {
23969
24250
  reject(error2);
23970
24251
  };
@@ -24042,7 +24323,7 @@ class Protocol {
24042
24323
  if (!parseResult.success) {
24043
24324
  reject(parseResult.error);
24044
24325
  } else {
24045
- resolve8(parseResult.data);
24326
+ resolve9(parseResult.data);
24046
24327
  }
24047
24328
  } catch (error2) {
24048
24329
  reject(error2);
@@ -24233,12 +24514,12 @@ class Protocol {
24233
24514
  interval = task.pollInterval;
24234
24515
  }
24235
24516
  } catch {}
24236
- return new Promise((resolve8, reject) => {
24517
+ return new Promise((resolve9, reject) => {
24237
24518
  if (signal.aborted) {
24238
24519
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
24239
24520
  return;
24240
24521
  }
24241
- const timeoutId = setTimeout(resolve8, interval);
24522
+ const timeoutId = setTimeout(resolve9, interval);
24242
24523
  signal.addEventListener("abort", () => {
24243
24524
  clearTimeout(timeoutId);
24244
24525
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -27223,7 +27504,7 @@ var require_compile = __commonJS((exports) => {
27223
27504
  const schOrFunc = root.refs[ref];
27224
27505
  if (schOrFunc)
27225
27506
  return schOrFunc;
27226
- let _sch = resolve8.call(this, root, ref);
27507
+ let _sch = resolve9.call(this, root, ref);
27227
27508
  if (_sch === undefined) {
27228
27509
  const schema = (_a = root.localRefs) === null || _a === undefined ? undefined : _a[ref];
27229
27510
  const { schemaId } = this.opts;
@@ -27250,7 +27531,7 @@ var require_compile = __commonJS((exports) => {
27250
27531
  function sameSchemaEnv(s1, s2) {
27251
27532
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
27252
27533
  }
27253
- function resolve8(root, ref) {
27534
+ function resolve9(root, ref) {
27254
27535
  let sch;
27255
27536
  while (typeof (sch = this.refs[ref]) == "string")
27256
27537
  ref = sch;
@@ -27780,7 +28061,7 @@ var require_fast_uri = __commonJS((exports, module) => {
27780
28061
  }
27781
28062
  return uri;
27782
28063
  }
27783
- function resolve8(baseURI, relativeURI, options) {
28064
+ function resolve9(baseURI, relativeURI, options) {
27784
28065
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
27785
28066
  const resolved = resolveComponent(parse6(baseURI, schemelessOptions), parse6(relativeURI, schemelessOptions), schemelessOptions, true);
27786
28067
  schemelessOptions.skipEscape = true;
@@ -28008,7 +28289,7 @@ var require_fast_uri = __commonJS((exports, module) => {
28008
28289
  var fastUri = {
28009
28290
  SCHEMES,
28010
28291
  normalize,
28011
- resolve: resolve8,
28292
+ resolve: resolve9,
28012
28293
  resolveComponent,
28013
28294
  equal,
28014
28295
  serialize,
@@ -31534,7 +31815,7 @@ class McpServer {
31534
31815
  let task = createTaskResult.task;
31535
31816
  const pollInterval = task.pollInterval ?? 5000;
31536
31817
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
31537
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
31818
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
31538
31819
  const updatedTask = await extra.taskStore.getTask(taskId);
31539
31820
  if (!updatedTask) {
31540
31821
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -32158,12 +32439,12 @@ class StdioServerTransport {
32158
32439
  this.onclose?.();
32159
32440
  }
32160
32441
  send(message) {
32161
- return new Promise((resolve8) => {
32442
+ return new Promise((resolve9) => {
32162
32443
  const json2 = serializeMessage(message);
32163
32444
  if (this._stdout.write(json2)) {
32164
- resolve8();
32445
+ resolve9();
32165
32446
  } else {
32166
- this._stdout.once("drain", resolve8);
32447
+ this._stdout.once("drain", resolve9);
32167
32448
  }
32168
32449
  });
32169
32450
  }
@@ -33570,7 +33851,7 @@ var init_task_crud2 = __esm(() => {
33570
33851
 
33571
33852
  // src/lib/project-bootstrap.ts
33572
33853
  import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
33573
- import { basename as basename3, dirname as dirname7, resolve as resolve8 } from "path";
33854
+ import { basename as basename3, dirname as dirname7, resolve as resolve9 } from "path";
33574
33855
  function safeStat(path) {
33575
33856
  try {
33576
33857
  return statSync4(path);
@@ -33579,7 +33860,7 @@ function safeStat(path) {
33579
33860
  }
33580
33861
  }
33581
33862
  function canonicalPath(input) {
33582
- const resolved = resolve8(input);
33863
+ const resolved = resolve9(input);
33583
33864
  const stats = safeStat(resolved);
33584
33865
  if (stats?.isFile())
33585
33866
  return dirname7(resolved);
@@ -33588,7 +33869,7 @@ function canonicalPath(input) {
33588
33869
  function findUp(start, marker) {
33589
33870
  let current = canonicalPath(start);
33590
33871
  while (true) {
33591
- if (existsSync9(resolve8(current, marker)))
33872
+ if (existsSync9(resolve9(current, marker)))
33592
33873
  return current;
33593
33874
  const parent = dirname7(current);
33594
33875
  if (parent === current)
@@ -33599,7 +33880,7 @@ function findUp(start, marker) {
33599
33880
  function readPackageJson(path) {
33600
33881
  if (!path)
33601
33882
  return null;
33602
- const file = resolve8(path, "package.json");
33883
+ const file = resolve9(path, "package.json");
33603
33884
  if (!existsSync9(file))
33604
33885
  return null;
33605
33886
  try {
@@ -33622,7 +33903,7 @@ function workspaceMarker(root, rootPackage) {
33622
33903
  if (rootPackage?.workspaces)
33623
33904
  markers.push("package.json#workspaces");
33624
33905
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
33625
- if (existsSync9(resolve8(root, marker)))
33906
+ if (existsSync9(resolve9(root, marker)))
33626
33907
  markers.push(marker);
33627
33908
  }
33628
33909
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -33684,7 +33965,19 @@ function bootstrapProject(options = {}, db) {
33684
33965
  }
33685
33966
  setMachineLocalPath(project.id, discovery.projectPath, d);
33686
33967
  const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
33687
- const taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
33968
+ let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
33969
+ if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
33970
+ taskList = updateTaskList(taskList.id, {
33971
+ metadata: {
33972
+ ...taskList.metadata,
33973
+ route_enabled: true,
33974
+ automation: {
33975
+ ...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
33976
+ no_auto: false
33977
+ }
33978
+ }
33979
+ }, d);
33980
+ }
33688
33981
  const createdSources = [];
33689
33982
  for (const source of [
33690
33983
  addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
@@ -34154,7 +34447,7 @@ var init_retention_cleanup = __esm(() => {
34154
34447
 
34155
34448
  // src/lib/mention-resolver.ts
34156
34449
  import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
34157
- import { basename as basename4, isAbsolute, join as join9, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
34450
+ import { basename as basename4, isAbsolute, join as join9, relative as relative3, resolve as resolve10, sep as sep3 } from "path";
34158
34451
  function blankResolution(parsed) {
34159
34452
  return {
34160
34453
  input: parsed.input,
@@ -34177,11 +34470,11 @@ function backlink(kind, key, label, target = key) {
34177
34470
  return { kind, key, label, target };
34178
34471
  }
34179
34472
  function normalizeWorkspace(workspace) {
34180
- return resolve9(workspace || process.cwd());
34473
+ return resolve10(workspace || process.cwd());
34181
34474
  }
34182
34475
  function isInside(root, absolutePath) {
34183
34476
  const rel = relative3(root, absolutePath);
34184
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
34477
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep3}`) && !isAbsolute(rel);
34185
34478
  }
34186
34479
  function normalizeRelativePath(value) {
34187
34480
  const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
@@ -34245,7 +34538,7 @@ function resolveFile(parsed, workspace) {
34245
34538
  resolution.warnings.push("path is empty or escapes the workspace");
34246
34539
  return resolution;
34247
34540
  }
34248
- const absolutePath = resolve9(workspace, relPath);
34541
+ const absolutePath = resolve10(workspace, relPath);
34249
34542
  if (!isInside(workspace, absolutePath)) {
34250
34543
  resolution.path = relPath;
34251
34544
  resolution.warnings.push("path escapes the workspace");
@@ -34590,9 +34883,9 @@ var init_mention_resolver = __esm(() => {
34590
34883
  });
34591
34884
 
34592
34885
  // src/lib/policy-packs.ts
34593
- import { relative as relative4, resolve as resolve10 } from "path";
34886
+ import { relative as relative4, resolve as resolve11 } from "path";
34594
34887
  function normalizePath3(path) {
34595
- return resolve10(path);
34888
+ return resolve11(path);
34596
34889
  }
34597
34890
  function unique4(values) {
34598
34891
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -34647,7 +34940,7 @@ function commandMatches(commands, pattern) {
34647
34940
  }
34648
34941
  function pathMatches(paths, pattern, root) {
34649
34942
  return paths.filter((path) => {
34650
- const candidate = path.startsWith("/") ? path : resolve10(root, path);
34943
+ const candidate = path.startsWith("/") ? path : resolve11(root, path);
34651
34944
  if (!isPathInside3(root, candidate))
34652
34945
  return matchesPattern3(path, pattern);
34653
34946
  return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
@@ -34994,7 +35287,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
34994
35287
  if (action === "approved" || action === "rejected" || action === "expired") {
34995
35288
  emitLocalEventHooksQuiet({
34996
35289
  type: "approval.decided",
34997
- payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id }
35290
+ payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
35291
+ databasePath: databasePathFromDatabase(db)
34998
35292
  });
34999
35293
  }
35000
35294
  }
@@ -35134,6 +35428,7 @@ var init_approval_gates = __esm(() => {
35134
35428
  init_task_runs();
35135
35429
  init_tasks();
35136
35430
  init_types();
35431
+ init_event_emission_safety();
35137
35432
  init_event_hooks();
35138
35433
  });
35139
35434
 
@@ -37700,7 +37995,7 @@ function canonicalize(value) {
37700
37995
  function hash(value) {
37701
37996
  return createHash5("sha256").update(value).digest("hex");
37702
37997
  }
37703
- function parsePayload(value) {
37998
+ function parsePayload2(value) {
37704
37999
  if (!value)
37705
38000
  return {};
37706
38001
  try {
@@ -37799,7 +38094,7 @@ function taskScopedRows(db, scope) {
37799
38094
  FROM handoffs h
37800
38095
  WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
37801
38096
  `).all(scope.project_id ?? null).filter((row) => {
37802
- const payload = parsePayload(row.payload_json);
38097
+ const payload = parsePayload2(row.payload_json);
37803
38098
  const taskRefs = parseStringArray(payload["task_ids"]);
37804
38099
  const runRefs = parseStringArray(payload["run_ids"]);
37805
38100
  if (scope.project_id && row.project_id !== scope.project_id)
@@ -37830,7 +38125,7 @@ function toLedgerEntries(rows) {
37830
38125
  });
37831
38126
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
37832
38127
  return ordered.map((row, index) => {
37833
- const payload = parsePayload(row.payload_json);
38128
+ const payload = parsePayload2(row.payload_json);
37834
38129
  const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
37835
38130
  const chainHash = hash(`${previous}
37836
38131
  ${payloadHash}`);
@@ -37972,7 +38267,7 @@ var init_audit_ledger = __esm(() => {
37972
38267
 
37973
38268
  // src/lib/release-compatibility.ts
37974
38269
  import { readFileSync as readFileSync6 } from "fs";
37975
- import { join as join10, resolve as resolve11 } from "path";
38270
+ import { join as join10, resolve as resolve12 } from "path";
37976
38271
  import { Database as Database2 } from "bun:sqlite";
37977
38272
  function pass(id, message, details) {
37978
38273
  return { id, status: "passed", message, details };
@@ -38080,7 +38375,7 @@ function checkChangelog() {
38080
38375
  ];
38081
38376
  }
38082
38377
  function createReleaseCompatibilityReport(options = {}) {
38083
- const root = resolve11(options.root ?? process.cwd());
38378
+ const root = resolve12(options.root ?? process.cwd());
38084
38379
  const packageJson = readPackageJson2(root);
38085
38380
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
38086
38381
  const checks4 = [
@@ -58357,7 +58652,7 @@ var require_to_json_schema = __commonJS((exports) => {
58357
58652
  if (val === undefined) {
58358
58653
  if (this.unrepresentable === "throw") {
58359
58654
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
58360
- } else {}
58655
+ }
58361
58656
  } else if (typeof val === "bigint") {
58362
58657
  if (this.unrepresentable === "throw") {
58363
58658
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -64999,7 +65294,7 @@ var require_protocol = __commonJS((exports) => {
64999
65294
  return;
65000
65295
  }
65001
65296
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
65002
- await new Promise((resolve12) => setTimeout(resolve12, pollInterval));
65297
+ await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
65003
65298
  options?.signal?.throwIfAborted();
65004
65299
  }
65005
65300
  } catch (error2) {
@@ -65011,7 +65306,7 @@ var require_protocol = __commonJS((exports) => {
65011
65306
  }
65012
65307
  request(request, resultSchema, options) {
65013
65308
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
65014
- return new Promise((resolve12, reject) => {
65309
+ return new Promise((resolve13, reject) => {
65015
65310
  const earlyReject = (error2) => {
65016
65311
  reject(error2);
65017
65312
  };
@@ -65089,7 +65384,7 @@ var require_protocol = __commonJS((exports) => {
65089
65384
  if (!parseResult.success) {
65090
65385
  reject(parseResult.error);
65091
65386
  } else {
65092
- resolve12(parseResult.data);
65387
+ resolve13(parseResult.data);
65093
65388
  }
65094
65389
  } catch (error2) {
65095
65390
  reject(error2);
@@ -65280,12 +65575,12 @@ var require_protocol = __commonJS((exports) => {
65280
65575
  interval = task.pollInterval;
65281
65576
  }
65282
65577
  } catch {}
65283
- return new Promise((resolve12, reject) => {
65578
+ return new Promise((resolve13, reject) => {
65284
65579
  if (signal.aborted) {
65285
65580
  reject(new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, "Request cancelled"));
65286
65581
  return;
65287
65582
  }
65288
- const timeoutId = setTimeout(resolve12, interval);
65583
+ const timeoutId = setTimeout(resolve13, interval);
65289
65584
  signal.addEventListener("abort", () => {
65290
65585
  clearTimeout(timeoutId);
65291
65586
  reject(new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, "Request cancelled"));
@@ -66431,7 +66726,7 @@ var require_mcp = __commonJS((exports) => {
66431
66726
  let task = createTaskResult.task;
66432
66727
  const pollInterval = task.pollInterval ?? 5000;
66433
66728
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
66434
- await new Promise((resolve12) => setTimeout(resolve12, pollInterval));
66729
+ await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
66435
66730
  const updatedTask = await extra.taskStore.getTask(taskId);
66436
66731
  if (!updatedTask) {
66437
66732
  throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -67840,7 +68135,7 @@ function classifyLog(text) {
67840
68135
  async function sleep2(ms) {
67841
68136
  if (ms <= 0)
67842
68137
  return;
67843
- await new Promise((resolve12) => setTimeout(resolve12, ms));
68138
+ await new Promise((resolve13) => setTimeout(resolve13, ms));
67844
68139
  }
67845
68140
  async function runCommandProvider(provider, input) {
67846
68141
  const commandTemplate = input.command || provider.command;
@@ -70569,7 +70864,7 @@ var init_local_bridge = __esm(() => {
70569
70864
  // src/lib/local-backups.ts
70570
70865
  import { createHash as createHash6 } from "crypto";
70571
70866
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
70572
- import { dirname as dirname8, resolve as resolve12 } from "path";
70867
+ import { dirname as dirname8, resolve as resolve13 } from "path";
70573
70868
  import { mkdirSync as mkdirSync6 } from "fs";
70574
70869
  function stableJson(value) {
70575
70870
  if (value === null || typeof value !== "object")
@@ -70671,14 +70966,14 @@ function createLocalBackup(options = {}, db) {
70671
70966
  return backup;
70672
70967
  }
70673
70968
  function writeLocalBackupFile(backup, outputPath) {
70674
- const path = resolve12(outputPath);
70969
+ const path = resolve13(outputPath);
70675
70970
  mkdirSync6(dirname8(path), { recursive: true });
70676
70971
  writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
70677
70972
  `);
70678
70973
  return path;
70679
70974
  }
70680
70975
  function readLocalBackupFile(path) {
70681
- return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
70976
+ return JSON.parse(readFileSync8(resolve13(path), "utf-8"));
70682
70977
  }
70683
70978
  function verifyLocalBackup(value, options = {}, db) {
70684
70979
  const verifiedAt = options.verified_at ?? now();
@@ -72128,7 +72423,7 @@ __export(exports_local_extensions, {
72128
72423
  });
72129
72424
  import { createHash as createHash9, createVerify } from "crypto";
72130
72425
  import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
72131
- import { basename as basename5, join as join11, resolve as resolve13 } from "path";
72426
+ import { basename as basename5, join as join11, resolve as resolve14 } from "path";
72132
72427
  function isObject3(value) {
72133
72428
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
72134
72429
  }
@@ -72386,7 +72681,7 @@ function verifyExtensionSignature(input) {
72386
72681
  return verifier.verify(input.public_key, decodeSignature(input.signature));
72387
72682
  }
72388
72683
  function inspectExtensionSource(source3) {
72389
- const resolved = resolve13(source3);
72684
+ const resolved = resolve14(source3);
72390
72685
  if (!existsSync13(resolved))
72391
72686
  throw new Error(`extension source not found: ${source3}`);
72392
72687
  const stat = statSync6(resolved);
@@ -72484,7 +72779,7 @@ function testExtensionCompatibility(sourceOrManifest) {
72484
72779
  function projectExtensionSources(projectPath) {
72485
72780
  if (!projectPath)
72486
72781
  return [];
72487
- const root = resolve13(projectPath);
72782
+ const root = resolve14(projectPath);
72488
72783
  const candidates = [
72489
72784
  join11(root, "todos.extension.json"),
72490
72785
  join11(root, ".todos", "todos.extension.json")
@@ -72503,7 +72798,7 @@ function projectExtensionSources(projectPath) {
72503
72798
  }
72504
72799
  function discoverLocalExtensions(options = {}) {
72505
72800
  const config2 = loadConfig();
72506
- const projectPath = options.project_path ? resolve13(options.project_path) : null;
72801
+ const projectPath = options.project_path ? resolve14(options.project_path) : null;
72507
72802
  const configuredSources = [
72508
72803
  ...config2.extension_sources || [],
72509
72804
  ...projectPath ? config2.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -72511,7 +72806,7 @@ function discoverLocalExtensions(options = {}) {
72511
72806
  const sources = Array.from(new Set([
72512
72807
  ...configuredSources,
72513
72808
  ...projectExtensionSources(projectPath || undefined)
72514
- ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
72809
+ ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve14(projectPath, source3) : resolve14(source3));
72515
72810
  const warnings = [];
72516
72811
  const discovered = [];
72517
72812
  for (const source3 of sources) {
@@ -75461,7 +75756,8 @@ function writeQueue(task2, queue, actor, action, db) {
75461
75756
  logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
75462
75757
  emitLocalEventHooksQuiet({
75463
75758
  type: `review.${action}`,
75464
- payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by }
75759
+ payload: { task_id: task2.id, queue: queue.queue, state: queue.state, reviewer: queue.reviewer, claimed_by: queue.claimed_by },
75760
+ databasePath: databasePathFromDatabase(d)
75465
75761
  });
75466
75762
  return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
75467
75763
  }
@@ -75765,6 +76061,7 @@ var init_review_queues = __esm(() => {
75765
76061
  init_database();
75766
76062
  init_tasks();
75767
76063
  init_config();
76064
+ init_event_emission_safety();
75768
76065
  init_event_hooks();
75769
76066
  init_task_contracts();
75770
76067
  STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
@@ -76920,7 +77217,7 @@ __export(exports_extract, {
76920
77217
  });
76921
77218
  import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
76922
77219
  import { createHash as createHash11 } from "crypto";
76923
- import { relative as relative5, resolve as resolve14, join as join12 } from "path";
77220
+ import { relative as relative5, resolve as resolve15, join as join12 } from "path";
76924
77221
  function stableHash(value) {
76925
77222
  return createHash11("sha256").update(value).digest("hex");
76926
77223
  }
@@ -76928,7 +77225,7 @@ function normalizePathForMatch(value) {
76928
77225
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
76929
77226
  }
76930
77227
  function readGitignorePatterns(basePath) {
76931
- const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
77228
+ const root = statSync7(basePath).isFile() ? resolve15(basePath, "..") : basePath;
76932
77229
  const gitignorePath = join12(root, ".gitignore");
76933
77230
  if (!existsSync14(gitignorePath))
76934
77231
  return [];
@@ -77064,7 +77361,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
77064
77361
  return files.sort();
77065
77362
  }
77066
77363
  function buildCodebaseIndex(options) {
77067
- const basePath = resolve14(options.path);
77364
+ const basePath = resolve15(options.path);
77068
77365
  const tags = options.patterns || [...EXTRACT_TAGS];
77069
77366
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
77070
77367
  const excludes = options.exclude || [];
@@ -77075,7 +77372,7 @@ function buildCodebaseIndex(options) {
77075
77372
  const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
77076
77373
  try {
77077
77374
  const source3 = readFileSync10(fullPath, "utf-8");
77078
- const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
77375
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
77079
77376
  indexed.push({
77080
77377
  file: relPath,
77081
77378
  checksum: stableHash(source3).slice(0, 24),
@@ -77095,7 +77392,7 @@ function buildCodebaseIndex(options) {
77095
77392
  };
77096
77393
  }
77097
77394
  function extractTodos(options, db) {
77098
- const basePath = resolve14(options.path);
77395
+ const basePath = resolve15(options.path);
77099
77396
  const tags = options.patterns || [...EXTRACT_TAGS];
77100
77397
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
77101
77398
  const excludes = options.exclude || [];
@@ -77106,7 +77403,7 @@ function extractTodos(options, db) {
77106
77403
  const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
77107
77404
  try {
77108
77405
  const source3 = readFileSync10(fullPath, "utf-8");
77109
- const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
77406
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
77110
77407
  const comments = extractFromSource(source3, relPath, tags);
77111
77408
  allComments.push(...comments);
77112
77409
  } catch {}
@@ -77200,7 +77497,7 @@ async function watchSourceTodos(options, onRun) {
77200
77497
  const interval = Math.max(100, options.interval_ms || 2000);
77201
77498
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
77202
77499
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
77203
- const root = resolve14(options.path);
77500
+ const root = resolve15(options.path);
77204
77501
  const runs = [];
77205
77502
  let previous = new Map;
77206
77503
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -78583,8 +78880,8 @@ __export(exports_environment_snapshots, {
78583
78880
  import { createHash as createHash12 } from "crypto";
78584
78881
  import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
78585
78882
  import { hostname as hostname3, platform, arch } from "os";
78586
- import { dirname as dirname9, join as join14, resolve as resolve15 } from "path";
78587
- import { tmpdir as tmpdir2 } from "os";
78883
+ import { dirname as dirname9, join as join14, resolve as resolve16 } from "path";
78884
+ import { tmpdir as tmpdir3 } from "os";
78588
78885
  function sha2566(value) {
78589
78886
  return createHash12("sha256").update(value).digest("hex");
78590
78887
  }
@@ -78697,15 +78994,15 @@ function commandEnv(env, includeValues) {
78697
78994
  function defaultSnapshotDir() {
78698
78995
  const dbPath = getDatabasePath();
78699
78996
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
78700
- return join14(tmpdir2(), "hasna-todos", "environment-snapshots");
78701
- return join14(dirname9(resolve15(dbPath)), "environment-snapshots");
78997
+ return join14(tmpdir3(), "hasna-todos", "environment-snapshots");
78998
+ return join14(dirname9(resolve16(dbPath)), "environment-snapshots");
78702
78999
  }
78703
79000
  function snapshotWithId(snapshot) {
78704
79001
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
78705
79002
  return { id: `env_${digest}`, ...snapshot };
78706
79003
  }
78707
79004
  function captureEnvironmentSnapshot(input = {}) {
78708
- const root = resolve15(input.root || process.cwd());
79005
+ const root = resolve16(input.root || process.cwd());
78709
79006
  const env = input.env || process.env;
78710
79007
  const warnings = [];
78711
79008
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -78745,13 +79042,13 @@ function captureEnvironmentSnapshot(input = {}) {
78745
79042
  });
78746
79043
  }
78747
79044
  function writeEnvironmentSnapshot(snapshot, outputPath) {
78748
- const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
79045
+ const path = outputPath ? resolve16(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
78749
79046
  ensureDir2(dirname9(path));
78750
79047
  writeJsonFile(path, snapshot);
78751
79048
  return path;
78752
79049
  }
78753
79050
  function readEnvironmentSnapshot(path) {
78754
- const snapshot = readJsonFile(resolve15(path));
79051
+ const snapshot = readJsonFile(resolve16(path));
78755
79052
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
78756
79053
  throw new Error(`Invalid environment snapshot: ${path}`);
78757
79054
  }