@hasna/todos 0.11.66 → 0.11.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +2 -2
  2. package/dashboard/dist/assets/{index-aJefI7kh.js → index-DVotjwab.js} +1 -1
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/cli/commands/storage-commands.d.ts +5 -0
  5. package/dist/cli/commands/storage-commands.d.ts.map +1 -1
  6. package/dist/cli/index.js +667 -169
  7. package/dist/contracts.js +301 -71
  8. package/dist/db/agents.d.ts.map +1 -1
  9. package/dist/db/audit.d.ts.map +1 -1
  10. package/dist/db/migrations.d.ts.map +1 -1
  11. package/dist/db/plans.d.ts.map +1 -1
  12. package/dist/db/projects.d.ts.map +1 -1
  13. package/dist/db/schema.d.ts.map +1 -1
  14. package/dist/db/storage-tombstones.d.ts +26 -0
  15. package/dist/db/storage-tombstones.d.ts.map +1 -0
  16. package/dist/db/task-crud.d.ts.map +1 -1
  17. package/dist/db/task-lifecycle.d.ts.map +1 -1
  18. package/dist/db/task-lists.d.ts.map +1 -1
  19. package/dist/db/task-runs.d.ts.map +1 -1
  20. package/dist/db/templates.d.ts.map +1 -1
  21. package/dist/index.js +628 -160
  22. package/dist/lib/approval-gates.d.ts.map +1 -1
  23. package/dist/lib/event-emission-safety.d.ts +9 -0
  24. package/dist/lib/event-emission-safety.d.ts.map +1 -0
  25. package/dist/lib/event-hooks.d.ts +1 -0
  26. package/dist/lib/event-hooks.d.ts.map +1 -1
  27. package/dist/lib/feature-manifest.d.ts.map +1 -1
  28. package/dist/lib/review-queues.d.ts.map +1 -1
  29. package/dist/lib/shared-events.d.ts +1 -0
  30. package/dist/lib/shared-events.d.ts.map +1 -1
  31. package/dist/mcp/index.js +343 -104
  32. package/dist/registry.js +301 -71
  33. package/dist/release-provenance.json +3 -3
  34. package/dist/server/index.js +366 -127
  35. package/dist/storage/interfaces.d.ts +20 -0
  36. package/dist/storage/interfaces.d.ts.map +1 -1
  37. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  38. package/dist/storage/postgres-sync.d.ts +6 -1
  39. package/dist/storage/postgres-sync.d.ts.map +1 -1
  40. package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
  41. package/dist/storage.js +569 -94
  42. package/dist/types/index.d.ts +22 -0
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/package.json +1 -1
package/dist/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,6 +9588,7 @@ 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
9594
  // src/lib/task-route-contract.ts
@@ -9614,6 +9792,8 @@ function readMachineLocalPath(project) {
9614
9792
  }
9615
9793
  }
9616
9794
  async function emitSharedTaskEvent(input) {
9795
+ if (!shouldEmitSharedTaskEvents(input.databasePath))
9796
+ return;
9617
9797
  const data = taskEventData(input.task, input.data);
9618
9798
  await new EventsClient().emit({
9619
9799
  source: SOURCE,
@@ -9637,6 +9817,7 @@ var init_shared_events = __esm(() => {
9637
9817
  init_database();
9638
9818
  init_projects();
9639
9819
  init_task_lists();
9820
+ init_event_emission_safety();
9640
9821
  });
9641
9822
 
9642
9823
  // src/lib/secret-redaction.ts
@@ -9885,8 +10066,9 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
9885
10066
  const d = db || getDatabase();
9886
10067
  const id = uuid();
9887
10068
  const timestamp = now();
9888
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
9889
- 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]);
9890
10072
  try {
9891
10073
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
9892
10074
  logActivity2({
@@ -9899,7 +10081,7 @@ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
9899
10081
  actor_id: agentId ?? undefined
9900
10082
  }, d);
9901
10083
  } catch {}
9902
- 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 };
9903
10085
  }
