@hasna/todos 0.13.6 → 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();
@@ -13798,6 +13848,10 @@ function updateTask(id, input, db) {
13798
13848
  sets.push("description = ?");
13799
13849
  params.push(input.description);
13800
13850
  }
13851
+ if (input.agent_id !== undefined) {
13852
+ sets.push("agent_id = ?");
13853
+ params.push(input.agent_id);
13854
+ }
13801
13855
  if (input.status !== undefined) {
13802
13856
  if (input.status === "completed") {
13803
13857
  checkCompletionGuard(task, task.assigned_to || task.agent_id || null, d);
@@ -15250,8 +15304,8 @@ var init_boards = __esm(() => {
15250
15304
 
15251
15305
  // src/lib/artifact-store.ts
15252
15306
  import { createHash as createHash2 } from "crypto";
15253
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
15254
- 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";
15255
15309
  import { tmpdir as tmpdir2 } from "os";
15256
15310
  function isInMemoryDb2(path) {
15257
15311
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -15263,15 +15317,15 @@ function artifactStoreRoot() {
15263
15317
  return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
15264
15318
  const dbPath = getDatabasePath();
15265
15319
  if (isInMemoryDb2(dbPath))
15266
- return join5(tmpdir2(), "hasna-todos-artifacts");
15267
- return join5(dirname4(resolve7(dbPath)), "artifacts");
15320
+ return join6(tmpdir2(), "hasna-todos-artifacts");
15321
+ return join6(dirname4(resolve7(dbPath)), "artifacts");
15268
15322
  }
15269
15323
  function artifactStorePath(relativePath) {
15270
15324
  const normalized = relativePath.replace(/\\/g, "/");
15271
15325
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
15272
15326
  throw new Error("Invalid artifact store path");
15273
15327
  }
15274
- return join5(artifactStoreRoot(), normalized);
15328
+ return join6(artifactStoreRoot(), normalized);
15275
15329
  }
15276
15330
  function sha256(buffer) {
15277
15331
  return createHash2("sha256").update(buffer).digest("hex");
@@ -15312,7 +15366,7 @@ function mediaTypeFor(path, textLike) {
15312
15366
  }
15313
15367
  function storeArtifactContent(input) {
15314
15368
  const sourcePath = resolve7(input.path);
15315
- if (!existsSync6(sourcePath))
15369
+ if (!existsSync7(sourcePath))
15316
15370
  return null;
15317
15371
  const sourceStat = statSync2(sourcePath);
15318
15372
  if (!sourceStat.isFile())
@@ -15329,9 +15383,9 @@ function storeArtifactContent(input) {
15329
15383
  redactionStatus = "redacted";
15330
15384
  }
15331
15385
  const storedSha = sha256(storedBuffer);
15332
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
15386
+ const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
15333
15387
  const destination = artifactStorePath(relativePath);
15334
- if (!existsSync6(destination)) {
15388
+ if (!existsSync7(destination)) {
15335
15389
  mkdirSync4(dirname4(destination), { recursive: true });
15336
15390
  writeFileSync2(destination, storedBuffer);
15337
15391
  }
@@ -15391,7 +15445,7 @@ function verifyStoredArtifact(input) {
15391
15445
  };
15392
15446
  }
15393
15447
  const storedPath = artifactStorePath(store.relative_path);
15394
- if (!existsSync6(storedPath)) {
15448
+ if (!existsSync7(storedPath)) {
15395
15449
  return {
15396
15450
  id: input.id,
15397
15451
  path: input.path,
@@ -17865,39 +17919,98 @@ var init_token_utils = __esm(() => {
17865
17919
  };
17866
17920
  });
17867
17921
 
17868
- // src/lib/creator-identity.ts
17869
- import { existsSync as existsSync7, rmSync as rmSync2 } from "fs";
17870
- import { join as join6 } from "path";
17871
- function identityFilePath() {
17872
- 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");
17873
17928
  }
17874
- function readPersistedIdentity() {
17875
- const path = identityFilePath();
17876
- if (!existsSync7(path))
17877
- return null;
17878
- const parsed = readJsonFile(path);
17879
- if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
17880
- return null;
17881
- return parsed;
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
+ }
17882
17942
  }
17883
- function canonicalAgentRef(value) {
17884
- return value.trim().toLowerCase();
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 };
17885
17988
  }
17886
- function resolveCreatorIdentity(explicit) {
17887
- const fromExplicit = explicit?.trim();
17888
- if (fromExplicit)
17889
- return { agent_id: canonicalAgentRef(fromExplicit), source: "explicit" };
17890
- const fromEnv = (process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"] || "").trim();
17891
- if (fromEnv)
17892
- return { agent_id: canonicalAgentRef(fromEnv), source: "env" };
17893
- const persisted = readPersistedIdentity();
17894
- if (persisted) {
17895
- 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
+ };
17896
18008
  }
17897
- return { agent_id: null, source: "none" };
18009
+ return { agents: cache.agents, seats: cache.seats, allowSeat, degraded: cache.degraded };
17898
18010
  }
17899
- var init_creator_identity = __esm(() => {
17900
- init_sync_utils();
18011
+ var ROSTER_TTL_MS = 15000, cache;
18012
+ var init_assignee_context = __esm(() => {
18013
+ init_assignee_validation();
17901
18014
  });
17902
18015
 
17903
18016
  // src/pr-groups/types.ts
@@ -20616,6 +20729,14 @@ var init_cloud_router = __esm(() => {
20616
20729
  });
20617
20730
 
20618
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
+ }
20619
20740
  function registerTaskCrudTools(server, ctx) {
20620
20741
  const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
20621
20742
  function mutationTaskResponse(task) {
@@ -20652,6 +20773,7 @@ function registerTaskCrudTools(server, ctx) {
20652
20773
  assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
20653
20774
  created_by: exports_external.string().optional().describe("Agent who FILED this task. Defaults to the ambient agent identity (todos init / TODOS_AGENT_ID)."),
20654
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."),
20655
20777
  depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
20656
20778
  short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
20657
20779
  tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
@@ -20662,16 +20784,18 @@ function registerTaskCrudTools(server, ctx) {
20662
20784
  retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
20663
20785
  }, async (params) => {
20664
20786
  try {
20665
- 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;
20666
20789
  const creator = resolveCreatorIdentity(created_by);
20667
- const assignee = assigned_to || (unassigned ? undefined : creator.agent_id || undefined);
20790
+ const router = resolveWritableIdentity(created_by);
20791
+ const assignee = requestedAssignee || (unassigned ? undefined : router.agent_id || undefined);
20668
20792
  const cloud = getTodosCloudClient();
20669
20793
  if (cloud) {
20670
20794
  const payload = { ...rest };
20671
- if (creator.agent_id) {
20795
+ if (creator.agent_id)
20672
20796
  payload.created_by = creator.agent_id;
20673
- payload.agent_id = payload.agent_id ?? creator.agent_id;
20674
- }
20797
+ if (router.agent_id)
20798
+ payload.agent_id = payload.agent_id ?? router.agent_id;
20675
20799
  if (assignee)
20676
20800
  payload.assigned_to = assignee;
20677
20801
  if (project_id)
@@ -20694,10 +20818,10 @@ function registerTaskCrudTools(server, ctx) {
20694
20818
  return { content: [{ type: "text", text: mutationTaskResponse(created) }] };
20695
20819
  }
20696
20820
  const resolved = { ...rest };
20697
- if (creator.agent_id) {
20821
+ if (creator.agent_id)
20698
20822
  resolved.created_by = creator.agent_id;
20699
- resolved.agent_id = resolved.agent_id ?? creator.agent_id;
20700
- }
20823
+ if (router.agent_id)
20824
+ resolved.agent_id = resolved.agent_id ?? router.agent_id;
20701
20825
  if (assignee)
20702
20826
  resolved.assigned_to = resolveAssignee(assignee);
20703
20827
  if (project_id)
@@ -20733,6 +20857,7 @@ function registerTaskCrudTools(server, ctx) {
20733
20857
  project_id: exports_external.string().optional().describe("Project ID"),
20734
20858
  task_list_id: exports_external.string().optional().describe("Task list ID"),
20735
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."),
20736
20861
  tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
20737
20862
  working_dir: exports_external.string().optional().describe("Working directory associated with the task"),
20738
20863
  metadata: exports_external.record(exports_external.unknown()).optional().describe("Metadata object to shallow-merge"),
@@ -20746,7 +20871,7 @@ function registerTaskCrudTools(server, ctx) {
20746
20871
  acceptance: exports_external.unknown().optional()
20747
20872
  }, async (params) => {
20748
20873
  try {
20749
- 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;
20750
20875
  const mergedMetadata = { ...metadata ?? {} };
20751
20876
  if (expectation_id !== undefined)
20752
20877
  mergedMetadata["expectation_id"] = expectation_id;
@@ -20765,8 +20890,9 @@ function registerTaskCrudTools(server, ctx) {
20765
20890
  if (acceptance !== undefined)
20766
20891
  mergedMetadata["acceptance"] = acceptance;
20767
20892
  const resolved = { ...rest, metadata: mergedMetadata };
20768
- if (assigned_to)
20769
- resolved.assigned_to = resolveAssignee(assigned_to);
20893
+ if (assigned_to) {
20894
+ resolved.assigned_to = resolveAssignee(await validateMcpAssignee(assigned_to, Boolean(params.allow_seat)));
20895
+ }
20770
20896
  if (project_id)
20771
20897
  resolved.project_id = resolveId(project_id, "projects");
20772
20898
  if (task_list_id)
@@ -20897,12 +21023,16 @@ ${task.description}` : null
20897
21023
  completed_at: exports_external.string().optional().describe("ISO timestamp for backdating completion"),
20898
21024
  deadline: exports_external.string().nullable().optional(),
20899
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."),
20900
21027
  version: exports_external.number().optional().describe("Expected version for optimistic locking")
20901
21028
  }, async (params) => {
20902
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
+ }
20903
21033
  const cloud = getTodosCloudClient();
20904
21034
  if (cloud) {
20905
- 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;
20906
21036
  const patch = { ...updates2 };
20907
21037
  if (patch.assigned_to === "")
20908
21038
  patch.assigned_to = null;
@@ -20927,7 +21057,7 @@ ${task.description}` : null
20927
21057
  return { content: [{ type: "text", text: mutationTaskResponse(updated) }] };
20928
21058
  }
20929
21059
  const resolvedId = resolveId(params.task_id);
20930
- const { task_id, version, ...updates } = params;
21060
+ const { task_id, version, allow_seat: _allowSeatLocal, ...updates } = params;
20931
21061
  const resolved = { ...updates };
20932
21062
  if (resolved.assigned_to === "")
20933
21063
  resolved.assigned_to = null;
@@ -20980,11 +21110,14 @@ var init_task_crud2 = __esm(() => {
20980
21110
  init_types();
20981
21111
  init_token_utils();
20982
21112
  init_creator_identity();
21113
+ init_assignee_validation();
21114
+ init_assignee_context();
21115
+ init_agents();
20983
21116
  init_cloud_router();
20984
21117
  });
20985
21118
 
20986
21119
  // src/lib/project-bootstrap.ts
20987
- 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";
20988
21121
  import { basename as basename2, dirname as dirname5, resolve as resolve8 } from "path";
20989
21122
  function safeStat(path) {
20990
21123
  try {
@@ -21018,7 +21151,7 @@ function readPackageJson(path) {
21018
21151
  if (!existsSync8(file))
21019
21152
  return null;
21020
21153
  try {
21021
- const parsed = JSON.parse(readFileSync3(file, "utf-8"));
21154
+ const parsed = JSON.parse(readFileSync4(file, "utf-8"));
21022
21155
  return parsed && typeof parsed === "object" ? parsed : null;
21023
21156
  } catch {
21024
21157
  return null;
@@ -21580,8 +21713,8 @@ var init_retention_cleanup = __esm(() => {
21580
21713
  });
21581
21714
 
21582
21715
  // src/lib/mention-resolver.ts
21583
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
21584
- 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";
21585
21718
  function blankResolution(parsed) {
21586
21719
  return {
21587
21720
  input: parsed.input,
@@ -21689,7 +21822,7 @@ function resolveFile(parsed, workspace) {
21689
21822
  return resolution;
21690
21823
  }
21691
21824
  if (parsed.line !== undefined) {
21692
- const lineCount = readFileSync4(absolutePath, "utf-8").split(/\r?\n/).length;
21825
+ const lineCount = readFileSync5(absolutePath, "utf-8").split(/\r?\n/).length;
21693
21826
  if (parsed.line < 1 || parsed.line > lineCount) {
21694
21827
  resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
21695
21828
  return resolution;
@@ -21712,7 +21845,7 @@ function walkSourceFiles(root, current = root, files = []) {
21712
21845
  if (SKIP_DIRS.has(entry.name))
21713
21846
  continue;
21714
21847
  }
21715
- const absolutePath = join7(current, entry.name);
21848
+ const absolutePath = join8(current, entry.name);
21716
21849
  if (entry.isDirectory()) {
21717
21850
  if (!SKIP_DIRS.has(entry.name))
21718
21851
  walkSourceFiles(root, absolutePath, files);
@@ -21742,7 +21875,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
21742
21875
  const pattern = symbolPattern(name);
21743
21876
  const matches = [];
21744
21877
  for (const file of walkSourceFiles(workspace)) {
21745
- const lines = readFileSync4(file, "utf-8").split(/\r?\n/);
21878
+ const lines = readFileSync5(file, "utf-8").split(/\r?\n/);
21746
21879
  for (let index = 0;index < lines.length; index += 1) {
21747
21880
  const line = lines[index];
21748
21881
  const found = pattern.exec(line);
@@ -25403,8 +25536,8 @@ var init_audit_ledger = __esm(() => {
25403
25536
  });
25404
25537
 
25405
25538
  // src/lib/release-compatibility.ts
25406
- import { readFileSync as readFileSync5 } from "fs";
25407
- 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";
25408
25541
  import { Database as Database2 } from "bun:sqlite";
25409
25542
  function pass(id, message, details) {
25410
25543
  return { id, status: "passed", message, details };
@@ -25416,7 +25549,7 @@ function warn(id, message, details) {
25416
25549
  return { id, status: "warning", message, details };
25417
25550
  }
25418
25551
  function readPackageJson2(root) {
25419
- return JSON.parse(readFileSync5(join8(root, "package.json"), "utf8"));
25552
+ return JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
25420
25553
  }
25421
25554
  function sortedKeys(value) {
25422
25555
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -29430,7 +29563,7 @@ __export(exports_doctor, {
29430
29563
  runTodosDoctor: () => runTodosDoctor
29431
29564
  });
29432
29565
  import { chmodSync, copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
29433
- 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";
29434
29567
  function tableExists2(db, table) {
29435
29568
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
29436
29569
  }
@@ -29587,13 +29720,13 @@ function createBackup(dbPath) {
29587
29720
  if (!existsSync11(dbPath))
29588
29721
  return;
29589
29722
  const stamp = now().replace(/[:.]/g, "-");
29590
- const backupDir = join9(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
29723
+ const backupDir = join10(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
29591
29724
  const files = [];
29592
29725
  mkdirSync5(backupDir, { recursive: true });
29593
29726
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
29594
29727
  if (!existsSync11(source))
29595
29728
  continue;
29596
- const target = join9(backupDir, basename4(source));
29729
+ const target = join10(backupDir, basename4(source));
29597
29730
  copyFileSync(source, target);
29598
29731
  files.push(target);
29599
29732
  }
@@ -32329,7 +32462,7 @@ var init_agent_run_dispatcher = __esm(() => {
32329
32462
  });
32330
32463
 
32331
32464
  // src/lib/verification-providers.ts
32332
- import { existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
32465
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
32333
32466
  function normalizeName5(name) {
32334
32467
  const normalized = name.trim().toLowerCase();
32335
32468
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -32481,7 +32614,7 @@ Timed out after ${provider.timeout_ms}ms`);
32481
32614
  };
32482
32615
  }
32483
32616
  function runCiLogProvider(input) {
32484
- 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") : "");
32485
32618
  return {
32486
32619
  status: classifyLog(text),
32487
32620
  attempts: 1,
@@ -34642,7 +34775,7 @@ var package_default;
34642
34775
  var init_package = __esm(() => {
34643
34776
  package_default = {
34644
34777
  name: "@hasna/todos",
34645
- version: "0.13.6",
34778
+ version: "0.13.8",
34646
34779
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
34647
34780
  type: "module",
34648
34781
  main: "dist/index.js",
@@ -35349,7 +35482,7 @@ var init_local_bridge = __esm(() => {
35349
35482
 
35350
35483
  // src/lib/local-backups.ts
35351
35484
  import { createHash as createHash6 } from "crypto";
35352
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
35485
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
35353
35486
  import { dirname as dirname7, resolve as resolve12 } from "path";
35354
35487
  import { mkdirSync as mkdirSync6 } from "fs";
35355
35488
  function stableJson2(value) {
@@ -35459,7 +35592,7 @@ function writeLocalBackupFile(backup, outputPath) {
35459
35592
  return path;
35460
35593
  }
35461
35594
  function readLocalBackupFile(path) {
35462
- return JSON.parse(readFileSync7(resolve12(path), "utf-8"));
35595
+ return JSON.parse(readFileSync8(resolve12(path), "utf-8"));
35463
35596
  }
35464
35597
  function verifyLocalBackup(value, options = {}, db) {
35465
35598
  const verifiedAt = options.verified_at ?? now();
@@ -36910,8 +37043,8 @@ __export(exports_local_extensions, {
36910
37043
  discoverLocalExtensions: () => discoverLocalExtensions
36911
37044
  });
36912
37045
  import { createHash as createHash9, createVerify } from "crypto";
36913
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
36914
- 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";
36915
37048
  function isObject2(value) {
36916
37049
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
36917
37050
  }
@@ -36992,7 +37125,7 @@ function normalizeManifest(input) {
36992
37125
  };
36993
37126
  }
36994
37127
  function parseJson(path) {
36995
- return JSON.parse(readFileSync8(path, "utf8"));
37128
+ return JSON.parse(readFileSync9(path, "utf8"));
36996
37129
  }
36997
37130
  function sha2566(bytes) {
36998
37131
  return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
@@ -37173,10 +37306,10 @@ function inspectExtensionSource(source3) {
37173
37306
  if (!existsSync13(resolved))
37174
37307
  throw new Error(`extension source not found: ${source3}`);
37175
37308
  const stat = statSync6(resolved);
37176
- 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;
37177
37310
  if (!manifestPath)
37178
37311
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
37179
- const raw = readFileSync8(manifestPath);
37312
+ const raw = readFileSync9(manifestPath);
37180
37313
  const parsed = parseJson(manifestPath);
37181
37314
  const bundle = isObject2(parsed) && isObject2(parsed["manifest"]);
37182
37315
  const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
@@ -37269,15 +37402,15 @@ function projectExtensionSources(projectPath) {
37269
37402
  return [];
37270
37403
  const root = resolve13(projectPath);
37271
37404
  const candidates = [
37272
- join10(root, "todos.extension.json"),
37273
- join10(root, ".todos", "todos.extension.json")
37405
+ join11(root, "todos.extension.json"),
37406
+ join11(root, ".todos", "todos.extension.json")
37274
37407
  ];
37275
- const extensionDir = join10(root, ".todos", "extensions");
37408
+ const extensionDir = join11(root, ".todos", "extensions");
37276
37409
  if (existsSync13(extensionDir)) {
37277
37410
  for (const entry of readdirSync3(extensionDir)) {
37278
37411
  if (entry.startsWith("."))
37279
37412
  continue;
37280
- const full = join10(extensionDir, entry);
37413
+ const full = join11(extensionDir, entry);
37281
37414
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
37282
37415
  candidates.push(full);
37283
37416
  }
@@ -41689,9 +41822,9 @@ __export(exports_extract, {
41689
41822
  buildCodebaseIndex: () => buildCodebaseIndex,
41690
41823
  EXTRACT_TAGS: () => EXTRACT_TAGS
41691
41824
  });
41692
- 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";
41693
41826
  import { createHash as createHash11 } from "crypto";
41694
- 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";
41695
41828
  function stableHash(value) {
41696
41829
  return createHash11("sha256").update(value).digest("hex");
41697
41830
  }
@@ -41700,11 +41833,11 @@ function normalizePathForMatch(value) {
41700
41833
  }
41701
41834
  function readGitignorePatterns(basePath) {
41702
41835
  const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
41703
- const gitignorePath = join11(root, ".gitignore");
41836
+ const gitignorePath = join12(root, ".gitignore");
41704
41837
  if (!existsSync14(gitignorePath))
41705
41838
  return [];
41706
41839
  try {
41707
- return readFileSync9(gitignorePath, "utf-8").split(`
41840
+ return readFileSync10(gitignorePath, "utf-8").split(`
41708
41841
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
41709
41842
  } catch {
41710
41843
  return [];
@@ -41843,9 +41976,9 @@ function buildCodebaseIndex(options) {
41843
41976
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
41844
41977
  const indexed = [];
41845
41978
  for (const file of files) {
41846
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
41979
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
41847
41980
  try {
41848
- const source3 = readFileSync9(fullPath, "utf-8");
41981
+ const source3 = readFileSync10(fullPath, "utf-8");
41849
41982
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
41850
41983
  indexed.push({
41851
41984
  file: relPath,
@@ -41874,9 +42007,9 @@ function extractTodos(options, db) {
41874
42007
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
41875
42008
  const allComments = [];
41876
42009
  for (const file of files) {
41877
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
42010
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
41878
42011
  try {
41879
- const source3 = readFileSync9(fullPath, "utf-8");
42012
+ const source3 = readFileSync10(fullPath, "utf-8");
41880
42013
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
41881
42014
  const comments = extractFromSource(source3, relPath, tags);
41882
42015
  allComments.push(...comments);
@@ -42825,7 +42958,7 @@ __export(exports_builtin_templates, {
42825
42958
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
42826
42959
  });
42827
42960
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
42828
- import { join as join12 } from "path";
42961
+ import { join as join13 } from "path";
42829
42962
  function templateMetadata(template) {
42830
42963
  return {
42831
42964
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -42884,7 +43017,7 @@ function writeBuiltinTemplateFiles(directory) {
42884
43017
  mkdirSync7(directory, { recursive: true });
42885
43018
  const files = [];
42886
43019
  for (const entry of exportBuiltinTemplateFiles()) {
42887
- const path = join12(directory, entry.filename);
43020
+ const path = join13(directory, entry.filename);
42888
43021
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
42889
43022
  `, "utf-8");
42890
43023
  files.push(path);
@@ -43410,28 +43543,28 @@ __export(exports_environment_snapshots, {
43410
43543
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
43411
43544
  });
43412
43545
  import { createHash as createHash12 } from "crypto";
43413
- 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";
43414
43547
  import { hostname as hostname2, platform, arch } from "os";
43415
- 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";
43416
43549
  import { tmpdir as tmpdir3 } from "os";
43417
43550
  function sha2567(value) {
43418
43551
  return createHash12("sha256").update(value).digest("hex");
43419
43552
  }
43420
43553
  function fileRecord(root, relativePath) {
43421
- const path = join13(root, relativePath);
43554
+ const path = join14(root, relativePath);
43422
43555
  if (!existsSync15(path))
43423
43556
  return null;
43424
43557
  const stat = statSync8(path);
43425
43558
  if (!stat.isFile())
43426
43559
  return null;
43427
- const content = readFileSync10(path);
43560
+ const content = readFileSync11(path);
43428
43561
  return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
43429
43562
  }
43430
43563
  function manifestRecord(root, relativePath) {
43431
43564
  const base = fileRecord(root, relativePath);
43432
43565
  if (!base)
43433
43566
  return null;
43434
- const parsed = readJsonFile(join13(root, relativePath));
43567
+ const parsed = readJsonFile(join14(root, relativePath));
43435
43568
  if (!parsed)
43436
43569
  return { ...base, redacted: {} };
43437
43570
  const redacted = redactValue({
@@ -43526,8 +43659,8 @@ function commandEnv(env, includeValues) {
43526
43659
  function defaultSnapshotDir() {
43527
43660
  const dbPath = getDatabasePath();
43528
43661
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
43529
- return join13(tmpdir3(), "hasna-todos", "environment-snapshots");
43530
- return join13(dirname8(resolve15(dbPath)), "environment-snapshots");
43662
+ return join14(tmpdir3(), "hasna-todos", "environment-snapshots");
43663
+ return join14(dirname8(resolve15(dbPath)), "environment-snapshots");
43531
43664
  }
43532
43665
  function snapshotWithId(snapshot) {
43533
43666
  const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
@@ -43574,7 +43707,7 @@ function captureEnvironmentSnapshot(input = {}) {
43574
43707
  });
43575
43708
  }
43576
43709
  function writeEnvironmentSnapshot(snapshot, outputPath) {
43577
- const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
43710
+ const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
43578
43711
  ensureDir2(dirname8(path));
43579
43712
  writeJsonFile(path, snapshot);
43580
43713
  return path;
@@ -47880,7 +48013,7 @@ var init_headless_boundaries = __esm(() => {
47880
48013
  });
47881
48014
 
47882
48015
  // src/server/routes.ts
47883
- 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";
47884
48017
  function parseFieldsParam(url) {
47885
48018
  const fieldsParam = url.searchParams.get("fields");
47886
48019
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -48675,7 +48808,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
48675
48808
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
48676
48809
  return null;
48677
48810
  if (path !== "/") {
48678
- const filePath = join14(ctx.dashboardDir, path);
48811
+ const filePath = join15(ctx.dashboardDir, path);
48679
48812
  const resolvedFile = resolve16(filePath);
48680
48813
  const resolvedBase = resolve16(ctx.dashboardDir);
48681
48814
  if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
@@ -48685,7 +48818,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
48685
48818
  if (res2)
48686
48819
  return res2;
48687
48820
  }
48688
- const indexPath = join14(ctx.dashboardDir, "index.html");
48821
+ const indexPath = join15(ctx.dashboardDir, "index.html");
48689
48822
  const res = serveStaticFile2(indexPath);
48690
48823
  if (res)
48691
48824
  return res;
@@ -51823,26 +51956,26 @@ __export(exports_serve, {
51823
51956
  MIME_TYPES: () => MIME_TYPES
51824
51957
  });
51825
51958
  import { existsSync as existsSync16 } from "fs";
51826
- import { join as join15, dirname as dirname9, extname } from "path";
51959
+ import { join as join16, dirname as dirname9, extname } from "path";
51827
51960
  import { fileURLToPath } from "url";
51828
51961
  function resolveDashboardDir() {
51829
51962
  const candidates = [];
51830
51963
  try {
51831
51964
  const scriptDir = dirname9(fileURLToPath(import.meta.url));
51832
- candidates.push(join15(scriptDir, "..", "dashboard", "dist"));
51833
- candidates.push(join15(scriptDir, "..", "..", "dashboard", "dist"));
51965
+ candidates.push(join16(scriptDir, "..", "dashboard", "dist"));
51966
+ candidates.push(join16(scriptDir, "..", "..", "dashboard", "dist"));
51834
51967
  } catch {}
51835
51968
  if (process.argv[1]) {
51836
51969
  const mainDir = dirname9(process.argv[1]);
51837
- candidates.push(join15(mainDir, "..", "dashboard", "dist"));
51838
- candidates.push(join15(mainDir, "..", "..", "dashboard", "dist"));
51970
+ candidates.push(join16(mainDir, "..", "dashboard", "dist"));
51971
+ candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
51839
51972
  }
51840
- candidates.push(join15(process.cwd(), "dashboard", "dist"));
51973
+ candidates.push(join16(process.cwd(), "dashboard", "dist"));
51841
51974
  for (const candidate of candidates) {
51842
51975
  if (existsSync16(candidate))
51843
51976
  return candidate;
51844
51977
  }
51845
- return join15(process.cwd(), "dashboard", "dist");
51978
+ return join16(process.cwd(), "dashboard", "dist");
51846
51979
  }
51847
51980
  function getProvidedApiKey(req) {
51848
51981
  const headerKey = req.headers.get("x-api-key");