@hasna/todos 0.13.7 → 0.13.8

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.
package/dist/mcp/index.js CHANGED
@@ -12080,6 +12080,50 @@ var init_checklists = __esm(() => {
12080
12080
  init_database();
12081
12081
  });
12082
12082
 
12083
+ // src/lib/creator-identity.ts
12084
+ import { existsSync as existsSync6, rmSync } from "fs";
12085
+ import { join as join5 } from "path";
12086
+ function identityFilePath() {
12087
+ return join5(getTodosGlobalDir(), "identity.json");
12088
+ }
12089
+ function readPersistedIdentity() {
12090
+ const path = identityFilePath();
12091
+ if (!existsSync6(path))
12092
+ return null;
12093
+ const parsed = readJsonFile(path);
12094
+ if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
12095
+ return null;
12096
+ return parsed;
12097
+ }
12098
+ function canonicalAgentRef(value) {
12099
+ return value.trim().toLowerCase();
12100
+ }
12101
+ function isProcessBoundSource(source) {
12102
+ return source === "explicit" || source === "env";
12103
+ }
12104
+ function resolveWritableIdentity(explicit) {
12105
+ const resolved = resolveCreatorIdentity(explicit);
12106
+ if (!isProcessBoundSource(resolved.source))
12107
+ return { agent_id: null, source: "none" };
12108
+ return resolved;
12109
+ }
12110
+ function resolveCreatorIdentity(explicit) {
12111
+ const fromExplicit = explicit?.trim();
12112
+ if (fromExplicit)
12113
+ return { agent_id: canonicalAgentRef(fromExplicit), source: "explicit" };
12114
+ const fromEnv = (process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"] || "").trim();
12115
+ if (fromEnv)
12116
+ return { agent_id: canonicalAgentRef(fromEnv), source: "env" };
12117
+ const persisted = readPersistedIdentity();
12118
+ if (persisted) {
12119
+ return { agent_id: canonicalAgentRef(persisted.agent_name || persisted.agent_id), source: "persisted" };
12120
+ }
12121
+ return { agent_id: null, source: "none" };
12122
+ }
12123
+ var init_creator_identity = __esm(() => {
12124
+ init_sync_utils();
12125
+ });
12126
+
12083
12127
  // src/lib/recurrence.ts
12084
12128
  function parseRecurrenceRule(rule) {
12085
12129
  const normalized = rule.trim().toLowerCase();
@@ -12806,6 +12850,11 @@ __export(exports_task_lifecycle, {
12806
12850
  claimOrSteal: () => claimOrSteal,
12807
12851
  claimNextTask: () => claimNextTask
12808
12852
  });
12853
+ function sameHolder(stored, incoming) {
12854
+ if (!stored || !incoming)
12855
+ return false;
12856
+ return canonicalAgentRef(stored) === canonicalAgentRef(incoming);
12857
+ }
12809
12858
  function lockExpiresAt(lockedAt) {
12810
12859
  if (!lockedAt)
12811
12860
  return null;
@@ -12856,13 +12905,13 @@ function startTask(id, agentId, db) {
12856
12905
  const cutoff = lockExpiryCutoff();
12857
12906
  const timestamp2 = now();
12858
12907
  const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
12859
- WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
12908
+ WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR LOWER(TRIM(locked_by)) = LOWER(TRIM(?)) OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
12860
12909
  if (result.changes === 0) {
12861
12910
  const current = getTask(id, d);
12862
12911
  if (!current)
12863
12912
  throw new TaskNotFoundError(id);
12864
12913
  assertStartable(current, agentId);
12865
- if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
12914
+ if (current.locked_by && !sameHolder(current.locked_by, agentId) && !isLockExpired(current.locked_at)) {
12866
12915
  throw new LockError(id, current.locked_by);
12867
12916
  }
12868
12917
  throw new Error(`Task ${id} could not be started because it changed during claim`);
@@ -12887,7 +12936,7 @@ function completeTask(id, agentId, db, options) {
12887
12936
  if (task.status === "cancelled") {
12888
12937
  throw new Error(`Task ${id} is cancelled and cannot be completed`);
12889
12938
  }
12890
- if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
12939
+ if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
12891
12940
  throw new LockError(id, task.locked_by);
12892
12941
  }
12893
12942
  checkCompletionGuard(task, agentId || null, d);
@@ -13009,16 +13058,16 @@ function lockTask(id, agentId, db) {
13009
13058
  error: `Task is ${task.status} and cannot be locked`
13010
13059
  };
13011
13060
  }
13012
- if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
13061
+ if (sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
13013
13062
  const timestamp3 = now();
13014
- d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [timestamp3, timestamp3, id, agentId]);
13063
+ d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND LOWER(TRIM(locked_by)) = LOWER(TRIM(?))`, [timestamp3, timestamp3, id, agentId]);
13015
13064
  logTaskChange(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
13016
13065
  return { success: true, locked_by: agentId, locked_at: timestamp3, expires_at: lockExpiresAt(timestamp3) };
13017
13066
  }
13018
13067
  const cutoff = lockExpiryCutoff();
13019
13068
  const timestamp2 = now();
13020
13069
  const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
13021
- WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
13070
+ WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR LOWER(TRIM(locked_by)) = LOWER(TRIM(?)) OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
13022
13071
  if (result.changes === 0) {
13023
13072
  const current = getTask(id, d);
13024
13073
  if (!current)
@@ -13050,7 +13099,7 @@ function unlockTask(id, agentId, db) {
13050
13099
  const task = getTask(id, d);
13051
13100
  if (!task)
13052
13101
  throw new TaskNotFoundError(id);
13053
- if (agentId && task.locked_by && task.locked_by !== agentId) {
13102
+ if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId)) {
13054
13103
  throw new LockError(id, task.locked_by);
13055
13104
  }
13056
13105
  const timestamp2 = now();
@@ -13343,6 +13392,7 @@ var MAX_SPAWN_DEPTH = 10;
13343
13392
  var init_task_lifecycle = __esm(() => {
13344
13393
  init_types();
13345
13394
  init_database();
13395
+ init_creator_identity();
13346
13396
  init_completion_guard();
13347
13397
  init_event_emission_safety();
13348
13398
  init_event_hooks();
@@ -15254,8 +15304,8 @@ var init_boards = __esm(() => {
15254
15304
 
15255
15305
  // src/lib/artifact-store.ts
15256
15306
  import { createHash as createHash2 } from "crypto";
15257
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
15258
- import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
15307
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
15308
+ import { basename, dirname as dirname4, join as join6, resolve as resolve7 } from "path";
15259
15309
  import { tmpdir as tmpdir2 } from "os";
15260
15310
  function isInMemoryDb2(path) {
15261
15311
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -15267,15 +15317,15 @@ function artifactStoreRoot() {
15267
15317
  return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
15268
15318
  const dbPath = getDatabasePath();
15269
15319
  if (isInMemoryDb2(dbPath))
15270
- return join5(tmpdir2(), "hasna-todos-artifacts");
15271
- return join5(dirname4(resolve7(dbPath)), "artifacts");
15320
+ return join6(tmpdir2(), "hasna-todos-artifacts");
15321
+ return join6(dirname4(resolve7(dbPath)), "artifacts");
15272
15322
  }
15273
15323
  function artifactStorePath(relativePath) {
15274
15324
  const normalized = relativePath.replace(/\\/g, "/");
15275
15325
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
15276
15326
  throw new Error("Invalid artifact store path");
15277
15327
  }
15278
- return join5(artifactStoreRoot(), normalized);
15328
+ return join6(artifactStoreRoot(), normalized);
15279
15329
  }
15280
15330
  function sha256(buffer) {
15281
15331
  return createHash2("sha256").update(buffer).digest("hex");
@@ -15316,7 +15366,7 @@ function mediaTypeFor(path, textLike) {
15316
15366
  }
15317
15367
  function storeArtifactContent(input) {
15318
15368
  const sourcePath = resolve7(input.path);
15319
- if (!existsSync6(sourcePath))
15369
+ if (!existsSync7(sourcePath))
15320
15370
  return null;
15321
15371
  const sourceStat = statSync2(sourcePath);
15322
15372
  if (!sourceStat.isFile())
@@ -15333,9 +15383,9 @@ function storeArtifactContent(input) {
15333
15383
  redactionStatus = "redacted";
15334
15384
  }
15335
15385
  const storedSha = sha256(storedBuffer);
15336
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
15386
+ const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
15337
15387
  const destination = artifactStorePath(relativePath);
15338
- if (!existsSync6(destination)) {
15388
+ if (!existsSync7(destination)) {
15339
15389
  mkdirSync4(dirname4(destination), { recursive: true });
15340
15390
  writeFileSync2(destination, storedBuffer);
15341
15391
  }
@@ -15395,7 +15445,7 @@ function verifyStoredArtifact(input) {
15395
15445
  };
15396
15446
  }
15397
15447
  const storedPath = artifactStorePath(store.relative_path);
15398
- if (!existsSync6(storedPath)) {
15448
+ if (!existsSync7(storedPath)) {
15399
15449
  return {
15400
15450
  id: input.id,
15401
15451
  path: input.path,
@@ -17869,48 +17919,98 @@ var init_token_utils = __esm(() => {
17869
17919
  };
17870
17920
  });
17871
17921
 
17872
- // src/lib/creator-identity.ts
17873
- import { existsSync as existsSync7, rmSync as rmSync2 } from "fs";
17874
- import { join as join6 } from "path";
17875
- function identityFilePath() {
17876
- return join6(getTodosGlobalDir(), "identity.json");
17922
+ // src/lib/assignee-validation.ts
17923
+ import { readFileSync as readFileSync3 } from "fs";
17924
+ import { homedir as homedir2 } from "os";
17925
+ import { join as join7 } from "path";
17926
+ function defaultSeatRosterPath() {
17927
+ return process.env["TODOS_SEAT_ROSTER_PATH"] || join7(homedir2(), ".hasna", "identities", "hasna-seats.roster.json");
17877
17928
  }
17878
- function readPersistedIdentity() {
17879
- const path = identityFilePath();
17880
- if (!existsSync7(path))
17881
- return null;
17882
- const parsed = readJsonFile(path);
17883
- if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
17884
- return null;
17885
- return parsed;
17886
- }
17887
- function canonicalAgentRef(value) {
17888
- return value.trim().toLowerCase();
17889
- }
17890
- function isProcessBoundSource(source) {
17891
- return source === "explicit" || source === "env";
17929
+ function loadSeatSlugs(path = defaultSeatRosterPath()) {
17930
+ try {
17931
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
17932
+ const slugs = new Set;
17933
+ for (const agent of parsed.agents ?? []) {
17934
+ if (typeof agent?.slug === "string" && agent.slug.trim()) {
17935
+ slugs.add(normalizeAgentNameInput(agent.slug));
17936
+ }
17937
+ }
17938
+ return slugs;
17939
+ } catch {
17940
+ return new Set;
17941
+ }
17892
17942
  }
17893
- function resolveWritableIdentity(explicit) {
17894
- const resolved = resolveCreatorIdentity(explicit);
17895
- if (!isProcessBoundSource(resolved.source))
17896
- return { agent_id: null, source: "none" };
17897
- return resolved;
17943
+ function validateAssignee(input, ctx) {
17944
+ const raw = input.trim();
17945
+ const normalized = normalizeAgentNameInput(raw);
17946
+ if (!normalized) {
17947
+ return {
17948
+ ok: true,
17949
+ assignee: raw,
17950
+ isSeat: false,
17951
+ warning: "Assignee is empty. Use --unassigned to file this deliberately with no owner."
17952
+ };
17953
+ }
17954
+ const byId = ctx.agents.find((a) => a.id.toLowerCase() === normalized);
17955
+ const byName = ctx.agents.filter((a) => normalizeAgentNameInput(a.name) === normalized);
17956
+ const effectiveName = byId ? normalizeAgentNameInput(byId.name) : normalized;
17957
+ if (ctx.seats.has(effectiveName)) {
17958
+ if (ctx.allowSeat) {
17959
+ return { ok: true, assignee: byId ? byId.name : raw, agentId: byId?.id, isSeat: true };
17960
+ }
17961
+ return {
17962
+ ok: false,
17963
+ reason: "seat",
17964
+ message: `'${raw}' is a durable SEAT, and a task assigned to a seat is assigned to nobody \u2014 no session is watching that queue. ` + `Assign a specific agent, use --unassigned to file it with no owner on purpose, or pass --assign-seat if filing at the seat is what you mean.`
17965
+ };
17966
+ }
17967
+ if (byId) {
17968
+ return { ok: true, assignee: byId.name, agentId: byId.id, isSeat: false };
17969
+ }
17970
+ if (byName.length === 0) {
17971
+ return {
17972
+ ok: true,
17973
+ assignee: raw,
17974
+ isSeat: false,
17975
+ warning: `No agent named '${raw}' is registered yet. If that is a typo the task is now routed to nobody \u2014 ` + `check with 'todos agents'.`
17976
+ };
17977
+ }
17978
+ if (byName.length > 1) {
17979
+ const ids = byName.map((a) => a.id).sort();
17980
+ return {
17981
+ ok: false,
17982
+ reason: "ambiguous",
17983
+ message: `'${raw}' names ${byName.length} registered agents (${ids.join(", ")}), so assigning by name would pick one at random \u2014 ` + `which is how a task lands on a stranger. Pass the agent ID instead.`,
17984
+ candidates: byName
17985
+ };
17986
+ }
17987
+ return { ok: true, assignee: byName[0].name, agentId: byName[0].id, isSeat: false };
17898
17988
  }
17899
- function resolveCreatorIdentity(explicit) {
17900
- const fromExplicit = explicit?.trim();
17901
- if (fromExplicit)
17902
- return { agent_id: canonicalAgentRef(fromExplicit), source: "explicit" };
17903
- const fromEnv = (process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"] || "").trim();
17904
- if (fromEnv)
17905
- return { agent_id: canonicalAgentRef(fromEnv), source: "env" };
17906
- const persisted = readPersistedIdentity();
17907
- if (persisted) {
17908
- return { agent_id: canonicalAgentRef(persisted.agent_name || persisted.agent_id), source: "persisted" };
17989
+ var init_assignee_validation = () => {};
17990
+
17991
+ // src/lib/assignee-context.ts
17992
+ async function loadAssigneeContext(listAgentsFn, allowSeat, nowMs = Date.now()) {
17993
+ if (!cache || nowMs - cache.at >= ROSTER_TTL_MS) {
17994
+ let agents;
17995
+ let degraded = false;
17996
+ try {
17997
+ agents = await listAgentsFn();
17998
+ } catch {
17999
+ agents = [];
18000
+ degraded = true;
18001
+ }
18002
+ cache = {
18003
+ at: nowMs,
18004
+ agents: agents.map((a) => ({ id: a.id, name: a.name })),
18005
+ seats: loadSeatSlugs(),
18006
+ degraded
18007
+ };
17909
18008
  }
17910
- return { agent_id: null, source: "none" };
18009
+ return { agents: cache.agents, seats: cache.seats, allowSeat, degraded: cache.degraded };
17911
18010
  }
17912
- var init_creator_identity = __esm(() => {
17913
- init_sync_utils();
18011
+ var ROSTER_TTL_MS = 15000, cache;
18012
+ var init_assignee_context = __esm(() => {
18013
+ init_assignee_validation();
17914
18014
  });
17915
18015
 
17916
18016
  // src/pr-groups/types.ts
@@ -20629,6 +20729,14 @@ var init_cloud_router = __esm(() => {
20629
20729
  });
20630
20730
 
20631
20731
  // src/mcp/tools/task-crud.ts
20732
+ async function validateMcpAssignee(value, allowSeat) {
20733
+ const cloud = getTodosCloudClient();
20734
+ const ctx = await loadAssigneeContext(() => cloud ? cloudListAgents(cloud) : listAgents(), allowSeat);
20735
+ const verdict = validateAssignee(value, ctx);
20736
+ if (!verdict.ok)
20737
+ throw new Error(`Cannot assign to '${value}'. ${verdict.message}`);
20738
+ return verdict.assignee;
20739
+ }
20632
20740
  function registerTaskCrudTools(server, ctx) {
20633
20741
  const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
20634
20742
  function mutationTaskResponse(task) {
@@ -20665,6 +20773,7 @@ function registerTaskCrudTools(server, ctx) {
20665
20773
  assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
20666
20774
  created_by: exports_external.string().optional().describe("Agent who FILED this task. Defaults to the ambient agent identity (todos init / TODOS_AGENT_ID)."),
20667
20775
  unassigned: exports_external.boolean().optional().describe("Deliberately file with no assignee. Without it, the task defaults to the filer."),
20776
+ allow_seat: exports_external.boolean().optional().describe("Allow assigned_to to name a durable seat. A seat queue has no session watching it, so this must be deliberate."),
20668
20777
  depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
20669
20778
  short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
20670
20779
  tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
@@ -20675,10 +20784,11 @@ function registerTaskCrudTools(server, ctx) {
20675
20784
  retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
20676
20785
  }, async (params) => {
20677
20786
  try {
20678
- const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, created_by, unassigned, ...rest } = params;
20787
+ const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, created_by, unassigned, allow_seat, ...rest } = params;
20788
+ const requestedAssignee = assigned_to ? await validateMcpAssignee(assigned_to, Boolean(allow_seat)) : undefined;
20679
20789
  const creator = resolveCreatorIdentity(created_by);
20680
20790
  const router = resolveWritableIdentity(created_by);
20681
- const assignee = assigned_to || (unassigned ? undefined : router.agent_id || undefined);
20791
+ const assignee = requestedAssignee || (unassigned ? undefined : router.agent_id || undefined);
20682
20792
  const cloud = getTodosCloudClient();
20683
20793
  if (cloud) {
20684
20794
  const payload = { ...rest };
@@ -20747,6 +20857,7 @@ function registerTaskCrudTools(server, ctx) {
20747
20857
  project_id: exports_external.string().optional().describe("Project ID"),
20748
20858
  task_list_id: exports_external.string().optional().describe("Task list ID"),
20749
20859
  assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
20860
+ allow_seat: exports_external.boolean().optional().describe("Allow assigned_to to name a durable seat. A seat queue has no session watching it, so this must be deliberate."),
20750
20861
  tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
20751
20862
  working_dir: exports_external.string().optional().describe("Working directory associated with the task"),
20752
20863
  metadata: exports_external.record(exports_external.unknown()).optional().describe("Metadata object to shallow-merge"),
@@ -20760,7 +20871,7 @@ function registerTaskCrudTools(server, ctx) {
20760
20871
  acceptance: exports_external.unknown().optional()
20761
20872
  }, async (params) => {
20762
20873
  try {
20763
- const { assigned_to, project_id, task_list_id, metadata, expectation_id, expectation_fingerprint, evidence_paths, origin_loop_id, origin_run_id, expected, observed, acceptance, ...rest } = params;
20874
+ const { assigned_to, allow_seat: _allowSeatUpsert, project_id, task_list_id, metadata, expectation_id, expectation_fingerprint, evidence_paths, origin_loop_id, origin_run_id, expected, observed, acceptance, ...rest } = params;
20764
20875
  const mergedMetadata = { ...metadata ?? {} };
20765
20876
  if (expectation_id !== undefined)
20766
20877
  mergedMetadata["expectation_id"] = expectation_id;
@@ -20779,8 +20890,9 @@ function registerTaskCrudTools(server, ctx) {
20779
20890
  if (acceptance !== undefined)
20780
20891
  mergedMetadata["acceptance"] = acceptance;
20781
20892
  const resolved = { ...rest, metadata: mergedMetadata };
20782
- if (assigned_to)
20783
- resolved.assigned_to = resolveAssignee(assigned_to);
20893
+ if (assigned_to) {
20894
+ resolved.assigned_to = resolveAssignee(await validateMcpAssignee(assigned_to, Boolean(params.allow_seat)));
20895
+ }
20784
20896
  if (project_id)
20785
20897
  resolved.project_id = resolveId(project_id, "projects");
20786
20898
  if (task_list_id)
@@ -20911,12 +21023,16 @@ ${task.description}` : null
20911
21023
  completed_at: exports_external.string().optional().describe("ISO timestamp for backdating completion"),
20912
21024
  deadline: exports_external.string().nullable().optional(),
20913
21025
  retry_count: exports_external.number().optional(),
21026
+ allow_seat: exports_external.boolean().optional().describe("Allow assigned_to to name a durable seat. A seat queue has no session watching it, so this must be deliberate."),
20914
21027
  version: exports_external.number().optional().describe("Expected version for optimistic locking")
20915
21028
  }, async (params) => {
20916
21029
  try {
21030
+ if (typeof params.assigned_to === "string" && params.assigned_to !== "") {
21031
+ params.assigned_to = await validateMcpAssignee(params.assigned_to, Boolean(params.allow_seat));
21032
+ }
20917
21033
  const cloud = getTodosCloudClient();
20918
21034
  if (cloud) {
20919
- const { task_id: task_id2, version: version2, estimate, deadline, ...updates2 } = params;
21035
+ const { task_id: task_id2, version: version2, estimate, deadline, allow_seat: _allowSeatCloud, ...updates2 } = params;
20920
21036
  const patch = { ...updates2 };
20921
21037
  if (patch.assigned_to === "")
20922
21038
  patch.assigned_to = null;
@@ -20941,7 +21057,7 @@ ${task.description}` : null
20941
21057
  return { content: [{ type: "text", text: mutationTaskResponse(updated) }] };
20942
21058
  }
20943
21059
  const resolvedId = resolveId(params.task_id);
20944
- const { task_id, version, ...updates } = params;
21060
+ const { task_id, version, allow_seat: _allowSeatLocal, ...updates } = params;
20945
21061
  const resolved = { ...updates };
20946
21062
  if (resolved.assigned_to === "")
20947
21063
  resolved.assigned_to = null;
@@ -20994,11 +21110,14 @@ var init_task_crud2 = __esm(() => {
20994
21110
  init_types();
20995
21111
  init_token_utils();
20996
21112
  init_creator_identity();
21113
+ init_assignee_validation();
21114
+ init_assignee_context();
21115
+ init_agents();
20997
21116
  init_cloud_router();
20998
21117
  });
20999
21118
 
21000
21119
  // src/lib/project-bootstrap.ts
21001
- import { existsSync as existsSync8, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
21120
+ import { existsSync as existsSync8, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
21002
21121
  import { basename as basename2, dirname as dirname5, resolve as resolve8 } from "path";
21003
21122
  function safeStat(path) {
21004
21123
  try {
@@ -21032,7 +21151,7 @@ function readPackageJson(path) {
21032
21151
  if (!existsSync8(file))
21033
21152
  return null;
21034
21153
  try {
21035
- const parsed = JSON.parse(readFileSync3(file, "utf-8"));
21154
+ const parsed = JSON.parse(readFileSync4(file, "utf-8"));
21036
21155
  return parsed && typeof parsed === "object" ? parsed : null;
21037
21156
  } catch {
21038
21157
  return null;
@@ -21594,8 +21713,8 @@ var init_retention_cleanup = __esm(() => {
21594
21713
  });
21595
21714
 
21596
21715
  // src/lib/mention-resolver.ts
21597
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
21598
- import { basename as basename3, isAbsolute, join as join7, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
21716
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
21717
+ import { basename as basename3, isAbsolute, join as join8, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
21599
21718
  function blankResolution(parsed) {
21600
21719
  return {
21601
21720
  input: parsed.input,
@@ -21703,7 +21822,7 @@ function resolveFile(parsed, workspace) {
21703
21822
  return resolution;
21704
21823
  }
21705
21824
  if (parsed.line !== undefined) {
21706
- const lineCount = readFileSync4(absolutePath, "utf-8").split(/\r?\n/).length;
21825
+ const lineCount = readFileSync5(absolutePath, "utf-8").split(/\r?\n/).length;
21707
21826
  if (parsed.line < 1 || parsed.line > lineCount) {
21708
21827
  resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
21709
21828
  return resolution;
@@ -21726,7 +21845,7 @@ function walkSourceFiles(root, current = root, files = []) {
21726
21845
  if (SKIP_DIRS.has(entry.name))
21727
21846
  continue;
21728
21847
  }
21729
- const absolutePath = join7(current, entry.name);
21848
+ const absolutePath = join8(current, entry.name);
21730
21849
  if (entry.isDirectory()) {
21731
21850
  if (!SKIP_DIRS.has(entry.name))
21732
21851
  walkSourceFiles(root, absolutePath, files);
@@ -21756,7 +21875,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
21756
21875
  const pattern = symbolPattern(name);
21757
21876
  const matches = [];
21758
21877
  for (const file of walkSourceFiles(workspace)) {
21759
- const lines = readFileSync4(file, "utf-8").split(/\r?\n/);
21878
+ const lines = readFileSync5(file, "utf-8").split(/\r?\n/);
21760
21879
  for (let index = 0;index < lines.length; index += 1) {
21761
21880
  const line = lines[index];
21762
21881
  const found = pattern.exec(line);
@@ -25417,8 +25536,8 @@ var init_audit_ledger = __esm(() => {
25417
25536
  });
25418
25537
 
25419
25538
  // src/lib/release-compatibility.ts
25420
- import { readFileSync as readFileSync5 } from "fs";
25421
- import { join as join8, resolve as resolve11 } from "path";
25539
+ import { readFileSync as readFileSync6 } from "fs";
25540
+ import { join as join9, resolve as resolve11 } from "path";
25422
25541
  import { Database as Database2 } from "bun:sqlite";
25423
25542
  function pass(id, message, details) {
25424
25543
  return { id, status: "passed", message, details };
@@ -25430,7 +25549,7 @@ function warn(id, message, details) {
25430
25549
  return { id, status: "warning", message, details };
25431
25550
  }
25432
25551
  function readPackageJson2(root) {
25433
- return JSON.parse(readFileSync5(join8(root, "package.json"), "utf8"));
25552
+ return JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
25434
25553
  }
25435
25554
  function sortedKeys(value) {
25436
25555
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -29444,7 +29563,7 @@ __export(exports_doctor, {
29444
29563
  runTodosDoctor: () => runTodosDoctor
29445
29564
  });
29446
29565
  import { chmodSync, copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
29447
- import { basename as basename4, dirname as dirname6, join as join9 } from "path";
29566
+ import { basename as basename4, dirname as dirname6, join as join10 } from "path";
29448
29567
  function tableExists2(db, table) {
29449
29568
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
29450
29569
  }
@@ -29601,13 +29720,13 @@ function createBackup(dbPath) {
29601
29720
  if (!existsSync11(dbPath))
29602
29721
  return;
29603
29722
  const stamp = now().replace(/[:.]/g, "-");
29604
- const backupDir = join9(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
29723
+ const backupDir = join10(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
29605
29724
  const files = [];
29606
29725
  mkdirSync5(backupDir, { recursive: true });
29607
29726
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
29608
29727
  if (!existsSync11(source))
29609
29728
  continue;
29610
- const target = join9(backupDir, basename4(source));
29729
+ const target = join10(backupDir, basename4(source));
29611
29730
  copyFileSync(source, target);
29612
29731
  files.push(target);
29613
29732
  }
@@ -32343,7 +32462,7 @@ var init_agent_run_dispatcher = __esm(() => {
32343
32462
  });
32344
32463
 
32345
32464
  // src/lib/verification-providers.ts
32346
- import { existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
32465
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
32347
32466
  function normalizeName5(name) {
32348
32467
  const normalized = name.trim().toLowerCase();
32349
32468
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -32495,7 +32614,7 @@ Timed out after ${provider.timeout_ms}ms`);
32495
32614
  };
32496
32615
  }
32497
32616
  function runCiLogProvider(input) {
32498
- const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync6(input.log_path, "utf-8") : "");
32617
+ const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync7(input.log_path, "utf-8") : "");
32499
32618
  return {
32500
32619
  status: classifyLog(text),
32501
32620
  attempts: 1,
@@ -34656,7 +34775,7 @@ var package_default;
34656
34775
  var init_package = __esm(() => {
34657
34776
  package_default = {
34658
34777
  name: "@hasna/todos",
34659
- version: "0.13.7",
34778
+ version: "0.13.8",
34660
34779
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
34661
34780
  type: "module",
34662
34781
  main: "dist/index.js",
@@ -35363,7 +35482,7 @@ var init_local_bridge = __esm(() => {
35363
35482
 
35364
35483
  // src/lib/local-backups.ts
35365
35484
  import { createHash as createHash6 } from "crypto";
35366
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
35485
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
35367
35486
  import { dirname as dirname7, resolve as resolve12 } from "path";
35368
35487
  import { mkdirSync as mkdirSync6 } from "fs";
35369
35488
  function stableJson2(value) {
@@ -35473,7 +35592,7 @@ function writeLocalBackupFile(backup, outputPath) {
35473
35592
  return path;
35474
35593
  }
35475
35594
  function readLocalBackupFile(path) {
35476
- return JSON.parse(readFileSync7(resolve12(path), "utf-8"));
35595
+ return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
35477
35596
  }
35478
35597
  function verifyLocalBackup(value, options = {}, db) {
35479
35598
  const verifiedAt = options.verified_at ?? now();
@@ -36924,8 +37043,8 @@ __export(exports_local_extensions, {
36924
37043
  discoverLocalExtensions: () => discoverLocalExtensions
36925
37044
  });
36926
37045
  import { createHash as createHash9, createVerify } from "crypto";
36927
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
36928
- import { basename as basename5, join as join10, resolve as resolve13 } from "path";
37046
+ import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
37047
+ import { basename as basename5, join as join11, resolve as resolve13 } from "path";
36929
37048
  function isObject2(value) {
36930
37049
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
36931
37050
  }
@@ -37006,7 +37125,7 @@ function normalizeManifest(input) {
37006
37125
  };
37007
37126
  }
37008
37127
  function parseJson(path) {
37009
- return JSON.parse(readFileSync8(path, "utf8"));
37128
+ return JSON.parse(readFileSync9(path, "utf8"));
37010
37129
  }
37011
37130
  function sha2566(bytes) {
37012
37131
  return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
@@ -37187,10 +37306,10 @@ function inspectExtensionSource(source3) {
37187
37306
  if (!existsSync13(resolved))
37188
37307
  throw new Error(`extension source not found: ${source3}`);
37189
37308
  const stat = statSync6(resolved);
37190
- const manifestPath = stat.isDirectory() ? [join10(resolved, "todos.extension.json"), join10(resolved, "extension.json")].find(existsSync13) : resolved;
37309
+ const manifestPath = stat.isDirectory() ? [join11(resolved, "todos.extension.json"), join11(resolved, "extension.json")].find(existsSync13) : resolved;
37191
37310
  if (!manifestPath)
37192
37311
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
37193
- const raw = readFileSync8(manifestPath);
37312
+ const raw = readFileSync9(manifestPath);
37194
37313
  const parsed = parseJson(manifestPath);
37195
37314
  const bundle = isObject2(parsed) && isObject2(parsed["manifest"]);
37196
37315
  const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
@@ -37283,15 +37402,15 @@ function projectExtensionSources(projectPath) {
37283
37402
  return [];
37284
37403
  const root = resolve13(projectPath);
37285
37404
  const candidates = [
37286
- join10(root, "todos.extension.json"),
37287
- join10(root, ".todos", "todos.extension.json")
37405
+ join11(root, "todos.extension.json"),
37406
+ join11(root, ".todos", "todos.extension.json")
37288
37407
  ];
37289
- const extensionDir = join10(root, ".todos", "extensions");
37408
+ const extensionDir = join11(root, ".todos", "extensions");
37290
37409
  if (existsSync13(extensionDir)) {
37291
37410
  for (const entry of readdirSync3(extensionDir)) {
37292
37411
  if (entry.startsWith("."))
37293
37412
  continue;
37294
- const full = join10(extensionDir, entry);
37413
+ const full = join11(extensionDir, entry);
37295
37414
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
37296
37415
  candidates.push(full);
37297
37416
  }
@@ -41703,9 +41822,9 @@ __export(exports_extract, {
41703
41822
  buildCodebaseIndex: () => buildCodebaseIndex,
41704
41823
  EXTRACT_TAGS: () => EXTRACT_TAGS
41705
41824
  });
41706
- import { existsSync as existsSync14, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
41825
+ import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
41707
41826
  import { createHash as createHash11 } from "crypto";
41708
- import { relative as relative5, resolve as resolve14, join as join11 } from "path";
41827
+ import { relative as relative5, resolve as resolve14, join as join12 } from "path";
41709
41828
  function stableHash(value) {
41710
41829
  return createHash11("sha256").update(value).digest("hex");
41711
41830
  }
@@ -41714,11 +41833,11 @@ function normalizePathForMatch(value) {
41714
41833
  }
41715
41834
  function readGitignorePatterns(basePath) {
41716
41835
  const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
41717
- const gitignorePath = join11(root, ".gitignore");
41836
+ const gitignorePath = join12(root, ".gitignore");
41718
41837
  if (!existsSync14(gitignorePath))
41719
41838
  return [];
41720
41839
  try {
41721
- return readFileSync9(gitignorePath, "utf-8").split(`
41840
+ return readFileSync10(gitignorePath, "utf-8").split(`
41722
41841
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
41723
41842
  } catch {
41724
41843
  return [];
@@ -41857,9 +41976,9 @@ function buildCodebaseIndex(options) {
41857
41976
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
41858
41977
  const indexed = [];
41859
41978
  for (const file of files) {
41860
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
41979
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
41861
41980
  try {
41862
- const source3 = readFileSync9(fullPath, "utf-8");
41981
+ const source3 = readFileSync10(fullPath, "utf-8");
41863
41982
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
41864
41983
  indexed.push({
41865
41984
  file: relPath,
@@ -41888,9 +42007,9 @@ function extractTodos(options, db) {
41888
42007
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
41889
42008
  const allComments = [];
41890
42009
  for (const file of files) {
41891
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
42010
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
41892
42011
  try {
41893
- const source3 = readFileSync9(fullPath, "utf-8");
42012
+ const source3 = readFileSync10(fullPath, "utf-8");
41894
42013
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
41895
42014
  const comments = extractFromSource(source3, relPath, tags);
41896
42015
  allComments.push(...comments);
@@ -42839,7 +42958,7 @@ __export(exports_builtin_templates, {
42839
42958
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
42840
42959
  });
42841
42960
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
42842
- import { join as join12 } from "path";
42961
+ import { join as join13 } from "path";
42843
42962
  function templateMetadata(template) {
42844
42963
  return {
42845
42964
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -42898,7 +43017,7 @@ function writeBuiltinTemplateFiles(directory) {
42898
43017
  mkdirSync7(directory, { recursive: true });
42899
43018
  const files = [];
42900
43019
  for (const entry of exportBuiltinTemplateFiles()) {
42901
- const path = join12(directory, entry.filename);
43020
+ const path = join13(directory, entry.filename);
42902
43021
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
42903
43022
  `, "utf-8");
42904
43023
  files.push(path);
@@ -43424,28 +43543,28 @@ __export(exports_environment_snapshots, {
43424
43543
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
43425
43544
  });
43426
43545
  import { createHash as createHash12 } from "crypto";
43427
- import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync8 } from "fs";
43546
+ import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
43428
43547
  import { hostname as hostname2, platform, arch } from "os";
43429
- import { dirname as dirname8, join as join13, resolve as resolve15 } from "path";
43548
+ import { dirname as dirname8, join as join14, resolve as resolve15 } from "path";
43430
43549
  import { tmpdir as tmpdir3 } from "os";
43431
43550
  function sha2567(value) {
43432
43551
  return createHash12("sha256").update(value).digest("hex");
43433
43552
  }
43434
43553
  function fileRecord(root, relativePath) {
43435
- const path = join13(root, relativePath);
43554
+ const path = join14(root, relativePath);
43436
43555
  if (!existsSync15(path))
43437
43556
  return null;
43438
43557
  const stat = statSync8(path);
43439
43558
  if (!stat.isFile())
43440
43559
  return null;
43441
- const content = readFileSync10(path);
43560
+ const content = readFileSync11(path);
43442
43561
  return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
43443
43562
  }
43444
43563
  function manifestRecord(root, relativePath) {
43445
43564
  const base = fileRecord(root, relativePath);
43446
43565
  if (!base)
43447
43566
  return null;
43448
- const parsed = readJsonFile(join13(root, relativePath));
43567
+ const parsed = readJsonFile(join14(root, relativePath));
43449
43568
  if (!parsed)
43450
43569
  return { ...base, redacted: {} };
43451
43570
  const redacted = redactValue({
@@ -43540,8 +43659,8 @@ function commandEnv(env, includeValues) {
43540
43659
  function defaultSnapshotDir() {
43541
43660
  const dbPath = getDatabasePath();
43542
43661
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
43543
- return join13(tmpdir3(), "hasna-todos", "environment-snapshots");
43544
- return join13(dirname8(resolve15(dbPath)), "environment-snapshots");
43662
+ return join14(tmpdir3(), "hasna-todos", "environment-snapshots");
43663
+ return join14(dirname8(resolve15(dbPath)), "environment-snapshots");
43545
43664
  }
43546
43665
  function snapshotWithId(snapshot) {
43547
43666
  const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
@@ -43588,7 +43707,7 @@ function captureEnvironmentSnapshot(input = {}) {
43588
43707
  });
43589
43708
  }
43590
43709
  function writeEnvironmentSnapshot(snapshot, outputPath) {
43591
- const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
43710
+ const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
43592
43711
  ensureDir2(dirname8(path));
43593
43712
  writeJsonFile(path, snapshot);
43594
43713
  return path;
@@ -47894,7 +48013,7 @@ var init_headless_boundaries = __esm(() => {
47894
48013
  });
47895
48014
 
47896
48015
  // src/server/routes.ts
47897
- import { join as join14, resolve as resolve16, sep as sep3 } from "path";
48016
+ import { join as join15, resolve as resolve16, sep as sep3 } from "path";
47898
48017
  function parseFieldsParam(url) {
47899
48018
  const fieldsParam = url.searchParams.get("fields");
47900
48019
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -48689,7 +48808,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
48689
48808
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
48690
48809
  return null;
48691
48810
  if (path !== "/") {
48692
- const filePath = join14(ctx.dashboardDir, path);
48811
+ const filePath = join15(ctx.dashboardDir, path);
48693
48812
  const resolvedFile = resolve16(filePath);
48694
48813
  const resolvedBase = resolve16(ctx.dashboardDir);
48695
48814
  if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
@@ -48699,7 +48818,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
48699
48818
  if (res2)
48700
48819
  return res2;
48701
48820
  }
48702
- const indexPath = join14(ctx.dashboardDir, "index.html");
48821
+ const indexPath = join15(ctx.dashboardDir, "index.html");
48703
48822
  const res = serveStaticFile2(indexPath);
48704
48823
  if (res)
48705
48824
  return res;
@@ -51837,26 +51956,26 @@ __export(exports_serve, {
51837
51956
  MIME_TYPES: () => MIME_TYPES
51838
51957
  });
51839
51958
  import { existsSync as existsSync16 } from "fs";
51840
- import { join as join15, dirname as dirname9, extname } from "path";
51959
+ import { join as join16, dirname as dirname9, extname } from "path";
51841
51960
  import { fileURLToPath } from "url";
51842
51961
  function resolveDashboardDir() {
51843
51962
  const candidates = [];
51844
51963
  try {
51845
51964
  const scriptDir = dirname9(fileURLToPath(import.meta.url));
51846
- candidates.push(join15(scriptDir, "..", "dashboard", "dist"));
51847
- candidates.push(join15(scriptDir, "..", "..", "dashboard", "dist"));
51965
+ candidates.push(join16(scriptDir, "..", "dashboard", "dist"));
51966
+ candidates.push(join16(scriptDir, "..", "..", "dashboard", "dist"));
51848
51967
  } catch {}
51849
51968
  if (process.argv[1]) {
51850
51969
  const mainDir = dirname9(process.argv[1]);
51851
- candidates.push(join15(mainDir, "..", "dashboard", "dist"));
51852
- candidates.push(join15(mainDir, "..", "..", "dashboard", "dist"));
51970
+ candidates.push(join16(mainDir, "..", "dashboard", "dist"));
51971
+ candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
51853
51972
  }
51854
- candidates.push(join15(process.cwd(), "dashboard", "dist"));
51973
+ candidates.push(join16(process.cwd(), "dashboard", "dist"));
51855
51974
  for (const candidate of candidates) {
51856
51975
  if (existsSync16(candidate))
51857
51976
  return candidate;
51858
51977
  }
51859
- return join15(process.cwd(), "dashboard", "dist");
51978
+ return join16(process.cwd(), "dashboard", "dist");
51860
51979
  }
51861
51980
  function getProvidedApiKey(req) {
51862
51981
  const headerKey = req.headers.get("x-api-key");