9904
10086
  function getTaskHistory(taskId, db) {
9905
10087
  const d = db || getDatabase();
@@ -9911,6 +10093,7 @@ function getRecentActivity(limit = 50, db) {
9911
10093
  }
9912
10094
  var init_audit = __esm(() => {
9913
10095
  init_database();
10096
+ init_storage_tombstones();
9914
10097
  });
9915
10098
 
9916
10099
  // src/db/webhooks.ts
@@ -10176,13 +10359,14 @@ function createTask(input, db) {
10176
10359
  const d = db || getDatabase();
10177
10360
  const timestamp = now();
10178
10361
  const tags = input.tags || [];
10362
+ const machineId = currentStorageMachineId(d);
10179
10363
  const assignedBy = input.assigned_by || input.agent_id;
10180
10364
  const assignedFromProject = input.assigned_from_project || null;
10181
10365
  let id = uuid();
10182
10366
  for (let attempt = 0;attempt < 3; attempt++) {
10183
10367
  try {
10184
- 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)
10185
- 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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10186
10370
  id,
10187
10371
  null,
10188
10372
  input.project_id || null,
@@ -10219,7 +10403,8 @@ function createTask(input, db) {
10219
10403
  input.spawned_from_session || null,
10220
10404
  assignedBy || null,
10221
10405
  assignedFromProject || null,
10222
- input.task_type || null
10406
+ input.task_type || null,
10407
+ machineId
10223
10408
  ]);
10224
10409
  break;
10225
10410
  } catch (e) {
@@ -10235,9 +10420,10 @@ function createTask(input, db) {
10235
10420
  }
10236
10421
  const task = getTask(id, d);
10237
10422
  const payload = taskEventData(task);
10423
+ const databasePath = databasePathFromDatabase(d);
10238
10424
  dispatchWebhook2("task.created", payload, d).catch(() => {});
10239
- emitLocalEventHooksQuiet({ type: "task.created", payload });
10240
- emitSharedTaskEventQuiet({ type: "task.created", task });
10425
+ emitLocalEventHooksQuiet({ type: "task.created", payload, databasePath });
10426
+ emitSharedTaskEventQuiet({ type: "task.created", task, databasePath });
10241
10427
  return task;
10242
10428
  }
10243
10429
  function getTask(id, db) {
@@ -10655,29 +10841,39 @@ function updateTask(id, input, db) {
10655
10841
  approved_by: input.approved_by ?? task.approved_by,
10656
10842
  approved_at: input.approved_by ? timestamp : task.approved_at
10657
10843
  };
10844
+ const databasePath = databasePathFromDatabase(d);
10658
10845
  if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
10659
10846
  const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
10660
10847
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
10661
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
10662
- 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 });
10663
10850
  }
10664
10851
  if (input.status !== undefined && input.status !== task.status) {
10665
10852
  const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
10666
10853
  dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
10667
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
10668
- 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 });
10669
10856
  }
10670
10857
  if (input.approved_by !== undefined) {
10671
- 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 });
10672
10859
  }
10673
10860
  const updatePayload = taskEventData(updatedTask);
10674
10861
  dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
10675
- emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
10676
- emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
10862
+ emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload, databasePath });
10863
+ emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask, databasePath });
10677
10864
  return updatedTask;
10678
10865
  }
10679
10866
  function deleteTask(id, db) {
10680
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);
10681
10877
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
10682
10878
  return result.changes > 0;
10683
10879
  }
@@ -10685,11 +10881,13 @@ var init_task_crud = __esm(() => {
10685
10881
  init_types();
10686
10882
  init_database();
10687
10883
  init_completion_guard();
10884
+ init_event_emission_safety();
10688
10885
  init_event_hooks();
10689
10886
  init_shared_events();
10690
10887
  init_audit();
10691
10888
  init_webhooks();
10692
10889
  init_checklists();
10890
+ init_storage_tombstones();
10693
10891
  });
10694
10892
 
10695
10893
  // src/lib/recurrence.ts
@@ -10838,8 +11036,9 @@ function resolveTemplateId(id, d) {
10838
11036
  function createTemplate(input, db) {
10839
11037
  const d = db || getDatabase();
10840
11038
  const id = uuid();
10841
- d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
10842
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10843
11042
  id,
10844
11043
  input.name,
10845
11044
  input.title_pattern,
@@ -10850,7 +11049,8 @@ function createTemplate(input, db) {
10850
11049
  input.project_id || null,
10851
11050
  input.plan_id || null,
10852
11051
  JSON.stringify(input.metadata || {}),
10853
- now()
11052
+ now(),
11053
+ machineId
10854
11054
  ]);
10855
11055
  if (input.tasks && input.tasks.length > 0) {
10856
11056
  addTemplateTasks(id, input.tasks, d);
@@ -10874,6 +11074,15 @@ function deleteTemplate(id, db) {
10874
11074
  const resolved = resolveTemplateId(id, d);
10875
11075
  if (!resolved)
10876
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);
10877
11086
  return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
10878
11087
  }
10879
11088
  function updateTemplate(id, updates, db) {
@@ -11243,6 +11452,7 @@ function previewTemplate(templateId, variables, db) {
11243
11452
  var init_templates = __esm(() => {
11244
11453
  init_database();
11245
11454
  init_tasks();
11455
+ init_storage_tombstones();
11246
11456
  });
11247
11457
 
11248
11458
  // src/db/task-graph.ts
@@ -11409,6 +11619,7 @@ function getBlockingDeps(id, db) {
11409
11619
  }
11410
11620
  function startTask(id, agentId, db) {
11411
11621
  const d = db || getDatabase();
11622
+ const databasePath = databasePathFromDatabase(d);
11412
11623
  const task = getTask(id, d);
11413
11624
  if (!task)
11414
11625
  throw new TaskNotFoundError(id);
@@ -11423,7 +11634,8 @@ function startTask(id, agentId, db) {
11423
11634
  agent_id: agentId,
11424
11635
  title: task.title,
11425
11636
  blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
11426
- }
11637
+ },
11638
+ databasePath
11427
11639
  });
11428
11640
  throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
11429
11641
  }
