@hasna/todos 0.11.85 → 0.11.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli/cloud-router.d.ts +55 -2
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/agent-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/task-commands.d.ts +2 -0
  5. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  6. package/dist/cli/index.js +1106 -417
  7. package/dist/contracts.js +1 -1
  8. package/dist/db/comments.d.ts.map +1 -1
  9. package/dist/index.js +180 -5
  10. package/dist/mcp/index.js +483 -39
  11. package/dist/registry.js +1 -1
  12. package/dist/release-provenance.json +3 -3
  13. package/dist/sdk/index.d.ts +1 -1
  14. package/dist/sdk/index.d.ts.map +1 -1
  15. package/dist/sdk/index.js +21 -0
  16. package/dist/sdk/v1.generated.d.ts +54 -0
  17. package/dist/sdk/v1.generated.d.ts.map +1 -1
  18. package/dist/server/cloud.d.ts +13 -0
  19. package/dist/server/cloud.d.ts.map +1 -1
  20. package/dist/server/index.js +900 -384
  21. package/dist/server/openapi.d.ts +171 -0
  22. package/dist/server/openapi.d.ts.map +1 -1
  23. package/dist/server/v1.d.ts +7 -1
  24. package/dist/server/v1.d.ts.map +1 -1
  25. package/dist/storage/comment-redaction-backfill.d.ts +32 -0
  26. package/dist/storage/comment-redaction-backfill.d.ts.map +1 -0
  27. package/dist/storage/index.d.ts +4 -2
  28. package/dist/storage/index.d.ts.map +1 -1
  29. package/dist/storage/interfaces.d.ts +23 -0
  30. package/dist/storage/interfaces.d.ts.map +1 -1
  31. package/dist/storage/local-sqlite.d.ts.map +1 -1
  32. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  33. package/dist/storage/postgres-sync.d.ts +6 -0
  34. package/dist/storage/postgres-sync.d.ts.map +1 -1
  35. package/dist/storage.d.ts +3 -3
  36. package/dist/storage.d.ts.map +1 -1
  37. package/dist/storage.js +184 -5
  38. package/package.json +2 -1
  39. package/vendor/hasna-contracts-0.5.1.tgz +0 -0
package/dist/cli/index.js CHANGED
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1011
1011
  this._exitCallback = (err) => {
1012
1012
  if (err.code !== "commander.executeSubCommandAsync") {
1013
1013
  throw err;
1014
- } else {}
1014
+ }
1015
1015
  };
1016
1016
  }
1017
1017
  return this;
@@ -8478,6 +8478,310 @@ var init_storage = __esm(() => {
8478
8478
  IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
8479
8479
  });
8480
8480
 
