@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/cli/assignee-guard.d.ts +13 -0
- package/dist/cli/assignee-guard.d.ts.map +1 -0
- package/dist/cli/claim-guard.d.ts +3 -0
- package/dist/cli/claim-guard.d.ts.map +1 -0
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +3299 -3076
- package/dist/contracts.js +81 -29
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/index.js +114 -58
- package/dist/lib/agent-tasks.d.ts.map +1 -1
- package/dist/lib/assignee-context.d.ts +40 -0
- package/dist/lib/assignee-context.d.ts.map +1 -0
- package/dist/lib/assignee-validation.d.ts +72 -0
- package/dist/lib/assignee-validation.d.ts.map +1 -0
- package/dist/lib/claude-tasks.d.ts.map +1 -1
- package/dist/lib/creator-identity.d.ts +30 -0
- package/dist/lib/creator-identity.d.ts.map +1 -1
- package/dist/lib/sync-utils.d.ts +7 -0
- package/dist/lib/sync-utils.d.ts.map +1 -1
- package/dist/mcp/index.js +245 -112
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +81 -29
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +249 -116
- package/dist/storage.js +66 -14
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
15254
|
-
import { basename, dirname as dirname4, join as
|
|
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
|
|
15267
|
-
return
|
|
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
|
|
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 (!
|
|
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 =
|
|
15386
|
+
const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
15333
15387
|
const destination = artifactStorePath(relativePath);
|
|
15334
|
-
if (!
|
|
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 (!
|
|
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/
|
|
17869
|
-
import {
|
|
17870
|
-
import {
|
|
17871
|
-
|
|
17872
|
-
|
|
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
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
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
|
|
17884
|
-
|
|
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
|
-
|
|
17887
|
-
|
|
17888
|
-
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17894
|
-
|
|
17895
|
-
|
|
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 {
|
|
18009
|
+
return { agents: cache.agents, seats: cache.seats, allowSeat, degraded: cache.degraded };
|
|
17898
18010
|
}
|
|
17899
|
-
var
|
|
17900
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
21584
|
-
import { basename as basename3, isAbsolute, join as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
25407
|
-
import { join as
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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) ?
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
36914
|
-
import { basename as basename5, join as
|
|
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(
|
|
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() ? [
|
|
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 =
|
|
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
|
-
|
|
37273
|
-
|
|
37405
|
+
join11(root, "todos.extension.json"),
|
|
37406
|
+
join11(root, ".todos", "todos.extension.json")
|
|
37274
37407
|
];
|
|
37275
|
-
const extensionDir =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
41836
|
+
const gitignorePath = join12(root, ".gitignore");
|
|
41704
41837
|
if (!existsSync14(gitignorePath))
|
|
41705
41838
|
return [];
|
|
41706
41839
|
try {
|
|
41707
|
-
return
|
|
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 :
|
|
41979
|
+
const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
|
|
41847
41980
|
try {
|
|
41848
|
-
const source3 =
|
|
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 :
|
|
42010
|
+
const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
|
|
41878
42011
|
try {
|
|
41879
|
-
const source3 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
43530
|
-
return
|
|
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) :
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
51833
|
-
candidates.push(
|
|
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(
|
|
51838
|
-
candidates.push(
|
|
51970
|
+
candidates.push(join16(mainDir, "..", "dashboard", "dist"));
|
|
51971
|
+
candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
|
|
51839
51972
|
}
|
|
51840
|
-
candidates.push(
|
|
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
|
|
51978
|
+
return join16(process.cwd(), "dashboard", "dist");
|
|
51846
51979
|
}
|
|
51847
51980
|
function getProvidedApiKey(req) {
|
|
51848
51981
|
const headerKey = req.headers.get("x-api-key");
|