@@ -11445,12 +11657,13 @@ function startTask(id, agentId, db) {
11445
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 };
11446
11658
  const payload = taskEventData(startedTask, { agent_id: agentId });
11447
11659
  dispatchWebhook2("task.started", payload, d).catch(() => {});
11448
- emitLocalEventHooksQuiet({ type: "task.started", payload });
11449
- 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 });
11450
11662
  return startedTask;
11451
11663
  }
11452
11664
  function completeTask(id, agentId, db, options) {
11453
11665
  const d = db || getDatabase();
11666
+ const databasePath = databasePathFromDatabase(d);
11454
11667
  const task = getTask(id, d);
11455
11668
  if (!task)
11456
11669
  throw new TaskNotFoundError(id);
@@ -11496,8 +11709,8 @@ function completeTask(id, agentId, db, options) {
11496
11709
  };
11497
11710
  const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
11498
11711
  dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
11499
- emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
11500
- 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 });
11501
11714
  let spawnedTask = null;
11502
11715
  if (task.recurrence_rule && !options?.skip_recurrence) {
11503
11716
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -11541,9 +11754,9 @@ function completeTask(id, agentId, db, options) {
11541
11754
  const depTask = getTask(dep.id, d);
11542
11755
  const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
11543
11756
  dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
11544
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
11757
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload, databasePath });
11545
11758
  if (depTask)
11546
- emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
11759
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
11547
11760
  }
11548
11761
  }
11549
11762
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -11713,6 +11926,7 @@ function getTasksChangedSince(since, filters, db) {
11713
11926
  }
11714
11927
  function failTask(id, agentId, reason, options, db) {
11715
11928
  const d = db || getDatabase();
11929
+ const databasePath = databasePathFromDatabase(d);
11716
11930
  const task = getTask(id, d);
11717
11931
  if (!task)
11718
11932
  throw new TaskNotFoundError(id);
@@ -11741,8 +11955,8 @@ function failTask(id, agentId, reason, options, db) {
11741
11955
  logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
11742
11956
  const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
11743
11957
  dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
11744
- emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
11745
- 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 });
11746
11960
  let retryTask;
11747
11961
  if (options?.retry) {
11748
11962
  const retryCount = (task.retry_count || 0) + 1;
@@ -11802,6 +12016,7 @@ function getStaleTasks(staleQuery = 30, filters, db) {
11802
12016
  }
11803
12017
  function stealTask(agentId, opts, db) {
11804
12018
  const d = db || getDatabase();
12019
+ const databasePath = databasePathFromDatabase(d);
11805
12020
  const staleMinutes = opts?.stale_minutes ?? 30;
11806
12021
  const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
11807
12022
  if (staleTasks.length === 0)
@@ -11820,8 +12035,8 @@ function stealTask(agentId, opts, db) {
11820
12035
  const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
11821
12036
  const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
11822
12037
  dispatchWebhook2("task.assigned", payload, d).catch(() => {});
11823
- emitLocalEventHooksQuiet({ type: "task.assigned", payload });
11824
- 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 });
11825
12040
  return stolenTask;
11826
12041
  }