8481
+ // src/lib/sync-utils.ts
8482
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
8483
+ import { join as join2 } from "path";
8484
+ function getHomeDir() {
8485
+ return process.env["HOME"] || process.env["USERPROFILE"] || "~";
8486
+ }
8487
+ function getTodosGlobalDir() {
8488
+ return join2(getHomeDir(), ".hasna", "todos");
8489
+ }
8490
+ function ensureDir(dir) {
8491
+ if (!existsSync2(dir))
8492
+ mkdirSync(dir, { recursive: true });
8493
+ }
8494
+ function listJsonFiles(dir) {
8495
+ if (!existsSync2(dir))
8496
+ return [];
8497
+ return readdirSync(dir).filter((f) => f.endsWith(".json"));
8498
+ }
8499
+ function readJsonFile(path) {
8500
+ try {
8501
+ return JSON.parse(readFileSync2(path, "utf-8"));
8502
+ } catch {
8503
+ return null;
8504
+ }
8505
+ }
8506
+ function writeJsonFile(path, data) {
8507
+ writeFileSync(path, JSON.stringify(data, null, 2) + `
8508
+ `);
8509
+ }
8510
+ function readHighWaterMark(dir) {
8511
+ const path = join2(dir, ".highwatermark");
8512
+ if (!existsSync2(path))
8513
+ return 1;
8514
+ const val = parseInt(readFileSync2(path, "utf-8").trim(), 10);
8515
+ return isNaN(val) ? 1 : val;
8516
+ }
8517
+ function writeHighWaterMark(dir, value) {
8518
+ writeFileSync(join2(dir, ".highwatermark"), String(value));
8519
+ }
8520
+ function getFileMtimeMs(path) {
8521
+ try {
8522
+ return statSync(path).mtimeMs;
8523
+ } catch {
8524
+ return null;
8525
+ }
8526
+ }
8527
+ function parseTimestamp(value) {
8528
+ if (typeof value !== "string")
8529
+ return null;
8530
+ const parsed = Date.parse(value);
8531
+ return Number.isNaN(parsed) ? null : parsed;
8532
+ }
8533
+ function appendSyncConflict(metadata, conflict, limit = 5) {
8534
+ const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
8535
+ const next = [conflict, ...current].slice(0, limit);
8536
+ return { ...metadata, sync_conflicts: next };
8537
+ }
8538
+ var HOME;
8539
+ var init_sync_utils = __esm(() => {
8540
+ HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
8541
+ });
8542
+
8543
+ // src/lib/config.ts
8544
+ var exports_config = {};
8545
+ __export(exports_config, {
8546
+ updateConfig: () => updateConfig,
8547
+ saveConfig: () => saveConfig,
8548
+ resetConfig: () => resetConfig,
8549
+ normalizeApiUrl: () => normalizeApiUrl,
8550
+ loadConfig: () => loadConfig,
8551
+ getTaskPrefixConfig: () => getTaskPrefixConfig,
8552
+ getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
8553
+ getLocalApiConfig: () => getLocalApiConfig,
8554
+ getConfigPath: () => getConfigPath,
8555
+ getCompletionGuardConfig: () => getCompletionGuardConfig,
8556
+ getAgentTasksDir: () => getAgentTasksDir,
8557
+ getAgentTaskListId: () => getAgentTaskListId,
8558
+ getAgentPoolForProject: () => getAgentPoolForProject
8559
+ });
8560
+ import { existsSync as existsSync3 } from "fs";
8561
+ import { dirname as dirname2, join as join3 } from "path";
8562
+ function getConfigPath() {
8563
+ return join3(getTodosGlobalDir(), "config.json");
8564
+ }
8565
+ function resetConfig() {
8566
+ cached = null;
8567
+ }
8568
+ function normalizeAgent(agent) {
8569
+ return agent.trim().toLowerCase();
8570
+ }
8571
+ function loadConfig() {
8572
+ if (cached)
8573
+ return cached;
8574
+ if (!existsSync3(getConfigPath())) {
8575
+ cached = {};
8576
+ return cached;
8577
+ }
8578
+ const config = readJsonFile(getConfigPath()) || {};
8579
+ if (typeof config.sync_agents === "string") {
8580
+ config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
8581
+ }
8582
+ cached = config;
8583
+ return cached;
8584
+ }
8585
+ function saveConfig(config) {
8586
+ const configPath = getConfigPath();
8587
+ ensureDir(dirname2(configPath));
8588
+ writeJsonFile(configPath, config);
8589
+ cached = config;
8590
+ return config;
8591
+ }
8592
+ function updateConfig(patch) {
8593
+ return saveConfig({ ...loadConfig(), ...patch });
8594
+ }
8595
+ function normalizeApiUrl(value) {
8596
+ const trimmed = value?.trim();
8597
+ if (!trimmed)
8598
+ return null;
8599
+ return trimmed.replace(/\/+$/, "");
8600
+ }
8601
+ function getLocalApiConfig(env = process.env) {
8602
+ const config = loadConfig();
8603
+ const envApiUrl = normalizeApiUrl(env["TODOS_URL"]);
8604
+ const configApiUrl = normalizeApiUrl(config.apiUrl);
8605
+ const apiUrl = envApiUrl ?? configApiUrl;
8606
+ const apiKey = env["TODOS_API_KEY"] || config.apiKey || null;
8607
+ return {
8608
+ apiUrl,
8609
+ apiKey,
8610
+ source: {
8611
+ apiUrl: envApiUrl ? "TODOS_URL" : configApiUrl ? "config" : "none",
8612
+ apiKey: env["TODOS_API_KEY"] ? "TODOS_API_KEY" : config.apiKey ? "config" : "none"
8613
+ }
8614
+ };
8615
+ }
8616
+ function getSyncAgentsFromConfig() {
8617
+ const config = loadConfig();
8618
+ const agents = config.sync_agents;
8619
+ if (Array.isArray(agents) && agents.length > 0)
8620
+ return agents.map(normalizeAgent);
8621
+ return null;
8622
+ }
8623
+ function getAgentTaskListId(agent) {
8624
+ const config = loadConfig();
8625
+ const key = normalizeAgent(agent);
8626
+ return config.agents?.[key]?.task_list_id || config.task_list_id || null;
8627
+ }
8628
+ function getAgentTasksDir(agent) {
8629
+ const config = loadConfig();
8630
+ const key = normalizeAgent(agent);
8631
+ return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
8632
+ }
8633
+ function getTaskPrefixConfig() {
8634
+ const config = loadConfig();
8635
+ return config.task_prefix || null;
8636
+ }
8637
+ function getAgentPoolForProject(workingDir) {
8638
+ const config = loadConfig();
8639
+ if (workingDir && config.project_pools) {
8640
+ let bestKey = null;
8641
+ let bestLen = 0;
8642
+ for (const key of Object.keys(config.project_pools)) {
8643
+ if (workingDir.startsWith(key) && key.length > bestLen) {
8644
+ bestKey = key;
8645
+ bestLen = key.length;
8646
+ }
8647
+ }
8648
+ if (bestKey && config.project_pools[bestKey]) {
8649
+ return config.project_pools[bestKey];
8650
+ }
8651
+ }
8652
+ return config.agent_pool || null;
8653
+ }
8654
+ function getCompletionGuardConfig(projectPath) {
8655
+ const config = loadConfig();
8656
+ const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
8657
+ if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
8658
+ return { ...global, ...config.project_overrides[projectPath].completion_guard };
8659
+ }
8660
+ return global;
8661
+ }
8662
+ var cached = null, GUARD_DEFAULTS;
8663
+ var init_config = __esm(() => {
8664
+ init_sync_utils();
8665
+ GUARD_DEFAULTS = {
8666
+ enabled: false,
8667
+ min_work_seconds: 30,
8668
+ max_completions_per_window: 5,
8669
+ window_minutes: 10,
8670
+ cooldown_seconds: 60
8671
+ };
8672
+ });
8673
+
8674
+ // src/lib/redaction.ts
8675
+ var exports_redaction = {};
8676
+ __export(exports_redaction, {
8677
+ upsertSecretSafetyConfig: () => upsertSecretSafetyConfig,
8678
+ redactValue: () => redactValue,
8679
+ redactEvidenceText: () => redactEvidenceText,
8680
+ listSecretFindings: () => listSecretFindings,
8681
+ hasSecretFindings: () => hasSecretFindings,
8682
+ getSecretSafetyConfig: () => getSecretSafetyConfig
8683
+ });
8684
+ function unique(values) {
8685
+ return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
8686
+ }
8687
+ function cloneRegex(regex) {
8688
+ return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
8689
+ }
8690
+ function customPatterns() {
8691
+ return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
8692
+ try {
8693
+ return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
8694
+ } catch {
8695
+ return [];
8696
+ }
8697
+ });
8698
+ }
8699
+ function secretPatterns() {
8700
+ return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
8701
+ }
8702
+ function isSecretKey(key) {
8703
+ if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
8704
+ return false;
8705
+ if (DEFAULT_SECRET_KEY_PATTERN.test(key))
8706
+ return true;
8707
+ return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
8708
+ }
8709
+ function redactEvidenceText(value) {
8710
+ let redacted = value;
8711
+ for (const pattern of secretPatterns()) {
8712
+ const regex = cloneRegex(pattern.regex);
8713
+ const replacement = pattern.replacement ?? "[REDACTED]";
8714
+ redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
8715
+ }
8716
+ return redacted;
8717
+ }
8718
+ function redactValue(value) {
8719
+ if (typeof value === "string")
8720
+ return redactEvidenceText(value);
8721
+ if (Array.isArray(value))
8722
+ return value.map(redactValue);
8723
+ if (value && typeof value === "object") {
8724
+ const redacted = {};
8725
+ for (const [key, child] of Object.entries(value)) {
8726
+ if (isSecretKey(key)) {
8727
+ redacted[key] = "[REDACTED]";
8728
+ } else {
8729
+ redacted[key] = redactValue(child);
8730
+ }
8731
+ }
8732
+ return redacted;
8733
+ }
8734
+ return value;
8735
+ }
8736
+ function listSecretFindings(value) {
8737
+ const findings = [];
8738
+ for (const pattern of secretPatterns()) {
8739
+ const matches = value.match(cloneRegex(pattern.regex));
8740
+ if (matches?.length)
8741
+ findings.push({ pattern: pattern.name, count: matches.length });
8742
+ }
8743
+ return findings;
8744
+ }
8745
+ function hasSecretFindings(value) {
8746
+ return listSecretFindings(value).length > 0;
8747
+ }
8748
+ function getSecretSafetyConfig() {
8749
+ return {
8750
+ redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
8751
+ redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
8752
+ };
8753
+ }
8754
+ function upsertSecretSafetyConfig(input) {
8755
+ const config = loadConfig();
8756
+ const next = {
8757
+ redaction_patterns: unique([...config.secret_safety?.redaction_patterns || [], ...input.redaction_patterns || []]),
8758
+ redaction_keys: unique([...config.secret_safety?.redaction_keys || [], ...input.redaction_keys || []])
8759
+ };
8760
+ saveConfig({ ...config, secret_safety: next });
8761
+ return next;
8762
+ }
8763
+ var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS;
8764
+ var init_redaction = __esm(() => {
8765
+ init_config();
8766
+ DEFAULT_SECRET_PATTERNS = [
8767
+ { name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
8768
+ { name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
8769
+ { name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
8770
+ { name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
8771
+ { name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
8772
+ ];
8773
+ DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
8774
+ NON_SECRET_USAGE_KEYS = new Set([
8775
+ "tokens",
8776
+ "total_tokens",
8777
+ "token_count",
8778
+ "input_tokens",
8779
+ "output_tokens",
8780
+ "prompt_tokens",
8781
+ "completion_tokens"
8782
+ ]);
8783
+ });
8784
+
8481
8785
  // src/cli/cloud-router.ts
8482
8786
  function getTodosCloudClient(env = process.env) {
8483
8787
  if (_cache !== undefined)
@@ -8503,14 +8807,18 @@ function unwrapTask(raw) {
8503
8807
  }
8504
8808
  function toListQuery(filter = {}) {
8505
8809
  const query = {};
8506
- if (typeof filter.status === "string")
8507
- query["status"] = filter.status;
8508
- if (typeof filter.priority === "string")
8509
- query["priority"] = filter.priority;
8810
+ if (filter.status)
8811
+ query["status"] = Array.isArray(filter.status) ? filter.status.join(",") : filter.status;
8812
+ if (filter.priority)
8813
+ query["priority"] = Array.isArray(filter.priority) ? filter.priority.join(",") : filter.priority;
8510
8814
  if (filter.project_id)
8511
8815
  query["project_id"] = filter.project_id;
8816
+ if (filter.parent_id !== undefined)
8817
+ query["parent_id"] = filter.parent_id ?? "";
8512
8818
  if (filter.plan_id)
8513
8819
  query["plan_id"] = filter.plan_id;
8820
+ if (filter.task_list_id)
8821
+ query["task_list_id"] = filter.task_list_id;
8514
8822
  if (filter.assigned_to)
8515
8823
  query["assigned_to"] = filter.assigned_to;
8516
8824
  if (filter.agent_id)
@@ -8566,10 +8874,97 @@ async function cloudListPlans(client, projectId) {
8566
8874
  }
8567
8875
  async function cloudAddComment(client, taskId, input) {
8568
8876
  const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/comments`, input);
8569
- if (raw && typeof raw === "object" && "comment" in raw) {
8570
- return raw.comment;
8877
+ const comment = raw && typeof raw === "object" && "comment" in raw ? raw.comment : raw;
8878
+ if (!isTaskComment(comment))
8879
+ throw new Error("Invalid cloud comment response");
8880
+ return redactComment(comment);
8881
+ }
8882
+ async function cloudListComments(client, taskId, options = {}) {
8883
+ const limit = options.limit ?? 100;
8884
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 500) {
8885
+ throw new Error("Cloud comment limit must be an integer between 1 and 500");
8571
8886
  }
8572
- return raw;
8887
+ if (options.cursor !== undefined && (typeof options.cursor !== "string" || !options.cursor || options.cursor.length > 1024)) {
8888
+ throw new Error("Cloud comment cursor must be a non-empty string");
8889
+ }
8890
+ let raw;
8891
+ try {
8892
+ raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/comments`, {
8893
+ query: { limit, ...options.cursor ? { cursor: options.cursor } : {} }
8894
+ });
8895
+ } catch (error) {
8896
+ const status = error && typeof error === "object" ? error.status : undefined;
8897
+ if (status === 404 || status === 405) {
8898
+ throw new Error("Cloud task comments require a compatible @hasna/todos server; deploy the server endpoint before this CLI.", { cause: error });
8899
+ }
8900
+ throw error;
8901
+ }
8902
+ const envelope = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
8903
+ const candidate = Array.isArray(raw) ? raw : envelope?.comments;
8904
+ if (!Array.isArray(candidate) || !candidate.every(isTaskComment)) {
8905
+ throw new Error("Invalid cloud comments response");
8906
+ }
8907
+ if (envelope?.count !== undefined && (!Number.isSafeInteger(envelope.count) || envelope.count < 0 || envelope.count !== candidate.length)) {
8908
+ throw new Error("Invalid cloud comments response count");
8909
+ }
8910
+ const hasHasMore = envelope ? Object.prototype.hasOwnProperty.call(envelope, "has_more") : false;
8911
+ const hasNextCursor = envelope ? Object.prototype.hasOwnProperty.call(envelope, "next_cursor") : false;
8912
+ if (hasHasMore !== hasNextCursor)
8913
+ throw new Error("Invalid cloud comments pagination response");
8914
+ const paginationSupported = hasHasMore && hasNextCursor;
8915
+ if (!paginationSupported) {
8916
+ const comments = candidate.slice(-limit).map(redactComment);
8917
+ return {
8918
+ comments,
8919
+ count: comments.length,
8920
+ has_more: candidate.length > limit,
8921
+ next_cursor: null,
8922
+ limit,
8923
+ pagination_supported: false
8924
+ };
8925
+ }
8926
+ if (candidate.length > limit)
8927
+ throw new Error("Invalid cloud comments response: page exceeds requested limit");
8928
+ const hasMore = envelope.has_more;
8929
+ const nextCursor = envelope.next_cursor;
8930
+ if (typeof hasMore !== "boolean" || nextCursor !== null && (typeof nextCursor !== "string" || !nextCursor)) {
8931
+ throw new Error("Invalid cloud comments pagination response");
8932
+ }
8933
+ if (hasMore && nextCursor === null || !hasMore && nextCursor !== null) {
8934
+ throw new Error("Invalid cloud comments pagination response");
8935
+ }
8936
+ return {
8937
+ comments: candidate.map(redactComment),
8938
+ count: candidate.length,
8939
+ has_more: hasMore,
8940
+ next_cursor: nextCursor,
8941
+ limit,
8942
+ pagination_supported: true
8943
+ };
8944
+ }
8945
+ function isTaskComment(value) {
8946
+ if (!value || typeof value !== "object" || Array.isArray(value))
8947
+ return false;
8948
+ const comment = value;
8949
+ return typeof comment["id"] === "string" && typeof comment["task_id"] === "string" && (comment["agent_id"] === null || typeof comment["agent_id"] === "string") && (comment["session_id"] === null || typeof comment["session_id"] === "string") && typeof comment["content"] === "string" && (comment["type"] === "comment" || comment["type"] === "progress" || comment["type"] === "note") && (comment["progress_pct"] === null || typeof comment["progress_pct"] === "number") && typeof comment["created_at"] === "string";
8950
+ }
8951
+ function redactComment(comment) {
8952
+ return { ...comment, content: redactEvidenceText(comment.content) };
8953
+ }
8954
+ async function cloudTaskHistory(client, taskId) {
8955
+ const raw = await client.transport.get(`/tasks/${encodeURIComponent(taskId)}/history`);
8956
+ const envelope = raw ?? {};
8957
+ if (Array.isArray(envelope.history))
8958
+ return envelope.history;
8959
+ return Array.isArray(raw) ? raw : [];
8960
+ }
8961
+ async function cloudUpsertTaskByFingerprint(client, input) {
8962
+ const raw = await client.transport.post("/tasks/upsert", input);
8963
+ const envelope = raw ?? {};
8964
+ return {
8965
+ task: unwrapTask(envelope.task ?? raw),
8966
+ created: Boolean(envelope.created)
8967
+ };
8573
8968
  }
8574
8969
  async function cloudCountTasks(client, filter = {}) {
8575
8970
  const { limit: _drop, offset: _o, ...rest } = filter;
@@ -8642,8 +9037,8 @@ async function cloudLockTask(client, id, agentId) {
8642
9037
  }
8643
9038
  return raw ?? { success: true };
8644
9039
  }
8645
- async function cloudUnlockTask(client, id, agentId) {
8646
- const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, agentId ? { agent_id: agentId } : {});
9040
+ async function cloudUnlockTask(client, id, agentId, force = false) {
9041
+ const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, { ...agentId ? { agent_id: agentId } : {}, ...force ? { force: true } : {} });
8647
9042
  if (raw && typeof raw === "object" && "success" in raw) {
8648
9043
  return Boolean(raw.success);
8649
9044
  }
@@ -8772,6 +9167,25 @@ async function cloudListTaskLists(client, projectId) {
8772
9167
  return envelope.taskLists;
8773
9168
  return Array.isArray(raw) ? raw : [];
8774
9169
  }
9170
+ async function cloudResolveTaskListRef(client, ref, projectId) {
9171
+ const lists = await cloudListTaskLists(client, projectId);
9172
+ const exact = lists.find((list) => list.id === ref || list.slug === ref);
9173
+ if (exact)
9174
+ return exact.id;
9175
+ const prefixes = lists.filter((list) => list.id.startsWith(ref));
9176
+ return prefixes.length === 1 ? prefixes[0].id : null;
9177
+ }
9178
+ async function cloudCreateTaskList(client, input) {
9179
+ const raw = await client.transport.post("/task-lists", input);
9180
+ if (raw && typeof raw === "object" && "task_list" in raw) {
9181
+ return raw.task_list;
9182
+ }
9183
+ return raw;
9184
+ }
9185
+ async function cloudDeleteTaskList(client, id) {
9186
+ await client.delete("task-lists", id);
9187
+ return true;
9188
+ }
8775
9189
  async function cloudNextTask(client, agent, filters) {
8776
9190
  const query = {};
8777
9191
  if (agent)
@@ -8796,11 +9210,11 @@ async function cloudAllDependencies(client) {
8796
9210
  return Array.isArray(raw) ? raw : [];
8797
9211
  }
8798
9212
  async function cloudGetTasksByIds(client, ids) {
8799
- const unique = Array.from(new Set(ids));
9213
+ const unique2 = Array.from(new Set(ids));
8800
9214
  const map = new Map;
8801
9215
  const CONCURRENCY = 8;
8802
- for (let i = 0;i < unique.length; i += CONCURRENCY) {
8803
- const batch = unique.slice(i, i + CONCURRENCY);
9216
+ for (let i = 0;i < unique2.length; i += CONCURRENCY) {
9217
+ const batch = unique2.slice(i, i + CONCURRENCY);
8804
9218
  const tasks = await Promise.all(batch.map((id) => cloudGetTask(client, id)));
8805
9219
  for (const task of tasks)
8806
9220
  if (task && task.id)
@@ -8898,6 +9312,7 @@ async function cloudTimeline(client, options = {}) {
8898
9312
  var _cache, PRIORITY_RANK;
8899
9313
  var init_cloud_router = __esm(() => {
8900
9314
  init_storage();
9315
+ init_redaction();
8901
9316
  PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
8902
9317
  });
8903
9318
 
@@ -11200,7 +11615,7 @@ var init_schema = __esm(() => {
11200
11615
  });
11201
11616
 
11202
11617
  // src/db/machines.ts
11203
- import { existsSync as existsSync2 } from "fs";
11618
+ import { existsSync as existsSync4 } from "fs";
11204
11619
  import { hostname as osHostname, platform as osPlatform, arch as osArch } from "os";
11205
11620
  import { resolve } from "path";
11206
11621
  import { spawnSync } from "child_process";
@@ -11407,7 +11822,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
11407
11822
  message: `${project.name} has ${distinctPaths.length} different machine-local paths`
11408
11823
  });
11409
11824
  }
11410
- if (localRow && !existsSync2(localRow.path)) {
11825
+ if (localRow && !existsSync4(localRow.path)) {
11411
11826
  pathIssues.push({
11412
11827
  type: "path_missing",
11413
11828
  project_id: project.id,
@@ -11418,7 +11833,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
11418
11833
  message: `Local path does not exist on this machine: ${localRow.path}`
11419
11834
  });
11420
11835
  }
11421
- if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync2(project.path)) {
11836
+ if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync4(project.path)) {
11422
11837
  pathIssues.push({
11423
11838
  type: "path_missing",
11424
11839
  project_id: project.id,
@@ -11526,8 +11941,8 @@ var init_machines = __esm(() => {
11526
11941
  });
11527
11942
 
11528
11943
  // src/storage/config.ts
11529
- var exports_config = {};
11530
- __export(exports_config, {
11944
+ var exports_config2 = {};
11945
+ __export(exports_config2, {
11531
11946
  parseStorageMode: () => parseStorageMode,
11532
11947
  loadTodosStorageConfig: () => loadTodosStorageConfig,
11533
11948
  loadStorageConfig: () => loadStorageConfig,
@@ -11690,7 +12105,7 @@ function parsePositiveInteger(value, fallback) {
11690
12105
  return parsed;
11691
12106
  }
11692
12107
  var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER = "hasna-xyz-infra-apps-prod-postgres", CANONICAL_TODOS_RDS_DATABASE = "todos", CANONICAL_TODOS_RDS_RUNTIME_PATH = "hasna/xyz/opensource/todos/prod/rds";
11693
- var init_config = __esm(() => {
12108
+ var init_config2 = __esm(() => {
11694
12109
  TODOS_STORAGE_TABLES = [
11695
12110
  "todos_sync_records",
11696
12111
  "todos_sync_cursors"
@@ -11829,8 +12244,8 @@ __export(exports_database, {
11829
12244
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
11830
12245
  });
11831
12246
  import { Database } from "bun:sqlite";
11832
- import { existsSync as existsSync3, mkdirSync } from "fs";
11833
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
12247
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
12248
+ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
11834
12249
  function isInMemoryDb(path) {
11835
12250
  return path === ":memory:" || path.startsWith("file::memory:");
11836
12251
  }
@@ -11839,12 +12254,12 @@ function findNearestProjectDb(startDir) {
11839
12254
  const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
11840
12255
  let dir = resolve2(startDir);
11841
12256
  while (true) {
11842
- const candidate = join2(dir, ".hasna", "todos", "todos.db");
11843
- if (existsSync3(candidate))
12257
+ const candidate = join4(dir, ".hasna", "todos", "todos.db");
12258
+ if (existsSync5(candidate))
11844
12259
  return candidate;
11845
12260
  if (dir === stopAt)
11846
12261
  break;
11847
- const parent = dirname2(dir);
12262
+ const parent = dirname3(dir);
11848
12263
  if (parent === dir)
11849
12264
  break;
11850
12265
  dir = parent;
@@ -11854,9 +12269,9 @@ function findNearestProjectDb(startDir) {
11854
12269
  function findGitRoot(startDir) {
11855
12270
  let dir = resolve2(startDir);
11856
12271
  while (true) {
11857
- if (existsSync3(join2(dir, ".git")))
12272
+ if (existsSync5(join4(dir, ".git")))
11858
12273
  return dir;
11859
- const parent = dirname2(dir);
12274
+ const parent = dirname3(dir);
11860
12275
  if (parent === dir)
11861
12276
  break;
11862
12277
  dir = parent;
@@ -11877,25 +12292,25 @@ function getDbPath() {
11877
12292
  if (process.env["TODOS_DB_SCOPE"] === "project") {
11878
12293
  const gitRoot = findGitRoot(cwd);
11879
12294
  if (gitRoot) {
11880
- return join2(gitRoot, ".hasna", "todos", "todos.db");
12295
+ return join4(gitRoot, ".hasna", "todos", "todos.db");
11881
12296
  }
11882
12297
  }
11883
12298
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
11884
- return join2(home, ".hasna", "todos", "todos.db");
12299
+ return join4(home, ".hasna", "todos", "todos.db");
11885
12300
  }
11886
12301
  function getDatabasePath() {
11887
12302
  return getDbPath();
11888
12303
  }
11889
- function ensureDir(filePath) {
12304
+ function ensureDir2(filePath) {
11890
12305
  if (isInMemoryDb(filePath))
11891
12306
  return;
11892
- const dir = dirname2(resolve2(filePath));
11893
- if (!existsSync3(dir)) {
11894
- mkdirSync(dir, { recursive: true });
12307
+ const dir = dirname3(resolve2(filePath));
12308
+ if (!existsSync5(dir)) {
12309
+ mkdirSync2(dir, { recursive: true });
11895
12310
  }
11896
12311
  }
11897
12312
  function openDatabase(path) {
11898
- ensureDir(path);
12313
+ ensureDir2(path);
11899
12314
  const db = new Database(path);
11900
12315
  db.run("PRAGMA journal_mode = WAL");
11901
12316
  db.run("PRAGMA busy_timeout = 5000");
@@ -11908,7 +12323,7 @@ function openDatabase(path) {
11908
12323
  }
11909
12324
  function maybeInstallShadowCapture(db) {
11910
12325
  try {
11911
- const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (init_config(), __toCommonJS(exports_config));
12326
+ const { isTodosShadowEnabled: isTodosShadowEnabled2 } = (init_config2(), __toCommonJS(exports_config2));
11912
12327
  if (!isTodosShadowEnabled2())
11913
12328
  return;
11914
12329
  const { installShadowOutboxSchema: installShadowOutboxSchema2 } = (init_shadow_outbox_schema(), __toCommonJS(exports_shadow_outbox_schema));
@@ -12689,199 +13104,6 @@ var init_helpers = __esm(() => {
12689
13104
  };
12690
13105
  });
12691
13106
 
12692
- // src/lib/sync-utils.ts
12693
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
12694
- import { join as join3 } from "path";
12695
- function getHomeDir() {
12696
- return process.env["HOME"] || process.env["USERPROFILE"] || "~";
12697
- }
12698
- function getTodosGlobalDir() {
12699
- return join3(getHomeDir(), ".hasna", "todos");
12700
- }
12701
- function ensureDir2(dir) {
12702
- if (!existsSync4(dir))
12703
- mkdirSync2(dir, { recursive: true });
12704
- }
12705
- function listJsonFiles(dir) {
12706
- if (!existsSync4(dir))
12707
- return [];
12708
- return readdirSync(dir).filter((f) => f.endsWith(".json"));
12709
- }
12710
- function readJsonFile(path) {
12711
- try {
12712
- return JSON.parse(readFileSync2(path, "utf-8"));
12713
- } catch {
12714
- return null;
12715
- }
12716
- }
12717
- function writeJsonFile(path, data) {
12718
- writeFileSync(path, JSON.stringify(data, null, 2) + `
12719
- `);
12720
- }
12721
- function readHighWaterMark(dir) {
12722
- const path = join3(dir, ".highwatermark");
12723
- if (!existsSync4(path))
12724
- return 1;
12725
- const val = parseInt(readFileSync2(path, "utf-8").trim(), 10);
12726
- return isNaN(val) ? 1 : val;
12727
- }
12728
- function writeHighWaterMark(dir, value) {
12729
- writeFileSync(join3(dir, ".highwatermark"), String(value));
12730
- }
12731
- function getFileMtimeMs(path) {
12732
- try {
12733
- return statSync(path).mtimeMs;
12734
- } catch {
12735
- return null;
12736
- }
12737
- }
12738
- function parseTimestamp(value) {
12739
- if (typeof value !== "string")
12740
- return null;
12741
- const parsed = Date.parse(value);
12742
- return Number.isNaN(parsed) ? null : parsed;
12743
- }
12744
- function appendSyncConflict(metadata, conflict, limit = 5) {
12745
- const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
12746
- const next = [conflict, ...current].slice(0, limit);
12747
- return { ...metadata, sync_conflicts: next };
12748
- }
12749
- var HOME;
12750
- var init_sync_utils = __esm(() => {
12751
- HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
12752
- });
12753
-
12754
- // src/lib/config.ts
12755
- var exports_config2 = {};
12756
- __export(exports_config2, {
12757
- updateConfig: () => updateConfig,
12758
- saveConfig: () => saveConfig,
12759
- resetConfig: () => resetConfig,
12760
- normalizeApiUrl: () => normalizeApiUrl,
12761
- loadConfig: () => loadConfig,
12762
- getTaskPrefixConfig: () => getTaskPrefixConfig,
12763
- getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
12764
- getLocalApiConfig: () => getLocalApiConfig,
12765
- getConfigPath: () => getConfigPath,
12766
- getCompletionGuardConfig: () => getCompletionGuardConfig,
12767
- getAgentTasksDir: () => getAgentTasksDir,
12768
- getAgentTaskListId: () => getAgentTaskListId,
12769
- getAgentPoolForProject: () => getAgentPoolForProject
12770
- });
12771
- import { existsSync as existsSync5 } from "fs";
12772
- import { dirname as dirname3, join as join4 } from "path";
12773
- function getConfigPath() {
12774
- return join4(getTodosGlobalDir(), "config.json");
12775
- }
12776
- function resetConfig() {
12777
- cached = null;
12778
- }
12779
- function normalizeAgent(agent) {
12780
- return agent.trim().toLowerCase();
12781
- }
12782
- function loadConfig() {
12783
- if (cached)
12784
- return cached;
12785
- if (!existsSync5(getConfigPath())) {
12786
- cached = {};
12787
- return cached;
12788
- }
12789
- const config = readJsonFile(getConfigPath()) || {};
12790
- if (typeof config.sync_agents === "string") {
12791
- config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
12792
- }
12793
- cached = config;
12794
- return cached;
12795
- }
12796
- function saveConfig(config) {
12797
- const configPath = getConfigPath();
12798
- ensureDir2(dirname3(configPath));
12799
- writeJsonFile(configPath, config);
12800
- cached = config;
12801
- return config;
12802
- }
12803
- function updateConfig(patch) {
12804
- return saveConfig({ ...loadConfig(), ...patch });
12805
- }
12806
- function normalizeApiUrl(value) {
12807
- const trimmed = value?.trim();
12808
- if (!trimmed)
12809
- return null;
12810
- return trimmed.replace(/\/+$/, "");
12811
- }
12812
- function getLocalApiConfig(env = process.env) {
12813
- const config = loadConfig();
12814
- const envApiUrl = normalizeApiUrl(env["TODOS_URL"]);
12815
- const configApiUrl = normalizeApiUrl(config.apiUrl);
12816
- const apiUrl = envApiUrl ?? configApiUrl;
12817
- const apiKey = env["TODOS_API_KEY"] || config.apiKey || null;
12818
- return {
12819
- apiUrl,
12820
- apiKey,
12821
- source: {
12822
- apiUrl: envApiUrl ? "TODOS_URL" : configApiUrl ? "config" : "none",
12823
- apiKey: env["TODOS_API_KEY"] ? "TODOS_API_KEY" : config.apiKey ? "config" : "none"
12824
- }
12825
- };
12826
- }
12827
- function getSyncAgentsFromConfig() {
12828
- const config = loadConfig();
12829
- const agents = config.sync_agents;
12830
- if (Array.isArray(agents) && agents.length > 0)
12831
- return agents.map(normalizeAgent);
12832
- return null;
12833
- }
12834
- function getAgentTaskListId(agent) {
12835
- const config = loadConfig();
12836
- const key = normalizeAgent(agent);
12837
- return config.agents?.[key]?.task_list_id || config.task_list_id || null;
12838
- }
12839
- function getAgentTasksDir(agent) {
12840
- const config = loadConfig();
12841
- const key = normalizeAgent(agent);
12842
- return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
12843
- }
12844
- function getTaskPrefixConfig() {
12845
- const config = loadConfig();
12846
- return config.task_prefix || null;
12847
- }
12848
- function getAgentPoolForProject(workingDir) {
12849
- const config = loadConfig();
12850
- if (workingDir && config.project_pools) {
12851
- let bestKey = null;
12852
- let bestLen = 0;
12853
- for (const key of Object.keys(config.project_pools)) {
12854
- if (workingDir.startsWith(key) && key.length > bestLen) {
12855
- bestKey = key;
12856
- bestLen = key.length;
12857
- }
12858
- }
12859
- if (bestKey && config.project_pools[bestKey]) {
12860
- return config.project_pools[bestKey];
12861
- }
12862
- }
12863
- return config.agent_pool || null;
12864
- }
12865
- function getCompletionGuardConfig(projectPath) {
12866
- const config = loadConfig();
12867
- const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
12868
- if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
12869
- return { ...global, ...config.project_overrides[projectPath].completion_guard };
12870
- }
12871
- return global;
12872
- }
12873
- var cached = null, GUARD_DEFAULTS;
12874
- var init_config2 = __esm(() => {
12875
- init_sync_utils();
12876
- GUARD_DEFAULTS = {
12877
- enabled: false,
12878
- min_work_seconds: 30,
12879
- max_completions_per_window: 5,
12880
- window_minutes: 10,
12881
- cooldown_seconds: 60
12882
- };
12883
- });
12884
-
12885
13107
  // src/lib/completion-guard.ts
12886
13108
  function checkCompletionGuard(task, agentId, db, configOverride) {
12887
13109
  let config;
@@ -12927,7 +13149,7 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
12927
13149
  }
12928
13150
  var init_completion_guard = __esm(() => {
12929
13151
  init_types();
12930
- init_config2();
13152
+ init_config();
12931
13153
  init_projects();
12932
13154
  });
12933
13155
 
@@ -12982,117 +13204,6 @@ var init_event_emission_safety = __esm(() => {
12982
13204
  init_sync_utils();
12983
13205
  });
12984
13206
 
12985
- // src/lib/redaction.ts
12986
- var exports_redaction = {};
12987
- __export(exports_redaction, {
12988
- upsertSecretSafetyConfig: () => upsertSecretSafetyConfig,
12989
- redactValue: () => redactValue,
12990
- redactEvidenceText: () => redactEvidenceText,
12991
- listSecretFindings: () => listSecretFindings,
12992
- hasSecretFindings: () => hasSecretFindings,
12993
- getSecretSafetyConfig: () => getSecretSafetyConfig
12994
- });
12995
- function unique(values) {
12996
- return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
12997
- }
12998
- function cloneRegex(regex) {
12999
- return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
13000
- }
13001
- function customPatterns() {
13002
- return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
13003
- try {
13004
- return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
13005
- } catch {
13006
- return [];
13007
- }
13008
- });
13009
- }
13010
- function secretPatterns() {
13011
- return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
13012
- }
13013
- function isSecretKey(key) {
13014
- if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
13015
- return false;
13016
- if (DEFAULT_SECRET_KEY_PATTERN.test(key))
13017
- return true;
13018
- return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
13019
- }
13020
- function redactEvidenceText(value) {
13021
- let redacted = value;
13022
- for (const pattern of secretPatterns()) {
13023
- const regex = cloneRegex(pattern.regex);
13024
- const replacement = pattern.replacement ?? "[REDACTED]";
13025
- redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
13026
- }
13027
- return redacted;
13028
- }
13029
- function redactValue(value) {
13030
- if (typeof value === "string")
13031
- return redactEvidenceText(value);
13032
- if (Array.isArray(value))
13033
- return value.map(redactValue);
13034
- if (value && typeof value === "object") {
13035
- const redacted = {};
13036
- for (const [key, child] of Object.entries(value)) {
13037
- if (isSecretKey(key)) {
13038
- redacted[key] = "[REDACTED]";
13039
- } else {
13040
- redacted[key] = redactValue(child);
13041
- }
13042
- }
13043
- return redacted;
13044
- }
13045
- return value;
13046
- }
13047
- function listSecretFindings(value) {
13048
- const findings = [];
13049
- for (const pattern of secretPatterns()) {
13050
- const matches = value.match(cloneRegex(pattern.regex));
13051
- if (matches?.length)
13052
- findings.push({ pattern: pattern.name, count: matches.length });
13053
- }
13054
- return findings;
13055
- }
13056
- function hasSecretFindings(value) {
13057
- return listSecretFindings(value).length > 0;
13058
- }
13059
- function getSecretSafetyConfig() {
13060
- return {
13061
- redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
13062
- redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
13063
- };
13064
- }
13065
- function upsertSecretSafetyConfig(input) {
13066
- const config = loadConfig();
13067
- const next = {
13068
- redaction_patterns: unique([...config.secret_safety?.redaction_patterns || [], ...input.redaction_patterns || []]),
13069
- redaction_keys: unique([...config.secret_safety?.redaction_keys || [], ...input.redaction_keys || []])
13070
- };
13071
- saveConfig({ ...config, secret_safety: next });
13072
- return next;
13073
- }
13074
- var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS;
13075
- var init_redaction = __esm(() => {
13076
- init_config2();
13077
- DEFAULT_SECRET_PATTERNS = [
13078
- { name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
13079
- { name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
13080
- { name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
13081
- { name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
13082
- { name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
13083
- ];
13084
- DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
13085
- NON_SECRET_USAGE_KEYS = new Set([
13086
- "tokens",
13087
- "total_tokens",
13088
- "token_count",
13089
- "input_tokens",
13090
- "output_tokens",
13091
- "prompt_tokens",
13092
- "completion_tokens"
13093
- ]);
13094
- });
13095
-
13096
13207
  // src/lib/workspace-trust.ts
13097
13208
  var exports_workspace_trust = {};
13098
13209
  __export(exports_workspace_trust, {
@@ -13238,7 +13349,7 @@ function checkWorkspacePermission(input = {}) {
13238
13349
  }
13239
13350
  var DEFAULT_DENYLIST, DEFAULT_ENV_REDACTIONS, PRESET_DEFAULTS;
13240
13351
  var init_workspace_trust = __esm(() => {
13241
- init_config2();
13352
+ init_config();
13242
13353
  DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
13243
13354
  DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
13244
13355
  PRESET_DEFAULTS = {
@@ -13481,7 +13592,7 @@ function explainRunnerSandbox(input = {}) {
13481
13592
  }
13482
13593
  var DEFAULT_COMMAND_DENYLIST, DEFAULT_ENV_REDACTIONS2;
13483
13594
  var init_runner_sandbox = __esm(() => {
13484
- init_config2();
13595
+ init_config();
13485
13596
  init_workspace_trust();
13486
13597
  DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
13487
13598
  DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
@@ -13728,7 +13839,7 @@ var LOCAL_EVENT_TYPES, VALID_TARGETS;
13728
13839
  var init_event_hooks = __esm(() => {
13729
13840
  init_redaction();
13730
13841
  init_runner_sandbox();
13731
- init_config2();
13842
+ init_config();
13732
13843
  init_event_emission_safety();
13733
13844
  LOCAL_EVENT_TYPES = [
13734
13845
  "task.created",
@@ -18613,7 +18724,7 @@ function getComment(id, db) {
18613
18724
  }
18614
18725
  function listComments(taskId, db) {
18615
18726
  const d = db || getDatabase();
18616
- return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
18727
+ return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at, rowid").all(taskId);
18617
18728
  }
18618
18729
  function updateComment(id, input, db) {
18619
18730
  const d = db || getDatabase();
@@ -20241,10 +20352,27 @@ var init_task_routing = __esm(() => {
20241
20352
  // src/cli/commands/task-commands.ts
20242
20353
  var exports_task_commands = {};
20243
20354
  __export(exports_task_commands, {
20244
- registerTaskCommands: () => registerTaskCommands
20355
+ registerTaskCommands: () => registerTaskCommands,
20356
+ escapeTerminalControls: () => escapeTerminalControls
20245
20357
  });
20246
20358
  import chalk2 from "chalk";
20247
20359
  import { basename as basename3, resolve as resolve9 } from "path";
20360
+ function escapeTerminalControls(value) {
20361
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => {
20362
+ const code = character.charCodeAt(0);
20363
+ if (code === 10)
20364
+ return "\\n";
20365
+ if (code === 13)
20366
+ return "\\r";
20367
+ if (code === 9)
20368
+ return "\\t";
20369
+ return `\\x${code.toString(16).padStart(2, "0")}`;
20370
+ });
20371
+ }
20372
+ function formatHumanComment(comment) {
20373
+ const agent = comment.agent_id ? chalk2.cyan(`[${escapeTerminalControls(comment.agent_id)}] `) : "";
20374
+ return ` ${agent}${chalk2.dim(escapeTerminalControls(comment.created_at))}: ${escapeTerminalControls(comment.content)}`;
20375
+ }
20248
20376
  function resolveProjectIdOrSlug(input) {
20249
20377
  const db = getDatabase();
20250
20378
  if (isPathLike(input)) {
@@ -20401,18 +20529,24 @@ function registerTaskCommands(program2) {
20401
20529
  if (cloud) {
20402
20530
  let task3;
20403
20531
  try {
20532
+ const cloudProjectId = opts.project || globalOpts.project;
20533
+ const cloudTaskListId = opts.list ? await cloudResolveTaskListRef(cloud, opts.list, cloudProjectId) : undefined;
20534
+ if (opts.list && !cloudTaskListId) {
20535
+ throw new Error(`Could not resolve task list ID or slug: ${opts.list}`);
20536
+ }
20404
20537
  task3 = await cloudCreateTask(cloud, {
20405
20538
  title,
20406
20539
  description: opts.description,
20407
20540
  priority: parsePriority(opts.priority),
20541
+ parent_id: opts.parent,
20408
20542
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
20409
20543
  plan_id: opts.plan,
20410
20544
  assigned_to: opts.assign,
20411
20545
  status: parseStatus(opts.status),
20412
- task_list_id: opts.list,
20546
+ task_list_id: cloudTaskListId,
20413
20547
  agent_id: globalOpts.agent,
20414
20548
  session_id: globalOpts.session,
20415
- project_id: opts.project || globalOpts.project,
20549
+ project_id: cloudProjectId,
20416
20550
  estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
20417
20551
  sla_minutes: opts.slaMinutes !== undefined || opts.sla !== undefined ? parseIntOption(opts.slaMinutes ?? opts.sla, "--sla-minutes") : undefined,
20418
20552
  requires_approval: opts.approval || undefined,
@@ -20478,7 +20612,7 @@ function registerTaskCommands(program2) {
20478
20612
  }
20479
20613
  });
20480
20614
  const task = program2.command("task").description("Task subcommands for deterministic automation");
20481
- task.command("upsert").description("Create or update a task by stable metadata fingerprint").requiredOption("--fingerprint <key>", "Stable dedupe fingerprint").requiredOption("--title <text>", "Task title").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("-s, --status <status>", "Task status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--metadata-json <json>", "JSON object merged into task metadata").option("--working-dir <path>", "Working directory to store on create/update").option("--project <id>", "Assign to project by ID, slug, or path").option("--assign <agent>", "Assign to agent").option("--expectation-id <id>", "Expectation metadata ID").option("--expectation-fingerprint <key>", "Expectation metadata fingerprint").option("--evidence-paths <paths>", "Comma-separated evidence paths").option("--origin-loop-id <id>", "Origin loop ID").option("--origin-run-id <id>", "Origin run ID").option("--expected <json-or-text>", "Expected value metadata").option("--observed <json-or-text>", "Observed value metadata").option("--acceptance <json-or-text>", "Acceptance metadata").action((opts) => {
20615
+ task.command("upsert").description("Create or update a task by stable metadata fingerprint").requiredOption("--fingerprint <key>", "Stable dedupe fingerprint").requiredOption("--title <text>", "Task title").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("-s, --status <status>", "Task status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--metadata-json <json>", "JSON object merged into task metadata").option("--working-dir <path>", "Working directory to store on create/update").option("--project <id>", "Assign to project by ID, slug, or path").option("--assign <agent>", "Assign to agent").option("--expectation-id <id>", "Expectation metadata ID").option("--expectation-fingerprint <key>", "Expectation metadata fingerprint").option("--evidence-paths <paths>", "Comma-separated evidence paths").option("--origin-loop-id <id>", "Origin loop ID").option("--origin-run-id <id>", "Origin run ID").option("--expected <json-or-text>", "Expected value metadata").option("--observed <json-or-text>", "Observed value metadata").option("--acceptance <json-or-text>", "Acceptance metadata").action(async (opts) => {
20482
20616
  const globalOpts = program2.opts();
20483
20617
  opts.tags = opts.tags || opts.tag;
20484
20618
  opts.list = opts.list || opts.taskList;
@@ -20493,6 +20627,34 @@ function registerTaskCommands(program2) {
20493
20627
  }
20494
20628
  return id;
20495
20629
  })() : undefined;
20630
+ const cloud = getTodosCloudClient();
20631
+ if (cloud) {
20632
+ let cloudResult;
20633
+ try {
20634
+ cloudResult = await cloudUpsertTaskByFingerprint(cloud, {
20635
+ fingerprint: opts.fingerprint,
20636
+ title: opts.title,
20637
+ description: opts.description,
20638
+ priority: parsePriority(opts.priority),
20639
+ status: parseStatus(opts.status),
20640
+ task_list_id: taskListId,
20641
+ tags: parseTags(opts.tags),
20642
+ metadata: buildExpectationMetadata(opts),
20643
+ working_dir: opts.workingDir ? resolve9(opts.workingDir) : process.cwd(),
20644
+ project_id: projectId,
20645
+ assigned_to: opts.assign
20646
+ });
20647
+ } catch (e) {
20648
+ handleError(e);
20649
+ }
20650
+ if (globalOpts.json) {
20651
+ output(cloudResult, true);
20652
+ } else {
20653
+ console.log(chalk2.green(cloudResult.created ? "Task created:" : "Task updated:"));
20654
+ console.log(formatTaskLine(cloudResult.task));
20655
+ }
20656
+ return;
20657
+ }
20496
20658
  let result;
20497
20659
  try {
20498
20660
  result = upsertTaskByFingerprint({
@@ -20743,7 +20905,22 @@ function registerTaskCommands(program2) {
20743
20905
  let task2;
20744
20906
  if (cloud) {
20745
20907
  const remote = await cloudGetTask(cloud, resolveTaskId(id));
20746
- task2 = remote ? { subtasks: [], dependencies: [], blocked_by: [], comments: [], ...remote, tags: remote.tags ?? [] } : null;
20908
+ const commentPage = remote ? await cloudListComments(cloud, remote.id) : null;
20909
+ task2 = remote ? {
20910
+ subtasks: [],
20911
+ dependencies: [],
20912
+ blocked_by: [],
20913
+ ...remote,
20914
+ tags: remote.tags ?? [],
20915
+ comments: commentPage.comments,
20916
+ comments_page: {
20917
+ count: commentPage.count,
20918
+ limit: commentPage.limit,
20919
+ has_more: commentPage.has_more,
20920
+ next_cursor: commentPage.next_cursor,
20921
+ pagination_supported: commentPage.pagination_supported
20922
+ }
20923
+ } : null;
20747
20924
  } else {
20748
20925
  const resolvedId = resolveTaskId(id);
20749
20926
  task2 = getTaskWithRelations(resolvedId);
@@ -20827,11 +21004,11 @@ function registerTaskCommands(program2) {
20827
21004
  }
20828
21005
  }
20829
21006
  if (task2.comments.length > 0) {
21007
+ const suffix = task2.comments_page?.has_more ? task2.comments_page.pagination_supported ? ", newer page shown; older comments available" : ", newer comments shown; older comments omitted until the server is upgraded" : "";
20830
21008
  console.log(chalk2.bold(`
20831
- Comments (${task2.comments.length}):`));
21009
+ Comments (${task2.comments.length}${suffix}):`));
20832
21010
  for (const c of task2.comments) {
20833
- const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
20834
- console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
21011
+ console.log(formatHumanComment(c));
20835
21012
  }
20836
21013
  }
20837
21014
  });
@@ -20857,7 +21034,23 @@ function registerTaskCommands(program2) {
20857
21034
  let task2;
20858
21035
  if (cloud) {
20859
21036
  const remote = await cloudGetTask(cloud, resolvedId);
20860
- task2 = remote ? { subtasks: [], dependencies: [], blocked_by: [], comments: [], checklist: [], ...remote, tags: remote.tags ?? [] } : null;
21037
+ const commentPage = remote ? await cloudListComments(cloud, remote.id) : null;
21038
+ task2 = remote ? {
21039
+ subtasks: [],
21040
+ dependencies: [],
21041
+ blocked_by: [],
21042
+ checklist: [],
21043
+ ...remote,
21044
+ tags: remote.tags ?? [],
21045
+ comments: commentPage.comments,
21046
+ comments_page: {
21047
+ count: commentPage.count,
21048
+ limit: commentPage.limit,
21049
+ has_more: commentPage.has_more,
21050
+ next_cursor: commentPage.next_cursor,
21051
+ pagination_supported: commentPage.pagination_supported
21052
+ }
21053
+ } : null;
20861
21054
  } else {
20862
21055
  task2 = getTaskWithRelations(resolvedId);
20863
21056
  }
@@ -20964,11 +21157,11 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
20964
21157
  console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
20965
21158
  }
20966
21159
  if (task2.comments.length > 0) {
21160
+ const suffix = task2.comments_page?.has_more ? task2.comments_page.pagination_supported ? ", newer page shown; older comments available" : ", newer comments shown; older comments omitted until the server is upgraded" : "";
20967
21161
  console.log(chalk2.bold(`
20968
- Comments (${task2.comments.length}):`));
21162
+ Comments (${task2.comments.length}${suffix}):`));
20969
21163
  for (const c of task2.comments) {
20970
- const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
20971
- console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
21164
+ console.log(formatHumanComment(c));
20972
21165
  }
20973
21166
  }
20974
21167
  if (task2.checklist && task2.checklist.length > 0) {
@@ -20985,8 +21178,18 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
20985
21178
  program2.command("history <id>").description("Show change history for a task (audit log)").action(async (id) => {
20986
21179
  const globalOpts = program2.opts();
20987
21180
  const resolvedId = resolveTaskId(id);
20988
- const { getTaskHistory: getTaskHistory2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
20989
- const history = getTaskHistory2(resolvedId);
21181
+ const cloud = getTodosCloudClient();
21182
+ let history;
21183
+ if (cloud) {
21184
+ try {
21185
+ history = await cloudTaskHistory(cloud, resolvedId);
21186
+ } catch (e) {
21187
+ handleError(e);
21188
+ }
21189
+ } else {
21190
+ const { getTaskHistory: getTaskHistory2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
21191
+ history = getTaskHistory2(resolvedId);
21192
+ }
20990
21193
  if (globalOpts.json) {
20991
21194
  output(history, true);
20992
21195
  return;
@@ -21254,7 +21457,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
21254
21457
  const resolvedId = resolveTaskId(id);
21255
21458
  try {
21256
21459
  if (cloud)
21257
- await cloudUnlockTask(cloud, resolvedId, globalOpts.agent);
21460
+ await cloudUnlockTask(cloud, resolvedId, globalOpts.agent, !globalOpts.agent);
21258
21461
  else
21259
21462
  unlockTask(resolvedId, globalOpts.agent);
21260
21463
  } catch (e) {
@@ -21292,7 +21495,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
21292
21495
  process.exit(1);
21293
21496
  }
21294
21497
  });
21295
- program2.command("bulk <action> <ids...>").description("Bulk operation on multiple tasks (done, start, delete, plan)").option("--plan <id>", "Plan ID for the plan/move-plan action").option("--clear-plan", "Remove plan assignment for the plan/move-plan action").action((action, ids, opts) => {
21498
+ program2.command("bulk <action> <ids...>").description("Bulk operation on multiple tasks (done, start, delete, plan)").option("--plan <id>", "Plan ID for the plan/move-plan action").option("--clear-plan", "Remove plan assignment for the plan/move-plan action").action(async (action, ids, opts) => {
21296
21499
  const globalOpts = program2.opts();
21297
21500
  const results = [];
21298
21501
  const isPlanAction = action === "plan" || action === "move-plan";
@@ -21301,6 +21504,45 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
21301
21504
  process.exit(1);
21302
21505
  }
21303
21506
  const planId = isPlanAction ? opts.plan ? resolvePlanId(opts.plan) : null : undefined;
21507
+ const knownActions = new Set(["done", "complete", "start", "delete", "plan", "move-plan"]);
21508
+ if (!knownActions.has(action)) {
21509
+ console.error(chalk2.red(`Unknown action: ${action}. Use: done, start, delete, plan`));
21510
+ process.exit(1);
21511
+ }
21512
+ const cloud = getTodosCloudClient();
21513
+ if (cloud) {
21514
+ for (const rawId of ids) {
21515
+ try {
21516
+ const resolvedId = resolveTaskId(rawId);
21517
+ if (action === "done" || action === "complete") {
21518
+ await cloudTaskAction(cloud, resolvedId, "complete", { agent_id: globalOpts.agent });
21519
+ } else if (action === "start") {
21520
+ await cloudTaskAction(cloud, resolvedId, "start", { agent_id: globalOpts.agent || "cli" });
21521
+ } else if (action === "delete") {
21522
+ await cloudDeleteTask(cloud, resolvedId);
21523
+ } else {
21524
+ const current = await cloudGetTask(cloud, resolvedId);
21525
+ if (!current)
21526
+ throw new Error(`Task not found: ${rawId}`);
21527
+ await cloudUpdateTask(cloud, resolvedId, { version: current.version, plan_id: planId });
21528
+ }
21529
+ results.push({ id: resolvedId, success: true });
21530
+ } catch (e) {
21531
+ results.push({ id: rawId, success: false, error: e instanceof Error ? e.message : String(e) });
21532
+ }
21533
+ }
21534
+ const succeededCloud = results.filter((r) => r.success).length;
21535
+ const failedCloud = results.filter((r) => !r.success).length;
21536
+ if (globalOpts.json) {
21537
+ output({ results, succeeded: succeededCloud, failed: failedCloud }, true);
21538
+ } else {
21539
+ console.log(chalk2.green(`${action}: ${succeededCloud} succeeded, ${failedCloud} failed`));
21540
+ for (const r of results.filter((r2) => !r2.success)) {
21541
+ console.log(chalk2.red(` ${r.id}: ${r.error}`));
21542
+ }
21543
+ }
21544
+ return;
21545
+ }
21304
21546
  for (const rawId of ids) {
21305
21547
  try {
21306
21548
  const resolvedId = resolveTaskId(rawId);
@@ -23170,7 +23412,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
23170
23412
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
23171
23413
  const dir = getTaskListDir(taskListId);
23172
23414
  if (!existsSync10(dir))
23173
- ensureDir2(dir);
23415
+ ensureDir(dir);
23174
23416
  const filter = {};
23175
23417
  if (projectId)
23176
23418
  filter["project_id"] = projectId;
@@ -23353,7 +23595,7 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
23353
23595
  }
23354
23596
  var init_claude_tasks = __esm(() => {
23355
23597
  init_tasks();
23356
- init_config2();
23598
+ init_config();
23357
23599
  init_sync_utils();
23358
23600
  });
23359
23601
 
@@ -23397,7 +23639,7 @@ function metadataKey(agent) {
23397
23639
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
23398
23640
  const dir = getTaskListDir2(agent, taskListId);
23399
23641
  if (!existsSync11(dir))
23400
- ensureDir2(dir);
23642
+ ensureDir(dir);
23401
23643
  const filter = {};
23402
23644
  if (projectId)
23403
23645
  filter["project_id"] = projectId;
@@ -23570,7 +23812,7 @@ function syncAgentTaskList(agent, taskListId, projectId, options = {}) {
23570
23812
  var init_agent_tasks = __esm(() => {
23571
23813
  init_tasks();
23572
23814
  init_sync_utils();
23573
- init_config2();
23815
+ init_config();
23574
23816
  });
23575
23817
 
23576
23818
  // src/lib/sync.ts
@@ -23642,7 +23884,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
23642
23884
  var init_sync = __esm(() => {
23643
23885
  init_claude_tasks();
23644
23886
  init_agent_tasks();
23645
- init_config2();
23887
+ init_config();
23646
23888
  });
23647
23889
 
23648
23890
  // src/lib/project-bootstrap.ts
@@ -31355,7 +31597,7 @@ function applyExportProfile(data, options = {}) {
31355
31597
  }
31356
31598
  var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value", TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge", TODOS_ENCRYPTION_SCHEMA_VERSION = 1, DEFAULT_ENCRYPTION_PROFILE = "default", DEFAULT_ENCRYPTION_KEY_ENV = "TODOS_ENCRYPTION_KEY", EncryptionKeyUnavailableError, EncryptedPayloadError;
31357
31599
  var init_local_encryption = __esm(() => {
31358
- init_config2();
31600
+ init_config();
31359
31601
  init_redaction();
31360
31602
  EncryptionKeyUnavailableError = class EncryptionKeyUnavailableError extends Error {
31361
31603
  keyEnv;
@@ -32534,7 +32776,7 @@ var init_project_commands = __esm(() => {
32534
32776
  init_cloud_router();
32535
32777
  init_saved_search_views();
32536
32778
  init_sync();
32537
- init_config2();
32779
+ init_config();
32538
32780
  init_helpers();
32539
32781
  });
32540
32782
 
@@ -33534,9 +33776,11 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
33534
33776
  program2.command("lists").aliases(["task-lists", "tl"]).description("List and manage task lists").option("--add <name>", "Create a task list").option("--slug <slug>", "Custom slug (with --add)").option("-d, --description <text>", "Description (with --add)").option("--delete <id>", "Delete a task list").action(async (opts) => {
33535
33777
  try {
33536
33778
  const globalOpts = program2.opts();
33537
- const projectId = autoProject(globalOpts);
33779
+ const cloud = getTodosCloudClient();
33780
+ const projectId = cloud ? globalOpts.project : autoProject(globalOpts);
33538
33781
  if (opts.add) {
33539
- const list = createTaskList({ name: opts.add, slug: opts.slug, description: opts.description, project_id: projectId });
33782
+ const input = { name: opts.add, slug: opts.slug, description: opts.description, project_id: projectId };
33783
+ const list = cloud ? await cloudCreateTaskList(cloud, input) : createTaskList(input);
33540
33784
  if (globalOpts.json) {
33541
33785
  output(list, true);
33542
33786
  return;
@@ -33548,6 +33792,14 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
33548
33792
  return;
33549
33793
  }
33550
33794
  if (opts.delete) {
33795
+ if (cloud) {
33796
+ const resolved2 = await cloudResolveTaskListRef(cloud, opts.delete, projectId ?? undefined);
33797
+ if (!resolved2)
33798
+ throw new Error(`Task list not found or ambiguous: ${opts.delete}`);
33799
+ await cloudDeleteTaskList(cloud, resolved2);
33800
+ console.log(chalk5.green("Task list deleted."));
33801
+ return;
33802
+ }
33551
33803
  const db = getDatabase();
33552
33804
  const resolved = resolvePartialId(db, "task_lists", opts.delete);
33553
33805
  if (!resolved) {
@@ -33558,7 +33810,6 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
33558
33810
  console.log(chalk5.green("Task list deleted."));
33559
33811
  return;
33560
33812
  }
33561
- const cloud = getTodosCloudClient();
33562
33813
  const lists = cloud ? await cloudListTaskLists(cloud, projectId ?? undefined) : listTaskLists(projectId);
33563
33814
  if (globalOpts.json) {
33564
33815
  output(lists, true);
@@ -34973,7 +35224,7 @@ function renderExtensionSummary(record) {
34973
35224
  }
34974
35225
  var BUILTIN_CLI_COMMANDS;
34975
35226
  var init_local_extensions = __esm(() => {
34976
- init_config2();
35227
+ init_config();
34977
35228
  init_mcp();
34978
35229
  init_package_version();
34979
35230
  init_redaction();
@@ -35405,7 +35656,7 @@ var init_policy_packs = __esm(() => {
35405
35656
  init_database();
35406
35657
  init_tasks();
35407
35658
  init_task_runs();
35408
- init_config2();
35659
+ init_config();
35409
35660
  });
35410
35661
 
35411
35662
  // src/db/checkpoints.ts
@@ -35928,7 +36179,7 @@ function describeTerminalNotificationRule(rule) {
35928
36179
  }
35929
36180
  var SEVERITY_ORDER, EVENT_SEVERITY, VALID_SEVERITIES, VALID_FORMATS;
35930
36181
  var init_terminal_notifications = __esm(() => {
35931
- init_config2();
36182
+ init_config();
35932
36183
  init_event_hooks();
35933
36184
  init_redaction();
35934
36185
  SEVERITY_ORDER = {
@@ -37431,7 +37682,7 @@ function createTodosCloudQueryClientFromEnv(env = process.env, options = {}) {
37431
37682
  return createTodosCloudQueryClient(url, options);
37432
37683
  }
37433
37684
  var init_cloud_client = __esm(() => {
37434
- init_config();
37685
+ init_config2();
37435
37686
  });
37436
37687
 
37437
37688
  // src/storage/sqlite-snapshot.ts
@@ -37813,6 +38064,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
37813
38064
  list: (filter = {}) => listTasks(filter, database()),
37814
38065
  count: (filter = {}) => countTasks(filter, database()),
37815
38066
  update: (id, input) => updateTask(id, input, database()),
38067
+ unlock: (id, agentId) => {
38068
+ unlockTask(id, agentId, database());
38069
+ return true;
38070
+ },
37816
38071
  delete: (id) => deleteTask(id, database()),
37817
38072
  start: (id, agentId) => startTask(id, agentId, database()),
37818
38073
  complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
@@ -37864,6 +38119,20 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
37864
38119
  logTaskChange: (taskId, action, field, oldValue, newValue, agentId) => logTaskChange(taskId, action, field, oldValue, newValue, agentId, database()),
37865
38120
  addComment: (input) => addComment(input, database()),
37866
38121
  getComments: (taskId) => listComments(taskId, database()),
38122
+ getCommentsPage: (taskId, options2) => {
38123
+ if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
38124
+ throw new Error("Comment limit must be an integer between 1 and 1001");
38125
+ }
38126
+ let comments = listComments(taskId, database());
38127
+ comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
38128
+ if (options2?.before) {
38129
+ const before = options2.before;
38130
+ comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
38131
+ }
38132
+ if (options2?.limit !== undefined)
38133
+ comments = comments.slice(-options2.limit);
38134
+ return comments;
38135
+ },
37867
38136
  getTaskHistory: (taskId) => getTaskHistory(taskId, database()),
37868
38137
  getRecentActivity: (limit) => getRecentActivity(limit, database())
37869
38138
  },
@@ -37921,6 +38190,12 @@ function postgresTodosSyncSchemaSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABL
37921
38190
  )`
37922
38191
  ];
37923
38192
  }
38193
+ function postgresTodosCommentCursorIndexSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABLE) {
38194
+ assertSafeIdentifier(tableName);
38195
+ return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${tableName}_comment_task_created_idx
38196
+ ON ${tableName} (service, (payload->>'task_id'), (payload->>'created_at'), object_id)
38197
+ WHERE object_type = 'comments' AND deleted_at IS NULL`;
38198
+ }
37924
38199
 
37925
38200
  class PostgresTodosSyncStore {
37926
38201
  client;
@@ -38446,7 +38721,7 @@ function __resetRuntimeShadowForTests() {
38446
38721
  }
38447
38722
  var _capturedDb = null, _outbox = null, _cloud = null, _exitRegistered = false;
38448
38723
  var init_shadow_runtime = __esm(() => {
38449
- init_config();
38724
+ init_config2();
38450
38725
  init_cloud_client();
38451
38726
  init_shadow_outbox();
38452
38727
  });
@@ -38833,7 +39108,8 @@ function createPostgresTodosStorageAdapter(options) {
38833
39108
  getActiveWork: (filters) => getActiveWork2(filters, store),
38834
39109
  getChangedSince: (since, filters) => getChangedSince(since, filters, store),
38835
39110
  lock: (id, agentId) => lockTask2(id, agentId, store),
38836
- unlock: (id, agentId) => unlockTask2(id, agentId, store)
39111
+ unlock: (id, agentId) => unlockTask2(id, agentId, store),
39112
+ getByFingerprint: (fingerprint) => store.getTaskByFingerprint(fingerprint)
38837
39113
  },
38838
39114
  dependencies: {
38839
39115
  add: (taskId, dependsOn, context) => addDependency2(taskId, dependsOn, store, context),
@@ -38901,7 +39177,24 @@ function createPostgresTodosStorageAdapter(options) {
38901
39177
  audit: {
38902
39178
  logTaskChange: (taskId, action, field, oldValue, newValue, agentId, context) => logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context),
38903
39179
  addComment: (input, context) => addComment2(input, store, context),
38904
- getComments: async (taskId) => (await store.list("comments")).filter((comment) => comment.task_id === taskId).sort((a, b) => a.created_at.localeCompare(b.created_at)),
39180
+ getComments: async (taskId) => {
39181
+ const pages = [];
39182
+ let before;
39183
+ while (true) {
39184
+ const page = await store.listComments(taskId, { limit: 1000, ...before ? { before } : {} });
39185
+ if (page.length === 0)
39186
+ break;
39187
+ pages.unshift(page);
39188
+ if (page.length < 1000)
39189
+ break;
39190
+ const oldest = page[0];
39191
+ before = { created_at: oldest.created_at, id: oldest.id };
39192
+ }
39193
+ return pages.flat().map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
39194
+ },
39195
+ getCommentsPage: async (taskId, options2) => {
39196
+ return (await store.listComments(taskId, options2)).map(redactComment2).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
39197
+ },
38905
39198
  getTaskHistory: async (taskId) => (await store.list("audit_history")).filter((entry2) => entry2.task_id === taskId).sort((a, b) => a.created_at.localeCompare(b.created_at)),
38906
39199
  getRecentActivity: async (limit = 20) => (await store.list("audit_history")).sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, limit)
38907
39200
  },
@@ -38951,6 +39244,27 @@ class PostgresJsonRecordStore {
38951
39244
  async list(type) {
38952
39245
  return (await this.listRecords(type)).map((record) => record.payload);
38953
39246
  }
39247
+ async listComments(taskId, options = {}) {
39248
+ await this.ensureSchema();
39249
+ const limit = options.limit ?? 100;
39250
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
39251
+ throw new Error("Postgres comment limit must be an integer between 1 and 1001");
39252
+ }
39253
+ const params = [this.service, taskId];
39254
+ let cursorPredicate = "";
39255
+ if (options.before) {
39256
+ params.push(options.before.created_at, options.before.id);
39257
+ cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
39258
+ }
39259
+ params.push(limit);
39260
+ const result = await this.options.client.query(`/* todos:list-comments */ SELECT payload FROM ${this.tableName}
39261
+ WHERE service = $1 AND object_type = 'comments' AND deleted_at IS NULL
39262
+ AND payload->>'task_id' = $2
39263
+ ${cursorPredicate}
39264
+ ORDER BY payload->>'created_at' DESC, object_id DESC
39265
+ LIMIT $${params.length}`, params);
39266
+ return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
39267
+ }
38954
39268
  async listRecords(type) {
38955
39269
  await this.ensureSchema();
38956
39270
  const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
@@ -39022,6 +39336,17 @@ class PostgresJsonRecordStore {
39022
39336
  const result = await this.options.client.query(sql, params);
39023
39337
  return result.rows.map((row) => payloadRecord2(row.payload));
39024
39338
  }
39339
+ async getTaskByFingerprint(fingerprint) {
39340
+ await this.ensureSchema();
39341
+ const sql = `/* todos:task-by-fingerprint */ SELECT payload FROM ${this.tableName}
39342
+ WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
39343
+ AND payload->'metadata'->>'fingerprint' = $3
39344
+ ORDER BY payload->>'created_at' ASC
39345
+ LIMIT 1`;
39346
+ const result = await this.options.client.query(sql, [this.service, "tasks", fingerprint]);
39347
+ const row = result.rows[0];
39348
+ return row ? payloadRecord2(row.payload) : null;
39349
+ }
39025
39350
  async countTasks(filter) {
39026
39351
  await this.ensureSchema();
39027
39352
  const { where, params } = this.buildTaskFilterSql(filter);
@@ -39333,7 +39658,7 @@ async function lockTask2(id, agentId, store) {
39333
39658
  async function unlockTask2(id, agentId, store) {
39334
39659
  const task = await requireRecord("tasks", id, store);
39335
39660
  if (agentId && task.locked_by && task.locked_by !== agentId) {
39336
- throw new Error(`Task ${id} is locked by ${task.locked_by}, not ${agentId}`);
39661
+ throw new LockError(id, task.locked_by);
39337
39662
  }
39338
39663
  await patchTask(task, { locked_by: null, locked_at: null }, store);
39339
39664
  return true;
@@ -39698,13 +40023,16 @@ async function addComment2(input, store, context) {
39698
40023
  task_id: input.task_id,
39699
40024
  agent_id: input.agent_id ?? context?.agentId ?? null,
39700
40025
  session_id: input.session_id ?? context?.sessionId ?? null,
39701
- content: input.content,
40026
+ content: redactEvidenceText(input.content),
39702
40027
  type: input.type ?? "comment",
39703
40028
  progress_pct: input.progress_pct ?? null,
39704
40029
  created_at: new Date().toISOString()
39705
40030
  };
39706
40031
  return store.upsert("comments", comment, context);
39707
40032
  }
40033
+ function redactComment2(comment) {
40034
+ return { ...comment, content: redactEvidenceText(comment.content) };
40035
+ }
39708
40036
  async function exportSnapshot(store) {
39709
40037
  return {
39710
40038
  exportedAt: new Date().toISOString(),
@@ -39850,7 +40178,109 @@ function numberValue2(value) {
39850
40178
  return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
39851
40179
  }
39852
40180
  var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_BY = "ORDER BY CASE payload->>'priority' WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END ASC, payload->>'created_at' ASC, payload->>'id' ASC";
39853
- var init_postgres_adapter = () => {};
40181
+ var init_postgres_adapter = __esm(() => {
40182
+ init_types();
40183
+ init_redaction();
40184
+ });
40185
+
40186
+ // src/storage/comment-redaction-backfill.ts
40187
+ function assertSafeIdentifier2(value) {
40188
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
40189
+ throw new Error(`Unsafe Postgres identifier: ${value}`);
40190
+ }
40191
+ }
40192
+ function payloadObject(value) {
40193
+ if (value && typeof value === "object" && !Array.isArray(value))
40194
+ return value;
40195
+ if (typeof value !== "string")
40196
+ return null;
40197
+ try {
40198
+ const parsed = JSON.parse(value);
40199
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
40200
+ } catch {
40201
+ return null;
40202
+ }
40203
+ }
40204
+ async function backfillPostgresCommentRedaction(client, options = {}) {
40205
+ const apply = options.apply === true;
40206
+ if (apply && options.confirmation !== COMMENT_REDACTION_BACKFILL_CONFIRMATION) {
40207
+ throw new Error(`Applying the comment redaction backfill requires confirmation ${COMMENT_REDACTION_BACKFILL_CONFIRMATION}`);
40208
+ }
40209
+ const tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
40210
+ assertSafeIdentifier2(tableName);
40211
+ const service = options.service ?? "todos";
40212
+ const batchSize = options.batchSize ?? 100;
40213
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 500) {
40214
+ throw new Error("Comment redaction backfill batchSize must be an integer between 1 and 500");
40215
+ }
40216
+ const result = {
40217
+ dry_run: !apply,
40218
+ scanned: 0,
40219
+ candidates: 0,
40220
+ updated: 0,
40221
+ conflicts: 0,
40222
+ batches: 0,
40223
+ remaining_candidates: 0
40224
+ };
40225
+ let afterId = "";
40226
+ while (true) {
40227
+ const page = await client.query(`/* todos:comment-redaction-backfill-scan */
40228
+ SELECT object_id, payload
40229
+ FROM ${tableName}
40230
+ WHERE service = $1 AND object_type = 'comments'
40231
+ AND object_id > $2
40232
+ ORDER BY object_id ASC
40233
+ LIMIT $3`, [service, afterId, batchSize]);
40234
+ if (page.rows.length === 0)
40235
+ break;
40236
+ result.batches += 1;
40237
+ for (const row of page.rows) {
40238
+ afterId = row.object_id;
40239
+ result.scanned += 1;
40240
+ const payload = payloadObject(row.payload);
40241
+ const original = payload?.["content"];
40242
+ if (typeof original !== "string")
40243
+ continue;
40244
+ const redacted = redactEvidenceText(original);
40245
+ if (redacted === original)
40246
+ continue;
40247
+ result.candidates += 1;
40248
+ if (!apply)
40249
+ continue;
40250
+ const nextPayload = { ...payload, content: redacted };
40251
+ const update = await client.query(`/* todos:comment-redaction-backfill-apply */
40252
+ UPDATE ${tableName}
40253
+ SET payload = $3::jsonb
40254
+ WHERE service = $1 AND object_type = 'comments' AND object_id = $2
40255
+ AND payload = $4::jsonb
40256
+ RETURNING object_id`, [service, row.object_id, nextPayload, row.payload]);
40257
+ if (update.rows.length === 1)
40258
+ result.updated += 1;
40259
+ else
40260
+ result.conflicts += 1;
40261
+ }
40262
+ if (page.rows.length < batchSize)
40263
+ break;
40264
+ }
40265
+ if (!apply) {
40266
+ result.remaining_candidates = result.candidates;
40267
+ return result;
40268
+ }
40269
+ const verification = await backfillPostgresCommentRedaction(client, {
40270
+ ...options,
40271
+ apply: false,
40272
+ confirmation: undefined
40273
+ });
40274
+ result.remaining_candidates = verification.candidates;
40275
+ return result;
40276
+ }
40277
+ function isCommentRedactionBackfillComplete(result) {
40278
+ return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
40279
+ }
40280
+ var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
40281
+ var init_comment_redaction_backfill = __esm(() => {
40282
+ init_redaction();
40283
+ });
39854
40284
 
39855
40285
  // src/server/cloud.ts
39856
40286
  var exports_cloud = {};
@@ -39864,7 +40294,9 @@ __export(exports_cloud, {
39864
40294
  getCloudStorageAdapter: () => getCloudStorageAdapter,
39865
40295
  getApiKeyStore: () => getApiKeyStore,
39866
40296
  ensureCloudSchema: () => ensureCloudSchema,
40297
+ ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
39867
40298
  closeCloud: () => closeCloud,
40299
+ backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
39868
40300
  TODOS_APP_SLUG: () => TODOS_APP_SLUG
39869
40301
  });
39870
40302
  function resolveCloudDatabaseUrl(env = process.env) {
@@ -39942,6 +40374,9 @@ async function ensureCloudSchema() {
39942
40374
  })();
39943
40375
  return schemaEnsured;
39944
40376
  }
40377
+ async function ensureCloudCommentCursorIndex() {
40378
+ await getClient().query(postgresTodosCommentCursorIndexSql());
40379
+ }
39945
40380
  async function normalizeCloudPayloads() {
39946
40381
  const client = getClient();
39947
40382
  const res = await client.query(`UPDATE todos_sync_records
@@ -39950,6 +40385,9 @@ async function normalizeCloudPayloads() {
39950
40385
  RETURNING object_id AS id`);
39951
40386
  return res.rows.length;
39952
40387
  }
40388
+ function backfillCloudCommentRedaction(options = {}) {
40389
+ return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
40390
+ }
39953
40391
  async function pingCloud() {
39954
40392
  const client = getClient();
39955
40393
  const res = await client.query("select 1 as ok");
@@ -39970,6 +40408,7 @@ var init_cloud = __esm(() => {
39970
40408
  init_auth();
39971
40409
  init_cloud_client();
39972
40410
  init_postgres_adapter();
40411
+ init_comment_redaction_backfill();
39973
40412
  });
39974
40413
 
39975
40414
  // src/server/openapi.ts
@@ -39993,6 +40432,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
39993
40432
  schemas: {
39994
40433
  Task: taskSchema,
39995
40434
  Project: projectSchema,
40435
+ TaskComment: taskCommentSchema,
39996
40436
  CreateTaskInput: {
39997
40437
  type: "object",
39998
40438
  required: ["title"],
@@ -40027,6 +40467,17 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
40027
40467
  description: { type: "string" },
40028
40468
  task_prefix: { type: "string" }
40029
40469
  }
40470
+ },
40471
+ CreateTaskCommentInput: {
40472
+ type: "object",
40473
+ required: ["content"],
40474
+ properties: {
40475
+ content: { type: "string", minLength: 1 },
40476
+ agent_id: { type: "string" },
40477
+ session_id: { type: "string" },
40478
+ type: { type: "string", enum: ["comment", "progress", "note"] },
40479
+ progress_pct: { type: "number" }
40480
+ }
40030
40481
  }
40031
40482
  }
40032
40483
  },
@@ -40125,6 +40576,61 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
40125
40576
  }
40126
40577
  }
40127
40578
  },
40579
+ "/v1/tasks/{id}/comments": {
40580
+ get: {
40581
+ operationId: "listTaskComments",
40582
+ summary: "List a bounded page of task comments",
40583
+ description: "Returns the newest page in oldest-to-newest display order. Use next_cursor to request older pages; count is the page size, not a total. Pagination-aware clients must send limit during the mixed-version rollout.",
40584
+ parameters: [
40585
+ { name: "id", in: "path", required: true, schema: { type: "string" } },
40586
+ { name: "limit", in: "query", required: true, schema: { type: "integer", minimum: 1, maximum: 500, default: 100 } },
40587
+ { name: "cursor", in: "query", schema: { type: "string" } }
40588
+ ],
40589
+ responses: {
40590
+ "200": {
40591
+ content: {
40592
+ "application/json": {
40593
+ schema: {
40594
+ type: "object",
40595
+ required: ["comments", "count", "has_more", "next_cursor"],
40596
+ properties: {
40597
+ comments: { type: "array", maxItems: 500, items: { $ref: "#/components/schemas/TaskComment" } },
40598
+ count: { type: "integer", minimum: 0, maximum: 500 },
40599
+ has_more: { type: "boolean" },
40600
+ next_cursor: { type: "string", nullable: true }
40601
+ }
40602
+ }
40603
+ }
40604
+ }
40605
+ },
40606
+ "426": {
40607
+ description: "Upgrade required: a predecessor client omitted limit and the complete legacy history exceeds 500 comments, or the configured storage adapter lacks cursor pagination support."
40608
+ }
40609
+ }
40610
+ },
40611
+ post: {
40612
+ operationId: "createTaskComment",
40613
+ summary: "Create a task comment",
40614
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
40615
+ requestBody: {
40616
+ required: true,
40617
+ content: { "application/json": { schema: { $ref: "#/components/schemas/CreateTaskCommentInput" } } }
40618
+ },
40619
+ responses: {
40620
+ "201": {
40621
+ content: {
40622
+ "application/json": {
40623
+ schema: {
40624
+ type: "object",
40625
+ required: ["comment"],
40626
+ properties: { comment: { $ref: "#/components/schemas/TaskComment" } }
40627
+ }
40628
+ }
40629
+ }
40630
+ }
40631
+ }
40632
+ }
40633
+ },
40128
40634
  "/v1/tasks/{id}/start": {
40129
40635
  post: {
40130
40636
  operationId: "startTask",
@@ -40243,7 +40749,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
40243
40749
  }
40244
40750
  };
40245
40751
  }
40246
- var taskSchema, projectSchema;
40752
+ var taskSchema, projectSchema, taskCommentSchema;
40247
40753
  var init_openapi = __esm(() => {
40248
40754
  init_package_version();
40249
40755
  taskSchema = {
@@ -40274,6 +40780,20 @@ var init_openapi = __esm(() => {
40274
40780
  updated_at: { type: "string" }
40275
40781
  }
40276
40782
  };
40783
+ taskCommentSchema = {
40784
+ type: "object",
40785
+ required: ["id", "task_id", "agent_id", "session_id", "content", "type", "progress_pct", "created_at"],
40786
+ properties: {
40787
+ id: { type: "string" },
40788
+ task_id: { type: "string" },
40789
+ agent_id: { type: "string", nullable: true },
40790
+ session_id: { type: "string", nullable: true },
40791
+ content: { type: "string" },
40792
+ type: { type: "string", enum: ["comment", "progress", "note"] },
40793
+ progress_pct: { type: "number", nullable: true },
40794
+ created_at: { type: "string", format: "date-time" }
40795
+ }
40796
+ };
40277
40797
  });
40278
40798
 
40279
40799
  // src/server/v1.ts
@@ -40303,6 +40823,29 @@ function contextFromPrincipal(principal, body) {
40303
40823
  const agentId = body?.agent_id || principal.agent || undefined;
40304
40824
  return agentId ? { agentId } : {};
40305
40825
  }
40826
+ function redactComment3(comment) {
40827
+ return { ...comment, content: redactEvidenceText(comment.content) };
40828
+ }
40829
+ function encodeCommentCursor(comment) {
40830
+ return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
40831
+ }
40832
+ function decodeCommentCursor(value) {
40833
+ if (value.length > 1024)
40834
+ throw new Error("invalid comment cursor");
40835
+ let parsed;
40836
+ try {
40837
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
40838
+ } catch {
40839
+ throw new Error("invalid comment cursor");
40840
+ }
40841
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
40842
+ throw new Error("invalid comment cursor");
40843
+ const cursor = parsed;
40844
+ if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
40845
+ throw new Error("invalid comment cursor");
40846
+ }
40847
+ return { created_at: cursor["created_at"], id: cursor["id"] };
40848
+ }
40306
40849
  function normalizeImportSnapshot(raw) {
40307
40850
  const body = raw && typeof raw === "object" ? raw : {};
40308
40851
  const arr = (v) => Array.isArray(v) ? v : [];
@@ -40323,7 +40866,7 @@ function normalizeImportSnapshot(raw) {
40323
40866
  function countSnapshotRecords(s) {
40324
40867
  return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
40325
40868
  }
40326
- async function handleV1Request(req, url) {
40869
+ async function handleV1Request(req, url, dependencies = {}) {
40327
40870
  const path = url.pathname;
40328
40871
  if (path !== "/v1" && !path.startsWith("/v1/"))
40329
40872
  return null;
@@ -40332,7 +40875,7 @@ async function handleV1Request(req, url) {
40332
40875
  const requiredScopes = [isWrite ? "todos:write" : "todos:read"];
40333
40876
  let verifier;
40334
40877
  try {
40335
- verifier = getCloudVerifier();
40878
+ verifier = (dependencies.getVerifier ?? getCloudVerifier)();
40336
40879
  } catch (e) {
40337
40880
  return error(503, e.message);
40338
40881
  }
@@ -40341,8 +40884,8 @@ async function handleV1Request(req, url) {
40341
40884
  return error(decision.status, decision.message, { reason: decision.reason });
40342
40885
  }
40343
40886
  const principal = decision.principal;
40344
- await ensureCloudSchema();
40345
- const store = getCloudStorageAdapter();
40887
+ await (dependencies.ensureSchema ?? ensureCloudSchema)();
40888
+ const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
40346
40889
  const segments = path.split("/").filter(Boolean);
40347
40890
  const resource = segments[1];
40348
40891
  const id = segments[2];
@@ -40370,13 +40913,74 @@ async function handleV1Request(req, url) {
40370
40913
  missing
40371
40914
  });
40372
40915
  }
40916
+ if (id === "upsert" && !action) {
40917
+ if (method !== "POST")
40918
+ return error(405, `method ${method} not allowed on /v1/tasks/upsert`);
40919
+ if (typeof store.tasks.getByFingerprint !== "function") {
40920
+ return error(501, "fingerprint upsert is not supported by this storage backend");
40921
+ }
40922
+ const body = await readJson(req) ?? {};
40923
+ const fingerprint = typeof body.fingerprint === "string" ? body.fingerprint.trim() : "";
40924
+ if (!fingerprint)
40925
+ return error(400, "fingerprint is required");
40926
+ if (typeof body.title !== "string" || !body.title.trim())
40927
+ return error(400, "title is required");
40928
+ const existing = await store.tasks.getByFingerprint(fingerprint);
40929
+ const metadata = {
40930
+ ...existing?.metadata ?? {},
40931
+ ...body.metadata ?? {},
40932
+ fingerprint
40933
+ };
40934
+ const fields = { metadata };
40935
+ for (const key of [
40936
+ "title",
40937
+ "description",
40938
+ "priority",
40939
+ "status",
40940
+ "project_id",
40941
+ "assigned_to",
40942
+ "working_dir",
40943
+ "plan_id",
40944
+ "task_list_id",
40945
+ "tags",
40946
+ "due_at",
40947
+ "estimated_minutes",
40948
+ "sla_minutes",
40949
+ "requires_approval",
40950
+ "recurrence_rule",
40951
+ "task_type"
40952
+ ]) {
40953
+ const bag = body;
40954
+ if (bag[key] !== undefined)
40955
+ fields[key] = bag[key];
40956
+ }
40957
+ if (!existing) {
40958
+ const task = await store.tasks.create({ ...fields, title: body.title }, contextFromPrincipal(principal, body));
40959
+ return json2({ task, created: true }, 201);
40960
+ }
40961
+ try {
40962
+ const task = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body));
40963
+ return json2({ task, created: false });
40964
+ } catch (e) {
40965
+ const msg = e.message || "";
40966
+ if (msg.includes("version conflict"))
40967
+ return error(409, msg);
40968
+ throw e;
40969
+ }
40970
+ }
40373
40971
  if (!id) {
40374
40972
  if (method === "GET") {
40375
40973
  const filter = {
40376
- ...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
40377
- ...url.searchParams.get("priority") ? { priority: url.searchParams.get("priority") } : {},
40974
+ ...url.searchParams.get("status") ? {
40975
+ status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
40976
+ } : {},
40977
+ ...url.searchParams.get("priority") ? {
40978
+ priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
40979
+ } : {},
40378
40980
  ...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
40981
+ ...url.searchParams.has("parent_id") ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : {},
40379
40982
  ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},
40983
+ ...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
40380
40984
  ...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
40381
40985
  ...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
40382
40986
  ...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
@@ -40400,8 +41004,47 @@ async function handleV1Request(req, url) {
40400
41004
  if (action) {
40401
41005
  if (action === "comments") {
40402
41006
  if (method === "GET") {
40403
- const comments = await store.audit.getComments(id);
40404
- return json2({ comments, count: comments.length });
41007
+ if (!await store.tasks.get(id))
41008
+ return error(404, "task not found");
41009
+ const rawLimit = url.searchParams.get("limit");
41010
+ const cursor = url.searchParams.get("cursor");
41011
+ if (rawLimit === null && cursor === null) {
41012
+ const storageContext = contextFromPrincipal(principal);
41013
+ const legacyPage = (await (store.audit.getCommentsPage ? store.audit.getCommentsPage(id, { limit: LEGACY_COMMENT_RESPONSE_LIMIT + 1 }, storageContext) : store.audit.getComments(id, storageContext))).map(redactComment3);
41014
+ if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
41015
+ return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
41016
+ }
41017
+ return json2({
41018
+ comments: legacyPage,
41019
+ count: legacyPage.length,
41020
+ has_more: false,
41021
+ next_cursor: null
41022
+ });
41023
+ }
41024
+ const limit = rawLimit === null ? DEFAULT_COMMENT_PAGE_SIZE : Number(rawLimit);
41025
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_COMMENT_PAGE_SIZE) {
41026
+ return error(400, `limit must be an integer between 1 and ${MAX_COMMENT_PAGE_SIZE}`);
41027
+ }
41028
+ let before;
41029
+ if (cursor) {
41030
+ try {
41031
+ before = decodeCommentCursor(cursor);
41032
+ } catch {
41033
+ return error(400, "invalid comment cursor");
41034
+ }
41035
+ }
41036
+ if (!store.audit.getCommentsPage) {
41037
+ return error(426, "storage adapter must be upgraded to support cursor-paginated comments");
41038
+ }
41039
+ const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
41040
+ const hasMore = page.length > limit;
41041
+ const comments = hasMore ? page.slice(1) : page;
41042
+ return json2({
41043
+ comments,
41044
+ count: comments.length,
41045
+ has_more: hasMore,
41046
+ next_cursor: hasMore && comments[0] ? encodeCommentCursor(comments[0]) : null
41047
+ });
40405
41048
  }
40406
41049
  if (method === "POST") {
40407
41050
  const body2 = await readJson(req) ?? {};
@@ -40419,24 +41062,45 @@ async function handleV1Request(req, url) {
40419
41062
  type: body2.type,
40420
41063
  progress_pct: body2.progress_pct
40421
41064
  }, contextFromPrincipal(principal, body2));
40422
- return json2({ comment }, 201);
41065
+ return json2({ comment: redactComment3(comment) }, 201);
40423
41066
  }
40424
41067
  return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
40425
41068
  }
41069
+ if (action === "history") {
41070
+ if (method !== "GET")
41071
+ return error(405, `method ${method} not allowed on /v1/tasks/:id/history`);
41072
+ if (!await store.tasks.get(id))
41073
+ return error(404, "task not found");
41074
+ const history = await store.audit.getTaskHistory(id);
41075
+ return json2({ history, count: history.length });
41076
+ }
40426
41077
  if (action === "lock" || action === "unlock") {
40427
41078
  if (method !== "POST")
40428
41079
  return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
40429
- if (typeof store.tasks.lock !== "function" || typeof store.tasks.unlock !== "function") {
40430
- return error(501, "task locking is not supported by this storage backend");
40431
- }
40432
41080
  const body2 = await readJson(req) ?? {};
40433
41081
  if (!await store.tasks.get(id))
40434
41082
  return error(404, "task not found");
40435
41083
  if (action === "lock") {
40436
- const agentId2 = body2.agent_id || principal.agent || "todos-serve";
40437
- return json2({ result: await store.tasks.lock(id, agentId2) });
41084
+ if (typeof store.tasks.lock !== "function")
41085
+ return error(501, "task locking is not supported by this storage backend");
41086
+ const agentId3 = body2.agent_id || principal.agent || "todos-serve";
41087
+ return json2({ result: await store.tasks.lock(id, agentId3) });
41088
+ }
41089
+ if (typeof store.tasks.unlock !== "function")
41090
+ return error(501, "task unlocking is not supported by this storage backend");
41091
+ if (body2.force === true) {
41092
+ if (!principal.scopes.includes("todos:*"))
41093
+ return error(403, "force unlock requires todos:* scope");
41094
+ const released2 = await store.tasks.unlock(id);
41095
+ return json2({ success: released2 });
40438
41096
  }
40439
- const released = await store.tasks.unlock(id, body2.agent_id || principal.agent || undefined);
41097
+ if (body2.agent_id && principal.agent && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
41098
+ return error(403, "unlock agent_id must match the authenticated agent");
41099
+ }
41100
+ const agentId2 = principal.agent || body2.agent_id;
41101
+ if (!agentId2)
41102
+ return error(403, "unlock requires an agent-bound key or force=true");
41103
+ const released = await store.tasks.unlock(id, agentId2);
40440
41104
  return json2({ success: released });
40441
41105
  }
40442
41106
  if (action === "dependencies") {
@@ -40719,12 +41383,28 @@ async function handleV1Request(req, url) {
40719
41383
  const activity = await store.audit.getRecentActivity(limit);
40720
41384
  return json2({ activity, count: activity.length });
40721
41385
  }
40722
- if (resource === "task-lists" && !id) {
40723
- if (method !== "GET")
40724
- return error(405, `method ${method} not allowed on /v1/task-lists`);
40725
- const projectId = url.searchParams.get("project_id") ?? undefined;
40726
- const taskLists = await store.taskLists.list(projectId);
40727
- return json2({ task_lists: taskLists, count: taskLists.length });
41386
+ if (resource === "task-lists") {
41387
+ if (!id && method === "GET") {
41388
+ const projectId = url.searchParams.get("project_id") ?? undefined;
41389
+ const taskLists = await store.taskLists.list(projectId);
41390
+ return json2({ task_lists: taskLists, count: taskLists.length });
41391
+ }
41392
+ if (!id && method === "POST") {
41393
+ const body = await readJson(req);
41394
+ if (!body || typeof body.name !== "string" || !body.name.trim())
41395
+ return error(400, "name is required");
41396
+ const taskList = await store.taskLists.create(body, contextFromPrincipal(principal));
41397
+ return json2({ task_list: taskList }, 201);
41398
+ }
41399
+ if (id && method === "GET") {
41400
+ const taskList = await store.taskLists.get(id);
41401
+ return taskList ? json2({ task_list: taskList }) : error(404, "task list not found");
41402
+ }
41403
+ if (id && method === "DELETE") {
41404
+ const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
41405
+ return deleted ? json2({ deleted: true, id }) : error(404, "task list not found");
41406
+ }
41407
+ return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
40728
41408
  }
40729
41409
  if (resource === "dependencies" && !id) {
40730
41410
  if (method !== "GET")
@@ -40732,8 +41412,8 @@ async function handleV1Request(req, url) {
40732
41412
  if (typeof store.dependencies?.listAll !== "function") {
40733
41413
  return error(501, "dependency edge listing is not supported by this storage backend");
40734
41414
  }
40735
- const dependencies = await store.dependencies.listAll();
40736
- return json2({ dependencies, count: dependencies.length });
41415
+ const dependencies2 = await store.dependencies.listAll();
41416
+ return json2({ dependencies: dependencies2, count: dependencies2.length });
40737
41417
  }
40738
41418
  if (resource === "commits" && id) {
40739
41419
  if (method !== "GET")
@@ -40790,12 +41470,16 @@ async function handleV1Request(req, url) {
40790
41470
  }
40791
41471
  return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
40792
41472
  } catch (e) {
41473
+ if (e instanceof LockError)
41474
+ return error(409, e.message, { code: LockError.code });
40793
41475
  return error(500, e.message || "internal error");
40794
41476
  }
40795
41477
  }
40796
- var JSON_HEADERS;
41478
+ var JSON_HEADERS, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
40797
41479
  var init_v1 = __esm(() => {
41480
+ init_types();
40798
41481
  init_cloud();
41482
+ init_redaction();
40799
41483
  JSON_HEADERS = { "Content-Type": "application/json" };
40800
41484
  });
40801
41485
 
@@ -47030,7 +47714,7 @@ var init_workflow_states = __esm(() => {
47030
47714
  init_tasks();
47031
47715
  init_types();
47032
47716
  init_database();
47033
- init_config2();
47717
+ init_config();
47034
47718
  init_local_fields();
47035
47719
  DEFAULT_WORKFLOW_STATES = [
47036
47720
  { name: "pending", canonical_status: "pending", aliases: ["todo", "backlog"], transitions: null, terminal: false },
@@ -48195,7 +48879,7 @@ var init_roadmaps = __esm(() => {
48195
48879
  init_tasks();
48196
48880
  init_plans();
48197
48881
  init_task_runs();
48198
- init_config2();
48882
+ init_config();
48199
48883
  });
48200
48884
 
48201
48885
  // src/lib/capacity-forecasts.ts
@@ -48420,7 +49104,7 @@ var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
48420
49104
  var init_capacity_forecasts = __esm(() => {
48421
49105
  init_tasks();
48422
49106
  init_task_relations();
48423
- init_config2();
49107
+ init_config();
48424
49108
  });
48425
49109
 
48426
49110
  // src/lib/audit-ledger.ts
@@ -48712,7 +49396,7 @@ var LOCAL_AUDIT_LEDGER_SCHEMA_VERSION = 1, LOCAL_AUDIT_LEDGER_HASH_ALGORITHM = "
48712
49396
  var init_audit_ledger = __esm(() => {
48713
49397
  init_database();
48714
49398
  init_task_runs();
48715
- init_config2();
49399
+ init_config();
48716
49400
  init_redaction();
48717
49401
  LOCAL_AUDIT_LEDGER_INITIAL_HASH = "0".repeat(64);
48718
49402
  });
@@ -54593,7 +55277,7 @@ var init_agent_run_dispatcher = __esm(() => {
54593
55277
  init_database();
54594
55278
  init_redaction();
54595
55279
  init_runner_sandbox();
54596
- init_config2();
55280
+ init_config();
54597
55281
  });
54598
55282
 
54599
55283
  // src/lib/verification-providers.ts
@@ -54938,7 +55622,7 @@ var init_verification_providers = __esm(() => {
54938
55622
  init_task_commits();
54939
55623
  init_database();
54940
55624
  init_tasks();
54941
- init_config2();
55625
+ init_config();
54942
55626
  init_redaction();
54943
55627
  DEFAULT_RETRY = {
54944
55628
  attempts: 1,
@@ -61764,7 +62448,7 @@ var init_review_queues = __esm(() => {
61764
62448
  init_audit();
61765
62449
  init_database();
61766
62450
  init_tasks();
61767
- init_config2();
62451
+ init_config();
61768
62452
  init_event_emission_safety();
61769
62453
  init_event_hooks();
61770
62454
  init_task_contracts();
@@ -63651,7 +64335,7 @@ ID: ${updated.id}${taskNote}`
63651
64335
  var init_agents2 = __esm(() => {
63652
64336
  init_zod();
63653
64337
  init_agents();
63654
- init_config2();
64338
+ init_config();
63655
64339
  init_database();
63656
64340
  init_cloud_router();
63657
64341
  });
@@ -64085,7 +64769,7 @@ function captureEnvironmentSnapshot(input = {}) {
64085
64769
  }
64086
64770
  function writeEnvironmentSnapshot(snapshot, outputPath) {
64087
64771
  const path = outputPath ? resolve20(outputPath) : join18(defaultSnapshotDir(), `${snapshot.id}.json`);
64088
- ensureDir2(dirname9(path));
64772
+ ensureDir(dirname9(path));
64089
64773
  writeJsonFile(path, snapshot);
64090
64774
  return path;
64091
64775
  }
@@ -67728,7 +68412,7 @@ Commit Links (${commitRows.length}):`));
67728
68412
  var init_config_serve_commands = __esm(() => {
67729
68413
  init_database();
67730
68414
  init_tasks();
67731
- init_config2();
68415
+ init_config();
67732
68416
  init_sync_utils();
67733
68417
  init_helpers();
67734
68418
  });
@@ -69934,7 +70618,7 @@ Findings`));
69934
70618
  checks.push({ name: "Migrations", ok: false, message: "Could not read migration version" });
69935
70619
  }
69936
70620
  try {
69937
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config2));
70621
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
69938
70622
  loadConfig2();
69939
70623
  checks.push({ name: "Config", ok: true, message: "Loaded successfully" });
69940
70624
  } catch (e) {
@@ -79365,7 +80049,7 @@ var init_factory = __esm(() => {
79365
80049
  init_postgres_adapter();
79366
80050
  init_shadow();
79367
80051
  init_cloud_client();
79368
- init_config();
80052
+ init_config2();
79369
80053
  });
79370
80054
 
79371
80055
  // src/storage/s3-artifacts.ts
@@ -79807,6 +80491,7 @@ __export(exports_storage, {
79807
80491
  signAwsV4Request: () => signAwsV4Request,
79808
80492
  registerShadowExitFlush: () => registerShadowExitFlush,
79809
80493
  postgresTodosSyncSchemaSql: () => postgresTodosSyncSchemaSql,
80494
+ postgresTodosCommentCursorIndexSql: () => postgresTodosCommentCursorIndexSql,
79810
80495
  planRunArtifactsS3Sync: () => planRunArtifactsS3Sync,
79811
80496
  parseStorageMode: () => parseStorageMode,
79812
80497
  maybeInstallShadowCapture: () => maybeInstallShadowCapture2,
@@ -79814,6 +80499,7 @@ __export(exports_storage, {
79814
80499
  loadStorageConfig: () => loadStorageConfig,
79815
80500
  isTodosShadowEnabled: () => isTodosShadowEnabled,
79816
80501
  isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
80502
+ isCommentRedactionBackfillComplete: () => isCommentRedactionBackfillComplete,
79817
80503
  installShadowOutboxSchema: () => installShadowOutboxSchema,
79818
80504
  importSqliteTodosStorageSnapshot: () => importSqliteTodosStorageSnapshot,
79819
80505
  getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
@@ -79841,6 +80527,7 @@ __export(exports_storage, {
79841
80527
  closeRuntimeShadowCloud: () => closeRuntimeShadowCloud,
79842
80528
  buildS3ObjectUrl: () => buildS3ObjectUrl,
79843
80529
  buildS3ObjectKey: () => buildS3ObjectKey,
80530
+ backfillPostgresCommentRedaction: () => backfillPostgresCommentRedaction,
79844
80531
  assertTodosShadowConfig: () => assertTodosShadowConfig,
79845
80532
  assertTodosRemoteStorageConfig: () => assertTodosRemoteStorageConfig,
79846
80533
  TodosShadowOutbox: () => TodosShadowOutbox,
@@ -79853,12 +80540,13 @@ __export(exports_storage, {
79853
80540
  PostgresTodosSyncStore: () => PostgresTodosSyncStore,
79854
80541
  DEFAULT_TODOS_POSTGRES_SYNC_TABLE: () => DEFAULT_TODOS_POSTGRES_SYNC_TABLE,
79855
80542
  DEFAULT_TODOS_POSTGRES_CURSOR_TABLE: () => DEFAULT_TODOS_POSTGRES_CURSOR_TABLE,
80543
+ COMMENT_REDACTION_BACKFILL_CONFIRMATION: () => COMMENT_REDACTION_BACKFILL_CONFIRMATION,
79856
80544
  CANONICAL_TODOS_RDS_RUNTIME_PATH: () => CANONICAL_TODOS_RDS_RUNTIME_PATH,
79857
80545
  CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
79858
80546
  CANONICAL_TODOS_RDS_CLUSTER: () => CANONICAL_TODOS_RDS_CLUSTER
79859
80547
  });
79860
80548
  var init_storage2 = __esm(() => {
79861
- init_config();
80549
+ init_config2();
79862
80550
  init_factory();
79863
80551
  init_shadow();
79864
80552
  init_shadow_outbox();
@@ -79867,6 +80555,7 @@ var init_storage2 = __esm(() => {
79867
80555
  init_hybrid();
79868
80556
  init_local_sqlite();
79869
80557
  init_sqlite_snapshot();
80558
+ init_comment_redaction_backfill();
79870
80559
  init_postgres_adapter();
79871
80560
  init_s3_artifacts();
79872
80561
  init_s3_artifact_sync();