11827
12042
  function claimOrSteal(agentId, filters, db) {
@@ -11869,6 +12084,7 @@ var init_task_lifecycle = __esm(() => {
11869
12084
  init_types();
11870
12085
  init_database();
11871
12086
  init_completion_guard();
12087
+ init_event_emission_safety();
11872
12088
  init_event_hooks();
11873
12089
  init_shared_events();
11874
12090
  init_audit();
@@ -12576,8 +12792,9 @@ function createPlan(input, db) {
12576
12792
  const d = db || getDatabase();
12577
12793
  const id = uuid();
12578
12794
  const timestamp = now();
12579
- d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
12580
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
12581
12798
  id,
12582
12799
  input.project_id || null,
12583
12800
  input.task_list_id || null,
@@ -12586,7 +12803,8 @@ function createPlan(input, db) {
12586
12803
  input.description || null,
12587
12804
  input.status || "active",
12588
12805
  timestamp,
12589
- timestamp
12806
+ timestamp,
12807
+ machineId
12590
12808
  ]);
12591
12809
  return getPlan(id, d);
12592
12810
  }
@@ -12634,19 +12852,30 @@ function updatePlan(id, input, db) {
12634
12852
  const updated = getPlan(id, d);
12635
12853
  emitLocalEventHooksQuiet({
12636
12854
  type: "plan.updated",
12637
- 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)
12638
12857
  });
12639
12858
  return updated;
12640
12859
  }
12641
12860
  function deletePlan(id, db) {
12642
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);
12643
12870
  const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
12644
12871
  return result.changes > 0;
12645
12872
  }
12646
12873
  var init_plans = __esm(() => {
12647
12874
  init_types();
12875
+ init_event_emission_safety();
12648
12876
  init_event_hooks();
12649
12877
  init_database();
12878
+ init_storage_tombstones();
12650
12879
  });
12651
12880
 
12652
12881
  // src/db/boards.ts
@@ -13053,20 +13282,20 @@ var init_boards = __esm(() => {
13053
13282
  // src/lib/artifact-store.ts
13054
13283
  import { createHash as createHash2 } from "crypto";
13055
13284
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
13056
- import { basename, dirname as dirname4, join as join5, resolve as resolve6 } from "path";
13057
- 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";
13058
13287
  function isInMemoryDb2(path) {
13059
13288
  return path === ":memory:" || path.startsWith("file::memory:");
13060
13289
  }
13061
13290
  function artifactStoreRoot() {
13062
13291
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
13063
- return resolve6(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
13292
+ return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
13064
13293
  if (process.env["TODOS_ARTIFACTS_DIR"])
13065
- return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
13294
+ return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
13066
13295
  const dbPath = getDatabasePath();
13067
13296
  if (isInMemoryDb2(dbPath))
13068
- return join5(tmpdir(), "hasna-todos-artifacts");
13069
- return join5(dirname4(resolve6(dbPath)), "artifacts");
13297
+ return join5(tmpdir2(), "hasna-todos-artifacts");
13298
+ return join5(dirname4(resolve7(dbPath)), "artifacts");
13070
13299
  }
13071
13300
  function artifactStorePath(relativePath) {
13072
13301
  const normalized = relativePath.replace(/\\/g, "/");
@@ -13113,7 +13342,7 @@ function mediaTypeFor(path, textLike) {
13113
13342
  return "application/octet-stream";
13114
13343
  }
13115
13344
  function storeArtifactContent(input) {
13116
- const sourcePath = resolve6(input.path);
13345
+ const sourcePath = resolve7(input.path);
13117
13346
  if (!existsSync6(sourcePath))
13118
13347
  return null;
13119
13348
  const sourceStat = statSync2(sourcePath);
@@ -13810,7 +14039,11 @@ function startTaskRun(input, db) {
13810
14039
  }, d);
13811
14040
  }
13812
14041
  const run = getTaskRun(id, d);
13813
- 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
+ });
13814
14047
  return run;
13815
14048
  }
13816
14049
  function beginTaskRunTransaction(input, db) {
@@ -14079,7 +14312,8 @@ function finishTaskRun(input, db) {
14079
14312
  const updated = getTaskRun(run.id, d);
14080
14313
  emitLocalEventHooksQuiet({
14081
14314
  type: `run.${input.status}`,
14082
- 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)
14083
14317
  });
14084
14318
  return updated;
14085
14319
  }
@@ -14163,6 +14397,7 @@ function getTaskRunLedger(runId, db) {
14163
14397
  var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
14164
14398
  var init_task_runs = __esm(() => {
14165
14399
  init_artifact_store();
14400
+ init_event_emission_safety();
14166
14401
  init_event_hooks();
14167
14402
  init_redaction();
14168
14403
  init_types();
@@ -15931,7 +16166,7 @@ var init_task_crud2 = __esm(() => {
15931
16166
 
15932
16167
  // src/lib/project-bootstrap.ts
15933
16168
  import { existsSync as existsSync7, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
15934
- 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";
15935
16170
  function safeStat(path) {
15936
16171
  try {
15937
16172
  return statSync3(path);
@@ -15940,7 +16175,7 @@ function safeStat(path) {
15940
16175
  }
15941
16176
  }
15942
16177
  function canonicalPath(input) {
15943
- const resolved = resolve7(input);
16178
+ const resolved = resolve8(input);
15944
16179
  const stats = safeStat(resolved);
15945
16180
  if (stats?.isFile())
15946
16181
  return dirname5(resolved);
@@ -15949,7 +16184,7 @@ function canonicalPath(input) {
15949
16184
  function findUp(start, marker) {
15950
16185
  let current = canonicalPath(start);
15951
16186
  while (true) {
15952
- if (existsSync7(resolve7(current, marker)))
16187
+ if (existsSync7(resolve8(current, marker)))
15953
16188
  return current;
15954
16189
  const parent = dirname5(current);
15955
16190
  if (parent === current)
@@ -15960,7 +16195,7 @@ function findUp(start, marker) {
15960
16195
  function readPackageJson(path) {
15961
16196
  if (!path)
15962
16197
  return null;
15963
- const file = resolve7(path, "package.json");
16198
+ const file = resolve8(path, "package.json");
15964
16199
  if (!existsSync7(file))
15965
16200
  return null;
15966
16201
  try {
@@ -15983,7 +16218,7 @@ function workspaceMarker(root, rootPackage) {
15983
16218
  if (rootPackage?.workspaces)
15984
16219
  markers.push("package.json#workspaces");
15985
16220
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
15986
- if (existsSync7(resolve7(root, marker)))
16221
+ if (existsSync7(resolve8(root, marker)))
15987
16222
  markers.push(marker);
15988
16223
  }
15989
16224
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -16527,7 +16762,7 @@ var init_retention_cleanup = __esm(() => {
16527
16762
 
16528
16763
  // src/lib/mention-resolver.ts
16529
16764
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
16530
- 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";
16531
16766
  function blankResolution(parsed) {
16532
16767
  return {
16533
16768
  input: parsed.input,
@@ -16550,11 +16785,11 @@ function backlink(kind, key, label, target = key) {
16550
16785
  return { kind, key, label, target };
16551
16786
  }
16552
16787
  function normalizeWorkspace(workspace) {
16553
- return resolve8(workspace || process.cwd());
16788
+ return resolve9(workspace || process.cwd());
16554
16789
  }
16555
16790
  function isInside(root, absolutePath) {
16556
16791
  const rel = relative3(root, absolutePath);
16557
- return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep}`) && !isAbsolute(rel);
16792
+ return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep2}`) && !isAbsolute(rel);
16558
16793
  }
16559
16794
  function normalizeRelativePath(value) {
16560
16795
  const normalized = value.trim().replace(/^\.?\//, "").replace(/\\/g, "/");
@@ -16618,7 +16853,7 @@ function resolveFile(parsed, workspace) {
16618
16853
  resolution.warnings.push("path is empty or escapes the workspace");
16619
16854
  return resolution;
16620
16855
  }
16621
- const absolutePath = resolve8(workspace, relPath);
16856
+ const absolutePath = resolve9(workspace, relPath);
16622
16857
  if (!isInside(workspace, absolutePath)) {
16623
16858
  resolution.path = relPath;
16624
16859
  resolution.warnings.push("path escapes the workspace");
@@ -16963,9 +17198,9 @@ var init_mention_resolver = __esm(() => {
16963
17198
  });
16964
17199
 
16965
17200
  // src/lib/policy-packs.ts
16966
- import { relative as relative4, resolve as resolve9 } from "path";
17201
+ import { relative as relative4, resolve as resolve10 } from "path";
16967
17202
  function normalizePath3(path) {
16968
- return resolve9(path);
17203
+ return resolve10(path);
16969
17204
  }
16970
17205
  function unique4(values) {
16971
17206
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -17020,7 +17255,7 @@ function commandMatches(commands, pattern) {
17020
17255
  }
17021
17256
  function pathMatches(paths, pattern, root) {
17022
17257
  return paths.filter((path) => {
17023
- const candidate = path.startsWith("/") ? path : resolve9(root, path);
17258
+ const candidate = path.startsWith("/") ? path : resolve10(root, path);
17024
17259
  if (!isPathInside3(root, candidate))
17025
17260
  return matchesPattern3(path, pattern);
17026
17261
  return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
@@ -17367,7 +17602,8 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
17367
17602
  if (action === "approved" || action === "rejected" || action === "expired") {
17368
17603
  emitLocalEventHooksQuiet({
17369
17604
  type: "approval.decided",
17370
- 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)
17371
17607
  });
17372
17608
  }
17373
17609
  }
@@ -17507,6 +17743,7 @@ var init_approval_gates = __esm(() => {
17507
17743
  init_task_runs();
17508
17744
  init_tasks();
17509
17745
  init_types();
17746
+ init_event_emission_safety();
17510
17747
  init_event_hooks();
17511
17748
  });
17512
17749
 
@@ -20073,7 +20310,7 @@ function canonicalize(value) {
20073
20310
  function hash(value) {
20074
20311
  return createHash4("sha256").update(value).digest("hex");
20075
20312
  }
20076
- function parsePayload(value) {
20313
+ function parsePayload2(value) {
20077
20314
  if (!value)
20078
20315
  return {};
20079
20316
  try {
@@ -20172,7 +20409,7 @@ function taskScopedRows(db, scope) {
20172
20409
  FROM handoffs h
20173
20410
  WHERE h.project_id IS NULL OR h.project_id = COALESCE(?, h.project_id)
20174
20411
  `).all(scope.project_id ?? null).filter((row) => {
20175
- const payload = parsePayload(row.payload_json);
20412
+ const payload = parsePayload2(row.payload_json);
20176
20413
  const taskRefs = parseStringArray(payload["task_ids"]);
20177
20414
  const runRefs = parseStringArray(payload["run_ids"]);
20178
20415
  if (scope.project_id && row.project_id !== scope.project_id)
@@ -20203,7 +20440,7 @@ function toLedgerEntries(rows) {
20203
20440
  });
20204
20441
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
20205
20442
  return ordered.map((row, index) => {
20206
- const payload = parsePayload(row.payload_json);
20443
+ const payload = parsePayload2(row.payload_json);
20207
20444
  const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
20208
20445
  const chainHash = hash(`${previous}
20209
20446
  ${payloadHash}`);
@@ -20345,7 +20582,7 @@ var init_audit_ledger = __esm(() => {
20345
20582
 
20346
20583
  // src/lib/release-compatibility.ts
20347
20584
  import { readFileSync as readFileSync5 } from "fs";
20348
- import { join as join7, resolve as resolve10 } from "path";
20585
+ import { join as join7, resolve as resolve11 } from "path";
20349
20586
  import { Database as Database2 } from "bun:sqlite";
20350
20587
  function pass(id, message, details) {
20351
20588
  return { id, status: "passed", message, details };
@@ -20453,7 +20690,7 @@ function checkChangelog() {
20453
20690
  ];
20454
20691
  }
20455
20692
  function createReleaseCompatibilityReport(options = {}) {
20456
- const root = resolve10(options.root ?? process.cwd());
20693
+ const root = resolve11(options.root ?? process.cwd());
20457
20694
  const packageJson = readPackageJson2(root);
20458
20695
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
20459
20696
  const checks = [
@@ -26833,7 +27070,7 @@ function classifyLog(text) {
26833
27070
  async function sleep2(ms) {
26834
27071
  if (ms <= 0)
26835
27072
  return;
26836
- await new Promise((resolve11) => setTimeout(resolve11, ms));
27073
+ await new Promise((resolve12) => setTimeout(resolve12, ms));
26837
27074
  }
26838
27075
  async function runCommandProvider(provider, input) {
26839
27076
  const commandTemplate = input.command || provider.command;
@@ -29586,7 +29823,7 @@ var init_local_bridge = __esm(() => {
29586
29823
  // src/lib/local-backups.ts
29587
29824
  import { createHash as createHash5 } from "crypto";
29588
29825
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
29589
- import { dirname as dirname8, resolve as resolve11 } from "path";
29826
+ import { dirname as dirname8, resolve as resolve12 } from "path";
29590
29827
  import { mkdirSync as mkdirSync6 } from "fs";
29591
29828
  function stableJson(value) {
29592
29829
  if (value === null || typeof value !== "object")
@@ -29688,14 +29925,14 @@ function createLocalBackup(options = {}, db) {
29688
29925
  return backup;
29689
29926
  }
29690
29927
  function writeLocalBackupFile(backup, outputPath) {
29691
- const path = resolve11(outputPath);
29928
+ const path = resolve12(outputPath);
29692
29929
  mkdirSync6(dirname8(path), { recursive: true });
29693
29930
  writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
29694
29931
  `);
29695
29932
  return path;
29696
29933
  }
29697
29934
  function readLocalBackupFile(path) {
29698
- return JSON.parse(readFileSync8(resolve11(path), "utf-8"));
29935
+ return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
29699
29936
  }
29700
29937
  function verifyLocalBackup(value, options = {}, db) {
29701
29938
  const verifiedAt = options.verified_at ?? now();
@@ -31145,7 +31382,7 @@ __export(exports_local_extensions, {
31145
31382
  });
31146
31383
  import { createHash as createHash8, createVerify } from "crypto";
31147
31384
  import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
31148
- 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";
31149
31386
  function isObject2(value) {
31150
31387
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
31151
31388
  }
@@ -31403,7 +31640,7 @@ function verifyExtensionSignature(input) {
31403
31640
  return verifier.verify(input.public_key, decodeSignature(input.signature));
31404
31641
  }
31405
31642
  function inspectExtensionSource(source3) {
31406
- const resolved = resolve12(source3);
31643
+ const resolved = resolve13(source3);
31407
31644
  if (!existsSync13(resolved))
31408
31645
  throw new Error(`extension source not found: ${source3}`);
31409
31646
  const stat = statSync6(resolved);
@@ -31501,7 +31738,7 @@ function testExtensionCompatibility(sourceOrManifest) {
31501
31738
  function projectExtensionSources(projectPath) {
31502
31739
  if (!projectPath)
31503
31740
  return [];
31504
- const root = resolve12(projectPath);
31741
+ const root = resolve13(projectPath);
31505
31742
  const candidates = [
31506
31743
  join10(root, "todos.extension.json"),
31507
31744
  join10(root, ".todos", "todos.extension.json")
@@ -31520,7 +31757,7 @@ function projectExtensionSources(projectPath) {
31520
31757
  }
31521
31758
  function discoverLocalExtensions(options = {}) {
31522
31759
  const config = loadConfig();
31523
- const projectPath = options.project_path ? resolve12(options.project_path) : null;
31760
+ const projectPath = options.project_path ? resolve13(options.project_path) : null;
31524
31761
  const configuredSources = [
31525
31762
  ...config.extension_sources || [],
31526
31763
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -31528,7 +31765,7 @@ function discoverLocalExtensions(options = {}) {
31528
31765
  const sources = Array.from(new Set([
31529
31766
  ...configuredSources,
31530
31767
  ...projectExtensionSources(projectPath || undefined)
31531
- ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve12(projectPath, source3) : resolve12(source3));
31768
+ ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
31532
31769
  const warnings = [];
31533
31770
  const discovered = [];
31534
31771
  for (const source3 of sources) {
@@ -34478,7 +34715,8 @@ function writeQueue(task2, queue, actor, action, db) {
34478
34715
  logTaskChange(task2.id, `review_queue.${action}`, "review_queue", null, JSON.stringify(queue), actor, d);
34479
34716
  emitLocalEventHooksQuiet({
34480
34717
  type: `review.${action}`,
34481
- 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)
34482
34720
  });
34483
34721
  return itemFromTask(taskOrThrow2(task2.id, d), queue, d);
34484
34722
  }
@@ -34782,6 +35020,7 @@ var init_review_queues = __esm(() => {
34782
35020
  init_database();
34783
35021
  init_tasks();
34784
35022
  init_config();
35023
+ init_event_emission_safety();
34785
35024
  init_event_hooks();
34786
35025
  init_task_contracts();
34787
35026
  STATES = new Set(["requested", "claimed", "approved", "changes_requested", "returned", "reopened"]);
@@ -35937,7 +36176,7 @@ __export(exports_extract, {
35937
36176
  });
35938
36177
  import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
35939
36178
  import { createHash as createHash10 } from "crypto";
35940
- 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";
35941
36180
  function stableHash(value) {
35942
36181
  return createHash10("sha256").update(value).digest("hex");
35943
36182
  }
@@ -35945,7 +36184,7 @@ function normalizePathForMatch(value) {
35945
36184
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
35946
36185
  }
35947
36186
  function readGitignorePatterns(basePath) {
35948
- const root = statSync7(basePath).isFile() ? resolve13(basePath, "..") : basePath;
36187
+ const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
35949
36188
  const gitignorePath = join11(root, ".gitignore");
35950
36189
  if (!existsSync14(gitignorePath))
35951
36190
  return [];
@@ -36081,7 +36320,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
36081
36320
  return files.sort();
36082
36321
  }
36083
36322
  function buildCodebaseIndex(options) {
36084
- const basePath = resolve13(options.path);
36323
+ const basePath = resolve14(options.path);
36085
36324
  const tags = options.patterns || [...EXTRACT_TAGS];
36086
36325
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
36087
36326
  const excludes = options.exclude || [];
@@ -36092,7 +36331,7 @@ function buildCodebaseIndex(options) {
36092
36331
  const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
36093
36332
  try {
36094
36333
  const source3 = readFileSync10(fullPath, "utf-8");
36095
- const relPath = statSync7(basePath).isFile() ? relative5(resolve13(basePath, ".."), fullPath) : file;
36334
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
36096
36335
  indexed.push({
36097
36336
  file: relPath,
36098
36337
  checksum: stableHash(source3).slice(0, 24),
@@ -36112,7 +36351,7 @@ function buildCodebaseIndex(options) {
36112
36351
  };
36113
36352
  }
36114
36353
  function extractTodos(options, db) {
36115
- const basePath = resolve13(options.path);
36354
+ const basePath = resolve14(options.path);
36116
36355
  const tags = options.patterns || [...EXTRACT_TAGS];
36117
36356
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
36118
36357
  const excludes = options.exclude || [];
@@ -36123,7 +36362,7 @@ function extractTodos(options, db) {
36123
36362
  const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
36124
36363
  try {
36125
36364
  const source3 = readFileSync10(fullPath, "utf-8");
36126
- const relPath = statSync7(basePath).isFile() ? relative5(resolve13(basePath, ".."), fullPath) : file;
36365
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
36127
36366
  const comments = extractFromSource(source3, relPath, tags);
36128
36367
  allComments.push(...comments);
36129
36368
  } catch {}
@@ -36217,7 +36456,7 @@ async function watchSourceTodos(options, onRun) {
36217
36456
  const interval = Math.max(100, options.interval_ms || 2000);
36218
36457
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
36219
36458
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
36220
- const root = resolve13(options.path);
36459
+ const root = resolve14(options.path);
36221
36460
  const runs = [];
36222
36461
  let previous = new Map;
36223
36462
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -37600,8 +37839,8 @@ __export(exports_environment_snapshots, {
37600
37839
  import { createHash as createHash11 } from "crypto";
37601
37840
  import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
37602
37841
  import { hostname as hostname2, platform, arch } from "os";
37603
- import { dirname as dirname9, join as join13, resolve as resolve14 } from "path";
37604
- 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";
37605
37844
  function sha2566(value) {
37606
37845
  return createHash11("sha256").update(value).digest("hex");
37607
37846
  }
@@ -37714,15 +37953,15 @@ function commandEnv(env, includeValues) {
37714
37953
  function defaultSnapshotDir() {
37715
37954
  const dbPath = getDatabasePath();
37716
37955
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
37717
- return join13(tmpdir2(), "hasna-todos", "environment-snapshots");
37718
- return join13(dirname9(resolve14(dbPath)), "environment-snapshots");
37956
+ return join13(tmpdir3(), "hasna-todos", "environment-snapshots");
37957
+ return join13(dirname9(resolve15(dbPath)), "environment-snapshots");
37719
37958
  }
37720
37959
  function snapshotWithId(snapshot) {
37721
37960
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
37722
37961
  return { id: `env_${digest}`, ...snapshot };
37723
37962
  }
37724
37963
  function captureEnvironmentSnapshot(input = {}) {
37725
- const root = resolve14(input.root || process.cwd());
37964
+ const root = resolve15(input.root || process.cwd());
37726
37965
  const env = input.env || process.env;
37727
37966
  const warnings = [];
37728
37967
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -37762,13 +38001,13 @@ function captureEnvironmentSnapshot(input = {}) {
37762
38001
  });
37763
38002
  }
37764
38003
  function writeEnvironmentSnapshot(snapshot, outputPath) {
37765
- const path = outputPath ? resolve14(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
38004
+ const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
37766
38005
  ensureDir2(dirname9(path));
37767
38006
  writeJsonFile(path, snapshot);
37768
38007
  return path;
37769
38008
  }
37770
38009
  function readEnvironmentSnapshot(path) {
37771
- const snapshot = readJsonFile(resolve14(path));
38010
+ const snapshot = readJsonFile(resolve15(path));
37772
38011
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
37773
38012
  throw new Error(`Invalid environment snapshot: ${path}`);
37774
38013
  }
@@ -38314,7 +38553,7 @@ var init_headless_boundaries = __esm(() => {
38314
38553
  });
38315
38554
 
38316
38555
  // src/server/routes.ts
38317
- 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";
38318
38557
  function parseFieldsParam(url) {
38319
38558
  const fieldsParam = url.searchParams.get("fields");
38320
38559
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -39052,9 +39291,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
39052
39291
  return null;
39053
39292
  if (path !== "/") {
39054
39293
  const filePath = join14(ctx.dashboardDir, path);
39055
- const resolvedFile = resolve15(filePath);
39056
- const resolvedBase = resolve15(ctx.dashboardDir);
39057
- 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) {
39058
39297
  return json2({ error: "Forbidden" }, 403);
39059
39298
  }
39060
39299
  const res2 = serveStaticFile2(filePath);