@hasna/todos 0.15.20 → 0.15.24

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 (40) hide show
  1. package/dist/ai-tools.d.ts +80 -0
  2. package/dist/ai-tools.d.ts.map +1 -0
  3. package/dist/ai.d.ts +313 -0
  4. package/dist/ai.d.ts.map +1 -0
  5. package/dist/cli/cloud-router.d.ts +10 -1
  6. package/dist/cli/cloud-router.d.ts.map +1 -1
  7. package/dist/cli/commands/ai-commands.d.ts +3 -0
  8. package/dist/cli/commands/ai-commands.d.ts.map +1 -0
  9. package/dist/cli/commands/help-commands.d.ts +2 -1
  10. package/dist/cli/commands/help-commands.d.ts.map +1 -1
  11. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  12. package/dist/cli/index.js +2856 -547
  13. package/dist/cli/stage-a.d.ts +27 -16
  14. package/dist/cli/stage-a.d.ts.map +1 -1
  15. package/dist/contracts.d.ts +1 -0
  16. package/dist/contracts.d.ts.map +1 -1
  17. package/dist/contracts.js +638 -18
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3142 -1089
  21. package/dist/lib/cli-help.d.ts +2 -2
  22. package/dist/lib/config.d.ts +4 -0
  23. package/dist/lib/config.d.ts.map +1 -1
  24. package/dist/lib/saved-search-views.d.ts.map +1 -1
  25. package/dist/lib/stale-lock-handoff.d.ts.map +1 -1
  26. package/dist/mcp/index.js +127 -72
  27. package/dist/mcp.js +1 -1
  28. package/dist/project-registration.js +67 -29
  29. package/dist/registry.js +603 -18
  30. package/dist/release-provenance.json +5 -5
  31. package/dist/sdk/index.js +1 -1
  32. package/dist/sdk/v1.generated.d.ts +2 -0
  33. package/dist/sdk/v1.generated.d.ts.map +1 -1
  34. package/dist/server/index.js +133 -78
  35. package/dist/server/openapi.d.ts +7 -0
  36. package/dist/server/openapi.d.ts.map +1 -1
  37. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  38. package/dist/storage.js +60 -20
  39. package/dist/task-manifest.js +19 -3
  40. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -2123,7 +2123,7 @@ var package_default;
2123
2123
  var init_package = __esm(() => {
2124
2124
  package_default = {
2125
2125
  name: "@hasna/todos",
2126
- version: "0.15.20",
2126
+ version: "0.15.24",
2127
2127
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
2128
2128
  type: "module",
2129
2129
  main: "dist/index.js",
@@ -2600,6 +2600,7 @@ __export(exports_config, {
2600
2600
  resetConfig: () => resetConfig,
2601
2601
  normalizeApiUrl: () => normalizeApiUrl,
2602
2602
  loadConfig: () => loadConfig,
2603
+ getTodosAiConfig: () => getTodosAiConfig,
2603
2604
  getTaskPrefixConfig: () => getTaskPrefixConfig,
2604
2605
  getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
2605
2606
  getLocalApiConfig: () => getLocalApiConfig,
@@ -2609,7 +2610,7 @@ __export(exports_config, {
2609
2610
  getAgentTaskListId: () => getAgentTaskListId,
2610
2611
  getAgentPoolForProject: () => getAgentPoolForProject
2611
2612
  });
2612
- import { existsSync as existsSync2 } from "fs";
2613
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2613
2614
  import { dirname, join as join2 } from "path";
2614
2615
  function getConfigPath() {
2615
2616
  return join2(getTodosGlobalDir(), "config.json");
@@ -2644,6 +2645,22 @@ function saveConfig(config) {
2644
2645
  function updateConfig(patch) {
2645
2646
  return saveConfig({ ...loadConfig(), ...patch });
2646
2647
  }
2648
+ function getTodosAiConfig() {
2649
+ const configPath = getConfigPath();
2650
+ if (!existsSync2(configPath))
2651
+ return {};
2652
+ const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
2653
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2654
+ throw new Error("Todos config must be a JSON object");
2655
+ }
2656
+ const ai = parsed["ai"];
2657
+ if (ai === undefined)
2658
+ return {};
2659
+ if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
2660
+ throw new Error("Todos AI configuration must be an object");
2661
+ }
2662
+ return { ...ai };
2663
+ }
2647
2664
  function normalizeApiUrl(value) {
2648
2665
  const trimmed = value?.trim();
2649
2666
  if (!trimmed)
@@ -5998,8 +6015,12 @@ async function cloudGetTask(client, id) {
5998
6015
  const raw = await client.get("tasks", id);
5999
6016
  return raw == null ? null : unwrapTask(raw);
6000
6017
  }
6001
- async function cloudCreateTask(client, input) {
6018
+ async function cloudCreateTask(client, input, verification = {}) {
6002
6019
  const expectedParentId = typeof input["parent_id"] === "string" ? input["parent_id"] : null;
6020
+ const expectedCreatedBy = typeof verification.expectedCreatedBy === "string" && verification.expectedCreatedBy.trim() ? verification.expectedCreatedBy : null;
6021
+ if (expectedCreatedBy !== null) {
6022
+ await requireTaskCreatorCapability(client);
6023
+ }
6003
6024
  const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () => client.transport.post("/tasks", input, {
6004
6025
  idempotencyKey: randomUUID(),
6005
6026
  retry: false
@@ -6008,8 +6029,9 @@ async function cloudCreateTask(client, input) {
6008
6029
  throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} returned a task create ` + "response without a stored task id; no success row or local SQLite fallback is permitted");
6009
6030
  }
6010
6031
  const persisted = await cloudGetTask(client, created.id);
6011
- if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId) {
6012
- throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + "stored task id and parent_id; no success row or local SQLite fallback is permitted");
6032
+ if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId || expectedCreatedBy !== null && persisted.created_by !== expectedCreatedBy) {
6033
+ const creatorDetail = expectedCreatedBy === null ? "" : ` and explicit created_by=${JSON.stringify(expectedCreatedBy)} ` + `(readback ${JSON.stringify(persisted?.created_by ?? null)})`;
6034
+ throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + `stored task id and parent_id${creatorDetail}; no success row or local SQLite fallback is permitted`);
6013
6035
  }
6014
6036
  return persisted;
6015
6037
  }
@@ -6115,6 +6137,33 @@ function resolveOpenApiSchema(document, schema) {
6115
6137
  }
6116
6138
  return current && typeof current === "object" && !Array.isArray(current) ? current : null;
6117
6139
  }
6140
+ function openApiSchemaProperty(document, schemaName, propertyName) {
6141
+ const schema = resolveOpenApiSchema(document, {
6142
+ $ref: `#/components/schemas/${schemaName}`
6143
+ });
6144
+ const properties = schema?.["properties"];
6145
+ if (!properties || typeof properties !== "object" || Array.isArray(properties))
6146
+ return null;
6147
+ const property = properties[propertyName];
6148
+ return property && typeof property === "object" && !Array.isArray(property) ? property : null;
6149
+ }
6150
+ async function fetchTaskCreatorCapability(client) {
6151
+ const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
6152
+ const inputProperty = openApiSchemaProperty(document, "CreateTaskInput", "created_by");
6153
+ const outputProperty = openApiSchemaProperty(document, "Task", "created_by");
6154
+ return inputProperty?.["type"] === "string" && outputProperty?.["type"] === "string" && outputProperty?.["nullable"] === true;
6155
+ }
6156
+ async function requireTaskCreatorCapability(client) {
6157
+ const authority = remoteAuthorityBase(client);
6158
+ let capability = taskCreatorCapabilityCache.get(authority);
6159
+ if (!capability) {
6160
+ capability = fetchTaskCreatorCapability(client);
6161
+ taskCreatorCapabilityCache.set(authority, capability);
6162
+ }
6163
+ if (!await capability) {
6164
+ throw new Error(`REMOTE_CREATED_BY_UNSUPPORTED: configured Todos authority ${authority} does not advertise created_by ` + "on both CreateTaskInput and Task in /v1/openapi.json; no task mutation was sent; deploy a compatible " + "@hasna/todos /v1 server before retrying; local SQLite fallback is disabled");
6165
+ }
6166
+ }
6118
6167
  async function fetchRetryCapability(client) {
6119
6168
  const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
6120
6169
  if (!document || typeof document !== "object" || Array.isArray(document))
@@ -6667,6 +6716,30 @@ function openApiHasOperation(document, path, method) {
6667
6716
  const route = paths[path];
6668
6717
  return Boolean(route && typeof route === "object" && !Array.isArray(route) && route[method] && typeof route[method] === "object");
6669
6718
  }
6719
+ async function fetchTodosRemoteCommandCapabilities(client) {
6720
+ const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
6721
+ const supported = new Set;
6722
+ if (openApiHasOperation(document, "/v1/tasks/{id}/stale-lock-handoff", "post")) {
6723
+ supported.add("stale-lock-handoff");
6724
+ }
6725
+ return supported;
6726
+ }
6727
+ async function getTodosRemoteCommandCapabilities(client) {
6728
+ const authority = remoteAuthorityBase(client);
6729
+ let capabilities = remoteCommandCapabilityCache.get(authority);
6730
+ if (!capabilities) {
6731
+ capabilities = fetchTodosRemoteCommandCapabilities(client);
6732
+ remoteCommandCapabilityCache.set(authority, capabilities);
6733
+ }
6734
+ return capabilities;
6735
+ }
6736
+ async function requireStaleLockHandoffCapability(client) {
6737
+ const authority = remoteAuthorityBase(client);
6738
+ const capabilities = await getTodosRemoteCommandCapabilities(client);
6739
+ if (!capabilities.has("stale-lock-handoff")) {
6740
+ throw new Error(`REMOTE_STALE_LOCK_HANDOFF_UNSUPPORTED: configured Todos authority ${authority} does not advertise ` + "POST /v1/tasks/{id}/stale-lock-handoff; no lock mutation was sent; deploy a compatible " + "@hasna/todos /v1 server before retrying; local SQLite fallback is disabled");
6741
+ }
6742
+ }
6670
6743
  async function fetchGitRefCapabilities(client) {
6671
6744
  const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
6672
6745
  const supported = new Set;
@@ -6766,6 +6839,7 @@ async function cloudUnlockTask(client, id, agentId, force = false) {
6766
6839
  return true;
6767
6840
  }
6768
6841
  async function cloudHandoffStaleTaskLock(client, input) {
6842
+ await requireStaleLockHandoffCapability(client);
6769
6843
  const raw = await client.transport.post(`/tasks/${encodeURIComponent(input.task_id)}/stale-lock-handoff`, {
6770
6844
  expected_holder: input.expected_holder,
6771
6845
  expected_lock_version: input.expected_lock_version,
@@ -7213,7 +7287,7 @@ async function cloudTimeline(client, options = {}) {
7213
7287
  const limit = options.limit ?? 50;
7214
7288
  return { entries: entries.slice(offset, offset + limit), total, limit, offset };
7215
7289
  }
7216
- var UUID_RE, TRANSPORT_TOKENS, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, retryCapabilityCache, gitRefCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache, PLAN_COMPLETION_PROTECTED_FIELDS, RELATION_HYDRATION_CONCURRENCY = 6;
7290
+ var UUID_RE, TRANSPORT_TOKENS, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache, PLAN_COMPLETION_PROTECTED_FIELDS, RELATION_HYDRATION_CONCURRENCY = 6;
7217
7291
  var init_cloud_router = __esm(() => {
7218
7292
  init_types();
7219
7293
  init_redaction();
@@ -7239,7 +7313,9 @@ var init_cloud_router = __esm(() => {
7239
7313
  ];
7240
7314
  completionCapabilityCache = new Map;
7241
7315
  retryCapabilityCache = new Map;
7316
+ taskCreatorCapabilityCache = new Map;
7242
7317
  gitRefCapabilityCache = new Map;
7318
+ remoteCommandCapabilityCache = new Map;
7243
7319
  SERVER_MODE_CANDIDATES = ["postgres", "cloud", "self_hosted"];
7244
7320
  PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
7245
7321
  listTagsCapabilityCache = new Map;
@@ -7264,23 +7340,38 @@ function applyTodosCliAuthorityEnvironment(authority, env = process.env) {
7264
7340
  env.HASNA_TODOS_STORAGE_MODE = "sqlite";
7265
7341
  env.TODOS_STORAGE_MODE = "sqlite";
7266
7342
  }
7267
- function isTodosCliCommandVisibleForRoute(command, route) {
7343
+ function isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities = new Set) {
7268
7344
  if (route === "local")
7269
7345
  return true;
7270
7346
  const owner = COMMAND_CAPABILITY_MATRIX.get(command);
7271
7347
  if (!owner)
7272
7348
  return true;
7273
- return owner !== "local-only";
7349
+ if (owner === "local-only")
7350
+ return false;
7351
+ const requiredCapability = REMOTE_COMMAND_CAPABILITIES.get(command);
7352
+ return requiredCapability ? remoteCapabilities.has(requiredCapability) : true;
7274
7353
  }
7275
- function applyTodosCliHelpVisibility(program2, route) {
7354
+ function applyTodosCliHelpVisibility(program2, route, remoteCapabilities = new Set) {
7276
7355
  if (route === "local")
7277
7356
  return;
7278
7357
  program2.configureHelp({
7279
7358
  visibleCommands(command) {
7280
- return Help.prototype.visibleCommands.call(this, command).filter((subcommand) => isTodosCliCommandVisibleForRoute(subcommand.name(), route));
7359
+ return Help.prototype.visibleCommands.call(this, command).filter((subcommand) => isTodosCliCommandVisibleForRoute(subcommand.name(), route, remoteCapabilities));
7281
7360
  }
7282
7361
  });
7283
7362
  }
7363
+ function getUnavailableTodosCliRemoteMetadataCommand(route, remoteCapabilities = new Set, args = []) {
7364
+ if (route === "local")
7365
+ return null;
7366
+ const invocation = parseInvocation([...args]);
7367
+ if (!isMetadataInvocation([...args], invocation))
7368
+ return null;
7369
+ const requestedCommand = invocation.command === "help" ? positionalArgs(invocation.commandArgs)[0] : invocation.command;
7370
+ if (!requestedCommand)
7371
+ return null;
7372
+ const requiredCapability = REMOTE_COMMAND_CAPABILITIES.get(requestedCommand);
7373
+ return requiredCapability && !remoteCapabilities.has(requiredCapability) ? requestedCommand : null;
7374
+ }
7284
7375
  function parseInvocation(args) {
7285
7376
  const localTokens = [];
7286
7377
  const globalOptions = new Set;
@@ -7337,6 +7428,12 @@ function hasOption(args, option) {
7337
7428
  function positionalArgs(args) {
7338
7429
  return args.filter((arg) => !arg.startsWith("-"));
7339
7430
  }
7431
+ function isHostedLocalInvocation(invocation) {
7432
+ if (invocation.command !== "redaction")
7433
+ return false;
7434
+ const subcommand = positionalArgs(invocation.commandArgs)[0];
7435
+ return subcommand === "status" || subcommand === "add" || subcommand === "scan";
7436
+ }
7340
7437
  function isBundledStaticInvocation(invocation) {
7341
7438
  const command = invocation.command;
7342
7439
  if (!command)
@@ -7492,7 +7589,7 @@ function assertInvocationRoutable(invocation) {
7492
7589
  function assertRemoteCommandSupported(invocation, owner) {
7493
7590
  const command = invocation.command;
7494
7591
  if (command && owner === "local-only") {
7495
- throw new Error(`LOCAL_COMMAND_ROUTING_INVARIANT: \`${command}\` must select the local command route before remote authority validation`);
7592
+ throw new Error(`REMOTE_COMMAND_UNSUPPORTED: \`${command}\` is a local-only command and the Todos /v1 authority does not ` + "serve it; local SQLite fallback is disabled. Run `todos --help` to see the commands this route supports.");
7496
7593
  }
7497
7594
  if (!command || !commandSupportsRemote(invocation)) {
7498
7595
  const blame = command ? disqualifyingArgument(invocation) : null;
@@ -7511,7 +7608,7 @@ function initializeTodosCliAuthority(args = process.argv.slice(2), env = process
7511
7608
  return { route: "remote-diagnostic", v1_base_url: status.v1_base_url };
7512
7609
  }
7513
7610
  const owner = assertInvocationRoutable(invocation);
7514
- if (owner === "local-only") {
7611
+ if (owner === "local-only" && isHostedLocalInvocation(invocation)) {
7515
7612
  return { route: "local", v1_base_url: null, selected_by: "local-only-command" };
7516
7613
  }
7517
7614
  assertRemoteCommandSupported(invocation, owner);
@@ -7521,7 +7618,7 @@ function initializeTodosCliAuthority(args = process.argv.slice(2), env = process
7521
7618
  }
7522
7619
  return { route: "remote-http", v1_base_url: client.baseUrl };
7523
7620
  }
7524
- var REGISTERED_CANONICAL_COMMANDS, TODOS_CLI_COMMAND_ALIASES, BUNDLED_STATIC_COMMANDS, BUNDLED_STATIC_STORE_BACKED_OPTIONS, DIAGNOSTIC_COMMANDS, REMOTE_COMMANDS, COMMAND_CAPABILITY_MATRIX, GLOBAL_OPTIONS_WITH_VALUES, GLOBAL_FLAGS, HELP_FLAGS, VERSION_FLAGS, dropIt = (blame) => ({ blame, remedy: "re-run without it" });
7621
+ var REGISTERED_CANONICAL_COMMANDS, TODOS_CLI_COMMAND_ALIASES, BUNDLED_STATIC_COMMANDS, BUNDLED_STATIC_STORE_BACKED_OPTIONS, DIAGNOSTIC_COMMANDS, REMOTE_COMMANDS, REMOTE_COMMAND_CAPABILITIES, COMMAND_CAPABILITY_MATRIX, GLOBAL_OPTIONS_WITH_VALUES, GLOBAL_FLAGS, HELP_FLAGS, VERSION_FLAGS, dropIt = (blame) => ({ blame, remedy: "re-run without it" });
7525
7622
  var init_stage_a = __esm(() => {
7526
7623
  init_esm();
7527
7624
  init_cloud_router();
@@ -7533,6 +7630,7 @@ var init_stage_a = __esm(() => {
7533
7630
  "agent-update",
7534
7631
  "agents",
7535
7632
  "agents-normalize",
7633
+ "ai",
7536
7634
  "api-keys",
7537
7635
  "approvals",
7538
7636
  "approve",
@@ -7740,6 +7838,7 @@ var init_stage_a = __esm(() => {
7740
7838
  "add",
7741
7839
  "agent",
7742
7840
  "agents",
7841
+ "ai",
7743
7842
  "approve",
7744
7843
  "assign",
7745
7844
  "bulk",
@@ -7793,6 +7892,16 @@ var init_stage_a = __esm(() => {
7793
7892
  "untag",
7794
7893
  "update"
7795
7894
  ]);
7895
+ REMOTE_COMMAND_CAPABILITIES = new Map([
7896
+ ["stale-lock-handoff", "stale-lock-handoff"]
7897
+ ]);
7898
+ for (const [canonical, aliases] of Object.entries(TODOS_CLI_COMMAND_ALIASES)) {
7899
+ const requiredCapability = REMOTE_COMMAND_CAPABILITIES.get(canonical);
7900
+ if (!requiredCapability)
7901
+ continue;
7902
+ for (const alias of aliases)
7903
+ REMOTE_COMMAND_CAPABILITIES.set(alias, requiredCapability);
7904
+ }
7796
7905
  COMMAND_CAPABILITY_MATRIX = new Map;
7797
7906
  for (const command of REGISTERED_CANONICAL_COMMANDS)
7798
7907
  COMMAND_CAPABILITY_MATRIX.set(command, "local-only");
@@ -12804,7 +12913,7 @@ __export(exports_helpers, {
12804
12913
  });
12805
12914
  import chalk from "chalk";
12806
12915
  import { execSync } from "child_process";
12807
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync } from "fs";
12916
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, writeSync } from "fs";
12808
12917
  import { tmpdir } from "os";
12809
12918
  import { dirname as dirname3, join as join4, resolve as resolve3, sep } from "path";
12810
12919
  function jsonModeRequested(argv = process.argv) {
@@ -12885,7 +12994,7 @@ function readCloudTaskIdCache() {
12885
12994
  if (!path || !existsSync5(path))
12886
12995
  return { version: 1, tasks: [] };
12887
12996
  try {
12888
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
12997
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
12889
12998
  if (parsed.version !== 1 || !Array.isArray(parsed.tasks))
12890
12999
  return { version: 1, tasks: [] };
12891
13000
  return {
@@ -16438,7 +16547,7 @@ function requireCanonicalLockVersion(value) {
16438
16547
  throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must be the exact canonical locked_at timestamp (YYYY-MM-DDTHH:mm:ss.sssZ)", { field: "expected_lock_version" });
16439
16548
  }
16440
16549
  const parsed = Date.parse(value);
16441
- if (Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
16550
+ if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
16442
16551
  throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
16443
16552
  }
16444
16553
  return value;
@@ -16463,9 +16572,6 @@ function prepareStaleLockHandoff(input, options = {}) {
16463
16572
  if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
16464
16573
  throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
16465
16574
  }
16466
- if (canonicalAgentRef(expectedHolder) === canonicalAgentRef(newHolder)) {
16467
- throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "new_holder must differ from expected_holder", { field: "new_holder" });
16468
- }
16469
16575
  const operationTimestamp = options.now ?? new Date().toISOString();
16470
16576
  if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
16471
16577
  throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
@@ -19201,7 +19307,7 @@ var init_boards = __esm(() => {
19201
19307
 
19202
19308
  // src/lib/artifact-store.ts
19203
19309
  import { createHash as createHash5 } from "crypto";
19204
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
19310
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
19205
19311
  import { basename as basename2, dirname as dirname5, join as join7, resolve as resolve8 } from "path";
19206
19312
  import { tmpdir as tmpdir3 } from "os";
19207
19313
  function isInMemoryDb2(path) {
@@ -19268,7 +19374,7 @@ function storeArtifactContent(input) {
19268
19374
  const sourceStat = statSync2(sourcePath);
19269
19375
  if (!sourceStat.isFile())
19270
19376
  throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
19271
- const sourceBuffer = readFileSync3(sourcePath);
19377
+ const sourceBuffer = readFileSync4(sourcePath);
19272
19378
  const sourceSha = sha2562(sourceBuffer);
19273
19379
  const textLike = isTextLike(sourceBuffer, input.path);
19274
19380
  let storedBuffer = sourceBuffer;
@@ -19355,7 +19461,7 @@ function verifyStoredArtifact(input) {
19355
19461
  message: "stored artifact content is missing"
19356
19462
  };
19357
19463
  }
19358
- const buffer = readFileSync3(storedPath);
19464
+ const buffer = readFileSync4(storedPath);
19359
19465
  const actualSha = sha2562(buffer);
19360
19466
  const actualSize = buffer.length;
19361
19467
  const ok = actualSha === store.sha256 && actualSize === store.size_bytes;
@@ -19375,7 +19481,7 @@ function exportStoredArtifactContent(input) {
19375
19481
  const report = verifyStoredArtifact(input);
19376
19482
  if (report.status !== "ok" || !report.relative_path || !report.actual_sha256 || report.actual_size_bytes === null)
19377
19483
  return null;
19378
- const content = readFileSync3(artifactStorePath(report.relative_path));
19484
+ const content = readFileSync4(artifactStorePath(report.relative_path));
19379
19485
  return {
19380
19486
  artifact_id: input.id,
19381
19487
  sha256: report.actual_sha256,
@@ -20934,7 +21040,7 @@ function normalizeAgentNameInput(name) {
20934
21040
  }
20935
21041
 
20936
21042
  // src/lib/assignee-validation.ts
20937
- import { readFileSync as readFileSync4 } from "fs";
21043
+ import { readFileSync as readFileSync5 } from "fs";
20938
21044
  import { homedir as homedir3 } from "os";
20939
21045
  import { join as join8 } from "path";
20940
21046
  function describeAssigneeFilter(input, ctx) {
@@ -20959,7 +21065,7 @@ function defaultSeatRosterPath() {
20959
21065
  }
20960
21066
  function loadSeatSlugs(path = defaultSeatRosterPath()) {
20961
21067
  try {
20962
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
21068
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
20963
21069
  const slugs = new Set;
20964
21070
  for (const agent of parsed.agents ?? []) {
20965
21071
  if (typeof agent?.slug === "string" && agent.slug.trim()) {
@@ -22114,6 +22220,21 @@ function warnMissingProject(resolvedProjectId, optedOut) {
22114
22220
  return;
22115
22221
  console.error(chalk3.yellow("Warning: task filed with no project \u2014 it will not appear in any project list, per-seat report, or drain. " + "Pass --project <id-or-slug>, or --no-project if filing it globally is deliberate."));
22116
22222
  }
22223
+ function warnTaskRouting(task, deliberatelyUnassigned) {
22224
+ const ownerless = !task.assigned_to && !deliberatelyUnassigned;
22225
+ const unattributable = !task.created_by;
22226
+ if (ownerless && unattributable) {
22227
+ console.error(chalk3.yellow("Warning: task is ownerless and unattributable \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
22228
+ return;
22229
+ }
22230
+ if (ownerless) {
22231
+ console.error(chalk3.yellow("Warning: task is ownerless \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
22232
+ return;
22233
+ }
22234
+ if (unattributable) {
22235
+ console.error(chalk3.yellow("Warning: task is unattributable \u2014 created_by will be recorded as null. Export TODOS_AGENT_ID=<name> for this session, or pass --agent <agent>, to record who filed it."));
22236
+ }
22237
+ }
22117
22238
  function parseStatus(value) {
22118
22239
  if (!value)
22119
22240
  return;
@@ -22287,6 +22408,27 @@ async function computeCloudReparent(cloud, current, opts) {
22287
22408
  patch.task_list_id = taskListId;
22288
22409
  return patch;
22289
22410
  }
22411
+ async function cloudUpdateTaskWithVerifiedReparent(cloud, taskId, updatePatch, reparent) {
22412
+ const acknowledged = await cloudUpdateTask(cloud, taskId, updatePatch);
22413
+ if (reparent.project_id === undefined && reparent.task_list_id === undefined) {
22414
+ return acknowledged;
22415
+ }
22416
+ const persisted = await cloudGetTask(cloud, taskId);
22417
+ if (!persisted) {
22418
+ throw new Error(`TASK_REPARENT_PERSISTENCE_UNVERIFIED: PATCH /v1/tasks/${taskId} was acknowledged, ` + "but the authoritative read-back did not return the task.");
22419
+ }
22420
+ const mismatches = [];
22421
+ if (reparent.project_id !== undefined && persisted.project_id !== reparent.project_id) {
22422
+ mismatches.push(`project_id expected ${reparent.project_id}, received ${persisted.project_id ?? "null"}`);
22423
+ }
22424
+ if (reparent.task_list_id !== undefined && (persisted.task_list_id ?? null) !== reparent.task_list_id) {
22425
+ mismatches.push(`task_list_id expected ${reparent.task_list_id ?? "null"}, received ${persisted.task_list_id ?? "null"}`);
22426
+ }
22427
+ if (mismatches.length > 0) {
22428
+ throw new Error(`TASK_REPARENT_PERSISTENCE_UNVERIFIED: PATCH /v1/tasks/${taskId} was acknowledged, ` + `but authoritative read-back disagreed (${mismatches.join("; ")}).`);
22429
+ }
22430
+ return persisted;
22431
+ }
22290
22432
  function computeLocalReparent(current, opts) {
22291
22433
  const targetProjectId = opts.projectRef ? resolveProjectIdOrSlug(opts.projectRef) : undefined;
22292
22434
  const scope = targetProjectId ?? current.project_id ?? null;
@@ -22317,12 +22459,6 @@ function registerTaskCommands(program2) {
22317
22459
  const router = resolveWritableIdentity(opts.createdBy || globalOpts.agent);
22318
22460
  const requestedAssign = opts.assign ? await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`) : undefined;
22319
22461
  const assignee = requestedAssign || (opts.unassigned ? undefined : router.agent_id || undefined);
22320
- const ownerless = !assignee && !opts.unassigned;
22321
- if (ownerless) {
22322
- console.error(chalk3.yellow("Warning: task is ownerless and unattributable \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
22323
- } else if (!router.agent_id) {
22324
- console.error(chalk3.yellow("Warning: task is unattributable \u2014 created_by will be recorded as null. Export TODOS_AGENT_ID=<name> for this session, or pass --agent <agent>, to record who filed it."));
22325
- }
22326
22462
  const cloud = getTodosCloudClient();
22327
22463
  if (cloud) {
22328
22464
  let task3;
@@ -22338,7 +22474,7 @@ function registerTaskCommands(program2) {
22338
22474
  if (opts.plan && !cloudPlan) {
22339
22475
  throw new Error(`Could not resolve plan ID or slug: ${opts.plan}`);
22340
22476
  }
22341
- task3 = await cloudCreateTask(cloud, {
22477
+ const taskInput = {
22342
22478
  title,
22343
22479
  description: opts.description,
22344
22480
  priority: parsePriority(opts.priority),
@@ -22359,10 +22495,12 @@ function registerTaskCommands(program2) {
22359
22495
  recurrence_rule: opts.recurrence,
22360
22496
  due_at: opts.due ? opts.due.length === 10 ? opts.due + "T00:00:00.000Z" : opts.due : undefined,
22361
22497
  reason: opts.reason
22362
- });
22498
+ };
22499
+ task3 = await cloudCreateTask(cloud, taskInput, opts.createdBy && router.agent_id ? { expectedCreatedBy: router.agent_id } : undefined);
22363
22500
  } catch (e) {
22364
22501
  handleError(e);
22365
22502
  }
22503
+ warnTaskRouting(task3, Boolean(opts.unassigned));
22366
22504
  if (globalOpts.json) {
22367
22505
  output(task3, true);
22368
22506
  } else {
@@ -22410,6 +22548,7 @@ function registerTaskCommands(program2) {
22410
22548
  } catch (e) {
22411
22549
  handleError(e);
22412
22550
  }
22551
+ warnTaskRouting(task2, Boolean(opts.unassigned));
22413
22552
  if (globalOpts.json) {
22414
22553
  output(task2, true);
22415
22554
  } else {
@@ -22827,8 +22966,10 @@ function registerTaskCommands(program2) {
22827
22966
  } else {
22828
22967
  const resolvedId = resolveTaskId(id);
22829
22968
  task2 = applyLocalCommentPage(getTaskWithRelations(resolvedId), page);
22830
- const { getTaskGitRefs: getTaskGitRefs2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
22831
- task2.git_refs = getTaskGitRefs2(resolvedId);
22969
+ if (task2) {
22970
+ const { getTaskGitRefs: getTaskGitRefs2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
22971
+ task2.git_refs = getTaskGitRefs2(resolvedId);
22972
+ }
22832
22973
  }
22833
22974
  if (!task2) {
22834
22975
  handleError(new Error(`Task not found: ${id}`));
@@ -23170,7 +23311,7 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
23170
23311
  listRef: opts.list,
23171
23312
  clearList: opts.clearList
23172
23313
  });
23173
- task3 = await cloudUpdateTask(cloud, currentId, {
23314
+ const updatePatch = {
23174
23315
  title: opts.title,
23175
23316
  description: opts.description,
23176
23317
  status: parseStatus(opts.status),
@@ -23186,7 +23327,8 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
23186
23327
  due_at: opts.due !== undefined ? opts.due === "" ? null : opts.due.length === 10 ? opts.due + "T00:00:00.000Z" : opts.due : undefined,
23187
23328
  recurrence_rule: opts.recurrence !== undefined ? opts.recurrence === "" ? null : opts.recurrence : undefined,
23188
23329
  requires_approval: opts.clearApproval ? false : opts.approval !== undefined ? true : undefined
23189
- });
23330
+ };
23331
+ task3 = await cloudUpdateTaskWithVerifiedReparent(cloud, currentId, updatePatch, reparent2);
23190
23332
  } catch (e) {
23191
23333
  handleError(e);
23192
23334
  }
@@ -23265,7 +23407,7 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
23265
23407
  if (reparent2.project_id === undefined && reparent2.task_list_id === undefined) {
23266
23408
  throw new Error("Nothing to move: the task is already in the requested project/list.");
23267
23409
  }
23268
- task3 = await cloudUpdateTask(cloud, currentId, reparent2);
23410
+ task3 = await cloudUpdateTaskWithVerifiedReparent(cloud, currentId, reparent2, reparent2);
23269
23411
  } catch (e) {
23270
23412
  handleError(e);
23271
23413
  }
@@ -23704,7 +23846,7 @@ var init_task_commands = __esm(() => {
23704
23846
  });
23705
23847
 
23706
23848
  // src/lib/plan-artifacts.ts
23707
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
23849
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
23708
23850
  import { join as join9, resolve as resolve10 } from "path";
23709
23851
  function assertSafePathSegment(value, label) {
23710
23852
  const trimmed = value.trim();
@@ -23943,7 +24085,7 @@ function readPlanArtifact(plan, db) {
23943
24085
  const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
23944
24086
  if (!path)
23945
24087
  return null;
23946
- const markdown = readFileSync5(path, "utf8");
24088
+ const markdown = readFileSync6(path, "utf8");
23947
24089
  return {
23948
24090
  path,
23949
24091
  markdown,
@@ -23967,7 +24109,7 @@ function inspectPlanArtifact(plan, db) {
23967
24109
  };
23968
24110
  }
23969
24111
  try {
23970
- const artifact = parsePlanArtifactMarkdown(readFileSync5(path, "utf8"));
24112
+ const artifact = parsePlanArtifactMarkdown(readFileSync6(path, "utf8"));
23971
24113
  return {
23972
24114
  path,
23973
24115
  exists: true,
@@ -26543,13 +26685,13 @@ function registerPlanTemplateCommands(program2) {
26543
26685
  });
26544
26686
  program2.command("template-import [file]").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file (alternative to positional arg)").action(async (file, opts) => {
26545
26687
  const globalOpts = program2.opts();
26546
- const { readFileSync: readFileSync6 } = await import("fs");
26688
+ const { readFileSync: readFileSync7 } = await import("fs");
26547
26689
  try {
26548
26690
  const filePath = file || opts.file;
26549
26691
  if (!filePath) {
26550
26692
  handleError(new Error("Provide a file path: todos template-import <file> or --file <path>"));
26551
26693
  }
26552
- const content = readFileSync6(filePath, "utf-8");
26694
+ const content = readFileSync7(filePath, "utf-8");
26553
26695
  const json = JSON.parse(content);
26554
26696
  const cloud = getTodosCloudClient();
26555
26697
  const template = cloud ? await cloudCreateTemplate(cloud, json) : (await Promise.resolve().then(() => (init_templates(), exports_templates))).importTemplate(json);
@@ -26939,6 +27081,8 @@ function searchTaskEntities(filters, db) {
26939
27081
  return tasks.filter((task) => taskMatchesSavedFilters(task, filters, db)).slice(0, normalizeLimit(filters.limit));
26940
27082
  }
26941
27083
  function searchProjects(filters, db) {
27084
+ if (filters.agent_id)
27085
+ return [];
26942
27086
  const params = [];
26943
27087
  let sql = "SELECT * FROM projects WHERE 1=1";
26944
27088
  if (filters.project_id) {
@@ -27144,7 +27288,7 @@ var init_saved_search_views = __esm(() => {
27144
27288
  });
27145
27289
 
27146
27290
  // src/lib/claude-tasks.ts
27147
- import { existsSync as existsSync11, readFileSync as readFileSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "fs";
27291
+ import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "fs";
27148
27292
  import { join as join11 } from "path";
27149
27293
  function getTaskListDir(taskListId) {
27150
27294
  return join11(HOME, ".claude", "tasks", taskListId);
@@ -27168,7 +27312,7 @@ function readPrefixCounter(dir) {
27168
27312
  const path = join11(dir, ".prefix-counter");
27169
27313
  if (!existsSync11(path))
27170
27314
  return 0;
27171
- const val = parseInt(readFileSync6(path, "utf-8").trim(), 10);
27315
+ const val = parseInt(readFileSync7(path, "utf-8").trim(), 10);
27172
27316
  return isNaN(val) ? 0 : val;
27173
27317
  }
27174
27318
  function writePrefixCounter(dir, value) {
@@ -27940,7 +28084,7 @@ __export(exports_project_bootstrap, {
27940
28084
  discoverProjectWorkspace: () => discoverProjectWorkspace,
27941
28085
  bootstrapProject: () => bootstrapProject
27942
28086
  });
27943
- import { existsSync as existsSync13, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
28087
+ import { existsSync as existsSync13, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
27944
28088
  import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
27945
28089
  function safeStat(path) {
27946
28090
  try {
@@ -27974,7 +28118,7 @@ function readPackageJson(path) {
27974
28118
  if (!existsSync13(file))
27975
28119
  return null;
27976
28120
  try {
27977
- const parsed = JSON.parse(readFileSync7(file, "utf-8"));
28121
+ const parsed = JSON.parse(readFileSync8(file, "utf-8"));
27978
28122
  return parsed && typeof parsed === "object" ? parsed : null;
27979
28123
  } catch {
27980
28124
  return null;
@@ -28327,7 +28471,7 @@ __export(exports_extract, {
28327
28471
  buildCodebaseIndex: () => buildCodebaseIndex,
28328
28472
  EXTRACT_TAGS: () => EXTRACT_TAGS
28329
28473
  });
28330
- import { existsSync as existsSync14, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
28474
+ import { existsSync as existsSync14, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
28331
28475
  import { createHash as createHash7 } from "crypto";
28332
28476
  import { relative as relative3, resolve as resolve12, join as join13 } from "path";
28333
28477
  function stableHash(value) {
@@ -28342,7 +28486,7 @@ function readGitignorePatterns(basePath) {
28342
28486
  if (!existsSync14(gitignorePath))
28343
28487
  return [];
28344
28488
  try {
28345
- return readFileSync8(gitignorePath, "utf-8").split(`
28489
+ return readFileSync9(gitignorePath, "utf-8").split(`
28346
28490
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
28347
28491
  } catch {
28348
28492
  return [];
@@ -28483,7 +28627,7 @@ function buildCodebaseIndex(options) {
28483
28627
  for (const file of files) {
28484
28628
  const fullPath = statSync5(basePath).isFile() ? basePath : join13(basePath, file);
28485
28629
  try {
28486
- const source = readFileSync8(fullPath, "utf-8");
28630
+ const source = readFileSync9(fullPath, "utf-8");
28487
28631
  const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
28488
28632
  indexed.push({
28489
28633
  file: relPath,
@@ -28514,7 +28658,7 @@ function extractTodos(options, db) {
28514
28658
  for (const file of files) {
28515
28659
  const fullPath = statSync5(basePath).isFile() ? basePath : join13(basePath, file);
28516
28660
  try {
28517
- const source = readFileSync8(fullPath, "utf-8");
28661
+ const source = readFileSync9(fullPath, "utf-8");
28518
28662
  const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
28519
28663
  const comments = extractFromSource(source, relPath, tags);
28520
28664
  allComments.push(...comments);
@@ -29844,7 +29988,7 @@ __export(exports_project_commands, {
29844
29988
  registerProjectCommands: () => registerProjectCommands
29845
29989
  });
29846
29990
  import chalk5 from "chalk";
29847
- import { readFileSync as readFileSync9, statSync as statSync6 } from "fs";
29991
+ import { readFileSync as readFileSync10, statSync as statSync6 } from "fs";
29848
29992
  import { basename as basename5, resolve as resolve13, sep as sep3 } from "path";
29849
29993
  function collectOption(value, previous = []) {
29850
29994
  return [...previous, value];
@@ -30057,7 +30201,7 @@ function registerProjectCommands(program2) {
30057
30201
  handleError(new Error(`Comment file "${opts.file}" must be a regular file.`));
30058
30202
  }
30059
30203
  try {
30060
- content = readFileSync9(commentFilePath, "utf8");
30204
+ content = readFileSync10(commentFilePath, "utf8");
30061
30205
  } catch (error) {
30062
30206
  handleError(new Error(`Unable to read comment file "${opts.file}".`, { cause: error }));
30063
30207
  }
@@ -30836,10 +30980,10 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
30836
30980
  program2.command("bridge-import <file>").description("Dry-run or apply a local hasna/todos bridge export bundle").option("--apply", "Apply the import. Defaults to dry-run.").option("--decrypt", "Decrypt an encrypted bridge export before importing").option("--resolve-conflicts", "Safely merge existing local tasks by filling blank fields, unioning tags, and recording unresolved divergences").action(async (file, opts) => {
30837
30981
  const globalOpts = program2.opts();
30838
30982
  try {
30839
- const { readFileSync: readFileSync10 } = await import("fs");
30983
+ const { readFileSync: readFileSync11 } = await import("fs");
30840
30984
  const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
30841
30985
  const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
30842
- const parsed = JSON.parse(readFileSync10(resolve13(file), "utf-8"));
30986
+ const parsed = JSON.parse(readFileSync11(resolve13(file), "utf-8"));
30843
30987
  const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
30844
30988
  throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
30845
30989
  })() : parsed;
@@ -30873,9 +31017,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
30873
31017
  program2.command("todos-md-import <file>").alias("markdown-import").alias("import-md").description("Dry-run or apply a local todos.md Markdown import").option("--apply", "Apply the import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge embedded bridge task conflicts while preserving local divergent fields").action(async (file, opts) => {
30874
31018
  const globalOpts = program2.opts();
30875
31019
  try {
30876
- const { readFileSync: readFileSync10 } = await import("fs");
31020
+ const { readFileSync: readFileSync11 } = await import("fs");
30877
31021
  const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
30878
- const result = importTodosMarkdown2(readFileSync10(resolve13(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
31022
+ const result = importTodosMarkdown2(readFileSync11(resolve13(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
30879
31023
  const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
30880
31024
  emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve13(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
30881
31025
  if (globalOpts.json) {
@@ -31483,6 +31627,2449 @@ var init_agent_commands = __esm(() => {
31483
31627
  init_cloud_router();
31484
31628
  });
31485
31629
 
31630
+ // src/ai.ts
31631
+ function isRecord(value) {
31632
+ return value !== null && typeof value === "object" && !Array.isArray(value);
31633
+ }
31634
+ function isJsonObject(value) {
31635
+ if (!isRecord(value))
31636
+ return false;
31637
+ const prototype = Object.getPrototypeOf(value);
31638
+ return prototype === Object.prototype || prototype === null;
31639
+ }
31640
+ function prototypeDefinesToJson(value) {
31641
+ let prototype = Object.getPrototypeOf(value);
31642
+ while (prototype !== null) {
31643
+ if (Object.getOwnPropertyDescriptor(prototype, "toJSON") !== undefined)
31644
+ return true;
31645
+ prototype = Object.getPrototypeOf(prototype);
31646
+ }
31647
+ return false;
31648
+ }
31649
+ function isStableJsonArray(value, ancestors, depth) {
31650
+ if (Object.getPrototypeOf(value) !== Array.prototype || prototypeDefinesToJson(value)) {
31651
+ return false;
31652
+ }
31653
+ const keys = Reflect.ownKeys(value);
31654
+ if (keys.length !== value.length + 1)
31655
+ return false;
31656
+ for (const key of keys) {
31657
+ if (key === "length")
31658
+ continue;
31659
+ if (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key))
31660
+ return false;
31661
+ const index = Number(key);
31662
+ if (!Number.isSafeInteger(index) || index < 0 || index >= value.length)
31663
+ return false;
31664
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
31665
+ if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
31666
+ return false;
31667
+ if (!isTodosAiJsonValueInternal(descriptor.value, ancestors, depth + 1))
31668
+ return false;
31669
+ }
31670
+ return true;
31671
+ }
31672
+ function isStableJsonObject(value, ancestors, depth) {
31673
+ if (!isJsonObject(value) || prototypeDefinesToJson(value))
31674
+ return false;
31675
+ for (const key of Reflect.ownKeys(value)) {
31676
+ if (typeof key !== "string")
31677
+ return false;
31678
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
31679
+ if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
31680
+ return false;
31681
+ if (!isTodosAiJsonValueInternal(descriptor.value, ancestors, depth + 1))
31682
+ return false;
31683
+ }
31684
+ return true;
31685
+ }
31686
+ function isOneOf(value, values) {
31687
+ return typeof value === "string" && values.includes(value);
31688
+ }
31689
+ function selectedString(...values) {
31690
+ for (const value of values) {
31691
+ if (value === undefined || value === null)
31692
+ continue;
31693
+ const trimmed = value.trim();
31694
+ if (trimmed)
31695
+ return trimmed;
31696
+ }
31697
+ return null;
31698
+ }
31699
+ function parseEnum(value, values, field, fallback) {
31700
+ if (value === null)
31701
+ return fallback;
31702
+ if (isOneOf(value, values))
31703
+ return value;
31704
+ throw new TodosAiContractError("invalid_configuration", `${field} must be one of: ${values.join(", ")}`);
31705
+ }
31706
+ function parseBoundedInteger(value, field, fallback, min, max) {
31707
+ if (value === undefined || value === null || value === "")
31708
+ return fallback;
31709
+ const parsed = typeof value === "number" ? value : Number(value);
31710
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
31711
+ throw new TodosAiContractError("invalid_configuration", `${field} must be an integer between ${min} and ${max}`);
31712
+ }
31713
+ return parsed;
31714
+ }
31715
+ function normalizeApprovalRefs(values) {
31716
+ const refs = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
31717
+ if (refs.length > TODOS_AI_LIMITS.max_approval_refs) {
31718
+ throw new TodosAiContractError("invalid_configuration", `--approval may be repeated at most ${TODOS_AI_LIMITS.max_approval_refs} times`);
31719
+ }
31720
+ for (const ref of refs) {
31721
+ if (new TextEncoder().encode(ref).byteLength > TODOS_AI_LIMITS.max_approval_ref_bytes) {
31722
+ throw new TodosAiContractError("invalid_configuration", `--approval references may not exceed ${TODOS_AI_LIMITS.max_approval_ref_bytes} bytes`);
31723
+ }
31724
+ }
31725
+ return refs;
31726
+ }
31727
+ function resolveTodosAiCommandOptions(input) {
31728
+ const cli = input.cli ?? {};
31729
+ const config = input.config ?? {};
31730
+ const env = input.env ?? {};
31731
+ const format = parseEnum(selectedString(cli.format, env["TODOS_AI_FORMAT"], config.format), TODOS_AI_FORMATS, "format", TODOS_AI_DEFAULTS.format);
31732
+ const configuredWriteMode = parseEnum(selectedString(cli.writeMode, env["TODOS_AI_WRITE_MODE"], config.write_mode), TODOS_AI_WRITE_MODES, "write mode", TODOS_AI_DEFAULTS.write_mode);
31733
+ if (cli.dryRun && cli.writeMode === "execute") {
31734
+ throw new TodosAiContractError("invalid_configuration", "--dry-run cannot be combined with --write-mode execute");
31735
+ }
31736
+ const writeMode = cli.dryRun ? "plan" : configuredWriteMode;
31737
+ const explicitApproval = cli.dryRun ? selectedString(cli.approvalMode) : selectedString(cli.approvalMode, env["TODOS_AI_APPROVAL_MODE"], config.approval_mode);
31738
+ const defaultApproval = writeMode === "execute" ? input.interactive ? "prompt" : "required" : "deny";
31739
+ const approvalMode = cli.dryRun ? parseEnum(explicitApproval, TODOS_AI_APPROVAL_MODES, "approval mode", "deny") : parseEnum(explicitApproval, TODOS_AI_APPROVAL_MODES, "approval mode", defaultApproval);
31740
+ const approvalRefs = normalizeApprovalRefs(cli.approvalRefs);
31741
+ if (cli.dryRun && approvalMode !== "deny") {
31742
+ throw new TodosAiContractError("invalid_configuration", "--dry-run requires approval mode deny");
31743
+ }
31744
+ if (writeMode !== "execute" && approvalMode !== "deny") {
31745
+ throw new TodosAiContractError("invalid_configuration", "approval modes other than deny require --write-mode execute");
31746
+ }
31747
+ if (writeMode === "execute" && approvalMode === "deny") {
31748
+ throw new TodosAiContractError("invalid_configuration", "--write-mode execute requires approval mode required, prompt, or existing");
31749
+ }
31750
+ if (!input.interactive && approvalMode === "prompt") {
31751
+ throw new TodosAiContractError("invalid_configuration", "approval mode prompt is unavailable in non-interactive mode");
31752
+ }
31753
+ if (approvalMode === "existing" && approvalRefs.length === 0) {
31754
+ throw new TodosAiContractError("invalid_configuration", "approval mode existing requires at least one --approval reference");
31755
+ }
31756
+ if (approvalMode !== "existing" && approvalRefs.length > 0) {
31757
+ throw new TodosAiContractError("invalid_configuration", "--approval references require --approval-mode existing");
31758
+ }
31759
+ return {
31760
+ provider: selectedString(cli.provider, env["TODOS_AI_PROVIDER"], config.provider),
31761
+ model: selectedString(cli.model, env["TODOS_AI_MODEL"], config.model),
31762
+ profile: selectedString(cli.profile, env["TODOS_AI_PROFILE"], config.profile),
31763
+ format,
31764
+ max_steps: parseBoundedInteger(cli.maxSteps ?? env["TODOS_AI_MAX_STEPS"] ?? config.max_steps, "max steps", TODOS_AI_DEFAULTS.max_steps, TODOS_AI_LIMITS.min_steps, TODOS_AI_LIMITS.max_steps),
31765
+ timeout_ms: parseBoundedInteger(cli.timeoutMs ?? env["TODOS_AI_TIMEOUT_MS"] ?? config.timeout_ms, "timeout", TODOS_AI_DEFAULTS.timeout_ms, TODOS_AI_LIMITS.min_timeout_ms, TODOS_AI_LIMITS.max_timeout_ms),
31766
+ write_mode: writeMode,
31767
+ approval_mode: approvalMode,
31768
+ approval_refs: approvalRefs,
31769
+ dry_run: cli.dryRun === true,
31770
+ interactive: input.interactive
31771
+ };
31772
+ }
31773
+ function normalizeTodosAiPrompt(value) {
31774
+ const prompt = value.trim();
31775
+ if (new TextEncoder().encode(prompt).byteLength > TODOS_AI_LIMITS.max_prompt_bytes) {
31776
+ throw new TodosAiContractError("invalid_input", `prompt exceeds ${TODOS_AI_LIMITS.max_prompt_bytes} bytes`);
31777
+ }
31778
+ return prompt;
31779
+ }
31780
+ function parseTodosAiJson(value, field) {
31781
+ if (new TextEncoder().encode(value).byteLength > TODOS_AI_LIMITS.max_json_bytes) {
31782
+ throw new TodosAiContractError("invalid_input", `${field} exceeds ${TODOS_AI_LIMITS.max_json_bytes} bytes`);
31783
+ }
31784
+ let parsed;
31785
+ try {
31786
+ parsed = JSON.parse(value);
31787
+ } catch (error) {
31788
+ const detail = error instanceof Error ? error.message : String(error);
31789
+ throw new TodosAiContractError("invalid_input", `${field} must be valid JSON: ${detail}`);
31790
+ }
31791
+ if (!isTodosAiJsonValue(parsed)) {
31792
+ throw new TodosAiContractError("invalid_input", `${field} must contain only stable JSON values`);
31793
+ }
31794
+ return parsed;
31795
+ }
31796
+ function parseTodosAiOutputSchema(value) {
31797
+ const parsed = parseTodosAiJson(value, "output schema");
31798
+ if (!isRecord(parsed)) {
31799
+ throw new TodosAiContractError("invalid_input", "output schema must be a JSON object");
31800
+ }
31801
+ return parsed;
31802
+ }
31803
+ function parseTodosAiVariables(values) {
31804
+ if (values.length > TODOS_AI_LIMITS.max_variable_count) {
31805
+ throw new TodosAiContractError("invalid_input", `--var may be repeated at most ${TODOS_AI_LIMITS.max_variable_count} times`);
31806
+ }
31807
+ const variables = Object.create(null);
31808
+ for (const entry of values) {
31809
+ const separator = entry.indexOf("=");
31810
+ const key = separator >= 0 ? entry.slice(0, separator).trim() : "";
31811
+ const value = separator >= 0 ? entry.slice(separator + 1) : "";
31812
+ if (!/^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/.test(key)) {
31813
+ throw new TodosAiContractError("invalid_input", `invalid --var entry ${JSON.stringify(entry)}; expected non-secret key=value`);
31814
+ }
31815
+ if (SENSITIVE_VARIABLE_KEY.test(key)) {
31816
+ throw new TodosAiContractError("invalid_input", `--var ${key} is credential-shaped; provide credentials through the runtime's secret configuration`);
31817
+ }
31818
+ if (Object.hasOwn(variables, key)) {
31819
+ throw new TodosAiContractError("invalid_input", `duplicate --var key: ${key}`);
31820
+ }
31821
+ if (new TextEncoder().encode(value).byteLength > TODOS_AI_LIMITS.max_variable_value_bytes) {
31822
+ throw new TodosAiContractError("invalid_input", `--var ${key} exceeds ${TODOS_AI_LIMITS.max_variable_value_bytes} bytes`);
31823
+ }
31824
+ variables[key] = value;
31825
+ }
31826
+ return variables;
31827
+ }
31828
+ function isTodosAiJsonValue(value) {
31829
+ return isTodosAiJsonValueInternal(value, new Set, 0);
31830
+ }
31831
+ function isTodosAiUpdateTaskResult(value) {
31832
+ if (!isTodosAiJsonValue(value) || !isRecord(value) || !hasOnlyKeys(value, [
31833
+ "schema",
31834
+ "operation",
31835
+ "mode",
31836
+ "applied",
31837
+ "readback_verified",
31838
+ "source",
31839
+ "target",
31840
+ "changed_fields",
31841
+ "approval_ref",
31842
+ "payload_digest",
31843
+ "idempotency"
31844
+ ]) || value["schema"] !== TODOS_AI_UPDATE_TASK_RESULT_SCHEMA || value["operation"] !== "update_task" || value["mode"] !== "plan" && value["mode"] !== "execute" || value["source"] !== "sqlite" && value["source"] !== "http") {
31845
+ return false;
31846
+ }
31847
+ const changedFields = value["changed_fields"];
31848
+ const target = value["target"];
31849
+ const idempotency = value["idempotency"];
31850
+ const payloadDigest = value["payload_digest"];
31851
+ const approvalRef = value["approval_ref"];
31852
+ if (!Array.isArray(changedFields) || changedFields.length === 0 || changedFields.length > TODOS_AI_UPDATE_TASK_FIELDS.length || !changedFields.every((field) => typeof field === "string" && TODOS_AI_UPDATE_TASK_FIELDS.includes(field)) || new Set(changedFields).size !== changedFields.length || !TODOS_AI_UPDATE_TASK_FIELDS.filter((field) => changedFields.includes(field)).every((field, index) => changedFields[index] === field) || !isRecord(target) || !hasOnlyKeys(target, ["task_id", "expected_version", "result_version"]) || typeof target["task_id"] !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target["task_id"]) || !Number.isSafeInteger(target["expected_version"]) || target["expected_version"] < 0 || typeof payloadDigest !== "string" || !/^[0-9a-f]{64}$/.test(payloadDigest) || approvalRef !== `todos-ai:update_task:${payloadDigest}` || !isRecord(idempotency) || !hasOnlyKeys(idempotency, ["key", "scope", "replay"]) || !boundedUtf8String(idempotency["key"], TODOS_AI_UPDATE_TASK_LIMITS.max_idempotency_key_bytes) || new TextEncoder().encode(idempotency["key"]).byteLength < TODOS_AI_UPDATE_TASK_LIMITS.min_idempotency_key_bytes || !/^[A-Za-z0-9._:-]+$/.test(idempotency["key"]) || idempotency["scope"] !== "run" || typeof idempotency["replay"] !== "boolean" || new TextEncoder().encode(JSON.stringify(value)).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_result_bytes) {
31853
+ return false;
31854
+ }
31855
+ if (value["mode"] === "plan") {
31856
+ return value["applied"] === false && value["readback_verified"] === false && target["result_version"] === null;
31857
+ }
31858
+ return value["applied"] === true && value["readback_verified"] === true && Number.isSafeInteger(target["result_version"]) && target["result_version"] === target["expected_version"] + 1;
31859
+ }
31860
+ function isTodosAiJsonValueInternal(value, ancestors, depth) {
31861
+ if (depth > 64)
31862
+ return false;
31863
+ if (value === null || typeof value === "string" || typeof value === "boolean")
31864
+ return true;
31865
+ if (typeof value === "number")
31866
+ return Number.isFinite(value);
31867
+ if (typeof value !== "object")
31868
+ return false;
31869
+ if (ancestors.has(value))
31870
+ return false;
31871
+ ancestors.add(value);
31872
+ try {
31873
+ return Array.isArray(value) ? isStableJsonArray(value, ancestors, depth) : isStableJsonObject(value, ancestors, depth);
31874
+ } catch {
31875
+ return false;
31876
+ } finally {
31877
+ ancestors.delete(value);
31878
+ }
31879
+ }
31880
+ function isUsage(value) {
31881
+ if (!isRecord(value))
31882
+ return false;
31883
+ return ["input_tokens", "output_tokens", "total_tokens"].every((key) => Number.isSafeInteger(value[key]) && value[key] >= 0);
31884
+ }
31885
+ function hasOnlyKeys(value, keys) {
31886
+ const allowed = new Set(keys);
31887
+ return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.has(key));
31888
+ }
31889
+ function boundedUtf8String(value, maximum, allowEmpty = false) {
31890
+ return typeof value === "string" && (allowEmpty || value.length > 0) && new TextEncoder().encode(value).byteLength <= maximum;
31891
+ }
31892
+ function isPendingInput(value) {
31893
+ if (!isRecord(value) || !hasOnlyKeys(value, ["prompt", "fields"]))
31894
+ return false;
31895
+ if (!boundedUtf8String(value["prompt"], TODOS_AI_LIMITS.max_pending_input_prompt_bytes)) {
31896
+ return false;
31897
+ }
31898
+ if (!Array.isArray(value["fields"]) || value["fields"].length === 0 || value["fields"].length > TODOS_AI_LIMITS.max_pending_input_fields) {
31899
+ return false;
31900
+ }
31901
+ const fields = value["fields"];
31902
+ const unique4 = new Set;
31903
+ for (const field of fields) {
31904
+ if (!boundedUtf8String(field, TODOS_AI_LIMITS.max_pending_input_field_bytes) || !/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(field) || unique4.has(field)) {
31905
+ return false;
31906
+ }
31907
+ unique4.add(field);
31908
+ }
31909
+ return true;
31910
+ }
31911
+ function isPendingApproval(value) {
31912
+ if (!isRecord(value) || !hasOnlyKeys(value, ["id", "summary", "operations"])) {
31913
+ return false;
31914
+ }
31915
+ if (!boundedUtf8String(value["id"], TODOS_AI_LIMITS.max_pending_approval_id_bytes) || !boundedUtf8String(value["summary"], TODOS_AI_LIMITS.max_pending_approval_summary_bytes) || !Array.isArray(value["operations"]) || value["operations"].length === 0 || value["operations"].length > TODOS_AI_LIMITS.max_pending_approval_operations || !value["operations"].every(isRecord) || !value["operations"].every(isTodosAiJsonValue)) {
31916
+ return false;
31917
+ }
31918
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength <= TODOS_AI_LIMITS.max_pending_approval_bytes;
31919
+ }
31920
+ function isAiError(value) {
31921
+ if (!isRecord(value))
31922
+ return false;
31923
+ const code = value["code"];
31924
+ const validCode = [
31925
+ "invalid_input",
31926
+ "invalid_configuration",
31927
+ "runtime_unavailable",
31928
+ "runtime_incompatible",
31929
+ "runtime_invalid_result",
31930
+ "needs_input",
31931
+ "needs_approval",
31932
+ "timeout",
31933
+ "interrupted",
31934
+ "provider_error",
31935
+ "tool_error",
31936
+ "schema_error",
31937
+ "internal_error"
31938
+ ];
31939
+ return typeof code === "string" && validCode.includes(code) && typeof value["message"] === "string" && typeof value["retryable"] === "boolean" && (value["details"] === null || isRecord(value["details"]) && isTodosAiJsonValue(value["details"]));
31940
+ }
31941
+ function isTodosAiRunResult(value) {
31942
+ if (!isRecord(value) || !isTodosAiJsonValue(value))
31943
+ return false;
31944
+ const structurallyValid = value["schema_version"] === TODOS_AI_SCHEMA_VERSION && typeof value["run_id"] === "string" && Boolean(value["run_id"]) && isOneOf(value["status"], TODOS_AI_RUN_STATUSES) && (value["answer"] === null || typeof value["answer"] === "string") && isTodosAiJsonValue(value["data"]) && Number.isSafeInteger(value["steps"]) && value["steps"] >= 0 && (value["usage"] === null || isUsage(value["usage"])) && (value["pending_input"] === null || isPendingInput(value["pending_input"])) && (value["pending_approval"] === null || isPendingApproval(value["pending_approval"])) && (value["error"] === null || isAiError(value["error"]));
31945
+ if (!structurallyValid)
31946
+ return false;
31947
+ switch (value["status"]) {
31948
+ case "answered":
31949
+ return typeof value["answer"] === "string" && value["pending_input"] === null && value["pending_approval"] === null && value["error"] === null;
31950
+ case "completed":
31951
+ return isTodosAiUpdateTaskResult(value["data"]) && value["data"]["mode"] === "execute" && value["data"]["applied"] === true && value["data"]["readback_verified"] === true && value["pending_input"] === null && value["pending_approval"] === null && value["error"] === null;
31952
+ case "needs_input":
31953
+ return value["pending_input"] !== null && value["pending_approval"] === null && value["error"] === null;
31954
+ case "needs_approval":
31955
+ return value["pending_input"] === null && value["pending_approval"] !== null && value["error"] === null;
31956
+ case "failed":
31957
+ return value["pending_input"] === null && value["pending_approval"] === null && value["error"] !== null;
31958
+ }
31959
+ return false;
31960
+ }
31961
+ function assertTodosAiRunResult(value) {
31962
+ if (!isTodosAiRunResult(value)) {
31963
+ throw new TodosAiContractError("runtime_invalid_result", "optional AI runtime returned a result that does not satisfy the Todos AI protocol", TODOS_AI_EXIT_CODES.failed);
31964
+ }
31965
+ return value;
31966
+ }
31967
+ function isTodosAiRuntimeEvent(value) {
31968
+ if (!isRecord(value) || !isTodosAiJsonValue(value))
31969
+ return false;
31970
+ return value["schema_version"] === TODOS_AI_SCHEMA_VERSION && typeof value["run_id"] === "string" && Boolean(value["run_id"]) && Number.isSafeInteger(value["sequence"]) && value["sequence"] >= 0 && isOneOf(value["type"], TODOS_AI_RUNTIME_EVENT_TYPES) && typeof value["timestamp"] === "string" && isRecord(value["data"]) && isTodosAiJsonValue(value["data"]);
31971
+ }
31972
+ function assertTodosAiRuntimeModule(value) {
31973
+ if (!isRecord(value) || value["TODOS_AI_RUNTIME_PROTOCOL_VERSION"] !== TODOS_AI_RUNTIME_PROTOCOL_VERSION || typeof value["createTodosAiRuntime"] !== "function") {
31974
+ throw new TodosAiContractError("runtime_incompatible", `optional AI runtime must implement protocol ${TODOS_AI_RUNTIME_PROTOCOL_VERSION}`, TODOS_AI_EXIT_CODES.runtime_unavailable);
31975
+ }
31976
+ return value;
31977
+ }
31978
+ function assertTodosAiRuntime(value) {
31979
+ if (!isRecord(value) || typeof value["run"] !== "function") {
31980
+ throw new TodosAiContractError("runtime_incompatible", `optional AI runtime must implement protocol ${TODOS_AI_RUNTIME_PROTOCOL_VERSION}`, TODOS_AI_EXIT_CODES.runtime_unavailable);
31981
+ }
31982
+ return value;
31983
+ }
31984
+ async function loadTodosAiRuntime(context, importer = defaultTodosAiRuntimeImporter) {
31985
+ let imported;
31986
+ try {
31987
+ imported = await importer(TODOS_AI_RUNTIME_SPECIFIER);
31988
+ } catch (cause) {
31989
+ throw new TodosAiContractError("runtime_unavailable", `optional AI runtime is unavailable; install a compatible ${TODOS_AI_RUNTIME_SPECIFIER}`, TODOS_AI_EXIT_CODES.runtime_unavailable, { cause });
31990
+ }
31991
+ const runtimeModule = assertTodosAiRuntimeModule(imported);
31992
+ return assertTodosAiRuntime(await runtimeModule.createTodosAiRuntime(context));
31993
+ }
31994
+ function createTodosAiFailureResult(runId, code, message, retryable = false, details = null) {
31995
+ return {
31996
+ schema_version: TODOS_AI_SCHEMA_VERSION,
31997
+ run_id: runId,
31998
+ status: "failed",
31999
+ answer: null,
32000
+ data: null,
32001
+ steps: 0,
32002
+ usage: null,
32003
+ pending_input: null,
32004
+ pending_approval: null,
32005
+ error: { code, message, retryable, details }
32006
+ };
32007
+ }
32008
+ function createTodosAiNeedsInputResult(runId, message) {
32009
+ return {
32010
+ schema_version: TODOS_AI_SCHEMA_VERSION,
32011
+ run_id: runId,
32012
+ status: "needs_input",
32013
+ answer: null,
32014
+ data: null,
32015
+ steps: 0,
32016
+ usage: null,
32017
+ pending_input: { prompt: message, fields: ["prompt"] },
32018
+ pending_approval: null,
32019
+ error: null
32020
+ };
32021
+ }
32022
+ function todosAiExitCodeForResult(result) {
32023
+ if (result.status === "answered" || result.status === "completed")
32024
+ return TODOS_AI_EXIT_CODES.success;
32025
+ if (result.status === "needs_input")
32026
+ return TODOS_AI_EXIT_CODES.needs_input;
32027
+ if (result.status === "needs_approval")
32028
+ return TODOS_AI_EXIT_CODES.needs_approval;
32029
+ switch (result.error?.code) {
32030
+ case "invalid_input":
32031
+ case "invalid_configuration":
32032
+ return TODOS_AI_EXIT_CODES.usage;
32033
+ case "runtime_unavailable":
32034
+ case "runtime_incompatible":
32035
+ return TODOS_AI_EXIT_CODES.runtime_unavailable;
32036
+ case "timeout":
32037
+ return TODOS_AI_EXIT_CODES.timeout;
32038
+ case "interrupted":
32039
+ return TODOS_AI_EXIT_CODES.interrupted;
32040
+ default:
32041
+ return TODOS_AI_EXIT_CODES.failed;
32042
+ }
32043
+ }
32044
+ var TODOS_AI_SCHEMA_VERSION = 1, TODOS_AI_RUNTIME_PROTOCOL_VERSION = 1, TODOS_AI_RUNTIME_SPECIFIER = "@hasna/todos-ai/runtime", TODOS_AI_UPDATE_TASK_RESULT_SCHEMA = "todos.ai.update_task.v1", TODOS_AI_FORMATS, TODOS_AI_WRITE_MODES, TODOS_AI_APPROVAL_MODES, TODOS_AI_RUN_STATUSES, TODOS_AI_RUNTIME_EVENT_TYPES, TODOS_AI_UPDATE_TASK_FIELDS, TODOS_AI_UPDATE_TASK_LIMITS, TODOS_AI_DEFAULTS, TODOS_AI_LIMITS, TODOS_AI_EXIT_CODES, TodosAiContractError, TodosAiNeedsInputSignal, TodosAiNeedsApprovalSignal, SENSITIVE_VARIABLE_KEY, defaultTodosAiRuntimeImporter = async (specifier) => import(specifier);
32045
+ var init_ai = __esm(() => {
32046
+ TODOS_AI_FORMATS = ["text", "json", "stream-json"];
32047
+ TODOS_AI_WRITE_MODES = ["read-only", "plan", "execute"];
32048
+ TODOS_AI_APPROVAL_MODES = ["deny", "required", "prompt", "existing"];
32049
+ TODOS_AI_RUN_STATUSES = [
32050
+ "answered",
32051
+ "needs_input",
32052
+ "needs_approval",
32053
+ "completed",
32054
+ "failed"
32055
+ ];
32056
+ TODOS_AI_RUNTIME_EVENT_TYPES = [
32057
+ "run.started",
32058
+ "run.progress",
32059
+ "text.delta",
32060
+ "tool.started",
32061
+ "tool.completed",
32062
+ "input.required",
32063
+ "approval.required"
32064
+ ];
32065
+ TODOS_AI_UPDATE_TASK_FIELDS = [
32066
+ "title",
32067
+ "description",
32068
+ "status",
32069
+ "priority",
32070
+ "assigned_to",
32071
+ "tags",
32072
+ "due_at"
32073
+ ];
32074
+ TODOS_AI_UPDATE_TASK_LIMITS = {
32075
+ max_title_bytes: 1024,
32076
+ max_description_bytes: 8192,
32077
+ max_assignee_bytes: 256,
32078
+ max_tags: 16,
32079
+ max_tag_bytes: 128,
32080
+ max_due_at_bytes: 128,
32081
+ min_idempotency_key_bytes: 8,
32082
+ max_idempotency_key_bytes: 128,
32083
+ max_result_bytes: 65536
32084
+ };
32085
+ TODOS_AI_DEFAULTS = {
32086
+ format: "text",
32087
+ max_steps: 8,
32088
+ timeout_ms: 60000,
32089
+ write_mode: "read-only"
32090
+ };
32091
+ TODOS_AI_LIMITS = {
32092
+ max_prompt_bytes: 1048576,
32093
+ max_json_bytes: 1048576,
32094
+ max_result_bytes: 4194304,
32095
+ max_variable_count: 100,
32096
+ max_variable_value_bytes: 65536,
32097
+ max_approval_refs: 32,
32098
+ max_approval_ref_bytes: 1024,
32099
+ max_resume_run_id_bytes: 1024,
32100
+ max_pending_input_prompt_bytes: 1024,
32101
+ max_pending_input_fields: 16,
32102
+ max_pending_input_field_bytes: 128,
32103
+ max_pending_approval_id_bytes: 256,
32104
+ max_pending_approval_summary_bytes: 1024,
32105
+ max_pending_approval_operations: 4,
32106
+ max_pending_approval_bytes: 8192,
32107
+ max_stream_events: 1000,
32108
+ max_stream_record_bytes: 262144,
32109
+ max_stream_bytes: 8388608,
32110
+ min_steps: 1,
32111
+ max_steps: 20,
32112
+ min_timeout_ms: 1000,
32113
+ max_timeout_ms: 600000
32114
+ };
32115
+ TODOS_AI_EXIT_CODES = {
32116
+ success: 0,
32117
+ usage: 2,
32118
+ needs_input: 3,
32119
+ needs_approval: 4,
32120
+ runtime_unavailable: 5,
32121
+ failed: 6,
32122
+ timeout: 124,
32123
+ interrupted: 130
32124
+ };
32125
+ TodosAiContractError = class TodosAiContractError extends Error {
32126
+ code;
32127
+ exitCode;
32128
+ constructor(code, message, exitCode = TODOS_AI_EXIT_CODES.usage, options) {
32129
+ super(message, options);
32130
+ this.code = code;
32131
+ this.exitCode = exitCode;
32132
+ this.name = "TodosAiContractError";
32133
+ }
32134
+ };
32135
+ TodosAiNeedsInputSignal = class TodosAiNeedsInputSignal extends Error {
32136
+ pending_input;
32137
+ constructor(pendingInput) {
32138
+ if (!isTodosAiJsonValue(pendingInput) || !isPendingInput(pendingInput)) {
32139
+ throw new TodosAiContractError("invalid_input", "Todos AI pending input must be bounded stable control data");
32140
+ }
32141
+ super("Todos AI input required");
32142
+ this.name = "TodosAiNeedsInputSignal";
32143
+ this.pending_input = {
32144
+ prompt: pendingInput.prompt,
32145
+ fields: [...pendingInput.fields]
32146
+ };
32147
+ }
32148
+ };
32149
+ TodosAiNeedsApprovalSignal = class TodosAiNeedsApprovalSignal extends Error {
32150
+ pending_approval;
32151
+ constructor(pendingApproval) {
32152
+ if (!isTodosAiJsonValue(pendingApproval) || !isPendingApproval(pendingApproval)) {
32153
+ throw new TodosAiContractError("invalid_input", "Todos AI pending approval must be bounded stable control data");
32154
+ }
32155
+ super("Todos AI approval required");
32156
+ this.name = "TodosAiNeedsApprovalSignal";
32157
+ this.pending_approval = {
32158
+ id: pendingApproval.id,
32159
+ summary: pendingApproval.summary,
32160
+ operations: pendingApproval.operations.map((operation) => JSON.parse(JSON.stringify(operation)))
32161
+ };
32162
+ }
32163
+ };
32164
+ SENSITIVE_VARIABLE_KEY = /(?:^|[_.-])(api[_-]?key|credential|password|secret|token)(?:$|[_.-])/i;
32165
+ });
32166
+
32167
+ // src/lib/access-profiles.ts
32168
+ function resolveAccessProfile(envValue) {
32169
+ const raw = (envValue ?? process.env["TODOS_PROFILE"] ?? "full").toLowerCase();
32170
+ if (ACCESS_PROFILES.includes(raw))
32171
+ return raw;
32172
+ if (raw === "readonly")
32173
+ return "read_only";
32174
+ if (raw === "agent-safe")
32175
+ return "agent_safe";
32176
+ return "full";
32177
+ }
32178
+ function shouldRegisterToolForProfile(toolName, profile) {
32179
+ const p = profile ?? resolveAccessProfile();
32180
+ if (ADMIN_ONLY_TOOLS.has(toolName) && p !== "admin")
32181
+ return false;
32182
+ switch (p) {
32183
+ case "read_only":
32184
+ return READ_ONLY_TOOLS.has(toolName);
32185
+ case "agent_safe":
32186
+ return AGENT_SAFE_TOOLS.has(toolName);
32187
+ case "minimal":
32188
+ return MINIMAL_TOOLS.has(toolName);
32189
+ case "standard":
32190
+ return !STANDARD_EXCLUDED.has(toolName);
32191
+ case "admin":
32192
+ case "full":
32193
+ default:
32194
+ return true;
32195
+ }
32196
+ }
32197
+ var ACCESS_PROFILES, READ_ONLY_TOOLS, MINIMAL_TOOLS, AGENT_SAFE_TOOLS, STANDARD_EXCLUDED, ADMIN_ONLY_TOOLS, DANGEROUS_TOOLS;
32198
+ var init_access_profiles = __esm(() => {
32199
+ ACCESS_PROFILES = ["read_only", "agent_safe", "minimal", "standard", "full", "admin"];
32200
+ READ_ONLY_TOOLS = new Set([
32201
+ "get_task",
32202
+ "list_tasks",
32203
+ "get_status",
32204
+ "get_context",
32205
+ "search_tasks",
32206
+ "list_projects",
32207
+ "get_project",
32208
+ "list_plans",
32209
+ "get_plan",
32210
+ "list_agents",
32211
+ "get_agent",
32212
+ "list_task_lists",
32213
+ "get_task_list",
32214
+ "get_task_commits",
32215
+ "get_task_traceability",
32216
+ "list_pending_approvals",
32217
+ "get_task_gate_status",
32218
+ "list_active_leases",
32219
+ "list_agent_runs",
32220
+ "list_agent_adapters",
32221
+ "discover_workspace",
32222
+ "get_bootstrap_status",
32223
+ "get_agent_workflow_demo_docs",
32224
+ "get_feature_manifest",
32225
+ "get_capability_discovery",
32226
+ "get_feature_manifest_docs",
32227
+ "get_cli_mcp_parity",
32228
+ "list_secret_patterns",
32229
+ "list_sandbox_profiles",
32230
+ "list_workspace_trust_profiles",
32231
+ "list_verification_providers",
32232
+ "list_verification_records",
32233
+ "get_tasks_changed_since",
32234
+ "get_stale_tasks",
32235
+ "get_next_task",
32236
+ "find_duplicate_tasks",
32237
+ "describe_tools",
32238
+ "search_tools",
32239
+ "inspect_git_commit",
32240
+ "list_task_findings",
32241
+ "scan_text_for_secrets",
32242
+ "check_workspace_permission",
32243
+ "check_sandbox_command",
32244
+ "parse_mentions",
32245
+ "resolve_mention",
32246
+ "resolve_mentions_in_text",
32247
+ "generate_release_notes",
32248
+ "format_release_notes_markdown",
32249
+ "analyze_branch_work",
32250
+ "generate_branch_work_plan",
32251
+ "get_branch_work_plan_docs"
32252
+ ]);
32253
+ MINIMAL_TOOLS = new Set([
32254
+ "claim_next_task",
32255
+ "complete_task",
32256
+ "fail_task",
32257
+ "get_status",
32258
+ "get_context",
32259
+ "get_task",
32260
+ "start_task",
32261
+ "add_comment",
32262
+ "get_next_task",
32263
+ "bootstrap",
32264
+ "get_tasks_changed_since",
32265
+ "heartbeat",
32266
+ "release_agent",
32267
+ "begin_task_run_transaction",
32268
+ "finish_task_run",
32269
+ "upsert_task_finding",
32270
+ "list_task_findings",
32271
+ "resolve_missing_task_findings"
32272
+ ]);
32273
+ AGENT_SAFE_TOOLS = new Set([
32274
+ ...READ_ONLY_TOOLS,
32275
+ ...MINIMAL_TOOLS,
32276
+ "create_task",
32277
+ "update_task",
32278
+ "lock_task",
32279
+ "unlock_task",
32280
+ "enqueue_agent_run",
32281
+ "claim_next_agent_run",
32282
+ "complete_agent_run",
32283
+ "fail_agent_run",
32284
+ "acquire_task_lease",
32285
+ "renew_task_lease",
32286
+ "release_task_lease",
32287
+ "request_approval",
32288
+ "run_verification",
32289
+ "link_task_git_trace",
32290
+ "assign_label_to_task",
32291
+ "set_task_custom_field",
32292
+ "log_progress"
32293
+ ]);
32294
+ STANDARD_EXCLUDED = new Set([
32295
+ "rename_agent",
32296
+ "delete_agent",
32297
+ "unarchive_agent",
32298
+ "create_webhook",
32299
+ "list_webhooks",
32300
+ "delete_webhook",
32301
+ "create_template",
32302
+ "list_templates",
32303
+ "create_task_from_template",
32304
+ "delete_template",
32305
+ "update_template",
32306
+ "init_templates",
32307
+ "preview_template",
32308
+ "export_template",
32309
+ "import_template",
32310
+ "template_history",
32311
+ "approve_task",
32312
+ "migrate_pg"
32313
+ ]);
32314
+ ADMIN_ONLY_TOOLS = new Set([
32315
+ "migrate_pg",
32316
+ "delete_task",
32317
+ "delete_project",
32318
+ "delete_agent",
32319
+ "delete_plan",
32320
+ "delete_task_list",
32321
+ "delete_webhook",
32322
+ "delete_template",
32323
+ "steal_task_lease",
32324
+ "recover_stale_leases"
32325
+ ]);
32326
+ DANGEROUS_TOOLS = new Set([
32327
+ ...ADMIN_ONLY_TOOLS,
32328
+ "bulk_update_tasks",
32329
+ "merge_tasks",
32330
+ "cancel_agent_run"
32331
+ ]);
32332
+ });
32333
+
32334
+ // src/db/checkpoints.ts
32335
+ function upsertCheckpoint(task_id, step, updates, db) {
32336
+ const d = db || getDatabase();
32337
+ const timestamp2 = now();
32338
+ const existing = d.query("SELECT id FROM task_checkpoints WHERE task_id = ? AND step = ?").get(task_id, step);
32339
+ const id = existing?.id ?? uuid();
32340
+ const agentId = updates.agent_id ?? null;
32341
+ const status = updates.status ?? "pending";
32342
+ const data = updates.data ? JSON.stringify(updates.data) : JSON.stringify({});
32343
+ const error = updates.error ?? null;
32344
+ const attempt = updates.attempt ?? 1;
32345
+ const maxAttempts = updates.max_attempts ?? 1;
32346
+ const startedAt = updates.started_at ?? null;
32347
+ const completedAt = updates.completed_at ?? null;
32348
+ d.run(`INSERT INTO task_checkpoints (id, task_id, agent_id, step, status, data, error, attempt, max_attempts, started_at, completed_at, updated_at)
32349
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32350
+ ON CONFLICT(id) DO UPDATE SET status=?, data=?, error=?, attempt=?, max_attempts=?, started_at=COALESCE(started_at,?), completed_at=?, updated_at=?`, [
32351
+ id,
32352
+ task_id,
32353
+ agentId,
32354
+ step,
32355
+ status,
32356
+ data,
32357
+ error,
32358
+ attempt,
32359
+ maxAttempts,
32360
+ startedAt,
32361
+ completedAt,
32362
+ timestamp2,
32363
+ status,
32364
+ data,
32365
+ error,
32366
+ attempt,
32367
+ maxAttempts,
32368
+ startedAt,
32369
+ completedAt,
32370
+ timestamp2
32371
+ ]);
32372
+ return rowToCheckpoint(d.query("SELECT * FROM task_checkpoints WHERE id = ?").get(id));
32373
+ }
32374
+ function getCheckpoint(taskId, step, db) {
32375
+ const d = db || getDatabase();
32376
+ const row = d.query("SELECT * FROM task_checkpoints WHERE task_id = ? AND step = ?").get(taskId, step);
32377
+ return row ? rowToCheckpoint(row) : null;
32378
+ }
32379
+ function getTaskCheckpoints(taskId, db) {
32380
+ const d = db || getDatabase();
32381
+ return d.query("SELECT * FROM task_checkpoints WHERE task_id = ? ORDER BY created_at ASC").all(taskId).map(rowToCheckpoint);
32382
+ }
32383
+ function rowToCheckpoint(row) {
32384
+ if (!row)
32385
+ return null;
32386
+ return {
32387
+ ...row,
32388
+ data: JSON.parse(row.data || "{}"),
32389
+ status: row.status,
32390
+ agent_id: row.agent_id || null,
32391
+ error: row.error || null,
32392
+ started_at: row.started_at || null,
32393
+ completed_at: row.completed_at || null
32394
+ };
32395
+ }
32396
+ var init_checkpoints = __esm(() => {
32397
+ init_database();
32398
+ });
32399
+
32400
+ // src/lib/approval-gates.ts
32401
+ var exports_approval_gates = {};
32402
+ __export(exports_approval_gates, {
32403
+ requestApprovalGate: () => requestApprovalGate,
32404
+ rejectApprovalGate: () => rejectApprovalGate,
32405
+ listApprovalGates: () => listApprovalGates,
32406
+ expireApprovalGate: () => expireApprovalGate,
32407
+ checkApprovalGate: () => checkApprovalGate,
32408
+ assertApprovalGate: () => assertApprovalGate,
32409
+ approveApprovalGate: () => approveApprovalGate
32410
+ });
32411
+ function stepForGate(gate) {
32412
+ const trimmed = gate.trim();
32413
+ if (!trimmed)
32414
+ throw new Error("Approval gate name is required");
32415
+ return `approval:${trimmed}`;
32416
+ }
32417
+ function approvalStatusToCheckpointStatus(status) {
32418
+ if (status === "approved")
32419
+ return "completed";
32420
+ if (status === "pending")
32421
+ return "pending";
32422
+ return "failed";
32423
+ }
32424
+ function checkpointStatusToApprovalStatus(checkpoint) {
32425
+ const value = checkpoint.data["approval_status"];
32426
+ if (value === "approved" || value === "rejected" || value === "expired" || value === "pending")
32427
+ return value;
32428
+ if (checkpoint.status === "completed")
32429
+ return "approved";
32430
+ if (checkpoint.status === "failed")
32431
+ return "rejected";
32432
+ return "pending";
32433
+ }
32434
+ function ensureTask(taskId, db) {
32435
+ if (!getTask(taskId, db))
32436
+ throw new TaskNotFoundError(taskId);
32437
+ }
32438
+ function isExpired(expiresAt, at = new Date) {
32439
+ return Boolean(expiresAt && new Date(expiresAt).getTime() <= at.getTime());
32440
+ }
32441
+ function gateFromCheckpoint(checkpoint) {
32442
+ const status = checkpointStatusToApprovalStatus(checkpoint);
32443
+ return {
32444
+ id: checkpoint.id,
32445
+ task_id: checkpoint.task_id,
32446
+ gate: String(checkpoint.data["approval_gate_name"] || checkpoint.step.replace(/^approval:/, "")),
32447
+ status,
32448
+ reviewer: typeof checkpoint.data["reviewer"] === "string" ? checkpoint.data["reviewer"] : null,
32449
+ requester: typeof checkpoint.data["requester"] === "string" ? checkpoint.data["requester"] : null,
32450
+ reason: typeof checkpoint.data["reason"] === "string" ? checkpoint.data["reason"] : null,
32451
+ note: typeof checkpoint.data["note"] === "string" ? checkpoint.data["note"] : null,
32452
+ plan_id: typeof checkpoint.data["plan_id"] === "string" ? checkpoint.data["plan_id"] : null,
32453
+ run_id: typeof checkpoint.data["run_id"] === "string" ? checkpoint.data["run_id"] : null,
32454
+ expires_at: typeof checkpoint.data["expires_at"] === "string" ? checkpoint.data["expires_at"] : null,
32455
+ decided_by: typeof checkpoint.data["decided_by"] === "string" ? checkpoint.data["decided_by"] : null,
32456
+ decided_at: typeof checkpoint.data["decided_at"] === "string" ? checkpoint.data["decided_at"] : null,
32457
+ created_at: checkpoint.created_at,
32458
+ updated_at: checkpoint.updated_at,
32459
+ checkpoint
32460
+ };
32461
+ }
32462
+ function logApprovalEvent(taskId, action, gate, agentId, db) {
32463
+ const payload = JSON.stringify({
32464
+ gate: gate.gate,
32465
+ status: gate.status,
32466
+ reviewer: gate.reviewer,
32467
+ requester: gate.requester,
32468
+ plan_id: gate.plan_id,
32469
+ run_id: gate.run_id,
32470
+ expires_at: gate.expires_at,
32471
+ decided_by: gate.decided_by,
32472
+ decided_at: gate.decided_at
32473
+ });
32474
+ logTaskChange(taskId, `approval_gate.${action}`, "approval_gate", null, payload, agentId, db);
32475
+ if (gate.run_id && getTaskRun(gate.run_id, db)) {
32476
+ addTaskRunEvent({
32477
+ run_id: gate.run_id,
32478
+ event_type: "progress",
32479
+ message: `approval gate ${action}: ${gate.gate}`,
32480
+ data: JSON.parse(payload),
32481
+ agent_id: agentId
32482
+ }, db);
32483
+ }
32484
+ if (action === "approved" || action === "rejected" || action === "expired") {
32485
+ emitLocalEventHooksQuiet({
32486
+ type: "approval.decided",
32487
+ payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
32488
+ databasePath: databasePathFromDatabase(db)
32489
+ });
32490
+ }
32491
+ }
32492
+ function writeGate(input, status, decision, db) {
32493
+ const d = db || getDatabase();
32494
+ ensureTask(input.task_id, d);
32495
+ const step = stepForGate(input.gate);
32496
+ const existing = getCheckpoint(input.task_id, step, d);
32497
+ const existingData = existing?.data || {};
32498
+ const timestamp2 = decision?.decided_at || now();
32499
+ const existingRunId = typeof existingData["run_id"] === "string" ? existingData["run_id"] : undefined;
32500
+ const runId = input.run_id ? resolveTaskRunId(input.run_id, d) : existingRunId;
32501
+ const data = {
32502
+ ...existingData,
32503
+ ...input.metadata || {},
32504
+ approval_gate: true,
32505
+ approval_gate_name: input.gate.trim(),
32506
+ approval_status: status,
32507
+ requester: input.requester ?? existingData["requester"] ?? null,
32508
+ reviewer: input.reviewer ?? decision?.decided_by ?? existingData["reviewer"] ?? null,
32509
+ reason: decision?.reason ?? input.reason ?? existingData["reason"] ?? null,
32510
+ note: decision?.note ?? existingData["note"] ?? null,
32511
+ plan_id: input.plan_id ?? existingData["plan_id"] ?? null,
32512
+ run_id: runId ?? null,
32513
+ expires_at: input.expires_at ?? existingData["expires_at"] ?? null,
32514
+ decided_by: decision?.decided_by ?? existingData["decided_by"] ?? null,
32515
+ decided_at: decision?.decided_at ?? existingData["decided_at"] ?? null
32516
+ };
32517
+ const checkpoint = upsertCheckpoint(input.task_id, step, {
32518
+ agent_id: decision?.decided_by || input.requester || input.reviewer,
32519
+ status: approvalStatusToCheckpointStatus(status),
32520
+ data,
32521
+ error: status === "rejected" || status === "expired" ? String(data.reason || status) : null,
32522
+ started_at: existing?.started_at || timestamp2,
32523
+ completed_at: status === "pending" ? null : timestamp2
32524
+ }, d);
32525
+ return gateFromCheckpoint(checkpoint);
32526
+ }
32527
+ function currentGate(taskId, gate, db) {
32528
+ const checkpoint = getCheckpoint(taskId, stepForGate(gate), db);
32529
+ return checkpoint ? gateFromCheckpoint(checkpoint) : null;
32530
+ }
32531
+ function requestApprovalGate(input, db) {
32532
+ const d = db || getDatabase();
32533
+ const existing = currentGate(input.task_id, input.gate, d);
32534
+ if (existing && existing.status !== "pending") {
32535
+ throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
32536
+ }
32537
+ const gate = writeGate(input, "pending", undefined, d);
32538
+ logApprovalEvent(input.task_id, "requested", gate, input.requester || input.reviewer, d);
32539
+ return gate;
32540
+ }
32541
+ function approveApprovalGate(input, db) {
32542
+ const d = db || getDatabase();
32543
+ const existing = currentGate(input.task_id, input.gate, d);
32544
+ if (!existing)
32545
+ throw new Error(`Approval gate not found: ${input.gate}`);
32546
+ if (existing.status !== "pending")
32547
+ throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
32548
+ if (isExpired(existing.expires_at))
32549
+ throw new Error(`Approval gate ${input.gate} is expired`);
32550
+ const gate = writeGate({
32551
+ task_id: input.task_id,
32552
+ gate: input.gate,
32553
+ requester: existing.requester || undefined,
32554
+ reviewer: input.reviewer || existing.reviewer || undefined,
32555
+ reason: existing.reason || undefined,
32556
+ plan_id: existing.plan_id || undefined,
32557
+ run_id: existing.run_id || undefined,
32558
+ expires_at: existing.expires_at || undefined
32559
+ }, "approved", { decided_by: input.reviewer, decided_at: now(), note: input.note }, d);
32560
+ logApprovalEvent(input.task_id, "approved", gate, input.reviewer, d);
32561
+ return gate;
32562
+ }
32563
+ function rejectApprovalGate(input, db) {
32564
+ const d = db || getDatabase();
32565
+ const existing = currentGate(input.task_id, input.gate, d);
32566
+ if (!existing)
32567
+ throw new Error(`Approval gate not found: ${input.gate}`);
32568
+ if (existing.status !== "pending")
32569
+ throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
32570
+ const gate = writeGate({
32571
+ task_id: input.task_id,
32572
+ gate: input.gate,
32573
+ requester: existing.requester || undefined,
32574
+ reviewer: input.reviewer || existing.reviewer || undefined,
32575
+ reason: existing.reason || undefined,
32576
+ plan_id: existing.plan_id || undefined,
32577
+ run_id: existing.run_id || undefined,
32578
+ expires_at: existing.expires_at || undefined
32579
+ }, "rejected", { decided_by: input.reviewer, decided_at: now(), note: input.note, reason: input.reason || input.note }, d);
32580
+ logApprovalEvent(input.task_id, "rejected", gate, input.reviewer, d);
32581
+ return gate;
32582
+ }
32583
+ function expireApprovalGate(input, db) {
32584
+ const d = db || getDatabase();
32585
+ const existing = currentGate(input.task_id, input.gate, d);
32586
+ if (!existing)
32587
+ throw new Error(`Approval gate not found: ${input.gate}`);
32588
+ if (existing.status !== "pending")
32589
+ throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
32590
+ const gate = writeGate({
32591
+ task_id: input.task_id,
32592
+ gate: input.gate,
32593
+ requester: existing.requester || undefined,
32594
+ reviewer: existing.reviewer || undefined,
32595
+ reason: existing.reason || undefined,
32596
+ plan_id: existing.plan_id || undefined,
32597
+ run_id: existing.run_id || undefined,
32598
+ expires_at: existing.expires_at || undefined
32599
+ }, "expired", { decided_by: input.reviewer, decided_at: now(), reason: input.reason || "expired" }, d);
32600
+ logApprovalEvent(input.task_id, "expired", gate, input.reviewer, d);
32601
+ return gate;
32602
+ }
32603
+ function listApprovalGates(taskId, db) {
32604
+ const d = db || getDatabase();
32605
+ ensureTask(taskId, d);
32606
+ return getTaskCheckpoints(taskId, d).filter((checkpoint) => checkpoint.data["approval_gate"] === true || checkpoint.step.startsWith("approval:")).map(gateFromCheckpoint);
32607
+ }
32608
+ function checkApprovalGate(taskId, gateName, db) {
32609
+ const d = db || getDatabase();
32610
+ ensureTask(taskId, d);
32611
+ const gate = currentGate(taskId, gateName, d);
32612
+ const reasons = [];
32613
+ if (!gate)
32614
+ reasons.push(`approval gate is required: ${gateName}`);
32615
+ else if (gate.status !== "approved")
32616
+ reasons.push(`approval gate ${gateName} is ${gate.status}`);
32617
+ if (gate && gate.status === "pending" && isExpired(gate.expires_at))
32618
+ reasons.push(`approval gate ${gateName} is expired`);
32619
+ return { allowed: reasons.length === 0, gate, reasons };
32620
+ }
32621
+ function assertApprovalGate(taskId, gateName, db) {
32622
+ const result = checkApprovalGate(taskId, gateName, db);
32623
+ if (!result.allowed)
32624
+ throw new Error(result.reasons.join("; "));
32625
+ return result.gate;
32626
+ }
32627
+ var init_approval_gates = __esm(() => {
32628
+ init_audit();
32629
+ init_checkpoints();
32630
+ init_database();
32631
+ init_task_runs();
32632
+ init_tasks();
32633
+ init_types();
32634
+ init_event_emission_safety();
32635
+ init_event_hooks();
32636
+ });
32637
+
32638
+ // src/ai-tools.ts
32639
+ import { createHash as createHash9 } from "crypto";
32640
+ function createLocalTodosAiReadAdapter(database) {
32641
+ return {
32642
+ source: "sqlite",
32643
+ getTask: (id) => getTask(id, database),
32644
+ listTasks: (filter) => listTasks(filter, database),
32645
+ listProjects: () => listProjects(database),
32646
+ listPlans: (projectId) => listPlans(projectId, database),
32647
+ updateTask: (id, patch) => updateTask(id, patch, database),
32648
+ verifyApproval: (input) => {
32649
+ const result = checkApprovalGate(input.task_id, input.ref, database);
32650
+ if (!result.gate)
32651
+ return null;
32652
+ return {
32653
+ ref: result.gate.gate,
32654
+ task_id: result.gate.task_id,
32655
+ operation: input.operation,
32656
+ payload_digest: input.payload_digest,
32657
+ status: result.gate.status,
32658
+ expires_at: result.gate.expires_at
32659
+ };
32660
+ }
32661
+ };
32662
+ }
32663
+ function createHttpTodosAiReadAdapter(client) {
32664
+ return {
32665
+ source: "http",
32666
+ getTask: (id) => cloudGetTask(client, id),
32667
+ listTasks: (filter) => cloudListTasks(client, filter),
32668
+ listProjects: () => cloudListProjects(client),
32669
+ listPlans: (projectId) => cloudListPlans(client, projectId),
32670
+ updateTask: (id, patch) => cloudUpdateTask(client, id, patch)
32671
+ };
32672
+ }
32673
+ function resolveAdapter(options) {
32674
+ if (options.adapter)
32675
+ return options.adapter;
32676
+ const client = getTodosCloudClient(options.env);
32677
+ return client ? createHttpTodosAiReadAdapter(client) : createLocalTodosAiReadAdapter(options.database);
32678
+ }
32679
+ function resolvePermission(options) {
32680
+ if (options.workspacePermission)
32681
+ return options.workspacePermission;
32682
+ const path = options.workspacePath ?? process.cwd();
32683
+ return (permission) => checkWorkspacePermission({ path, tool: permission }).allowed;
32684
+ }
32685
+ function configuredProfileIsKnown(value) {
32686
+ if (value === undefined || value.trim() === "")
32687
+ return true;
32688
+ const normalized = value.trim().toLowerCase();
32689
+ return ACCESS_PROFILES.includes(normalized) || normalized === "readonly" || normalized === "agent-safe";
32690
+ }
32691
+ function createTodosAiToolSource(options = {}) {
32692
+ const adapter = resolveAdapter(options);
32693
+ const configuredProfile = options.env ? options.env["TODOS_PROFILE"] : process.env["TODOS_PROFILE"];
32694
+ const profile = options.accessProfile ?? resolveAccessProfile(configuredProfile ?? "minimal");
32695
+ const permission = resolvePermission(options);
32696
+ const enabled = new Set(TODOS_AI_READ_TOOL_NAMES.filter((name) => shouldRegisterToolForProfile(name, profile) && permission(name === "get_task" ? "read" : "list", name)));
32697
+ const hostAllowsUpdate = (options.accessProfile !== undefined || configuredProfileIsKnown(configuredProfile)) && shouldRegisterToolForProfile("update_task", profile) && permission("write", "update_task");
32698
+ const approvalVerifier = options.approvalVerifier ?? adapter.verifyApproval;
32699
+ return ({ request }) => {
32700
+ const state = {
32701
+ calls: 0,
32702
+ evidence: [],
32703
+ idempotency: new Map,
32704
+ now: options.now ?? (() => new Date)
32705
+ };
32706
+ return createTools(adapter, enabled, state, request, hostAllowsUpdate, approvalVerifier);
32707
+ };
32708
+ }
32709
+ function createTools(adapter, enabled, state, request, hostAllowsUpdate, approvalVerifier) {
32710
+ const tools = [];
32711
+ if (enabled.has("get_task")) {
32712
+ tools.push({
32713
+ name: "get_task",
32714
+ description: "Read one Todos task by its stable identifier.",
32715
+ effect: "read",
32716
+ inputSchema: {
32717
+ type: "object",
32718
+ properties: {
32719
+ id: {
32720
+ type: "string",
32721
+ minLength: 1,
32722
+ maxLength: TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes
32723
+ }
32724
+ },
32725
+ required: ["id"],
32726
+ additionalProperties: false
32727
+ },
32728
+ execute: (input) => executeGetTask(adapter, state, request, input)
32729
+ });
32730
+ }
32731
+ if (enabled.has("list_tasks")) {
32732
+ tools.push({
32733
+ name: "list_tasks",
32734
+ description: "List a bounded set of Todos tasks using read-only filters.",
32735
+ effect: "read",
32736
+ inputSchema: {
32737
+ type: "object",
32738
+ properties: {
32739
+ project_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
32740
+ plan_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
32741
+ task_list_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
32742
+ assigned_to: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes),
32743
+ status: { type: "string", enum: [...TASK_STATUSES] },
32744
+ priority: { type: "string", enum: [...TASK_PRIORITIES] },
32745
+ tags: {
32746
+ type: "array",
32747
+ maxItems: TODOS_AI_READ_TOOL_LIMITS.max_tags,
32748
+ items: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes)
32749
+ },
32750
+ limit: {
32751
+ type: "integer",
32752
+ minimum: 1,
32753
+ maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_items,
32754
+ default: TODOS_AI_READ_TOOL_LIMITS.default_list_items
32755
+ },
32756
+ offset: {
32757
+ type: "integer",
32758
+ minimum: 0,
32759
+ maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_offset
32760
+ }
32761
+ },
32762
+ additionalProperties: false
32763
+ },
32764
+ execute: (input) => executeListTasks(adapter, state, request, input)
32765
+ });
32766
+ }
32767
+ if (enabled.has("list_projects")) {
32768
+ tools.push({
32769
+ name: "list_projects",
32770
+ description: "List a bounded set of Todos projects.",
32771
+ effect: "read",
32772
+ inputSchema: listOnlySchema(),
32773
+ execute: (input) => executeListProjects(adapter, state, request, input)
32774
+ });
32775
+ }
32776
+ if (enabled.has("list_plans")) {
32777
+ tools.push({
32778
+ name: "list_plans",
32779
+ description: "List a bounded set of Todos plans, optionally scoped to a project.",
32780
+ effect: "read",
32781
+ inputSchema: {
32782
+ type: "object",
32783
+ properties: {
32784
+ project_id: boundedStringSchema(TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes),
32785
+ limit: {
32786
+ type: "integer",
32787
+ minimum: 1,
32788
+ maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_items,
32789
+ default: TODOS_AI_READ_TOOL_LIMITS.default_list_items
32790
+ },
32791
+ offset: {
32792
+ type: "integer",
32793
+ minimum: 0,
32794
+ maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_offset
32795
+ }
32796
+ },
32797
+ additionalProperties: false
32798
+ },
32799
+ execute: (input) => executeListPlans(adapter, state, request, input)
32800
+ });
32801
+ }
32802
+ tools.push({
32803
+ name: "request_input",
32804
+ description: "Stop without mutation and request bounded clarification fields.",
32805
+ effect: "control",
32806
+ inputSchema: {
32807
+ type: "object",
32808
+ properties: {
32809
+ prompt: boundedStringSchema(TODOS_AI_CONTROL_TOOL_LIMITS.max_prompt_bytes),
32810
+ fields: {
32811
+ type: "array",
32812
+ minItems: 1,
32813
+ maxItems: TODOS_AI_CONTROL_TOOL_LIMITS.max_fields,
32814
+ items: boundedStringSchema(TODOS_AI_CONTROL_TOOL_LIMITS.max_field_bytes)
32815
+ }
32816
+ },
32817
+ required: ["prompt", "fields"],
32818
+ additionalProperties: false
32819
+ },
32820
+ execute: (input) => executeRequestInput(state, input)
32821
+ });
32822
+ const authorityAllowsUpdate = request.authority.write_mode === "plan" || request.authority.write_mode === "execute" && request.authority.approval_mode !== "deny";
32823
+ if (hostAllowsUpdate && authorityAllowsUpdate) {
32824
+ tools.push({
32825
+ name: "update_task",
32826
+ description: "Plan or execute one approved, version-checked update of one exact Todos task.",
32827
+ effect: "write",
32828
+ inputSchema: updateTaskInputSchema(),
32829
+ execute: (input, context) => executeUpdateTask(adapter, approvalVerifier, state, request, input, context.signal)
32830
+ });
32831
+ }
32832
+ return tools;
32833
+ }
32834
+ function boundedStringSchema(maxLength) {
32835
+ return {
32836
+ type: "string",
32837
+ minLength: 1,
32838
+ maxLength
32839
+ };
32840
+ }
32841
+ function listOnlySchema() {
32842
+ return {
32843
+ type: "object",
32844
+ properties: {
32845
+ limit: {
32846
+ type: "integer",
32847
+ minimum: 1,
32848
+ maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_items,
32849
+ default: TODOS_AI_READ_TOOL_LIMITS.default_list_items
32850
+ },
32851
+ offset: {
32852
+ type: "integer",
32853
+ minimum: 0,
32854
+ maximum: TODOS_AI_READ_TOOL_LIMITS.max_list_offset
32855
+ }
32856
+ },
32857
+ additionalProperties: false
32858
+ };
32859
+ }
32860
+ function updateTaskInputSchema() {
32861
+ return {
32862
+ type: "object",
32863
+ properties: {
32864
+ task_id: {
32865
+ type: "string",
32866
+ pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
32867
+ },
32868
+ expected_version: {
32869
+ type: "integer",
32870
+ minimum: 0
32871
+ },
32872
+ patch: {
32873
+ type: "object",
32874
+ minProperties: 1,
32875
+ properties: {
32876
+ title: boundedStringSchema(TODOS_AI_UPDATE_TASK_LIMITS.max_title_bytes),
32877
+ description: {
32878
+ type: "string",
32879
+ maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_description_bytes
32880
+ },
32881
+ status: { type: "string", enum: [...TASK_STATUSES] },
32882
+ priority: { type: "string", enum: [...TASK_PRIORITIES] },
32883
+ assigned_to: {
32884
+ type: ["string", "null"],
32885
+ maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_assignee_bytes
32886
+ },
32887
+ tags: {
32888
+ type: "array",
32889
+ maxItems: TODOS_AI_UPDATE_TASK_LIMITS.max_tags,
32890
+ items: boundedStringSchema(TODOS_AI_UPDATE_TASK_LIMITS.max_tag_bytes)
32891
+ },
32892
+ due_at: {
32893
+ type: ["string", "null"],
32894
+ maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_due_at_bytes
32895
+ }
32896
+ },
32897
+ additionalProperties: false
32898
+ },
32899
+ idempotency_key: {
32900
+ type: "string",
32901
+ minLength: TODOS_AI_UPDATE_TASK_LIMITS.min_idempotency_key_bytes,
32902
+ maxLength: TODOS_AI_UPDATE_TASK_LIMITS.max_idempotency_key_bytes,
32903
+ pattern: "^[A-Za-z0-9._:-]+$"
32904
+ }
32905
+ },
32906
+ required: ["task_id", "expected_version", "patch", "idempotency_key"],
32907
+ additionalProperties: false
32908
+ };
32909
+ }
32910
+ function deriveTodosAiUpdateTaskApprovalIdentity(input) {
32911
+ if (!UUID_RE2.test(input.task_id) || !Number.isSafeInteger(input.expected_version) || input.expected_version < 0) {
32912
+ throw new Error("update_task approval identity requires an exact task UUID and version");
32913
+ }
32914
+ const patch = normalizeUpdatePatch(input.patch);
32915
+ const canonical = JSON.stringify({
32916
+ operation: "update_task",
32917
+ task_id: input.task_id,
32918
+ expected_version: input.expected_version,
32919
+ patch
32920
+ });
32921
+ const payloadDigest = createHash9("sha256").update(canonical).digest("hex");
32922
+ return {
32923
+ ref: `todos-ai:update_task:${payloadDigest}`,
32924
+ payload_digest: payloadDigest
32925
+ };
32926
+ }
32927
+ function executeRequestInput(state, input) {
32928
+ beginCall(state);
32929
+ const record = assertInputObject(input, "request_input", ["prompt", "fields"]);
32930
+ const prompt = boundedRequiredString(record, "prompt", TODOS_AI_CONTROL_TOOL_LIMITS.max_prompt_bytes);
32931
+ const rawFields = record["fields"];
32932
+ if (!Array.isArray(rawFields) || rawFields.length === 0 || rawFields.length > TODOS_AI_CONTROL_TOOL_LIMITS.max_fields) {
32933
+ throw new Error(`fields must contain 1 to ${TODOS_AI_CONTROL_TOOL_LIMITS.max_fields} strings`);
32934
+ }
32935
+ const fields = [];
32936
+ const seen = new Set;
32937
+ for (const [index, field] of rawFields.entries()) {
32938
+ if (typeof field !== "string" || !/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(field) || ENCODER.encode(field).byteLength > TODOS_AI_CONTROL_TOOL_LIMITS.max_field_bytes || seen.has(field)) {
32939
+ throw new Error(`fields[${index}] must be a unique bounded field name`);
32940
+ }
32941
+ seen.add(field);
32942
+ fields.push(field);
32943
+ }
32944
+ const redactedPrompt = truncateUtf8(redactEvidenceText(prompt), TODOS_AI_CONTROL_TOOL_LIMITS.max_prompt_bytes);
32945
+ throw new TodosAiNeedsInputSignal({
32946
+ prompt: redactedPrompt || "Additional input is required.",
32947
+ fields
32948
+ });
32949
+ }
32950
+ async function executeUpdateTask(adapter, approvalVerifier, state, request, input, signal) {
32951
+ beginCall(state);
32952
+ const normalized = normalizeUpdateTaskInput(input);
32953
+ const existing = state.idempotency.get(normalized.idempotency_key);
32954
+ if (existing) {
32955
+ if (existing.payload_digest !== normalized.payload_digest) {
32956
+ throw new Error("update_task idempotency key was reused with a different payload");
32957
+ }
32958
+ return replayUpdateTaskResult(await existing.promise);
32959
+ }
32960
+ const claim = {
32961
+ payload_digest: normalized.payload_digest,
32962
+ mutation_started: false,
32963
+ promise: Promise.resolve({})
32964
+ };
32965
+ claim.promise = performUpdateTask(adapter, approvalVerifier, state, request, normalized, signal, claim);
32966
+ state.idempotency.set(normalized.idempotency_key, claim);
32967
+ try {
32968
+ return await claim.promise;
32969
+ } catch (error) {
32970
+ if (!claim.mutation_started) {
32971
+ state.idempotency.delete(normalized.idempotency_key);
32972
+ }
32973
+ throw error;
32974
+ }
32975
+ }
32976
+ async function performUpdateTask(adapter, approvalVerifier, state, request, normalized, signal, claim) {
32977
+ throwIfToolAborted(signal);
32978
+ const before = await adapter.getTask(normalized.task_id);
32979
+ throwIfToolAborted(signal);
32980
+ assertExactTaskVersion(before, normalized.task_id, normalized.expected_version);
32981
+ if (taskMatchesPatch(before, normalized.patch)) {
32982
+ throw new Error("update_task patch does not change the authoritative task");
32983
+ }
32984
+ if (request.authority.write_mode === "plan") {
32985
+ return updateTaskResult(normalized, {
32986
+ mode: "plan",
32987
+ applied: false,
32988
+ readback_verified: false,
32989
+ result_version: null,
32990
+ replay: false,
32991
+ source: adapter.source
32992
+ });
32993
+ }
32994
+ if (request.authority.write_mode !== "execute") {
32995
+ throw new Error("update_task is unavailable without plan or execute authority");
32996
+ }
32997
+ if (request.authority.approval_mode === "required" || request.authority.approval_mode === "prompt") {
32998
+ throw new TodosAiNeedsApprovalSignal({
32999
+ id: normalized.approval_ref,
33000
+ summary: "Approve one exact version-checked task update.",
33001
+ operations: [approvalOperation(normalized)]
33002
+ });
33003
+ }
33004
+ if (request.authority.approval_mode !== "existing") {
33005
+ throw new Error("update_task execution requires a verified existing approval");
33006
+ }
33007
+ await verifyExistingApproval(approvalVerifier, request, normalized, state.now());
33008
+ if (!adapter.updateTask) {
33009
+ throw new Error("update_task is unavailable for the selected authority");
33010
+ }
33011
+ throwIfToolAborted(signal);
33012
+ claim.mutation_started = true;
33013
+ let updateFailure = null;
33014
+ try {
33015
+ await adapter.updateTask(normalized.task_id, {
33016
+ ...normalized.patch,
33017
+ version: normalized.expected_version
33018
+ });
33019
+ } catch (error) {
33020
+ updateFailure = error;
33021
+ }
33022
+ const after = await adapter.getTask(normalized.task_id);
33023
+ if (after && after.id === normalized.task_id && after.version === normalized.expected_version + 1 && taskMatchesPatch(after, normalized.patch)) {
33024
+ return updateTaskResult(normalized, {
33025
+ mode: "execute",
33026
+ applied: true,
33027
+ readback_verified: true,
33028
+ result_version: after.version,
33029
+ replay: false,
33030
+ source: adapter.source
33031
+ });
33032
+ }
33033
+ if (updateFailure !== null) {
33034
+ throw new Error("update_task mutation could not be reconciled by authoritative readback");
33035
+ }
33036
+ throw new Error("update_task authoritative readback did not verify the applied patch");
33037
+ }
33038
+ async function verifyExistingApproval(approvalVerifier, request, normalized, at) {
33039
+ if (request.authority.approval_refs.length !== 1 || request.authority.approval_refs[0] !== normalized.approval_ref) {
33040
+ throw new Error("update_task approval reference does not match the exact operation");
33041
+ }
33042
+ if (!approvalVerifier) {
33043
+ throw new Error("update_task approval cannot be verified for the selected authority");
33044
+ }
33045
+ const verification = await approvalVerifier({
33046
+ ref: normalized.approval_ref,
33047
+ task_id: normalized.task_id,
33048
+ operation: "update_task",
33049
+ payload_digest: normalized.payload_digest
33050
+ });
33051
+ if (!verification || verification.ref !== normalized.approval_ref || verification.task_id !== normalized.task_id || verification.operation !== "update_task" || verification.payload_digest !== normalized.payload_digest || verification.status !== "approved") {
33052
+ throw new Error("update_task approval is missing, rejected, expired, or unverifiable");
33053
+ }
33054
+ if (verification.expires_at !== null) {
33055
+ const expiry = Date.parse(verification.expires_at);
33056
+ if (!Number.isFinite(expiry) || expiry <= at.getTime()) {
33057
+ throw new Error("update_task approval is expired or unverifiable");
33058
+ }
33059
+ }
33060
+ }
33061
+ function normalizeUpdateTaskInput(input) {
33062
+ const record = assertInputObject(input, "update_task", [
33063
+ "task_id",
33064
+ "expected_version",
33065
+ "patch",
33066
+ "idempotency_key"
33067
+ ]);
33068
+ const taskId = boundedRequiredString(record, "task_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
33069
+ if (!UUID_RE2.test(taskId)) {
33070
+ throw new Error("task_id must be one exact task UUID");
33071
+ }
33072
+ const expectedVersion = boundedInteger(record, "expected_version", -1, 0, Number.MAX_SAFE_INTEGER);
33073
+ if (!Object.hasOwn(record, "expected_version")) {
33074
+ throw new Error("expected_version is required");
33075
+ }
33076
+ const idempotencyKey = boundedRequiredString(record, "idempotency_key", TODOS_AI_UPDATE_TASK_LIMITS.max_idempotency_key_bytes);
33077
+ const idempotencyBytes = ENCODER.encode(idempotencyKey).byteLength;
33078
+ if (idempotencyBytes < TODOS_AI_UPDATE_TASK_LIMITS.min_idempotency_key_bytes || !/^[A-Za-z0-9._:-]+$/.test(idempotencyKey)) {
33079
+ throw new Error("idempotency_key must be a bounded stable identifier");
33080
+ }
33081
+ const patchValue = record["patch"];
33082
+ if (!isTodosAiJsonValue(patchValue) || patchValue === null || typeof patchValue !== "object" || Array.isArray(patchValue)) {
33083
+ throw new Error("patch must be a stable JSON object");
33084
+ }
33085
+ const patch = normalizeUpdatePatch(patchValue);
33086
+ const changedFields = Object.keys(patch);
33087
+ const identity = deriveTodosAiUpdateTaskApprovalIdentity({
33088
+ task_id: taskId,
33089
+ expected_version: expectedVersion,
33090
+ patch
33091
+ });
33092
+ return {
33093
+ task_id: taskId,
33094
+ expected_version: expectedVersion,
33095
+ patch,
33096
+ changed_fields: changedFields,
33097
+ idempotency_key: idempotencyKey,
33098
+ payload_digest: identity.payload_digest,
33099
+ approval_ref: identity.ref
33100
+ };
33101
+ }
33102
+ function normalizeUpdatePatch(value) {
33103
+ if (!isTodosAiJsonValue(value) || value === null || Array.isArray(value)) {
33104
+ throw new Error("patch must be a stable JSON object");
33105
+ }
33106
+ const allowed = new Set(TODOS_AI_UPDATE_TASK_FIELDS);
33107
+ const supplied = Object.keys(value);
33108
+ if (supplied.length === 0)
33109
+ throw new Error("patch must contain at least one field");
33110
+ for (const key of supplied) {
33111
+ if (!allowed.has(key)) {
33112
+ throw new Error(`update_task patch contains unsupported field: ${key}`);
33113
+ }
33114
+ }
33115
+ const patch = {};
33116
+ for (const field of TODOS_AI_UPDATE_TASK_FIELDS) {
33117
+ if (!Object.hasOwn(value, field))
33118
+ continue;
33119
+ const candidate = value[field];
33120
+ switch (field) {
33121
+ case "title":
33122
+ if (typeof candidate !== "string" || candidate.trim().length === 0 || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_title_bytes) {
33123
+ throw new Error("patch.title must be a bounded non-empty string");
33124
+ }
33125
+ patch[field] = candidate;
33126
+ break;
33127
+ case "description":
33128
+ if (typeof candidate !== "string" || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_description_bytes) {
33129
+ throw new Error("patch.description must be a bounded string");
33130
+ }
33131
+ patch[field] = candidate;
33132
+ break;
33133
+ case "status":
33134
+ if (typeof candidate !== "string" || !TASK_STATUSES.includes(candidate)) {
33135
+ throw new Error(`patch.status must be one of: ${TASK_STATUSES.join(", ")}`);
33136
+ }
33137
+ patch[field] = candidate;
33138
+ break;
33139
+ case "priority":
33140
+ if (typeof candidate !== "string" || !TASK_PRIORITIES.includes(candidate)) {
33141
+ throw new Error(`patch.priority must be one of: ${TASK_PRIORITIES.join(", ")}`);
33142
+ }
33143
+ patch[field] = candidate;
33144
+ break;
33145
+ case "assigned_to":
33146
+ if (candidate !== null && (typeof candidate !== "string" || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_assignee_bytes)) {
33147
+ throw new Error("patch.assigned_to must be null or a bounded string");
33148
+ }
33149
+ patch[field] = candidate;
33150
+ break;
33151
+ case "tags": {
33152
+ if (!Array.isArray(candidate) || candidate.length > TODOS_AI_UPDATE_TASK_LIMITS.max_tags) {
33153
+ throw new Error(`patch.tags must contain at most ${TODOS_AI_UPDATE_TASK_LIMITS.max_tags} strings`);
33154
+ }
33155
+ const tags = [];
33156
+ const seen = new Set;
33157
+ for (const [index, tag] of candidate.entries()) {
33158
+ if (typeof tag !== "string" || tag.length === 0 || ENCODER.encode(tag).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_tag_bytes || seen.has(tag)) {
33159
+ throw new Error(`patch.tags[${index}] must be a unique bounded string`);
33160
+ }
33161
+ seen.add(tag);
33162
+ tags.push(tag);
33163
+ }
33164
+ patch[field] = tags;
33165
+ break;
33166
+ }
33167
+ case "due_at":
33168
+ if (candidate !== null && (typeof candidate !== "string" || ENCODER.encode(candidate).byteLength > TODOS_AI_UPDATE_TASK_LIMITS.max_due_at_bytes || !Number.isFinite(Date.parse(candidate)))) {
33169
+ throw new Error("patch.due_at must be null or a bounded ISO date-time");
33170
+ }
33171
+ patch[field] = candidate;
33172
+ break;
33173
+ }
33174
+ }
33175
+ return patch;
33176
+ }
33177
+ function assertExactTaskVersion(task, taskId, expectedVersion) {
33178
+ if (!task || task.id !== taskId) {
33179
+ throw new Error("update_task exact target was not found");
33180
+ }
33181
+ if (task.version !== expectedVersion) {
33182
+ throw new Error("update_task expected_version is stale");
33183
+ }
33184
+ }
33185
+ function taskMatchesPatch(task, patch) {
33186
+ return Object.entries(patch).every(([field, expected]) => {
33187
+ const actual = task[field];
33188
+ return Array.isArray(expected) ? Array.isArray(actual) && JSON.stringify(actual) === JSON.stringify(expected) : actual === expected;
33189
+ });
33190
+ }
33191
+ function approvalOperation(normalized) {
33192
+ return {
33193
+ operation: "update_task",
33194
+ task_id: normalized.task_id,
33195
+ expected_version: normalized.expected_version,
33196
+ fields: normalized.changed_fields,
33197
+ payload_digest: normalized.payload_digest
33198
+ };
33199
+ }
33200
+ function updateTaskResult(normalized, result) {
33201
+ const value = {
33202
+ schema: TODOS_AI_UPDATE_TASK_RESULT_SCHEMA,
33203
+ operation: "update_task",
33204
+ mode: result.mode,
33205
+ applied: result.applied,
33206
+ readback_verified: result.readback_verified,
33207
+ source: result.source,
33208
+ target: {
33209
+ task_id: normalized.task_id,
33210
+ expected_version: normalized.expected_version,
33211
+ result_version: result.result_version
33212
+ },
33213
+ changed_fields: normalized.changed_fields,
33214
+ approval_ref: normalized.approval_ref,
33215
+ payload_digest: normalized.payload_digest,
33216
+ idempotency: {
33217
+ key: normalized.idempotency_key,
33218
+ scope: "run",
33219
+ replay: result.replay
33220
+ }
33221
+ };
33222
+ if (!isTodosAiUpdateTaskResult(value)) {
33223
+ throw new Error("update_task result does not satisfy its stable receipt contract");
33224
+ }
33225
+ return value;
33226
+ }
33227
+ function replayUpdateTaskResult(value) {
33228
+ const idempotency = value["idempotency"];
33229
+ if (!idempotency || typeof idempotency !== "object" || Array.isArray(idempotency)) {
33230
+ throw new Error("update_task cached result is invalid");
33231
+ }
33232
+ const replayed = {
33233
+ ...value,
33234
+ idempotency: {
33235
+ ...idempotency,
33236
+ replay: true
33237
+ }
33238
+ };
33239
+ if (!isTodosAiUpdateTaskResult(replayed)) {
33240
+ throw new Error("update_task cached result is invalid");
33241
+ }
33242
+ return replayed;
33243
+ }
33244
+ function throwIfToolAborted(signal) {
33245
+ if (signal.aborted)
33246
+ throw new DOMException("Aborted", "AbortError");
33247
+ }
33248
+ function beginCall(state) {
33249
+ if (state.calls >= TODOS_AI_READ_TOOL_LIMITS.max_tool_calls) {
33250
+ throw new Error(`Todos AI tool-call limit of ${TODOS_AI_READ_TOOL_LIMITS.max_tool_calls} exceeded`);
33251
+ }
33252
+ state.calls += 1;
33253
+ }
33254
+ function assertInputObject(input, tool, allowed) {
33255
+ if (!isTodosAiJsonValue(input) || input === null || Array.isArray(input)) {
33256
+ throw new Error(`${tool} input must be a stable JSON object`);
33257
+ }
33258
+ const record = input;
33259
+ const allowedKeys = new Set(allowed);
33260
+ for (const key of Object.keys(record)) {
33261
+ if (!allowedKeys.has(key))
33262
+ throw new Error(`${tool} input contains unsupported field: ${key}`);
33263
+ }
33264
+ return record;
33265
+ }
33266
+ function boundedRequiredString(record, key, maximum) {
33267
+ const value = record[key];
33268
+ if (typeof value !== "string" || value.length === 0) {
33269
+ throw new Error(`${key} must be a non-empty string`);
33270
+ }
33271
+ if (ENCODER.encode(value).byteLength > maximum) {
33272
+ throw new Error(`${key} exceeds ${maximum} bytes`);
33273
+ }
33274
+ return value;
33275
+ }
33276
+ function boundedOptionalString(record, key, maximum) {
33277
+ if (!Object.hasOwn(record, key))
33278
+ return;
33279
+ return boundedRequiredString(record, key, maximum);
33280
+ }
33281
+ function boundedInteger(record, key, fallback, minimum, maximum) {
33282
+ if (!Object.hasOwn(record, key))
33283
+ return fallback;
33284
+ const value = record[key];
33285
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
33286
+ throw new Error(`${key} must be an integer from ${minimum} to ${maximum}`);
33287
+ }
33288
+ return value;
33289
+ }
33290
+ function boundedEnum(record, key, values) {
33291
+ if (!Object.hasOwn(record, key))
33292
+ return;
33293
+ const value = record[key];
33294
+ if (typeof value !== "string" || !values.includes(value)) {
33295
+ throw new Error(`${key} must be one of: ${values.join(", ")}`);
33296
+ }
33297
+ return value;
33298
+ }
33299
+ function boundedTags(record) {
33300
+ if (!Object.hasOwn(record, "tags"))
33301
+ return;
33302
+ const value = record["tags"];
33303
+ if (!Array.isArray(value) || value.length > TODOS_AI_READ_TOOL_LIMITS.max_tags) {
33304
+ throw new Error(`tags must contain at most ${TODOS_AI_READ_TOOL_LIMITS.max_tags} strings`);
33305
+ }
33306
+ return value.map((tag, index) => {
33307
+ if (typeof tag !== "string" || tag.length === 0) {
33308
+ throw new Error(`tags[${index}] must be a non-empty string`);
33309
+ }
33310
+ if (ENCODER.encode(tag).byteLength > TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes) {
33311
+ throw new Error(`tags[${index}] exceeds ${TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes} bytes`);
33312
+ }
33313
+ return tag;
33314
+ });
33315
+ }
33316
+ async function executeGetTask(adapter, state, request, input) {
33317
+ beginCall(state);
33318
+ const record = assertInputObject(input, "get_task", ["id"]);
33319
+ const id = boundedRequiredString(record, "id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
33320
+ const task = await adapter.getTask(id);
33321
+ const item = task ? compactTask(task) : null;
33322
+ const evidence = task ? [taskEvidence(task)] : [];
33323
+ return boundedSingleResult(adapter.source, "get_task", item, evidence, state, request);
33324
+ }
33325
+ async function executeListTasks(adapter, state, request, input) {
33326
+ beginCall(state);
33327
+ const record = assertInputObject(input, "list_tasks", [
33328
+ "project_id",
33329
+ "plan_id",
33330
+ "task_list_id",
33331
+ "assigned_to",
33332
+ "status",
33333
+ "priority",
33334
+ "tags",
33335
+ "limit",
33336
+ "offset"
33337
+ ]);
33338
+ const { limit, offset } = listWindow(record);
33339
+ const filter = {
33340
+ limit: limit + 1,
33341
+ offset
33342
+ };
33343
+ const projectId = boundedOptionalString(record, "project_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
33344
+ const planId = boundedOptionalString(record, "plan_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
33345
+ const taskListId = boundedOptionalString(record, "task_list_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
33346
+ const assignedTo = boundedOptionalString(record, "assigned_to", TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes);
33347
+ const status = boundedEnum(record, "status", TASK_STATUSES);
33348
+ const priority = boundedEnum(record, "priority", TASK_PRIORITIES);
33349
+ const tags = boundedTags(record);
33350
+ if (projectId !== undefined)
33351
+ filter.project_id = projectId;
33352
+ if (planId !== undefined)
33353
+ filter.plan_id = planId;
33354
+ if (taskListId !== undefined)
33355
+ filter.task_list_id = taskListId;
33356
+ if (assignedTo !== undefined)
33357
+ filter.assigned_to = assignedTo;
33358
+ if (status !== undefined)
33359
+ filter.status = status;
33360
+ if (priority !== undefined)
33361
+ filter.priority = priority;
33362
+ if (tags !== undefined)
33363
+ filter.tags = tags;
33364
+ const rows = await adapter.listTasks(filter);
33365
+ return boundedListResult(adapter.source, "list_tasks", rows.map(compactTask), rows.map(taskEvidence), limit, offset, state, request);
33366
+ }
33367
+ async function executeListProjects(adapter, state, request, input) {
33368
+ beginCall(state);
33369
+ const record = assertInputObject(input, "list_projects", ["limit", "offset"]);
33370
+ const { limit, offset } = listWindow(record);
33371
+ const rows = await adapter.listProjects();
33372
+ const window = rows.slice(offset, offset + limit + 1);
33373
+ return boundedListResult(adapter.source, "list_projects", window.map(compactProject), window.map(projectEvidence), limit, offset, state, request);
33374
+ }
33375
+ async function executeListPlans(adapter, state, request, input) {
33376
+ beginCall(state);
33377
+ const record = assertInputObject(input, "list_plans", [
33378
+ "project_id",
33379
+ "limit",
33380
+ "offset"
33381
+ ]);
33382
+ const { limit, offset } = listWindow(record);
33383
+ const projectId = boundedOptionalString(record, "project_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
33384
+ const rows = await adapter.listPlans(projectId);
33385
+ const window = rows.slice(offset, offset + limit + 1);
33386
+ return boundedListResult(adapter.source, "list_plans", window.map(compactPlan), window.map(planEvidence), limit, offset, state, request);
33387
+ }
33388
+ function listWindow(record) {
33389
+ return {
33390
+ limit: boundedInteger(record, "limit", TODOS_AI_READ_TOOL_LIMITS.default_list_items, 1, TODOS_AI_READ_TOOL_LIMITS.max_list_items),
33391
+ offset: boundedInteger(record, "offset", 0, 0, TODOS_AI_READ_TOOL_LIMITS.max_list_offset)
33392
+ };
33393
+ }
33394
+ function compactTask(task) {
33395
+ if (!TASK_STATUSES.includes(task.status)) {
33396
+ throw new Error("Todos AI task result has an invalid status");
33397
+ }
33398
+ if (!TASK_PRIORITIES.includes(task.priority)) {
33399
+ throw new Error("Todos AI task result has an invalid priority");
33400
+ }
33401
+ return {
33402
+ id: boundedOutputText(task.id),
33403
+ short_id: boundedNullableOutputText(task.short_id),
33404
+ version: requiredNonNegativeInteger(task.version, "task version"),
33405
+ project_id: boundedNullableOutputText(task.project_id),
33406
+ parent_id: boundedNullableOutputText(task.parent_id),
33407
+ plan_id: boundedNullableOutputText(task.plan_id),
33408
+ task_list_id: boundedNullableOutputText(task.task_list_id),
33409
+ title: boundedOutputText(task.title),
33410
+ description: boundedNullableOutputText(task.description),
33411
+ status: task.status,
33412
+ priority: task.priority,
33413
+ assigned_to: boundedNullableOutputText(task.assigned_to),
33414
+ agent_id: boundedNullableOutputText(task.agent_id),
33415
+ tags: boundedOutputTags(task.tags),
33416
+ task_type: boundedNullableOutputText(task.task_type),
33417
+ created_by: boundedNullableOutputText(task.created_by),
33418
+ assigned_by: boundedNullableOutputText(task.assigned_by),
33419
+ created_at: boundedOutputText(task.created_at),
33420
+ updated_at: boundedOutputText(task.updated_at),
33421
+ started_at: boundedNullableOutputText(task.started_at),
33422
+ completed_at: boundedNullableOutputText(task.completed_at),
33423
+ due_at: boundedNullableOutputText(task.due_at)
33424
+ };
33425
+ }
33426
+ function compactProject(project) {
33427
+ return {
33428
+ id: boundedOutputText(project.id),
33429
+ name: boundedOutputText(project.name),
33430
+ description: boundedNullableOutputText(project.description),
33431
+ task_list_id: boundedNullableOutputText(project.task_list_id),
33432
+ task_prefix: boundedNullableOutputText(project.task_prefix),
33433
+ created_at: boundedOutputText(project.created_at),
33434
+ updated_at: boundedOutputText(project.updated_at)
33435
+ };
33436
+ }
33437
+ function compactPlan(plan) {
33438
+ if (!PLAN_STATUSES.includes(plan.status)) {
33439
+ throw new Error("Todos AI plan result has an invalid status");
33440
+ }
33441
+ return {
33442
+ id: boundedOutputText(plan.id),
33443
+ slug: boundedNullableOutputText(plan.slug),
33444
+ project_id: boundedNullableOutputText(plan.project_id),
33445
+ task_list_id: boundedNullableOutputText(plan.task_list_id),
33446
+ agent_id: boundedNullableOutputText(plan.agent_id),
33447
+ name: boundedOutputText(plan.name),
33448
+ description: boundedNullableOutputText(plan.description),
33449
+ status: plan.status,
33450
+ created_at: boundedOutputText(plan.created_at),
33451
+ updated_at: boundedOutputText(plan.updated_at)
33452
+ };
33453
+ }
33454
+ function taskEvidence(task) {
33455
+ return {
33456
+ resource: "task",
33457
+ id: boundedOutputText(task.id),
33458
+ version: requiredNonNegativeInteger(task.version, "task version")
33459
+ };
33460
+ }
33461
+ function projectEvidence(project) {
33462
+ return {
33463
+ resource: "project",
33464
+ id: boundedOutputText(project.id),
33465
+ version: boundedOutputText(project.updated_at)
33466
+ };
33467
+ }
33468
+ function planEvidence(plan) {
33469
+ return {
33470
+ resource: "plan",
33471
+ id: boundedOutputText(plan.id),
33472
+ version: boundedOutputText(plan.updated_at)
33473
+ };
33474
+ }
33475
+ function boundedOutputTags(tags) {
33476
+ if (!Array.isArray(tags))
33477
+ return [];
33478
+ return tags.filter((tag) => typeof tag === "string").slice(0, TODOS_AI_READ_TOOL_LIMITS.max_output_tags).map(boundedOutputText);
33479
+ }
33480
+ function boundedNullableOutputText(value) {
33481
+ return typeof value === "string" ? boundedOutputText(value) : null;
33482
+ }
33483
+ function boundedOutputText(value) {
33484
+ return truncateUtf8(redactEvidenceText(value), TODOS_AI_READ_TOOL_LIMITS.max_output_string_bytes);
33485
+ }
33486
+ function requiredNonNegativeInteger(value, field) {
33487
+ if (!Number.isSafeInteger(value) || value < 0) {
33488
+ throw new Error(`Todos AI result has an invalid ${field}`);
33489
+ }
33490
+ return value;
33491
+ }
33492
+ function truncateUtf8(value, maximum) {
33493
+ if (ENCODER.encode(value).byteLength <= maximum)
33494
+ return value;
33495
+ const suffix = "...";
33496
+ const suffixBytes = ENCODER.encode(suffix).byteLength;
33497
+ let output2 = "";
33498
+ let bytes = 0;
33499
+ for (const character of value) {
33500
+ const characterBytes = ENCODER.encode(character).byteLength;
33501
+ if (bytes + characterBytes + suffixBytes > maximum)
33502
+ break;
33503
+ output2 += character;
33504
+ bytes += characterBytes;
33505
+ }
33506
+ return `${output2}${suffix}`;
33507
+ }
33508
+ function boundedSingleResult(source, tool, item, evidence, state, request) {
33509
+ const mergedEvidence = mergeEvidence(state.evidence, evidence);
33510
+ const result = redactValue({
33511
+ source,
33512
+ tool,
33513
+ item,
33514
+ evidence,
33515
+ context: runContext(request, state.calls, mergedEvidence)
33516
+ });
33517
+ assertResultBound(result);
33518
+ state.evidence = mergedEvidence;
33519
+ return result;
33520
+ }
33521
+ function boundedListResult(source, tool, sourceItems, sourceEvidence, limit, offset, state, request) {
33522
+ const available = Math.min(sourceItems.length, sourceEvidence.length);
33523
+ let count = Math.min(limit, available);
33524
+ let truncated = available > count;
33525
+ while (count >= 0) {
33526
+ const items = sourceItems.slice(0, count);
33527
+ const evidence = sourceEvidence.slice(0, count);
33528
+ const mergedEvidence = mergeEvidence(state.evidence, evidence);
33529
+ const result = redactValue({
33530
+ source,
33531
+ tool,
33532
+ items,
33533
+ returned: items.length,
33534
+ offset,
33535
+ truncated,
33536
+ evidence,
33537
+ context: runContext(request, state.calls, mergedEvidence)
33538
+ });
33539
+ if (serializedBytes(result) <= TODOS_AI_READ_TOOL_LIMITS.max_result_bytes) {
33540
+ state.evidence = mergedEvidence;
33541
+ return result;
33542
+ }
33543
+ count -= 1;
33544
+ truncated = true;
33545
+ }
33546
+ throw new Error("Todos AI read result could not be bounded");
33547
+ }
33548
+ function runContext(request, calls, evidence) {
33549
+ return {
33550
+ project: boundedNullableOutputText(request.context.project),
33551
+ agent: boundedNullableOutputText(request.context.agent),
33552
+ session: boundedNullableOutputText(request.context.session),
33553
+ tool_calls: calls,
33554
+ evidence
33555
+ };
33556
+ }
33557
+ function mergeEvidence(existing, incoming) {
33558
+ const merged = [];
33559
+ const seen = new Set;
33560
+ for (const pointer of [...existing, ...incoming]) {
33561
+ const key = JSON.stringify(pointer);
33562
+ if (seen.has(key))
33563
+ continue;
33564
+ seen.add(key);
33565
+ merged.push(pointer);
33566
+ if (merged.length >= TODOS_AI_READ_TOOL_LIMITS.max_evidence_pointers)
33567
+ break;
33568
+ }
33569
+ return merged;
33570
+ }
33571
+ function assertResultBound(value) {
33572
+ if (serializedBytes(value) > TODOS_AI_READ_TOOL_LIMITS.max_result_bytes) {
33573
+ throw new Error(`Todos AI read result exceeds ${TODOS_AI_READ_TOOL_LIMITS.max_result_bytes} bytes`);
33574
+ }
33575
+ }
33576
+ function serializedBytes(value) {
33577
+ return ENCODER.encode(JSON.stringify(value)).byteLength;
33578
+ }
33579
+ var TODOS_AI_READ_TOOL_NAMES, TODOS_AI_READ_TOOL_LIMITS, TODOS_AI_CONTROL_TOOL_LIMITS, ENCODER, UUID_RE2;
33580
+ var init_ai_tools = __esm(() => {
33581
+ init_ai();
33582
+ init_ai();
33583
+ init_cloud_router();
33584
+ init_task_crud();
33585
+ init_projects();
33586
+ init_plans();
33587
+ init_access_profiles();
33588
+ init_approval_gates();
33589
+ init_redaction();
33590
+ init_workspace_trust();
33591
+ init_types();
33592
+ TODOS_AI_READ_TOOL_NAMES = [
33593
+ "get_task",
33594
+ "list_tasks",
33595
+ "list_projects",
33596
+ "list_plans"
33597
+ ];
33598
+ TODOS_AI_READ_TOOL_LIMITS = {
33599
+ default_list_items: 20,
33600
+ max_list_items: 50,
33601
+ max_list_offset: 1e4,
33602
+ max_identifier_bytes: 256,
33603
+ max_filter_string_bytes: 256,
33604
+ max_tags: 16,
33605
+ max_output_tags: 16,
33606
+ max_output_string_bytes: 2048,
33607
+ max_result_bytes: 65536,
33608
+ max_tool_calls: 32,
33609
+ max_evidence_pointers: 32
33610
+ };
33611
+ TODOS_AI_CONTROL_TOOL_LIMITS = {
33612
+ max_prompt_bytes: 1024,
33613
+ max_fields: 16,
33614
+ max_field_bytes: 128
33615
+ };
33616
+ ENCODER = new TextEncoder;
33617
+ UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
33618
+ });
33619
+
33620
+ // src/cli/commands/ai-commands.ts
33621
+ var exports_ai_commands = {};
33622
+ __export(exports_ai_commands, {
33623
+ registerAiCommands: () => registerAiCommands
33624
+ });
33625
+ import { randomUUID as randomUUID4 } from "crypto";
33626
+ import { writeSync as writeSync2 } from "fs";
33627
+ function appendOption(value, previous) {
33628
+ return [...previous, value];
33629
+ }
33630
+ function globalOptions(program2) {
33631
+ const command = program2;
33632
+ return command.optsWithGlobals?.() ?? program2.opts();
33633
+ }
33634
+ function commandRawArgs(command) {
33635
+ let root = command;
33636
+ while (root.parent)
33637
+ root = root.parent;
33638
+ const rawArgs = root.rawArgs;
33639
+ return rawArgs && rawArgs.length > 0 ? rawArgs : process.argv;
33640
+ }
33641
+ function commanderJsonRequested(command) {
33642
+ const args = commandRawArgs(command);
33643
+ let globalJson = false;
33644
+ let format;
33645
+ for (let index = 0;index < args.length; index += 1) {
33646
+ const arg = args[index];
33647
+ if (arg === undefined)
33648
+ continue;
33649
+ if (arg === "--")
33650
+ break;
33651
+ if (arg === "--json" || arg === "-j") {
33652
+ globalJson = true;
33653
+ } else if (arg.startsWith("--format=")) {
33654
+ format = arg.slice("--format=".length);
33655
+ } else if (arg === "--format") {
33656
+ format = args[index + 1];
33657
+ index += 1;
33658
+ }
33659
+ }
33660
+ return format === undefined ? globalJson : format === "json";
33661
+ }
33662
+ function optionalString(value) {
33663
+ if (typeof value !== "string")
33664
+ return null;
33665
+ const trimmed = value.trim();
33666
+ return trimmed || null;
33667
+ }
33668
+ function requestedErrorFormat(options, globalOpts, configFormat) {
33669
+ if (TODOS_AI_FORMATS.includes(options.format)) {
33670
+ return options.format;
33671
+ }
33672
+ if (globalOpts["json"] === true)
33673
+ return "json";
33674
+ const envFormat = process.env["TODOS_AI_FORMAT"];
33675
+ if (TODOS_AI_FORMATS.includes(envFormat))
33676
+ return envFormat;
33677
+ if (TODOS_AI_FORMATS.includes(configFormat)) {
33678
+ return configFormat;
33679
+ }
33680
+ return TODOS_AI_DEFAULTS.format;
33681
+ }
33682
+ function loadTodosAiStoredConfig() {
33683
+ let ai;
33684
+ try {
33685
+ ai = getTodosAiConfig();
33686
+ } catch (cause) {
33687
+ throw new TodosAiContractError("invalid_configuration", "Todos AI configuration could not be read", TODOS_AI_EXIT_CODES.usage, { cause });
33688
+ }
33689
+ if (ai !== undefined && (ai === null || typeof ai !== "object" || Array.isArray(ai))) {
33690
+ throw new TodosAiContractError("invalid_configuration", "Todos AI configuration must be an object");
33691
+ }
33692
+ if (ai === undefined)
33693
+ return;
33694
+ const record = ai;
33695
+ for (const field of [
33696
+ "provider",
33697
+ "model",
33698
+ "profile",
33699
+ "format",
33700
+ "write_mode",
33701
+ "approval_mode"
33702
+ ]) {
33703
+ if (record[field] !== undefined && typeof record[field] !== "string") {
33704
+ throw new TodosAiContractError("invalid_configuration", `Todos AI configuration field ${field} must be a string`);
33705
+ }
33706
+ }
33707
+ for (const field of ["max_steps", "timeout_ms"]) {
33708
+ if (record[field] !== undefined && typeof record[field] !== "number") {
33709
+ throw new TodosAiContractError("invalid_configuration", `Todos AI configuration field ${field} must be a number`);
33710
+ }
33711
+ }
33712
+ return record;
33713
+ }
33714
+ async function readBoundedStdin() {
33715
+ const chunks = [];
33716
+ let bytes = 0;
33717
+ for await (const chunk of process.stdin) {
33718
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
33719
+ bytes += buffer.byteLength;
33720
+ if (bytes > TODOS_AI_LIMITS.max_prompt_bytes) {
33721
+ throw new TodosAiContractError("invalid_input", `stdin prompt exceeds ${TODOS_AI_LIMITS.max_prompt_bytes} bytes`);
33722
+ }
33723
+ chunks.push(buffer);
33724
+ }
33725
+ return Buffer.concat(chunks, bytes).toString("utf8");
33726
+ }
33727
+ function normalizeResumeRunId(value) {
33728
+ const runId = optionalString(value);
33729
+ if (!runId)
33730
+ return null;
33731
+ if (Buffer.byteLength(runId, "utf8") > TODOS_AI_LIMITS.max_resume_run_id_bytes) {
33732
+ throw new TodosAiContractError("invalid_input", `--resume may not exceed ${TODOS_AI_LIMITS.max_resume_run_id_bytes} bytes`);
33733
+ }
33734
+ return runId;
33735
+ }
33736
+ function resultJson(result) {
33737
+ const serialized = JSON.stringify(result);
33738
+ if (Buffer.byteLength(serialized, "utf8") > TODOS_AI_LIMITS.max_result_bytes) {
33739
+ throw new TodosAiContractError("runtime_invalid_result", `optional AI runtime result exceeds ${TODOS_AI_LIMITS.max_result_bytes} bytes`, TODOS_AI_EXIT_CODES.failed);
33740
+ }
33741
+ return serialized;
33742
+ }
33743
+ function renderTextResult(result) {
33744
+ switch (result.status) {
33745
+ case "answered":
33746
+ case "completed":
33747
+ if (result.answer) {
33748
+ console.log(result.answer);
33749
+ } else if (result.data !== null) {
33750
+ console.log(JSON.stringify(result.data, null, 2));
33751
+ } else {
33752
+ console.log(`AI run ${result.run_id} completed.`);
33753
+ }
33754
+ return;
33755
+ case "needs_input":
33756
+ console.log(result.pending_input?.prompt ?? "The AI runtime needs additional input.");
33757
+ return;
33758
+ case "needs_approval":
33759
+ console.log(result.pending_approval?.summary ?? "The AI runtime needs approval.");
33760
+ if (result.pending_approval?.id)
33761
+ console.log(`Approval: ${result.pending_approval.id}`);
33762
+ return;
33763
+ case "failed":
33764
+ console.error(result.error?.message ?? "The AI runtime failed.");
33765
+ }
33766
+ }
33767
+ function renderTerminalResult(result, format) {
33768
+ const serialized = resultJson(result);
33769
+ if (format === "json") {
33770
+ console.log(serialized);
33771
+ return;
33772
+ }
33773
+ if (format === "stream-json") {
33774
+ const record = {
33775
+ schema_version: TODOS_AI_SCHEMA_VERSION,
33776
+ kind: "result",
33777
+ result
33778
+ };
33779
+ console.log(JSON.stringify(record));
33780
+ return;
33781
+ }
33782
+ renderTextResult(result);
33783
+ }
33784
+ function errorResult(runId, error) {
33785
+ if (error instanceof TodosAiContractError) {
33786
+ return createTodosAiFailureResult(runId, error.code, error.message);
33787
+ }
33788
+ return createTodosAiFailureResult(runId, "internal_error", "optional AI runtime failed");
33789
+ }
33790
+ async function withLoadDeadline(operation, controller, timeoutMs) {
33791
+ return new Promise((resolve14, reject) => {
33792
+ let settled = false;
33793
+ const settle = (callback) => {
33794
+ if (settled)
33795
+ return;
33796
+ settled = true;
33797
+ clearTimeout(timer);
33798
+ callback();
33799
+ };
33800
+ const timer = setTimeout(() => {
33801
+ const error = new TodosAiContractError("timeout", `AI run timed out after ${timeoutMs}ms`, TODOS_AI_EXIT_CODES.timeout);
33802
+ controller.abort(error);
33803
+ settle(() => reject(error));
33804
+ }, timeoutMs);
33805
+ Promise.resolve().then(operation).then((value) => settle(() => resolve14(value)), (error) => settle(() => reject(error)));
33806
+ });
33807
+ }
33808
+ async function withInterrupt(operation, controller) {
33809
+ return new Promise((resolve14, reject) => {
33810
+ let settled = false;
33811
+ const settle = (callback) => {
33812
+ if (settled)
33813
+ return;
33814
+ settled = true;
33815
+ process.removeListener("SIGINT", onInterrupt);
33816
+ callback();
33817
+ };
33818
+ const onInterrupt = () => {
33819
+ const error = new TodosAiContractError("interrupted", "AI run interrupted", TODOS_AI_EXIT_CODES.interrupted);
33820
+ controller.abort(error);
33821
+ settle(() => reject(error));
33822
+ };
33823
+ process.once("SIGINT", onInterrupt);
33824
+ Promise.resolve().then(operation).then((value) => settle(() => resolve14(value)), (error) => settle(() => reject(error)));
33825
+ });
33826
+ }
33827
+ function throwIfAborted(signal) {
33828
+ if (!signal.aborted)
33829
+ return;
33830
+ if (signal.reason instanceof Error)
33831
+ throw signal.reason;
33832
+ throw new TodosAiContractError("interrupted", "AI run interrupted", TODOS_AI_EXIT_CODES.interrupted);
33833
+ }
33834
+ function createRuntimeEventSink(format, controller, expectedRunId) {
33835
+ let eventCount = 0;
33836
+ let streamBytes = 0;
33837
+ let lastSequence = -1;
33838
+ let runId = expectedRunId;
33839
+ let failure = null;
33840
+ let closed = false;
33841
+ let rejectFailure;
33842
+ const failurePromise = new Promise((_resolve, reject) => {
33843
+ rejectFailure = reject;
33844
+ });
33845
+ failurePromise.catch(() => {
33846
+ return;
33847
+ });
33848
+ const fail = (message) => {
33849
+ if (failure)
33850
+ return failure;
33851
+ const error = new TodosAiContractError("runtime_invalid_result", message, TODOS_AI_EXIT_CODES.failed);
33852
+ failure = error;
33853
+ controller.abort(error);
33854
+ rejectFailure(error);
33855
+ return error;
33856
+ };
33857
+ return {
33858
+ emit(event) {
33859
+ if (closed)
33860
+ return;
33861
+ if (failure)
33862
+ return;
33863
+ if (!isTodosAiRuntimeEvent(event)) {
33864
+ fail("optional AI runtime emitted an invalid event");
33865
+ return;
33866
+ }
33867
+ if (runId !== null && event.run_id !== runId) {
33868
+ fail("optional AI runtime changed run_id while streaming");
33869
+ return;
33870
+ }
33871
+ if (event.sequence <= lastSequence) {
33872
+ fail("optional AI runtime event sequence must increase");
33873
+ return;
33874
+ }
33875
+ if (eventCount >= TODOS_AI_LIMITS.max_stream_events) {
33876
+ fail(`optional AI runtime exceeded ${TODOS_AI_LIMITS.max_stream_events} events`);
33877
+ return;
33878
+ }
33879
+ const record = {
33880
+ schema_version: TODOS_AI_SCHEMA_VERSION,
33881
+ kind: "event",
33882
+ event
33883
+ };
33884
+ let serialized;
33885
+ try {
33886
+ serialized = JSON.stringify(record);
33887
+ } catch {
33888
+ fail("optional AI runtime emitted an event that could not be serialized");
33889
+ return;
33890
+ }
33891
+ const recordBytes = Buffer.byteLength(serialized, "utf8");
33892
+ if (recordBytes > TODOS_AI_LIMITS.max_stream_record_bytes) {
33893
+ fail(`optional AI runtime event exceeds ${TODOS_AI_LIMITS.max_stream_record_bytes} bytes`);
33894
+ return;
33895
+ }
33896
+ if (streamBytes + recordBytes + 1 > TODOS_AI_LIMITS.max_stream_bytes) {
33897
+ fail(`optional AI runtime stream exceeds ${TODOS_AI_LIMITS.max_stream_bytes} bytes`);
33898
+ return;
33899
+ }
33900
+ runId = event.run_id;
33901
+ lastSequence = event.sequence;
33902
+ eventCount += 1;
33903
+ streamBytes += recordBytes + 1;
33904
+ if (format === "stream-json")
33905
+ process.stdout.write(`${serialized}
33906
+ `);
33907
+ },
33908
+ assertResult(result, maxSteps) {
33909
+ if (failure)
33910
+ throw failure;
33911
+ if (runId !== null && result.run_id !== runId) {
33912
+ throw fail("optional AI runtime result run_id does not match its streamed events");
33913
+ }
33914
+ if (result.steps > maxSteps) {
33915
+ throw fail(`optional AI runtime exceeded the max step limit of ${maxSteps}`);
33916
+ }
33917
+ },
33918
+ raceWithFailure(operation) {
33919
+ return Promise.race([failurePromise, operation]);
33920
+ },
33921
+ failureRunId(fallback) {
33922
+ return runId ?? fallback;
33923
+ },
33924
+ close() {
33925
+ closed = true;
33926
+ }
33927
+ };
33928
+ }
33929
+ async function runTodosAi(promptParts, options, program2) {
33930
+ const globalOpts = globalOptions(program2);
33931
+ let format = requestedErrorFormat(options, globalOpts);
33932
+ const hostRunId = randomUUID4();
33933
+ let eventSink = null;
33934
+ try {
33935
+ const config = loadTodosAiStoredConfig();
33936
+ format = requestedErrorFormat(options, globalOpts, config?.format);
33937
+ const interactive = options.nonInteractive !== true && process.stdin.isTTY === true && process.stdout.isTTY === true;
33938
+ const resolved = resolveTodosAiCommandOptions({
33939
+ cli: {
33940
+ provider: options.provider,
33941
+ model: options.model,
33942
+ profile: options.profile,
33943
+ format: options.format ?? (globalOpts["json"] === true ? "json" : undefined),
33944
+ maxSteps: options.maxSteps,
33945
+ timeoutMs: options.timeoutMs,
33946
+ writeMode: options.writeMode,
33947
+ approvalMode: options.approvalMode,
33948
+ approvalRefs: options.approval,
33949
+ dryRun: options.dryRun
33950
+ },
33951
+ config,
33952
+ env: process.env,
33953
+ interactive
33954
+ });
33955
+ format = resolved.format;
33956
+ const resumeRunId = normalizeResumeRunId(options.resume);
33957
+ let rawPrompt = promptParts.join(" ");
33958
+ if (!rawPrompt.trim() && process.stdin.isTTY !== true) {
33959
+ rawPrompt = await readBoundedStdin();
33960
+ }
33961
+ const prompt = normalizeTodosAiPrompt(rawPrompt);
33962
+ if (!prompt && !resumeRunId) {
33963
+ if (!interactive) {
33964
+ throw new TodosAiContractError("invalid_input", "Provide a prompt as arguments or non-TTY stdin.");
33965
+ }
33966
+ const result2 = createTodosAiNeedsInputResult(hostRunId, "Provide a prompt as arguments or non-TTY stdin.");
33967
+ renderTerminalResult(result2, format);
33968
+ process.exitCode = todosAiExitCodeForResult(result2);
33969
+ return;
33970
+ }
33971
+ if (options.inputJson !== undefined && options.input !== undefined) {
33972
+ throw new TodosAiContractError("invalid_input", "Pass either --input-json or --input, not both.");
33973
+ }
33974
+ const inputJson = options.inputJson ?? options.input;
33975
+ const request = {
33976
+ schema_version: TODOS_AI_SCHEMA_VERSION,
33977
+ prompt,
33978
+ input: inputJson === undefined ? null : parseTodosAiJson(inputJson, "input"),
33979
+ variables: parseTodosAiVariables(options.var ?? []),
33980
+ output_schema: options.outputSchema === undefined ? null : parseTodosAiOutputSchema(options.outputSchema),
33981
+ provider: resolved.provider,
33982
+ model: resolved.model,
33983
+ profile: resolved.profile,
33984
+ format: resolved.format,
33985
+ interactive: resolved.interactive,
33986
+ context: {
33987
+ project: optionalString(globalOpts["project"]),
33988
+ agent: optionalString(globalOpts["agent"]),
33989
+ session: optionalString(globalOpts["session"])
33990
+ },
33991
+ authority: {
33992
+ write_mode: resolved.write_mode,
33993
+ approval_mode: resolved.approval_mode,
33994
+ approval_refs: resolved.approval_refs,
33995
+ dry_run: resolved.dry_run
33996
+ },
33997
+ limits: {
33998
+ max_steps: resolved.max_steps,
33999
+ timeout_ms: resolved.timeout_ms
34000
+ },
34001
+ resume_run_id: resumeRunId
34002
+ };
34003
+ const controller = new AbortController;
34004
+ const sink = createRuntimeEventSink(format, controller, resumeRunId);
34005
+ eventSink = sink;
34006
+ const runtimeResult = await withInterrupt(async () => {
34007
+ const runtime = await withLoadDeadline(() => loadTodosAiRuntime({
34008
+ package_name: "@hasna/todos",
34009
+ package_version: getPackageVersion(),
34010
+ protocol_version: TODOS_AI_RUNTIME_PROTOCOL_VERSION,
34011
+ tool_source: createTodosAiToolSource({
34012
+ env: process.env,
34013
+ workspacePath: process.cwd()
34014
+ })
34015
+ }), controller, resolved.timeout_ms);
34016
+ throwIfAborted(controller.signal);
34017
+ const runtimeOperation = Promise.resolve().then(() => {
34018
+ throwIfAborted(controller.signal);
34019
+ return runtime.run(request, {
34020
+ signal: controller.signal,
34021
+ emit: (event) => sink.emit(event)
34022
+ });
34023
+ });
34024
+ return sink.raceWithFailure(runtimeOperation);
34025
+ }, controller);
34026
+ const result = assertTodosAiRunResult(runtimeResult);
34027
+ eventSink.assertResult(result, resolved.max_steps);
34028
+ eventSink.close();
34029
+ renderTerminalResult(result, format);
34030
+ process.exitCode = todosAiExitCodeForResult(result);
34031
+ } catch (error) {
34032
+ eventSink?.close();
34033
+ const result = errorResult(eventSink?.failureRunId(hostRunId) ?? hostRunId, error);
34034
+ renderTerminalResult(result, format);
34035
+ process.exitCode = error instanceof TodosAiContractError ? error.exitCode : TODOS_AI_EXIT_CODES.failed;
34036
+ }
34037
+ }
34038
+ function registerAiCommands(program2) {
34039
+ const command = program2.command("ai [prompt...]").description("Run the optional provider-neutral Todos AI runtime").option("--format <format>", "Output format: text, json, or stream-json").option("--input-json <json>", "Structured JSON input supplied to the runtime").option("--input <json>", "Compatibility alias for --input-json").option("--var <key=value>", "Non-secret runtime variable; repeatable", appendOption, []).option("--output-schema <json>", "JSON Schema object for structured output").option("--provider <name>", "Runtime provider override").option("--model <name>", "Runtime model override").option("--profile <name>", "Runtime profile override").option("--write-mode <mode>", "Authority: read-only, plan, or execute").option("--approval-mode <mode>", "Approval handling: deny, required, prompt, or existing").option("--approval <reference>", "Existing approval reference; repeatable", appendOption, []).option("--dry-run", "Force plan-only authority without mutation").option("--max-steps <n>", `Maximum runtime steps (${TODOS_AI_LIMITS.min_steps}-${TODOS_AI_LIMITS.max_steps})`).option("--timeout-ms <ms>", `Timeout in milliseconds (${TODOS_AI_LIMITS.min_timeout_ms}-${TODOS_AI_LIMITS.max_timeout_ms})`).option("--resume <run-id>", "Resume a runtime run, optionally with a new prompt").option("--non-interactive", "Disable all runtime prompting");
34040
+ const defaultOutputError = command.configureOutput().outputError;
34041
+ command.configureOutput({
34042
+ outputError: (message, write) => {
34043
+ if (commanderJsonRequested(command))
34044
+ return;
34045
+ if (defaultOutputError) {
34046
+ defaultOutputError(message, write);
34047
+ } else {
34048
+ write(message);
34049
+ }
34050
+ }
34051
+ });
34052
+ command.exitOverride((error) => {
34053
+ if (error.exitCode === 0)
34054
+ process.exit(0);
34055
+ if (commanderJsonRequested(command)) {
34056
+ const result = createTodosAiFailureResult(randomUUID4(), "invalid_input", error.message);
34057
+ writeSync2(process.stdout.fd, `${resultJson(result)}
34058
+ `);
34059
+ }
34060
+ process.exit(TODOS_AI_EXIT_CODES.usage);
34061
+ });
34062
+ command.action(async (prompt, options) => {
34063
+ await runTodosAi(prompt ?? [], options, program2);
34064
+ });
34065
+ }
34066
+ var init_ai_commands = __esm(() => {
34067
+ init_ai();
34068
+ init_ai_tools();
34069
+ init_config();
34070
+ init_package_version();
34071
+ });
34072
+
31486
34073
  // src/server/port.ts
31487
34074
  var exports_port = {};
31488
34075
  __export(exports_port, {
@@ -31522,7 +34109,7 @@ async function findFreePort(start) {
31522
34109
  var DEFAULT_PORT = 19427;
31523
34110
 
31524
34111
  // src/lib/db-backup.ts
31525
- import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync10, renameSync, statSync as statSync7, writeFileSync as writeFileSync7, unlinkSync } from "fs";
34112
+ import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync, statSync as statSync7, writeFileSync as writeFileSync7, unlinkSync } from "fs";
31526
34113
  import { dirname as dirname7, join as join14, resolve as resolve14 } from "path";
31527
34114
  import { Database as Database2 } from "bun:sqlite";
31528
34115
  function resolveDbPath(dbPath) {
@@ -32250,7 +34837,7 @@ function addGroupTools(toolNames, groupName) {
32250
34837
  toolNames.add(tool);
32251
34838
  return true;
32252
34839
  }
32253
- function shouldRegisterToolForProfile(name, profileValue = process.env["TODOS_PROFILE"], groupValue = process.env["TODOS_TOOL_GROUPS"]) {
34840
+ function shouldRegisterToolForProfile2(name, profileValue = process.env["TODOS_PROFILE"], groupValue = process.env["TODOS_TOOL_GROUPS"]) {
32254
34841
  const profileTokens = splitTokens(profileValue || "minimal");
32255
34842
  const groupTokens = splitTokens(groupValue);
32256
34843
  if (profileTokens.includes("full") || profileTokens.includes("all"))
@@ -32295,7 +34882,7 @@ function truncateText(value, maxChars = 240) {
32295
34882
  return value;
32296
34883
  return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
32297
34884
  }
32298
- function compactTask(task, maxDescriptionChars = 180) {
34885
+ function compactTask2(task, maxDescriptionChars = 180) {
32299
34886
  const summary = {
32300
34887
  id: task.id,
32301
34888
  short_id: task.short_id || task.id.slice(0, 8),
@@ -32838,7 +35425,7 @@ function profilesForTool(toolName) {
32838
35425
  function getMcpToolNames(options = {}) {
32839
35426
  const profile = options.profile ?? "minimal";
32840
35427
  const groups = options.groups ?? "";
32841
- return getAllMcpToolNames().filter((toolName) => shouldRegisterToolForProfile(toolName, profile, groups));
35428
+ return getAllMcpToolNames().filter((toolName) => shouldRegisterToolForProfile2(toolName, profile, groups));
32842
35429
  }
32843
35430
  function createMcpManifest(options = {}) {
32844
35431
  const version = options.version ?? getPackageVersion(import.meta.url);
@@ -32887,8 +35474,8 @@ __export(exports_local_extensions, {
32887
35474
  getLocalExtension: () => getLocalExtension,
32888
35475
  discoverLocalExtensions: () => discoverLocalExtensions
32889
35476
  });
32890
- import { createHash as createHash9, createVerify } from "crypto";
32891
- import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
35477
+ import { createHash as createHash10, createVerify } from "crypto";
35478
+ import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
32892
35479
  import { basename as basename6, join as join16, resolve as resolve15 } from "path";
32893
35480
  function isObject(value) {
32894
35481
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -32970,10 +35557,10 @@ function normalizeManifest(input) {
32970
35557
  };
32971
35558
  }
32972
35559
  function parseJson(path) {
32973
- return JSON.parse(readFileSync11(path, "utf8"));
35560
+ return JSON.parse(readFileSync12(path, "utf8"));
32974
35561
  }
32975
35562
  function sha2564(bytes) {
32976
- return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
35563
+ return `sha256:${createHash10("sha256").update(bytes).digest("hex")}`;
32977
35564
  }
32978
35565
  function compareVersions(a, b) {
32979
35566
  const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -33154,7 +35741,7 @@ function inspectExtensionSource(source2) {
33154
35741
  const manifestPath = stat.isDirectory() ? [join16(resolved, "todos.extension.json"), join16(resolved, "extension.json")].find(existsSync17) : resolved;
33155
35742
  if (!manifestPath)
33156
35743
  throw new Error(`extension directory ${source2} is missing todos.extension.json`);
33157
- const raw = readFileSync11(manifestPath);
35744
+ const raw = readFileSync12(manifestPath);
33158
35745
  const parsed = parseJson(manifestPath);
33159
35746
  const bundle = isObject(parsed) && isObject(parsed["manifest"]);
33160
35747
  const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
@@ -33793,310 +36380,6 @@ var init_policy_packs = __esm(() => {
33793
36380
  init_config();
33794
36381
  });
33795
36382
 
33796
- // src/db/checkpoints.ts
33797
- function upsertCheckpoint(task_id, step, updates, db) {
33798
- const d = db || getDatabase();
33799
- const timestamp2 = now();
33800
- const existing = d.query("SELECT id FROM task_checkpoints WHERE task_id = ? AND step = ?").get(task_id, step);
33801
- const id = existing?.id ?? uuid();
33802
- const agentId = updates.agent_id ?? null;
33803
- const status = updates.status ?? "pending";
33804
- const data = updates.data ? JSON.stringify(updates.data) : JSON.stringify({});
33805
- const error = updates.error ?? null;
33806
- const attempt = updates.attempt ?? 1;
33807
- const maxAttempts = updates.max_attempts ?? 1;
33808
- const startedAt = updates.started_at ?? null;
33809
- const completedAt = updates.completed_at ?? null;
33810
- d.run(`INSERT INTO task_checkpoints (id, task_id, agent_id, step, status, data, error, attempt, max_attempts, started_at, completed_at, updated_at)
33811
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
33812
- ON CONFLICT(id) DO UPDATE SET status=?, data=?, error=?, attempt=?, max_attempts=?, started_at=COALESCE(started_at,?), completed_at=?, updated_at=?`, [
33813
- id,
33814
- task_id,
33815
- agentId,
33816
- step,
33817
- status,
33818
- data,
33819
- error,
33820
- attempt,
33821
- maxAttempts,
33822
- startedAt,
33823
- completedAt,
33824
- timestamp2,
33825
- status,
33826
- data,
33827
- error,
33828
- attempt,
33829
- maxAttempts,
33830
- startedAt,
33831
- completedAt,
33832
- timestamp2
33833
- ]);
33834
- return rowToCheckpoint(d.query("SELECT * FROM task_checkpoints WHERE id = ?").get(id));
33835
- }
33836
- function getCheckpoint(taskId, step, db) {
33837
- const d = db || getDatabase();
33838
- const row = d.query("SELECT * FROM task_checkpoints WHERE task_id = ? AND step = ?").get(taskId, step);
33839
- return row ? rowToCheckpoint(row) : null;
33840
- }
33841
- function getTaskCheckpoints(taskId, db) {
33842
- const d = db || getDatabase();
33843
- return d.query("SELECT * FROM task_checkpoints WHERE task_id = ? ORDER BY created_at ASC").all(taskId).map(rowToCheckpoint);
33844
- }
33845
- function rowToCheckpoint(row) {
33846
- if (!row)
33847
- return null;
33848
- return {
33849
- ...row,
33850
- data: JSON.parse(row.data || "{}"),
33851
- status: row.status,
33852
- agent_id: row.agent_id || null,
33853
- error: row.error || null,
33854
- started_at: row.started_at || null,
33855
- completed_at: row.completed_at || null
33856
- };
33857
- }
33858
- var init_checkpoints = __esm(() => {
33859
- init_database();
33860
- });
33861
-
33862
- // src/lib/approval-gates.ts
33863
- var exports_approval_gates = {};
33864
- __export(exports_approval_gates, {
33865
- requestApprovalGate: () => requestApprovalGate,
33866
- rejectApprovalGate: () => rejectApprovalGate,
33867
- listApprovalGates: () => listApprovalGates,
33868
- expireApprovalGate: () => expireApprovalGate,
33869
- checkApprovalGate: () => checkApprovalGate,
33870
- assertApprovalGate: () => assertApprovalGate,
33871
- approveApprovalGate: () => approveApprovalGate
33872
- });
33873
- function stepForGate(gate) {
33874
- const trimmed = gate.trim();
33875
- if (!trimmed)
33876
- throw new Error("Approval gate name is required");
33877
- return `approval:${trimmed}`;
33878
- }
33879
- function approvalStatusToCheckpointStatus(status) {
33880
- if (status === "approved")
33881
- return "completed";
33882
- if (status === "pending")
33883
- return "pending";
33884
- return "failed";
33885
- }
33886
- function checkpointStatusToApprovalStatus(checkpoint) {
33887
- const value = checkpoint.data["approval_status"];
33888
- if (value === "approved" || value === "rejected" || value === "expired" || value === "pending")
33889
- return value;
33890
- if (checkpoint.status === "completed")
33891
- return "approved";
33892
- if (checkpoint.status === "failed")
33893
- return "rejected";
33894
- return "pending";
33895
- }
33896
- function ensureTask(taskId, db) {
33897
- if (!getTask(taskId, db))
33898
- throw new TaskNotFoundError(taskId);
33899
- }
33900
- function isExpired(expiresAt, at = new Date) {
33901
- return Boolean(expiresAt && new Date(expiresAt).getTime() <= at.getTime());
33902
- }
33903
- function gateFromCheckpoint(checkpoint) {
33904
- const status = checkpointStatusToApprovalStatus(checkpoint);
33905
- return {
33906
- id: checkpoint.id,
33907
- task_id: checkpoint.task_id,
33908
- gate: String(checkpoint.data["approval_gate_name"] || checkpoint.step.replace(/^approval:/, "")),
33909
- status,
33910
- reviewer: typeof checkpoint.data["reviewer"] === "string" ? checkpoint.data["reviewer"] : null,
33911
- requester: typeof checkpoint.data["requester"] === "string" ? checkpoint.data["requester"] : null,
33912
- reason: typeof checkpoint.data["reason"] === "string" ? checkpoint.data["reason"] : null,
33913
- note: typeof checkpoint.data["note"] === "string" ? checkpoint.data["note"] : null,
33914
- plan_id: typeof checkpoint.data["plan_id"] === "string" ? checkpoint.data["plan_id"] : null,
33915
- run_id: typeof checkpoint.data["run_id"] === "string" ? checkpoint.data["run_id"] : null,
33916
- expires_at: typeof checkpoint.data["expires_at"] === "string" ? checkpoint.data["expires_at"] : null,
33917
- decided_by: typeof checkpoint.data["decided_by"] === "string" ? checkpoint.data["decided_by"] : null,
33918
- decided_at: typeof checkpoint.data["decided_at"] === "string" ? checkpoint.data["decided_at"] : null,
33919
- created_at: checkpoint.created_at,
33920
- updated_at: checkpoint.updated_at,
33921
- checkpoint
33922
- };
33923
- }
33924
- function logApprovalEvent(taskId, action, gate, agentId, db) {
33925
- const payload = JSON.stringify({
33926
- gate: gate.gate,
33927
- status: gate.status,
33928
- reviewer: gate.reviewer,
33929
- requester: gate.requester,
33930
- plan_id: gate.plan_id,
33931
- run_id: gate.run_id,
33932
- expires_at: gate.expires_at,
33933
- decided_by: gate.decided_by,
33934
- decided_at: gate.decided_at
33935
- });
33936
- logTaskChange(taskId, `approval_gate.${action}`, "approval_gate", null, payload, agentId, db);
33937
- if (gate.run_id && getTaskRun(gate.run_id, db)) {
33938
- addTaskRunEvent({
33939
- run_id: gate.run_id,
33940
- event_type: "progress",
33941
- message: `approval gate ${action}: ${gate.gate}`,
33942
- data: JSON.parse(payload),
33943
- agent_id: agentId
33944
- }, db);
33945
- }
33946
- if (action === "approved" || action === "rejected" || action === "expired") {
33947
- emitLocalEventHooksQuiet({
33948
- type: "approval.decided",
33949
- payload: { task_id: taskId, gate: gate.gate, status: gate.status, reviewer: gate.reviewer, run_id: gate.run_id },
33950
- databasePath: databasePathFromDatabase(db)
33951
- });
33952
- }
33953
- }
33954
- function writeGate(input, status, decision, db) {
33955
- const d = db || getDatabase();
33956
- ensureTask(input.task_id, d);
33957
- const step = stepForGate(input.gate);
33958
- const existing = getCheckpoint(input.task_id, step, d);
33959
- const existingData = existing?.data || {};
33960
- const timestamp2 = decision?.decided_at || now();
33961
- const existingRunId = typeof existingData["run_id"] === "string" ? existingData["run_id"] : undefined;
33962
- const runId = input.run_id ? resolveTaskRunId(input.run_id, d) : existingRunId;
33963
- const data = {
33964
- ...existingData,
33965
- ...input.metadata || {},
33966
- approval_gate: true,
33967
- approval_gate_name: input.gate.trim(),
33968
- approval_status: status,
33969
- requester: input.requester ?? existingData["requester"] ?? null,
33970
- reviewer: input.reviewer ?? decision?.decided_by ?? existingData["reviewer"] ?? null,
33971
- reason: decision?.reason ?? input.reason ?? existingData["reason"] ?? null,
33972
- note: decision?.note ?? existingData["note"] ?? null,
33973
- plan_id: input.plan_id ?? existingData["plan_id"] ?? null,
33974
- run_id: runId ?? null,
33975
- expires_at: input.expires_at ?? existingData["expires_at"] ?? null,
33976
- decided_by: decision?.decided_by ?? existingData["decided_by"] ?? null,
33977
- decided_at: decision?.decided_at ?? existingData["decided_at"] ?? null
33978
- };
33979
- const checkpoint = upsertCheckpoint(input.task_id, step, {
33980
- agent_id: decision?.decided_by || input.requester || input.reviewer,
33981
- status: approvalStatusToCheckpointStatus(status),
33982
- data,
33983
- error: status === "rejected" || status === "expired" ? String(data.reason || status) : null,
33984
- started_at: existing?.started_at || timestamp2,
33985
- completed_at: status === "pending" ? null : timestamp2
33986
- }, d);
33987
- return gateFromCheckpoint(checkpoint);
33988
- }
33989
- function currentGate(taskId, gate, db) {
33990
- const checkpoint = getCheckpoint(taskId, stepForGate(gate), db);
33991
- return checkpoint ? gateFromCheckpoint(checkpoint) : null;
33992
- }
33993
- function requestApprovalGate(input, db) {
33994
- const d = db || getDatabase();
33995
- const existing = currentGate(input.task_id, input.gate, d);
33996
- if (existing && existing.status !== "pending") {
33997
- throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
33998
- }
33999
- const gate = writeGate(input, "pending", undefined, d);
34000
- logApprovalEvent(input.task_id, "requested", gate, input.requester || input.reviewer, d);
34001
- return gate;
34002
- }
34003
- function approveApprovalGate(input, db) {
34004
- const d = db || getDatabase();
34005
- const existing = currentGate(input.task_id, input.gate, d);
34006
- if (!existing)
34007
- throw new Error(`Approval gate not found: ${input.gate}`);
34008
- if (existing.status !== "pending")
34009
- throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
34010
- if (isExpired(existing.expires_at))
34011
- throw new Error(`Approval gate ${input.gate} is expired`);
34012
- const gate = writeGate({
34013
- task_id: input.task_id,
34014
- gate: input.gate,
34015
- requester: existing.requester || undefined,
34016
- reviewer: input.reviewer || existing.reviewer || undefined,
34017
- reason: existing.reason || undefined,
34018
- plan_id: existing.plan_id || undefined,
34019
- run_id: existing.run_id || undefined,
34020
- expires_at: existing.expires_at || undefined
34021
- }, "approved", { decided_by: input.reviewer, decided_at: now(), note: input.note }, d);
34022
- logApprovalEvent(input.task_id, "approved", gate, input.reviewer, d);
34023
- return gate;
34024
- }
34025
- function rejectApprovalGate(input, db) {
34026
- const d = db || getDatabase();
34027
- const existing = currentGate(input.task_id, input.gate, d);
34028
- if (!existing)
34029
- throw new Error(`Approval gate not found: ${input.gate}`);
34030
- if (existing.status !== "pending")
34031
- throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
34032
- const gate = writeGate({
34033
- task_id: input.task_id,
34034
- gate: input.gate,
34035
- requester: existing.requester || undefined,
34036
- reviewer: input.reviewer || existing.reviewer || undefined,
34037
- reason: existing.reason || undefined,
34038
- plan_id: existing.plan_id || undefined,
34039
- run_id: existing.run_id || undefined,
34040
- expires_at: existing.expires_at || undefined
34041
- }, "rejected", { decided_by: input.reviewer, decided_at: now(), note: input.note, reason: input.reason || input.note }, d);
34042
- logApprovalEvent(input.task_id, "rejected", gate, input.reviewer, d);
34043
- return gate;
34044
- }
34045
- function expireApprovalGate(input, db) {
34046
- const d = db || getDatabase();
34047
- const existing = currentGate(input.task_id, input.gate, d);
34048
- if (!existing)
34049
- throw new Error(`Approval gate not found: ${input.gate}`);
34050
- if (existing.status !== "pending")
34051
- throw new Error(`Approval gate ${input.gate} is already ${existing.status}`);
34052
- const gate = writeGate({
34053
- task_id: input.task_id,
34054
- gate: input.gate,
34055
- requester: existing.requester || undefined,
34056
- reviewer: existing.reviewer || undefined,
34057
- reason: existing.reason || undefined,
34058
- plan_id: existing.plan_id || undefined,
34059
- run_id: existing.run_id || undefined,
34060
- expires_at: existing.expires_at || undefined
34061
- }, "expired", { decided_by: input.reviewer, decided_at: now(), reason: input.reason || "expired" }, d);
34062
- logApprovalEvent(input.task_id, "expired", gate, input.reviewer, d);
34063
- return gate;
34064
- }
34065
- function listApprovalGates(taskId, db) {
34066
- const d = db || getDatabase();
34067
- ensureTask(taskId, d);
34068
- return getTaskCheckpoints(taskId, d).filter((checkpoint) => checkpoint.data["approval_gate"] === true || checkpoint.step.startsWith("approval:")).map(gateFromCheckpoint);
34069
- }
34070
- function checkApprovalGate(taskId, gateName, db) {
34071
- const d = db || getDatabase();
34072
- ensureTask(taskId, d);
34073
- const gate = currentGate(taskId, gateName, d);
34074
- const reasons = [];
34075
- if (!gate)
34076
- reasons.push(`approval gate is required: ${gateName}`);
34077
- else if (gate.status !== "approved")
34078
- reasons.push(`approval gate ${gateName} is ${gate.status}`);
34079
- if (gate && gate.status === "pending" && isExpired(gate.expires_at))
34080
- reasons.push(`approval gate ${gateName} is expired`);
34081
- return { allowed: reasons.length === 0, gate, reasons };
34082
- }
34083
- function assertApprovalGate(taskId, gateName, db) {
34084
- const result = checkApprovalGate(taskId, gateName, db);
34085
- if (!result.allowed)
34086
- throw new Error(result.reasons.join("; "));
34087
- return result.gate;
34088
- }
34089
- var init_approval_gates = __esm(() => {
34090
- init_audit();
34091
- init_checkpoints();
34092
- init_database();
34093
- init_task_runs();
34094
- init_tasks();
34095
- init_types();
34096
- init_event_emission_safety();
34097
- init_event_hooks();
34098
- });
34099
-
34100
36383
  // src/lib/terminal-notifications.ts
34101
36384
  var exports_terminal_notifications = {};
34102
36385
  __export(exports_terminal_notifications, {
@@ -34330,7 +36613,7 @@ var init_terminal_notifications = __esm(() => {
34330
36613
  });
34331
36614
 
34332
36615
  // src/db/api-keys.ts
34333
- import { createHash as createHash10, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
36616
+ import { createHash as createHash11, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
34334
36617
  function rowToRecord(row) {
34335
36618
  return {
34336
36619
  id: row.id,
@@ -34344,7 +36627,7 @@ function rowToRecord(row) {
34344
36627
  };
34345
36628
  }
34346
36629
  function hashApiKey(key) {
34347
- return createHash10("sha256").update(key).digest("hex");
36630
+ return createHash11("sha256").update(key).digest("hex");
34348
36631
  }
34349
36632
  function safeEqualHex(a, b) {
34350
36633
  if (a.length !== b.length)
@@ -34352,8 +36635,8 @@ function safeEqualHex(a, b) {
34352
36635
  return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
34353
36636
  }
34354
36637
  function safeEqualStrings(a, b) {
34355
- const ah = createHash10("sha256").update(a, "utf8").digest();
34356
- const bh = createHash10("sha256").update(b, "utf8").digest();
36638
+ const ah = createHash11("sha256").update(a, "utf8").digest();
36639
+ const bh = createHash11("sha256").update(b, "utf8").digest();
34357
36640
  return timingSafeEqual3(ah, bh);
34358
36641
  }
34359
36642
  function generatePlaintextKey() {
@@ -34895,8 +37178,33 @@ var init_postgres_sync = __esm(() => {
34895
37178
  };
34896
37179
  });
34897
37180
 
37181
+ // src/task-manifest/canonical.ts
37182
+ import { createHash as createHash12 } from "crypto";
37183
+ function canonicalize2(value) {
37184
+ if (Array.isArray(value))
37185
+ return value.map(canonicalize2);
37186
+ if (value !== null && typeof value === "object") {
37187
+ return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize2(entry2)]));
37188
+ }
37189
+ return value;
37190
+ }
37191
+ function canonicalJson2(value) {
37192
+ return JSON.stringify(canonicalize2(value));
37193
+ }
37194
+ function canonicalDigest(value) {
37195
+ return createHash12("sha256").update(canonicalJson2(value)).digest("hex");
37196
+ }
37197
+ function deterministicUuid(namespace, ...parts) {
37198
+ const bytes = createHash12("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
37199
+ bytes[6] = bytes[6] & 15 | 80;
37200
+ bytes[8] = bytes[8] & 63 | 128;
37201
+ const hex = bytes.toString("hex");
37202
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
37203
+ }
37204
+ var init_canonical = () => {};
37205
+
34898
37206
  // src/storage/postgres-adapter.ts
34899
- import { randomUUID as randomUUID4 } from "crypto";
37207
+ import { randomUUID as randomUUID5 } from "crypto";
34900
37208
  function createPostgresTodosStorageAdapter(options) {
34901
37209
  const store = new PostgresJsonRecordStore(options);
34902
37210
  const adapter = {
@@ -35120,7 +37428,7 @@ class PostgresJsonRecordStore {
35120
37428
  AND task_record.deleted_at IS NULL
35121
37429
  AND target.payload->>'locked_by' = $3
35122
37430
  AND target.payload->>'locked_at' = $4
35123
- AND todos_try_timestamptz(target.payload->>'locked_at') < $5::timestamptz
37431
+ AND $4::timestamptz < $5::timestamptz
35124
37432
  AND COALESCE(target.payload->>'status', '') NOT IN ('completed', 'failed', 'cancelled')
35125
37433
  RETURNING task_record.payload
35126
37434
  ),
@@ -36274,7 +38582,7 @@ async function createTask2(input, store, context) {
36274
38582
  const effectiveProjectId = linkedPlan?.project_id ?? requestedProjectId;
36275
38583
  const shortId = effectiveProjectId ? await nextTaskShortId2(effectiveProjectId, store, context) : null;
36276
38584
  const task = {
36277
- id: randomUUID4(),
38585
+ id: randomUUID5(),
36278
38586
  short_id: shortId,
36279
38587
  project_id: effectiveProjectId,
36280
38588
  parent_id: input.parent_id ?? null,
@@ -36520,7 +38828,7 @@ async function addVerification(input, store, context) {
36520
38828
  throw new Error(`Task not found: ${input.task_id}`);
36521
38829
  const timestamp2 = new Date().toISOString();
36522
38830
  const verification = {
36523
- id: randomUUID4(),
38831
+ id: randomUUID5(),
36524
38832
  task_id: input.task_id,
36525
38833
  command: input.command,
36526
38834
  status: input.status ?? "unknown",
@@ -36541,7 +38849,7 @@ async function addCommit(input, store, context) {
36541
38849
  throw new Error(`Task not found: ${input.task_id}`);
36542
38850
  const timestamp2 = new Date().toISOString();
36543
38851
  const commit = {
36544
- id: randomUUID4(),
38852
+ id: randomUUID5(),
36545
38853
  task_id: input.task_id,
36546
38854
  sha: input.sha,
36547
38855
  message: input.message ?? null,
@@ -36562,16 +38870,17 @@ async function findCommit(sha, store) {
36562
38870
  async function addGitRef(input, store, context) {
36563
38871
  if (!await store.get("tasks", input.task_id))
36564
38872
  throw new Error(`Task not found: ${input.task_id}`);
38873
+ const existing = (await store.list("refs")).filter((ref) => ref.task_id === input.task_id && ref.ref_type === input.ref_type && ref.name === input.name).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id))[0];
36565
38874
  const timestamp2 = new Date().toISOString();
36566
38875
  const gitRef = {
36567
- id: randomUUID4(),
38876
+ id: existing?.id ?? deterministicUuid("todos:git-ref:v1", input.task_id, input.ref_type, input.name),
36568
38877
  task_id: input.task_id,
36569
38878
  ref_type: input.ref_type,
36570
38879
  name: input.name,
36571
- url: input.url ?? null,
36572
- provider: input.provider ?? null,
38880
+ url: input.url ?? existing?.url ?? null,
38881
+ provider: input.provider ?? existing?.provider ?? null,
36573
38882
  metadata: input.metadata ?? {},
36574
- created_at: timestamp2,
38883
+ created_at: existing?.created_at ?? timestamp2,
36575
38884
  updated_at: timestamp2
36576
38885
  };
36577
38886
  await store.upsert("refs", gitRef, context);
@@ -36634,7 +38943,7 @@ async function createProject2(input, store, context) {
36634
38943
  if (!derivedSlug || !taskListId)
36635
38944
  throw new Error("Project name and task-list slug must be non-empty");
36636
38945
  const project = {
36637
- id: randomUUID4(),
38946
+ id: randomUUID5(),
36638
38947
  name: input.name,
36639
38948
  path: input.path,
36640
38949
  description: input.description ?? null,
@@ -36666,7 +38975,7 @@ async function createPlan2(input, store, context) {
36666
38975
  store
36667
38976
  });
36668
38977
  return store.upsert("plans", {
36669
- id: randomUUID4(),
38978
+ id: randomUUID5(),
36670
38979
  slug,
36671
38980
  project_id: projectId,
36672
38981
  task_list_id: input.task_list_id ?? context?.taskListId ?? null,
@@ -36718,7 +39027,7 @@ async function registerAgent2(input, store, context) {
36718
39027
  }
36719
39028
  const timestamp2 = new Date().toISOString();
36720
39029
  const agent = {
36721
- id: existing?.id ?? randomUUID4().slice(0, 8),
39030
+ id: existing?.id ?? randomUUID5().slice(0, 8),
36722
39031
  name: canonicalName,
36723
39032
  description: input.description ?? existing?.description ?? null,
36724
39033
  role: input.role ?? existing?.role ?? null,
@@ -36791,7 +39100,7 @@ async function createTaskList2(input, store, context) {
36791
39100
  if (!slug)
36792
39101
  throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
36793
39102
  return store.upsert("task_lists", {
36794
- id: randomUUID4(),
39103
+ id: randomUUID5(),
36795
39104
  project_id: input.project_id ?? context?.projectId ?? null,
36796
39105
  slug,
36797
39106
  name: input.name,
@@ -36826,7 +39135,7 @@ async function updateTaskList2(id, input, store) {
36826
39135
  async function createTemplate2(input, store, context) {
36827
39136
  const timestamp2 = new Date().toISOString();
36828
39137
  const template = {
36829
- id: randomUUID4(),
39138
+ id: randomUUID5(),
36830
39139
  name: input.name,
36831
39140
  title_pattern: input.title_pattern,
36832
39141
  description: input.description ?? null,
@@ -36847,7 +39156,7 @@ async function createTemplate2(input, store, context) {
36847
39156
  }
36848
39157
  function buildTemplateTasks(templateId, inputs, timestamp2) {
36849
39158
  return inputs.map((input, position) => ({
36850
- id: randomUUID4(),
39159
+ id: randomUUID5(),
36851
39160
  template_id: templateId,
36852
39161
  position,
36853
39162
  title_pattern: input.title_pattern,
@@ -36880,7 +39189,7 @@ async function updateTemplate2(id, input, store) {
36880
39189
  }
36881
39190
  async function logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context) {
36882
39191
  const entry2 = {
36883
- id: randomUUID4(),
39192
+ id: randomUUID5(),
36884
39193
  task_id: taskId,
36885
39194
  action,
36886
39195
  field: field ?? null,
@@ -36894,7 +39203,7 @@ async function logTaskChange2(taskId, action, field, oldValue, newValue, agentId
36894
39203
  }
36895
39204
  async function addComment2(input, store, context) {
36896
39205
  const comment = {
36897
- id: randomUUID4(),
39206
+ id: randomUUID5(),
36898
39207
  task_id: input.task_id,
36899
39208
  agent_id: input.agent_id ?? context?.agentId ?? null,
36900
39209
  session_id: input.session_id ?? context?.sessionId ?? null,
@@ -37133,6 +39442,7 @@ var init_postgres_adapter = __esm(() => {
37133
39442
  init_integrity();
37134
39443
  init_redaction();
37135
39444
  init_audit_history_import();
39445
+ init_canonical();
37136
39446
  TASK_ORDER_BY = `ORDER BY ${TASK_ORDER_TIEBREAK}`;
37137
39447
  });
37138
39448
 
@@ -38620,25 +40930,25 @@ var init_sqlite = __esm(() => {
38620
40930
  });
38621
40931
 
38622
40932
  // src/project-registration/authority.ts
38623
- import { createHash as createHash11 } from "crypto";
40933
+ import { createHash as createHash13 } from "crypto";
38624
40934
  function canonicalProjectRegistrationJson(value) {
38625
- return JSON.stringify(canonicalize2(value));
40935
+ return JSON.stringify(canonicalize3(value));
38626
40936
  }
38627
- function canonicalize2(value) {
40937
+ function canonicalize3(value) {
38628
40938
  if (Array.isArray(value))
38629
- return value.map(canonicalize2);
40939
+ return value.map(canonicalize3);
38630
40940
  if (!value || typeof value !== "object")
38631
40941
  return value;
38632
40942
  const out = {};
38633
40943
  for (const key of Object.keys(value).sort()) {
38634
40944
  const entry2 = value[key];
38635
40945
  if (entry2 !== undefined)
38636
- out[key] = canonicalize2(entry2);
40946
+ out[key] = canonicalize3(entry2);
38637
40947
  }
38638
40948
  return out;
38639
40949
  }
38640
40950
  function digestProjectRegistrationValue(value) {
38641
- return createHash11("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
40951
+ return createHash13("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
38642
40952
  }
38643
40953
  function deriveTodosProjectRegistrationIdempotencyKey(input) {
38644
40954
  return `prk_${digestProjectRegistrationValue({
@@ -39633,31 +41943,6 @@ var init_project_registration = __esm(() => {
39633
41943
  init_types3();
39634
41944
  });
39635
41945
 
39636
- // src/task-manifest/canonical.ts
39637
- import { createHash as createHash12 } from "crypto";
39638
- function canonicalize3(value) {
39639
- if (Array.isArray(value))
39640
- return value.map(canonicalize3);
39641
- if (value !== null && typeof value === "object") {
39642
- return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize3(entry2)]));
39643
- }
39644
- return value;
39645
- }
39646
- function canonicalJson2(value) {
39647
- return JSON.stringify(canonicalize3(value));
39648
- }
39649
- function canonicalDigest(value) {
39650
- return createHash12("sha256").update(canonicalJson2(value)).digest("hex");
39651
- }
39652
- function deterministicUuid(namespace, ...parts) {
39653
- const bytes = createHash12("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
39654
- bytes[6] = bytes[6] & 15 | 80;
39655
- bytes[8] = bytes[8] & 63 | 128;
39656
- const hex = bytes.toString("hex");
39657
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
39658
- }
39659
- var init_canonical = () => {};
39660
-
39661
41946
  // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
39662
41947
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
39663
41948
  const t = typeof data;
@@ -44211,7 +46496,7 @@ class PostgresTodosTaskManifestBackend {
44211
46496
  result_digest: input.result_digest
44212
46497
  };
44213
46498
  const manifestJson = canonicalJson2(manifest);
44214
- const resultJson = canonicalJson2(result);
46499
+ const resultJson2 = canonicalJson2(result);
44215
46500
  await tx.query(`INSERT INTO todos_task_manifest_receipts (
44216
46501
  receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
44217
46502
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
@@ -44223,7 +46508,7 @@ class PostgresTodosTaskManifestBackend {
44223
46508
  input.request_digest,
44224
46509
  input.result_digest,
44225
46510
  manifestJson,
44226
- resultJson,
46511
+ resultJson2,
44227
46512
  input.now
44228
46513
  ]);
44229
46514
  for (const entry2 of input.outbox) {
@@ -44250,7 +46535,7 @@ class PostgresTodosTaskManifestBackend {
44250
46535
  input.result_digest,
44251
46536
  input.receipt_id,
44252
46537
  manifestJson,
44253
- resultJson,
46538
+ resultJson2,
44254
46539
  input.now
44255
46540
  ]);
44256
46541
  fault(faults, "after_receipt_write");
@@ -47160,6 +49445,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
47160
49445
  plan_id: { type: "string" },
47161
49446
  assigned_to: { type: "string" },
47162
49447
  agent_id: { type: "string" },
49448
+ created_by: { type: "string" },
47163
49449
  tags: { type: "array", items: { type: "string" } }
47164
49450
  }
47165
49451
  },
@@ -49002,6 +51288,7 @@ var init_openapi = __esm(() => {
49002
51288
  parent_id: { type: "string", nullable: true },
49003
51289
  assigned_to: { type: "string", nullable: true },
49004
51290
  agent_id: { type: "string", nullable: true },
51291
+ created_by: { type: "string", nullable: true },
49005
51292
  reason: { type: "string", nullable: true },
49006
51293
  tags: { type: "array", items: { type: "string" } },
49007
51294
  version: { type: "number" },
@@ -51598,7 +53885,7 @@ async function validateMcpAssignee(value, allowSeat) {
51598
53885
  function registerTaskCrudTools(server, ctx) {
51599
53886
  const { shouldRegisterTool, resolveId, formatError, formatTask, applyFocus } = ctx;
51600
53887
  function mutationTaskResponse(task2) {
51601
- const compact = compactTask(task2, 240);
53888
+ const compact = compactTask2(task2, 240);
51602
53889
  compact["version"] = task2.version;
51603
53890
  compact["created_at"] = task2.created_at;
51604
53891
  compact["task_list_id"] = task2.task_list_id;
@@ -51820,7 +54107,7 @@ function registerTaskCrudTools(server, ctx) {
51820
54107
  throw new TaskNotFoundError(task_id);
51821
54108
  const focus = ctx.getAgentFocus(task2.assigned_to || "");
51822
54109
  if (detail !== "full") {
51823
- const compact = compactTask(task2, max_description_chars || 240);
54110
+ const compact = compactTask2(task2, max_description_chars || 240);
51824
54111
  compact["version"] = task2.version;
51825
54112
  compact["created_at"] = task2.created_at;
51826
54113
  compact["focus"] = focus ? { agent_id: focus.agent_id, project_id: focus.project_id || null } : null;
@@ -52168,7 +54455,7 @@ var exports_mention_resolver = {};
52168
54455
  __export(exports_mention_resolver, {
52169
54456
  resolveMentions: () => resolveMentions
52170
54457
  });
52171
- import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync10 } from "fs";
54458
+ import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync10 } from "fs";
52172
54459
  import { basename as basename8, isAbsolute, join as join19, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
52173
54460
  function blankResolution(parsed) {
52174
54461
  return {
@@ -52277,7 +54564,7 @@ function resolveFile(parsed, workspace) {
52277
54564
  return resolution;
52278
54565
  }
52279
54566
  if (parsed.line !== undefined) {
52280
- const lineCount = readFileSync12(absolutePath, "utf-8").split(/\r?\n/).length;
54567
+ const lineCount = readFileSync13(absolutePath, "utf-8").split(/\r?\n/).length;
52281
54568
  if (parsed.line < 1 || parsed.line > lineCount) {
52282
54569
  resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
52283
54570
  return resolution;
@@ -52330,7 +54617,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
52330
54617
  const pattern = symbolPattern(name);
52331
54618
  const matches = [];
52332
54619
  for (const file of walkSourceFiles(workspace)) {
52333
- const lines = readFileSync12(file, "utf-8").split(/\r?\n/);
54620
+ const lines = readFileSync13(file, "utf-8").split(/\r?\n/);
52334
54621
  for (let index = 0;index < lines.length; index += 1) {
52335
54622
  const line = lines[index];
52336
54623
  const found = pattern.exec(line);
@@ -54783,7 +57070,7 @@ __export(exports_audit_ledger, {
54783
57070
  LOCAL_AUDIT_LEDGER_INITIAL_HASH: () => LOCAL_AUDIT_LEDGER_INITIAL_HASH,
54784
57071
  LOCAL_AUDIT_LEDGER_HASH_ALGORITHM: () => LOCAL_AUDIT_LEDGER_HASH_ALGORITHM
54785
57072
  });
54786
- import { createHash as createHash13 } from "crypto";
57073
+ import { createHash as createHash14 } from "crypto";
54787
57074
  function canonicalize4(value) {
54788
57075
  if (value === null || typeof value !== "object")
54789
57076
  return JSON.stringify(value);
@@ -54793,7 +57080,7 @@ function canonicalize4(value) {
54793
57080
  return `{${Object.keys(object).sort().map((key2) => `${JSON.stringify(key2)}:${canonicalize4(object[key2])}`).join(",")}}`;
54794
57081
  }
54795
57082
  function hash(value) {
54796
- return createHash13("sha256").update(value).digest("hex");
57083
+ return createHash14("sha256").update(value).digest("hex");
54797
57084
  }
54798
57085
  function parsePayload3(value) {
54799
57086
  if (!value)
@@ -55072,7 +57359,7 @@ __export(exports_release_compatibility, {
55072
57359
  createReleaseCompatibilityReport: () => createReleaseCompatibilityReport,
55073
57360
  LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
55074
57361
  });
55075
- import { readFileSync as readFileSync13 } from "fs";
57362
+ import { readFileSync as readFileSync14 } from "fs";
55076
57363
  import { join as join20, resolve as resolve19 } from "path";
55077
57364
  import { Database as Database3 } from "bun:sqlite";
55078
57365
  function pass(id, message, details) {
@@ -55085,7 +57372,7 @@ function warn(id, message, details) {
55085
57372
  return { id, status: "warning", message, details };
55086
57373
  }
55087
57374
  function readPackageJson2(root) {
55088
- return JSON.parse(readFileSync13(join20(root, "package.json"), "utf8"));
57375
+ return JSON.parse(readFileSync14(join20(root, "package.json"), "utf8"));
55089
57376
  }
55090
57377
  function sortedKeys(value) {
55091
57378
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -58193,7 +60480,7 @@ function registerTaskWorkflowTools(server, ctx) {
58193
60480
  type: "text",
58194
60481
  text: compactJson({
58195
60482
  status: compactStatus(cloudStatus),
58196
- next_task: next_task2 ? compactTask(next_task2, max_description_chars || 180) : null,
60483
+ next_task: next_task2 ? compactTask2(next_task2, max_description_chars || 180) : null,
58197
60484
  overdue_count: 0,
58198
60485
  latest_handoff: null,
58199
60486
  as_of: payload2.as_of
@@ -58232,7 +60519,7 @@ function registerTaskWorkflowTools(server, ctx) {
58232
60519
  type: "text",
58233
60520
  text: compactJson({
58234
60521
  status: compactStatus(status2),
58235
- next_task: next_task ? compactTask(next_task, max_description_chars || 180) : null,
60522
+ next_task: next_task ? compactTask2(next_task, max_description_chars || 180) : null,
58236
60523
  overdue_count: overdue.length,
58237
60524
  latest_handoff: compactHandoff(latest_handoff),
58238
60525
  as_of: payload.as_of
@@ -59634,7 +61921,7 @@ function registerTaskAdvTools(server, ctx) {
59634
61921
  const task3 = await cloudGetTask(cloud, task_id);
59635
61922
  if (!task3)
59636
61923
  throw new Error(`Task not found: ${task_id}`);
59637
- return { content: [{ type: "text", text: compactJson({ source: "cloud", task: compactTask(task3, 160) }) }] };
61924
+ return { content: [{ type: "text", text: compactJson({ source: "cloud", task: compactTask2(task3, 160) }) }] };
59638
61925
  }
59639
61926
  const resolvedId = resolveId(task_id);
59640
61927
  const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
@@ -59655,7 +61942,7 @@ function registerTaskAdvTools(server, ctx) {
59655
61942
  content: [{
59656
61943
  type: "text",
59657
61944
  text: compactJson({
59658
- task: compactTask(task2, 160),
61945
+ task: compactTask2(task2, 160),
59659
61946
  dependencies: {
59660
61947
  count: deps.length,
59661
61948
  items: deps.slice(0, 10).map((d) => ({ direction: d.direction, task_id: d.task_id, status: d.status })),
@@ -59737,7 +62024,7 @@ Files (${files.length}):` : null,
59737
62024
  content: [{
59738
62025
  type: "text",
59739
62026
  text: compactJson({
59740
- task: compactTask(task2, max_description_chars || 240),
62027
+ task: compactTask2(task2, max_description_chars || 240),
59741
62028
  dependencies: {
59742
62029
  count: deps.length,
59743
62030
  items: deps.slice(0, 10).map((d) => ({ direction: d.direction, task_id: d.task_id, status: d.status })),
@@ -60749,7 +63036,7 @@ function updateDispatcherMetadata(runId, dispatcher, db) {
60749
63036
  d.run("UPDATE task_runs SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now(), run.id]);
60750
63037
  return getTaskRun(run.id, d);
60751
63038
  }
60752
- function resolveAdapter(adapter) {
63039
+ function resolveAdapter2(adapter) {
60753
63040
  if (!adapter)
60754
63041
  return null;
60755
63042
  return loadConfig().agent_run_adapters?.[adapter] || null;
@@ -60800,7 +63087,7 @@ function removeAgentRunAdapter(name) {
60800
63087
  return true;
60801
63088
  }
60802
63089
  function queueAgentRun(input, db) {
60803
- const adapter = resolveAdapter(input.adapter);
63090
+ const adapter = resolveAdapter2(input.adapter);
60804
63091
  const command = input.command || adapter?.command;
60805
63092
  if (!command)
60806
63093
  throw new Error("agent run requires --command or a configured adapter command");
@@ -60867,7 +63154,7 @@ async function runNextAgentDispatch(input = {}, db) {
60867
63154
  const next = listAgentRunQueue(d).find((item) => item.dispatcher.state === "queued" && (!input.adapter || item.dispatcher.adapter === input.adapter));
60868
63155
  if (!next)
60869
63156
  return null;
60870
- const adapter = resolveAdapter(next.dispatcher.adapter);
63157
+ const adapter = resolveAdapter2(next.dispatcher.adapter);
60871
63158
  const command = renderCommand(next.dispatcher.command, next.run);
60872
63159
  const cwd = next.dispatcher.cwd || adapter?.cwd || process.cwd();
60873
63160
  const sandbox = next.dispatcher.sandbox || adapter?.sandbox;
@@ -60963,7 +63250,7 @@ function queuedRunById(runId, db) {
60963
63250
  return item.dispatcher ? item : null;
60964
63251
  }
60965
63252
  function enqueueAgentRun(input, db) {
60966
- const command = input.command || resolveAdapter(input.adapter)?.command || `agent-run:${input.adapter ?? "custom"}`;
63253
+ const command = input.command || resolveAdapter2(input.adapter)?.command || `agent-run:${input.adapter ?? "custom"}`;
60967
63254
  const queued = queueAgentRun({
60968
63255
  task_id: input.task_id,
60969
63256
  agent_id: input.agent_id,
@@ -61055,7 +63342,7 @@ __export(exports_verification_providers, {
61055
63342
  getVerificationRecord: () => getVerificationRecord,
61056
63343
  discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
61057
63344
  });
61058
- import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
63345
+ import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
61059
63346
  function normalizeName6(name) {
61060
63347
  const normalized = name.trim().toLowerCase();
61061
63348
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -61207,7 +63494,7 @@ Timed out after ${provider.timeout_ms}ms`);
61207
63494
  };
61208
63495
  }
61209
63496
  function runCiLogProvider(input) {
61210
- const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ? readFileSync14(input.log_path, "utf-8") : "");
63497
+ const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ? readFileSync15(input.log_path, "utf-8") : "");
61211
63498
  return {
61212
63499
  status: classifyLog(text),
61213
63500
  attempts: 1,
@@ -63509,8 +65796,8 @@ __export(exports_local_backups, {
63509
65796
  TODOS_LOCAL_BACKUP_KIND: () => TODOS_LOCAL_BACKUP_KIND,
63510
65797
  LOCAL_BACKUP_CHECKSUM_ALGORITHM: () => LOCAL_BACKUP_CHECKSUM_ALGORITHM
63511
65798
  });
63512
- import { createHash as createHash14 } from "crypto";
63513
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
65799
+ import { createHash as createHash15 } from "crypto";
65800
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
63514
65801
  import { dirname as dirname9, resolve as resolve20 } from "path";
63515
65802
  import { mkdirSync as mkdirSync10 } from "fs";
63516
65803
  function stableJson2(value) {
@@ -63522,7 +65809,7 @@ function stableJson2(value) {
63522
65809
  return `{${Object.keys(record).sort().map((key2) => `${JSON.stringify(key2)}:${stableJson2(record[key2])}`).join(",")}}`;
63523
65810
  }
63524
65811
  function sha2565(value) {
63525
- return createHash14("sha256").update(stableJson2(value)).digest("hex");
65812
+ return createHash15("sha256").update(stableJson2(value)).digest("hex");
63526
65813
  }
63527
65814
  function sqliteIntegrity(db) {
63528
65815
  let quick = "unknown";
@@ -63620,7 +65907,7 @@ function writeLocalBackupFile(backup, outputPath) {
63620
65907
  return path;
63621
65908
  }
63622
65909
  function readLocalBackupFile(path) {
63623
- return JSON.parse(readFileSync15(resolve20(path), "utf-8"));
65910
+ return JSON.parse(readFileSync16(resolve20(path), "utf-8"));
63624
65911
  }
63625
65912
  function verifyLocalBackup(value, options = {}, db) {
63626
65913
  const verifiedAt = options.verified_at ?? now();
@@ -64203,7 +66490,7 @@ __export(exports_local_snapshots, {
64203
66490
  getLocalSnapshot: () => getLocalSnapshot,
64204
66491
  TODOS_LOCAL_SNAPSHOT_SCHEMA_VERSION: () => TODOS_LOCAL_SNAPSHOT_SCHEMA_VERSION
64205
66492
  });
64206
- import { createHash as createHash15 } from "crypto";
66493
+ import { createHash as createHash16 } from "crypto";
64207
66494
  function source2(version) {
64208
66495
  return {
64209
66496
  packageName: "@hasna/todos",
@@ -64227,7 +66514,7 @@ function stable(value) {
64227
66514
  return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => [key2, stable(item)]));
64228
66515
  }
64229
66516
  function sha2566(value) {
64230
- return createHash15("sha256").update(JSON.stringify(stable(value))).digest("hex");
66517
+ return createHash16("sha256").update(JSON.stringify(stable(value))).digest("hex");
64231
66518
  }
64232
66519
  function latestTimestamp2(items, fallback) {
64233
66520
  const timestamps = [];
@@ -64799,8 +67086,8 @@ __export(exports_agent_replay_simulator, {
64799
67086
  simulateAgentReplay: () => simulateAgentReplay,
64800
67087
  renderAgentReplaySimulationMarkdown: () => renderAgentReplaySimulationMarkdown
64801
67088
  });
64802
- import { createHash as createHash16 } from "crypto";
64803
- import { readFileSync as readFileSync16 } from "fs";
67089
+ import { createHash as createHash17 } from "crypto";
67090
+ import { readFileSync as readFileSync17 } from "fs";
64804
67091
  function isObject2(value) {
64805
67092
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
64806
67093
  }
@@ -64821,7 +67108,7 @@ function stable2(value) {
64821
67108
  return Object.fromEntries(Object.keys(value).sort().map((key2) => [key2, stable2(value[key2])]));
64822
67109
  }
64823
67110
  function fingerprint2(value) {
64824
- return createHash16("sha256").update(JSON.stringify(stable2(value))).digest("hex");
67111
+ return createHash17("sha256").update(JSON.stringify(stable2(value))).digest("hex");
64825
67112
  }
64826
67113
  function unpackFixture(input) {
64827
67114
  if (!isObject2(input))
@@ -65033,7 +67320,7 @@ function simulateAgentReplay(input, options = {}) {
65033
67320
  };
65034
67321
  }
65035
67322
  function simulateAgentReplayFile(path, options = {}) {
65036
- const parsed = JSON.parse(readFileSync16(path, "utf8"));
67323
+ const parsed = JSON.parse(readFileSync17(path, "utf8"));
65037
67324
  return simulateAgentReplay(parsed, options);
65038
67325
  }
65039
67326
  function renderAgentReplaySimulationMarkdown(simulation) {
@@ -65139,7 +67426,7 @@ __export(exports_inbox, {
65139
67426
  deriveInboxTitle: () => deriveInboxTitle,
65140
67427
  createInboxItem: () => createInboxItem
65141
67428
  });
65142
- import { createHash as createHash17 } from "crypto";
67429
+ import { createHash as createHash18 } from "crypto";
65143
67430
  function parseMetadata3(value) {
65144
67431
  if (!value)
65145
67432
  return {};
@@ -65159,7 +67446,7 @@ function compactWhitespace(value) {
65159
67446
  function fingerprintInboxInput(input) {
65160
67447
  const sourceType = input.source_type || detectInboxSourceType(input.body, input.source_url);
65161
67448
  const normalized = compactWhitespace(sanitizePreWriteText(input.body, "inbox.fingerprint")).slice(0, 8000);
65162
- return createHash17("sha256").update(`${sourceType}
67449
+ return createHash18("sha256").update(`${sourceType}
65163
67450
  ${input.source_url || ""}
65164
67451
  ${normalized}`).digest("hex");
65165
67452
  }
@@ -70371,13 +72658,13 @@ __export(exports_environment_snapshots, {
70371
72658
  compareEnvironmentSnapshotFiles: () => compareEnvironmentSnapshotFiles,
70372
72659
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
70373
72660
  });
70374
- import { createHash as createHash18 } from "crypto";
70375
- import { existsSync as existsSync21, readFileSync as readFileSync17, statSync as statSync11 } from "fs";
72661
+ import { createHash as createHash19 } from "crypto";
72662
+ import { existsSync as existsSync21, readFileSync as readFileSync18, statSync as statSync11 } from "fs";
70376
72663
  import { hostname as hostname2, platform, arch } from "os";
70377
72664
  import { dirname as dirname10, join as join22, resolve as resolve21 } from "path";
70378
72665
  import { tmpdir as tmpdir4 } from "os";
70379
72666
  function sha2567(value) {
70380
- return createHash18("sha256").update(value).digest("hex");
72667
+ return createHash19("sha256").update(value).digest("hex");
70381
72668
  }
70382
72669
  function fileRecord(root, relativePath) {
70383
72670
  const path = join22(root, relativePath);
@@ -70386,7 +72673,7 @@ function fileRecord(root, relativePath) {
70386
72673
  const stat = statSync11(path);
70387
72674
  if (!stat.isFile())
70388
72675
  return null;
70389
- const content = readFileSync17(path);
72676
+ const content = readFileSync18(path);
70390
72677
  return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
70391
72678
  }
70392
72679
  function manifestRecord(root, relativePath) {
@@ -70756,7 +73043,7 @@ Environment:
70756
73043
  TODOS_TOOL_GROUPS=<list> Comma-separated tool group filter`);
70757
73044
  }
70758
73045
  function shouldRegisterTool(name) {
70759
- return shouldRegisterToolForProfile(name);
73046
+ return shouldRegisterToolForProfile2(name);
70760
73047
  }
70761
73048
  function getAgentFocus(agentId) {
70762
73049
  const sessionFocus = agentFocusMap.get(agentId);
@@ -73432,7 +75719,7 @@ __export(exports_config_serve_commands, {
73432
75719
  registerConfigServeCommands: () => registerConfigServeCommands
73433
75720
  });
73434
75721
  import chalk7 from "chalk";
73435
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
75722
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
73436
75723
  import { dirname as dirname12, join as join24 } from "path";
73437
75724
  function registerConfigServeCommands(program2) {
73438
75725
  program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
@@ -73463,7 +75750,7 @@ function registerConfigServeCommands(program2) {
73463
75750
  }
73464
75751
  let config2 = {};
73465
75752
  try {
73466
- config2 = JSON.parse(readFileSync18(configPath, "utf-8"));
75753
+ config2 = JSON.parse(readFileSync19(configPath, "utf-8"));
73467
75754
  } catch {}
73468
75755
  const keys = key2.split(".");
73469
75756
  let obj = config2;
@@ -73603,7 +75890,7 @@ function registerConfigServeCommands(program2) {
73603
75890
  redaction.command("scan [text]").description("Scan text or a file for secret-like values without printing values").option("--file <path>", "File to scan").action(async (text2, opts) => {
73604
75891
  const globalOpts = program2.opts();
73605
75892
  const { listSecretFindings: listSecretFindings2 } = await Promise.resolve().then(() => (init_redaction(), exports_redaction));
73606
- const value = opts.file ? readFileSync18(opts.file, "utf-8") : text2 || "";
75893
+ const value = opts.file ? readFileSync19(opts.file, "utf-8") : text2 || "";
73607
75894
  const findings = listSecretFindings2(value);
73608
75895
  if (globalOpts.json) {
73609
75896
  output({ ok: findings.length === 0, findings }, true);
@@ -75168,14 +77455,14 @@ __export(exports_task_route_sources, {
75168
77455
  TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION: () => TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION
75169
77456
  });
75170
77457
  import { Database as Database4 } from "bun:sqlite";
75171
- import { createHash as createHash19 } from "crypto";
77458
+ import { createHash as createHash20 } from "crypto";
75172
77459
  import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as statSync12 } from "fs";
75173
77460
  import { basename as basename10, dirname as dirname14, join as join26, resolve as resolve22 } from "path";
75174
77461
  function normalizePath6(input) {
75175
77462
  return resolve22(input);
75176
77463
  }
75177
77464
  function sourceStoreId(sourceDbPath) {
75178
- const digest2 = createHash19("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
77465
+ const digest2 = createHash20("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
75179
77466
  return `sqlite:${digest2}`;
75180
77467
  }
75181
77468
  function inferSourceRepoPath(sourceDbPath) {
@@ -75507,7 +77794,7 @@ __export(exports_tester_issue_reports, {
75507
77794
  TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION: () => TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
75508
77795
  TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION: () => TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION
75509
77796
  });
75510
- import { createHash as createHash20 } from "crypto";
77797
+ import { createHash as createHash21 } from "crypto";
75511
77798
  function asObject3(value) {
75512
77799
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
75513
77800
  }
@@ -75679,7 +77966,7 @@ function fingerprintTesterIssueReport(report) {
75679
77966
  normalizeText4(report.failure?.message || report.summary || report.title).slice(0, 240),
75680
77967
  normalizeText4(stackTop).slice(0, 160)
75681
77968
  ].join("::");
75682
- return `testers:${createHash20("sha256").update(raw).digest("hex").slice(0, 16)}`;
77969
+ return `testers:${createHash21("sha256").update(raw).digest("hex").slice(0, 16)}`;
75683
77970
  }
75684
77971
  function priorityForSeverity(severity, fallback) {
75685
77972
  return PRIORITIES5.includes(severity) ? severity : fallback;
@@ -75978,7 +78265,7 @@ __export(exports_query_commands, {
75978
78265
  registerQueryCommands: () => registerQueryCommands
75979
78266
  });
75980
78267
  import chalk9 from "chalk";
75981
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync12 } from "fs";
78268
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
75982
78269
  function parseJsonObjectOption2(value, label) {
75983
78270
  if (!value)
75984
78271
  return;
@@ -77742,7 +80029,7 @@ Findings`));
77742
80029
  const sessionId = opts.session || globalOpts.session || undefined;
77743
80030
  try {
77744
80031
  if (opts.import) {
77745
- const bundle = JSON.parse(readFileSync19(opts.import, "utf-8"));
80032
+ const bundle = JSON.parse(readFileSync20(opts.import, "utf-8"));
77746
80033
  const result = importHandoffBundle(bundle, { apply: opts.apply }, db);
77747
80034
  if (opts.json || globalOpts.json) {
77748
80035
  console.log(JSON.stringify(result));
@@ -78116,7 +80403,7 @@ Findings`));
78116
80403
  });
78117
80404
  calendar.command("import <path>").description("Import VEVENT entries from an ICS file as local imported calendar items").option("-j, --json", "Output JSON").action((path, opts) => {
78118
80405
  try {
78119
- const result = importCalendarIcs(readFileSync19(path, "utf-8"));
80406
+ const result = importCalendarIcs(readFileSync20(path, "utf-8"));
78120
80407
  if (opts.json || program2.opts().json) {
78121
80408
  output(result, true);
78122
80409
  return;
@@ -78267,7 +80554,7 @@ Findings`));
78267
80554
  });
78268
80555
  board.command("import <path>").description("Import local board definitions from a JSON bundle").option("-j, --json", "Output JSON").action((path, opts) => {
78269
80556
  try {
78270
- const bundle = JSON.parse(readFileSync19(path, "utf-8"));
80557
+ const bundle = JSON.parse(readFileSync20(path, "utf-8"));
78271
80558
  const result = importTaskBoardBundle(bundle);
78272
80559
  if (opts.json || program2.opts().json) {
78273
80560
  output(result, true);
@@ -78643,7 +80930,7 @@ Findings`));
78643
80930
  const { importExternalIssues: importExternalIssues2 } = await Promise.resolve().then(() => (init_external_issue_importers(), exports_external_issue_importers));
78644
80931
  let body2 = text2 || "";
78645
80932
  if (opts.file)
78646
- body2 = readFileSync19(opts.file, "utf-8");
80933
+ body2 = readFileSync20(opts.file, "utf-8");
78647
80934
  if (!body2 && !opts.url && !process.stdin.isTTY)
78648
80935
  body2 = await Bun.stdin.text();
78649
80936
  if (!body2.trim() && !opts.url) {
@@ -78700,7 +80987,7 @@ Findings`));
78700
80987
  } = await Promise.resolve().then(() => (init_tester_issue_reports(), exports_tester_issue_reports));
78701
80988
  let body2 = jsonText || "";
78702
80989
  if (opts.file)
78703
- body2 = readFileSync19(opts.file, "utf-8");
80990
+ body2 = readFileSync20(opts.file, "utf-8");
78704
80991
  if (!body2 && !process.stdin.isTTY)
78705
80992
  body2 = await Bun.stdin.text();
78706
80993
  if (!body2.trim()) {
@@ -78743,11 +81030,11 @@ Findings`));
78743
81030
  const inbox = program2.command("inbox").description("Capture local inbox items from pasted errors, CI logs, git context, files, or GitHub issue URLs");
78744
81031
  inbox.command("add [text]").description("Create a local inbox item and linked task from text, stdin, or a file").option("--file <path>", "Read captured context from a file").option("--source-type <type>", "pasted_error, ci_log, git_context, github_issue, file, or other").option("--source-name <name>", "Human-readable source name").option("--source-url <url>", "Source URL, including GitHub issue URLs").option("--title <title>", "Task/inbox title").option("--priority <priority>", "Task priority").option("--tags <tags>", "Comma-separated extra tags").option("--metadata <json>", "Additional JSON metadata").option("--no-task", "Only store inbox item; do not create a linked task").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
78745
81032
  const globalOpts = program2.opts();
78746
- const { readFileSync: readFileSync20 } = await import("fs");
81033
+ const { readFileSync: readFileSync21 } = await import("fs");
78747
81034
  const { createInboxItem: createInboxItem2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
78748
81035
  let body2 = text2 || "";
78749
81036
  if (opts.file)
78750
- body2 = readFileSync20(opts.file, "utf-8");
81037
+ body2 = readFileSync21(opts.file, "utf-8");
78751
81038
  if (!body2 && !process.stdin.isTTY)
78752
81039
  body2 = await Bun.stdin.text();
78753
81040
  if (!body2.trim()) {
@@ -78807,11 +81094,11 @@ ${diff}` : null].filter(Boolean).join(`
78807
81094
  });
78808
81095
  inbox.command("parse [text]").description("Preview or apply deterministic local natural-language task intake").option("--file <path>", "Read natural-language input from a file").option("--priority <priority>", "Default priority for parsed tasks", "medium").option("--project <id>", "Project ID for applied tasks").option("--list <id>", "Task list ID for applied tasks").option("--reference-date <iso>", "Reference date for due today/tomorrow/next week").option("--apply", "Create parsed tasks; default is dry-run preview").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
78809
81096
  const globalOpts = program2.opts();
78810
- const { readFileSync: readFileSync20 } = await import("fs");
81097
+ const { readFileSync: readFileSync21 } = await import("fs");
78811
81098
  const { previewNaturalLanguageIntake: previewNaturalLanguageIntake2 } = await Promise.resolve().then(() => (init_natural_language_intake(), exports_natural_language_intake));
78812
81099
  let body2 = text2 || "";
78813
81100
  if (opts.file)
78814
- body2 = readFileSync20(opts.file, "utf-8");
81101
+ body2 = readFileSync21(opts.file, "utf-8");
78815
81102
  if (!body2 && !process.stdin.isTTY)
78816
81103
  body2 = await Bun.stdin.text();
78817
81104
  if (!body2.trim()) {
@@ -79138,7 +81425,7 @@ __export(exports_mcp_hooks_commands, {
79138
81425
  });
79139
81426
  import chalk10 from "chalk";
79140
81427
  import { execSync as execSync3 } from "child_process";
79141
- import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
81428
+ import { existsSync as existsSync26, readFileSync as readFileSync21, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
79142
81429
  import { dirname as dirname15, join as join27 } from "path";
79143
81430
  function getMcpBinaryPath() {
79144
81431
  try {
@@ -79155,7 +81442,7 @@ function readJsonFile2(path) {
79155
81442
  if (!existsSync26(path))
79156
81443
  return {};
79157
81444
  try {
79158
- return JSON.parse(readFileSync20(path, "utf-8"));
81445
+ return JSON.parse(readFileSync21(path, "utf-8"));
79159
81446
  } catch {
79160
81447
  return {};
79161
81448
  }
@@ -79170,7 +81457,7 @@ function writeJsonFile2(path, data) {
79170
81457
  function readTomlFile(path) {
79171
81458
  if (!existsSync26(path))
79172
81459
  return "";
79173
- return readFileSync20(path, "utf-8");
81460
+ return readFileSync21(path, "utf-8");
79174
81461
  }
79175
81462
  function writeTomlFile(path, content) {
79176
81463
  const dir = dirname15(path);
@@ -80361,7 +82648,7 @@ Artifacts:`));
80361
82648
  const hookPath = `${gitDir}/hooks/post-commit`;
80362
82649
  const marker = "# todos-auto-link";
80363
82650
  if (existsSync26(hookPath)) {
80364
- const existing = readFileSync20(hookPath, "utf-8");
82651
+ const existing = readFileSync21(hookPath, "utf-8");
80365
82652
  if (existing.includes(marker)) {
80366
82653
  console.log(chalk10.yellow("Hook already installed."));
80367
82654
  return;
@@ -80391,7 +82678,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
80391
82678
  console.log(chalk10.dim("No post-commit hook found."));
80392
82679
  return;
80393
82680
  }
80394
- const content = readFileSync20(hookPath, "utf-8");
82681
+ const content = readFileSync21(hookPath, "utf-8");
80395
82682
  if (!content.includes(marker)) {
80396
82683
  console.log(chalk10.dim("Hook not managed by todos."));
80397
82684
  return;
@@ -80574,7 +82861,7 @@ var init_dispatch3 = __esm(() => {
80574
82861
  });
80575
82862
 
80576
82863
  // src/lib/delegation-brief.ts
80577
- import { createHash as createHash21 } from "crypto";
82864
+ import { createHash as createHash22 } from "crypto";
80578
82865
  function resolveDelegationBrief(input, sources) {
80579
82866
  const hasPath = typeof input.briefPath === "string" && input.briefPath.length > 0;
80580
82867
  const hasText = typeof input.briefText === "string" && input.briefText.length > 0;
@@ -80634,7 +82921,7 @@ function resolveDelegationBrief(input, sources) {
80634
82921
  ok: true,
80635
82922
  text: text2,
80636
82923
  source: source3,
80637
- sha256: createHash21("sha256").update(text2, "utf8").digest("hex"),
82924
+ sha256: createHash22("sha256").update(text2, "utf8").digest("hex"),
80638
82925
  bytes: Buffer.byteLength(text2, "utf8")
80639
82926
  };
80640
82927
  }
@@ -80642,7 +82929,7 @@ var STDIN_SENTINEL = "-";
80642
82929
  var init_delegation_brief = () => {};
80643
82930
 
80644
82931
  // src/lib/delegation-policy.ts
80645
- import { readFileSync as readFileSync21 } from "fs";
82932
+ import { readFileSync as readFileSync22 } from "fs";
80646
82933
  import { homedir as homedir4 } from "os";
80647
82934
  import { join as join28 } from "path";
80648
82935
  function defaultDelegationEmbargoPath() {
@@ -80650,7 +82937,7 @@ function defaultDelegationEmbargoPath() {
80650
82937
  }
80651
82938
  function loadDelegationEmbargo(path = defaultDelegationEmbargoPath()) {
80652
82939
  try {
80653
- const parsed = JSON.parse(readFileSync21(path, "utf8"));
82940
+ const parsed = JSON.parse(readFileSync22(path, "utf8"));
80654
82941
  const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.embargoed) ? parsed.embargoed : [];
80655
82942
  const names = new Set;
80656
82943
  for (const entry2 of entries) {
@@ -80733,15 +83020,15 @@ __export(exports_delegate, {
80733
83020
  registerDelegateCommands: () => registerDelegateCommands
80734
83021
  });
80735
83022
  import chalk12 from "chalk";
80736
- import { readFileSync as readFileSync22 } from "fs";
83023
+ import { readFileSync as readFileSync23 } from "fs";
80737
83024
  function registerDelegateCommands(program2) {
80738
83025
  program2.command("delegate <task> <worker>").description("Hand a filed task to a worker in one call: brief, depth, lineage, assignment, record and notice").option("--brief <path>", "Path to the self-sufficient brief; `-` reads stdin").option("--brief-text <text>", "Inline brief, as an alternative to --brief").option("--depth-threshold <n>", "Open-task count above which this delegation parks").option("--despite-depth", "Proceed past an armed depth threshold; recorded in the [DISPATCH] comment").option("--owner-directive", "Mark as an owner-directive dispatch: depth warns and never parks").option("--seat <slug>", "Seat whose open count is read (default: the lineage parent)").option("--runtime <name>", "Worker runtime label recorded in the comment (e.g. claude-code-subagent)").option("--reports-to <agent>", "Lineage parent for the worker identity (default: the dispatcher)").option("--reuse-identity", "Skip registration and reuse an existing worker identity").option("--depth <n>", "Explicit delegation_depth (default: the task's current depth + 1)").option("--channel <name>", "Channel for the one-line notice (default: $TODOS_DELEGATE_NOTICE_CHANNEL)").option("--no-post", "Skip the channel notice").option("--claim-window <minutes>", `Minutes before the claim deadline (default: ${DEFAULT_CLAIM_WINDOW_MINUTES})`).option("--assign-seat", "Allow <worker> to name a durable seat (a seat queue has no session watching it)").option("--dry-run", "Report all seven effects and perform none").option("-j, --json", "Output as JSON").action(async (taskRef, workerInput, opts) => {
80739
83026
  const globalOpts = program2.opts();
80740
83027
  const useJson = Boolean(opts.json || globalOpts.json);
80741
83028
  try {
80742
83029
  const brief = resolveDelegationBrief({ briefPath: opts.brief, briefText: opts.briefText }, {
80743
- readFile: (path) => readFileSync22(path, "utf8"),
80744
- readStdin: () => readFileSync22(0, "utf8")
83030
+ readFile: (path) => readFileSync23(path, "utf8"),
83031
+ readStdin: () => readFileSync23(0, "utf8")
80745
83032
  });
80746
83033
  if (!brief.ok)
80747
83034
  handleError(new Error(brief.message));
@@ -80989,7 +83276,7 @@ __export(exports_machines, {
80989
83276
  });
80990
83277
  import chalk13 from "chalk";
80991
83278
  import { execSync as execSync4 } from "child_process";
80992
- import { readFileSync as readFileSync23, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
83279
+ import { readFileSync as readFileSync24, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
80993
83280
  import { tmpdir as tmpdir5 } from "os";
80994
83281
  import { join as join29 } from "path";
80995
83282
  function getOrCreateLocalMachineName() {
@@ -81033,7 +83320,7 @@ function readRemoteBridgeBundle(sshAddress) {
81033
83320
  try {
81034
83321
  runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
81035
83322
  scpFromRemote(sshAddress, remotePath, localPath);
81036
- return JSON.parse(readFileSync23(localPath, "utf-8"));
83323
+ return JSON.parse(readFileSync24(localPath, "utf-8"));
81037
83324
  } finally {
81038
83325
  try {
81039
83326
  runSsh(sshAddress, `rm -f ${shellQuote(remotePath)}`, 1e4);
@@ -85991,7 +88278,7 @@ var exports_roadmap_commands = {};
85991
88278
  __export(exports_roadmap_commands, {
85992
88279
  registerRoadmapCommands: () => registerRoadmapCommands
85993
88280
  });
85994
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "fs";
88281
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
85995
88282
  import chalk24 from "chalk";
85996
88283
  function splitList3(value) {
85997
88284
  return value?.split(";").flatMap((part) => part.split(",")).map((item) => item.trim()).filter(Boolean);
@@ -86016,14 +88303,14 @@ function resolveMany(table, values) {
86016
88303
  return resolved;
86017
88304
  });
86018
88305
  }
86019
- function globalOptions(program2) {
88306
+ function globalOptions2(program2) {
86020
88307
  const command = program2;
86021
88308
  return command.optsWithGlobals?.() ?? program2.opts();
86022
88309
  }
86023
88310
  function registerRoadmapCommands(program2) {
86024
88311
  const roadmaps = program2.command("roadmaps").alias("roadmap").description("Manage local roadmaps, milestones, and release groupings");
86025
88312
  roadmaps.command("create <name>").description("Create a local roadmap").option("--description <text>", "Description").option("--project <id>", "Project ID").option("--status <status>", "planned, active, completed, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--release <name>", "Default release label").action(async (name, opts) => {
86026
- const globalOpts = globalOptions(program2);
88313
+ const globalOpts = globalOptions2(program2);
86027
88314
  try {
86028
88315
  const { createRoadmap: createRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86029
88316
  const roadmap = createRoadmap2({
@@ -86045,7 +88332,7 @@ function registerRoadmapCommands(program2) {
86045
88332
  }
86046
88333
  });
86047
88334
  roadmaps.command("list").description("List local roadmaps").option("--project <id>", "Project ID").option("--status <status>", "Filter by status").action(async (opts) => {
86048
- const globalOpts = globalOptions(program2);
88335
+ const globalOpts = globalOptions2(program2);
86049
88336
  try {
86050
88337
  const { listRoadmaps: listRoadmaps2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86051
88338
  const items = listRoadmaps2({ project_id: resolveOptional("projects", opts.project || globalOpts.project), status: opts.status });
@@ -86064,7 +88351,7 @@ function registerRoadmapCommands(program2) {
86064
88351
  }
86065
88352
  });
86066
88353
  roadmaps.command("show <roadmap>").description("Show a roadmap summary").option("--format <format>", "json or markdown", "json").action(async (roadmap, opts) => {
86067
- const globalOpts = globalOptions(program2);
88354
+ const globalOpts = globalOptions2(program2);
86068
88355
  try {
86069
88356
  const { renderRoadmapMarkdown: renderRoadmapMarkdown2, summarizeRoadmap: summarizeRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86070
88357
  if (opts.format === "markdown") {
@@ -86082,7 +88369,7 @@ function registerRoadmapCommands(program2) {
86082
88369
  }
86083
88370
  });
86084
88371
  roadmaps.command("update <roadmap>").description("Update a local roadmap").option("--name <name>", "New name").option("--description <text>", "Description").option("--project <id>", "Project ID").option("--status <status>", "planned, active, completed, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--release <name>", "Release label").action(async (roadmap, opts) => {
86085
- const globalOpts = globalOptions(program2);
88372
+ const globalOpts = globalOptions2(program2);
86086
88373
  try {
86087
88374
  const { updateRoadmap: updateRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86088
88375
  const updated = updateRoadmap2(roadmap, {
@@ -86104,7 +88391,7 @@ function registerRoadmapCommands(program2) {
86104
88391
  }
86105
88392
  });
86106
88393
  roadmaps.command("delete <roadmap>").description("Delete a local roadmap and its local milestone/release config").action(async (roadmap) => {
86107
- const globalOpts = globalOptions(program2);
88394
+ const globalOpts = globalOptions2(program2);
86108
88395
  try {
86109
88396
  const { deleteRoadmap: deleteRoadmap2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86110
88397
  const deleted = deleteRoadmap2(roadmap);
@@ -86119,7 +88406,7 @@ function registerRoadmapCommands(program2) {
86119
88406
  });
86120
88407
  const milestones = roadmaps.command("milestones").description("Manage roadmap milestones");
86121
88408
  milestones.command("add <roadmap> <title>").description("Add a milestone to a roadmap").option("--description <text>", "Description").option("--due <iso>", "Due date or timestamp").option("--status <status>", "planned, active, completed, blocked, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--tasks <list>", "Comma-separated task IDs").option("--plans <list>", "Comma-separated plan IDs").option("--runs <list>", "Comma-separated run IDs").option("--release <name>", "Release label").option("--tags <list>", "Comma-separated tags").action(async (roadmap, title, opts) => {
86122
- const globalOpts = globalOptions(program2);
88409
+ const globalOpts = globalOptions2(program2);
86123
88410
  try {
86124
88411
  const { createMilestone: createMilestone2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86125
88412
  const milestone = createMilestone2({
@@ -86146,7 +88433,7 @@ function registerRoadmapCommands(program2) {
86146
88433
  }
86147
88434
  });
86148
88435
  milestones.command("update <milestone>").description("Update a roadmap milestone").option("--title <title>", "Title").option("--description <text>", "Description").option("--due <iso>", "Due date or timestamp").option("--status <status>", "planned, active, completed, blocked, archived").option("--owner <name>", "Owner name").option("--agent <name>", "Agent owner").option("--tasks <list>", "Comma-separated task IDs").option("--plans <list>", "Comma-separated plan IDs").option("--runs <list>", "Comma-separated run IDs").option("--release <name>", "Release label").option("--tags <list>", "Comma-separated tags").action(async (milestone, opts) => {
86149
- const globalOpts = globalOptions(program2);
88436
+ const globalOpts = globalOptions2(program2);
86150
88437
  try {
86151
88438
  const { updateMilestone: updateMilestone2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86152
88439
  const updated = updateMilestone2(milestone, {
@@ -86173,7 +88460,7 @@ function registerRoadmapCommands(program2) {
86173
88460
  });
86174
88461
  const releases = roadmaps.command("releases").description("Manage roadmap release groups");
86175
88462
  releases.command("set <roadmap> <name>").description("Create or update a release grouping").option("--release-version <version>", "Version label").option("--status <status>", "planned, active, completed, blocked, archived").option("--milestones <list>", "Comma-separated milestone IDs").option("--tasks <list>", "Comma-separated task IDs").option("--plans <list>", "Comma-separated plan IDs").option("--runs <list>", "Comma-separated run IDs").option("--notes <text>", "Release notes").action(async (roadmap, name, opts) => {
86176
- const globalOpts = globalOptions(program2);
88463
+ const globalOpts = globalOptions2(program2);
86177
88464
  try {
86178
88465
  const { upsertReleaseGroup: upsertReleaseGroup2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86179
88466
  const release = upsertReleaseGroup2({
@@ -86197,7 +88484,7 @@ function registerRoadmapCommands(program2) {
86197
88484
  }
86198
88485
  });
86199
88486
  roadmaps.command("export <roadmap>").description("Export a roadmap as JSON bundle or Markdown").option("--format <format>", "json or markdown", "json").option("--out <path>", "Write output to a file").action(async (roadmap, opts) => {
86200
- const globalOpts = globalOptions(program2);
88487
+ const globalOpts = globalOptions2(program2);
86201
88488
  try {
86202
88489
  const { exportRoadmapBundle: exportRoadmapBundle2, renderRoadmapMarkdown: renderRoadmapMarkdown2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86203
88490
  const content = opts.format === "markdown" ? renderRoadmapMarkdown2(roadmap) : JSON.stringify(exportRoadmapBundle2(roadmap), null, 2);
@@ -86217,10 +88504,10 @@ function registerRoadmapCommands(program2) {
86217
88504
  }
86218
88505
  });
86219
88506
  roadmaps.command("import <path>").description("Preview or apply a roadmap JSON bundle").option("--apply", "Apply the import").action(async (path, opts) => {
86220
- const globalOpts = globalOptions(program2);
88507
+ const globalOpts = globalOptions2(program2);
86221
88508
  try {
86222
88509
  const { importRoadmapBundle: importRoadmapBundle2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
86223
- const bundle = JSON.parse(readFileSync24(path, "utf8"));
88510
+ const bundle = JSON.parse(readFileSync25(path, "utf8"));
86224
88511
  const result = importRoadmapBundle2(bundle, { apply: Boolean(opts.apply) });
86225
88512
  if (globalOpts.json) {
86226
88513
  output(result, true);
@@ -86254,14 +88541,14 @@ function resolveOptional2(table, value) {
86254
88541
  throw new Error(`Could not resolve ${table} ID: ${value}`);
86255
88542
  return resolved;
86256
88543
  }
86257
- function globalOptions2(program2) {
88544
+ function globalOptions3(program2) {
86258
88545
  const command = program2;
86259
88546
  return command.optsWithGlobals?.() ?? program2.opts();
86260
88547
  }
86261
88548
  function registerCapacityCommands(program2) {
86262
88549
  const capacity = program2.command("capacity").description("Manage local capacity profiles and planning forecasts");
86263
88550
  capacity.command("set <agent>").description("Create or update a local agent capacity profile").requiredOption("--minutes-per-day <minutes>", "Available minutes per working day").option("--project <id>", "Project ID").option("--days <list>", "Working days as 0-6, where 0 is Sunday", "1,2,3,4,5").option("--from <date>", "Effective date").action(async (agent, opts) => {
86264
- const globalOpts = globalOptions2(program2);
88551
+ const globalOpts = globalOptions3(program2);
86265
88552
  try {
86266
88553
  const { upsertCapacityProfile: upsertCapacityProfile2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
86267
88554
  const profile = upsertCapacityProfile2({
@@ -86281,7 +88568,7 @@ function registerCapacityCommands(program2) {
86281
88568
  }
86282
88569
  });
86283
88570
  capacity.command("list").description("List local capacity profiles").option("--agent <id>", "Filter by agent").option("--project <id>", "Filter by project").action(async (opts) => {
86284
- const globalOpts = globalOptions2(program2);
88571
+ const globalOpts = globalOptions3(program2);
86285
88572
  try {
86286
88573
  const { listCapacityProfiles: listCapacityProfiles2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
86287
88574
  const profiles = listCapacityProfiles2({
@@ -86304,7 +88591,7 @@ function registerCapacityCommands(program2) {
86304
88591
  }
86305
88592
  });
86306
88593
  capacity.command("remove <agent-or-id>").description("Remove a local capacity profile").option("--project <id>", "Project ID for agent-scoped removal").action(async (agentOrId, opts) => {
86307
- const globalOpts = globalOptions2(program2);
88594
+ const globalOpts = globalOptions3(program2);
86308
88595
  try {
86309
88596
  const { removeCapacityProfile: removeCapacityProfile2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
86310
88597
  const removed = removeCapacityProfile2(agentOrId, opts.project ? resolveOptional2("projects", opts.project) : undefined);
@@ -86318,7 +88605,7 @@ function registerCapacityCommands(program2) {
86318
88605
  }
86319
88606
  });
86320
88607
  capacity.command("forecast").description("Forecast local plan or project completion from estimates and capacity").option("--project <id>", "Project ID").option("--plan <id>", "Plan ID").option("--agent <id>", "Agent filter").option("--start-date <date>", "Forecast start date").option("--format <format>", "json or markdown", "json").action(async (opts) => {
86321
- const globalOpts = globalOptions2(program2);
88608
+ const globalOpts = globalOptions3(program2);
86322
88609
  try {
86323
88610
  const { getPlanningForecast: getPlanningForecast2, renderPlanningForecastMarkdown: renderPlanningForecastMarkdown2 } = await Promise.resolve().then(() => (init_capacity_forecasts(), exports_capacity_forecasts));
86324
88611
  const forecast = getPlanningForecast2({
@@ -86352,7 +88639,7 @@ __export(exports_audit_ledger_commands, {
86352
88639
  registerAuditLedgerCommands: () => registerAuditLedgerCommands
86353
88640
  });
86354
88641
  import chalk26 from "chalk";
86355
- function globalOptions3(program2) {
88642
+ function globalOptions4(program2) {
86356
88643
  const command = program2;
86357
88644
  return command.optsWithGlobals?.() ?? program2.opts();
86358
88645
  }
@@ -86369,7 +88656,7 @@ function resolveOptional3(table, value) {
86369
88656
  function registerAuditLedgerCommands(program2) {
86370
88657
  const audit = program2.command("audit-ledger").description("Create and verify tamper-evident local audit ledger checkpoints");
86371
88658
  audit.command("show").description("Build a local audit hash chain from current evidence").option("--project <id>", "Project ID").option("--task <id>", "Task ID").option("--run <id>", "Run ID").option("--entries", "Include per-entry hashes and redacted payloads").option("--format <format>", "json or markdown", "json").action(async (opts) => {
86372
- const globalOpts = globalOptions3(program2);
88659
+ const globalOpts = globalOptions4(program2);
86373
88660
  try {
86374
88661
  const { getLocalAuditLedger: getLocalAuditLedger2, renderLocalAuditLedgerMarkdown: renderLocalAuditLedgerMarkdown2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
86375
88662
  const ledger2 = getLocalAuditLedger2({
@@ -86392,7 +88679,7 @@ function registerAuditLedgerCommands(program2) {
86392
88679
  }
86393
88680
  });
86394
88681
  audit.command("seal <name>").description("Store a local audit ledger checkpoint for later verification").option("--project <id>", "Project ID").option("--task <id>", "Task ID").option("--run <id>", "Run ID").option("--note <text>", "Checkpoint note").action(async (name, opts) => {
86395
- const globalOpts = globalOptions3(program2);
88682
+ const globalOpts = globalOptions4(program2);
86396
88683
  try {
86397
88684
  const { sealLocalAuditLedger: sealLocalAuditLedger2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
86398
88685
  const checkpoint = sealLocalAuditLedger2({
@@ -86413,7 +88700,7 @@ function registerAuditLedgerCommands(program2) {
86413
88700
  }
86414
88701
  });
86415
88702
  audit.command("list").description("List local audit ledger checkpoints").action(async () => {
86416
- const globalOpts = globalOptions3(program2);
88703
+ const globalOpts = globalOptions4(program2);
86417
88704
  try {
86418
88705
  const { listLocalAuditLedgerCheckpoints: listLocalAuditLedgerCheckpoints2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
86419
88706
  const checkpoints = listLocalAuditLedgerCheckpoints2();
@@ -86433,7 +88720,7 @@ function registerAuditLedgerCommands(program2) {
86433
88720
  }
86434
88721
  });
86435
88722
  audit.command("verify <checkpoint>").description("Verify current local evidence against a sealed checkpoint").option("--format <format>", "json or markdown", "json").action(async (checkpoint, opts) => {
86436
- const globalOpts = globalOptions3(program2);
88723
+ const globalOpts = globalOptions4(program2);
86437
88724
  try {
86438
88725
  const { renderLocalAuditLedgerMarkdown: renderLocalAuditLedgerMarkdown2, verifyLocalAuditLedger: verifyLocalAuditLedger2 } = await Promise.resolve().then(() => (init_audit_ledger(), exports_audit_ledger));
86439
88726
  const result = verifyLocalAuditLedger2(checkpoint);
@@ -86465,7 +88752,7 @@ __export(exports_release_compatibility_commands, {
86465
88752
  registerReleaseCompatibilityCommands: () => registerReleaseCompatibilityCommands
86466
88753
  });
86467
88754
  import chalk27 from "chalk";
86468
- function globalOptions4(program2) {
88755
+ function globalOptions5(program2) {
86469
88756
  const command = program2;
86470
88757
  return command.optsWithGlobals?.() ?? program2.opts();
86471
88758
  }
@@ -86477,7 +88764,7 @@ function parseLevels(value) {
86477
88764
  function registerReleaseCompatibilityCommands(program2) {
86478
88765
  const releaseCompat = program2.command("release-compat").description("Check local release compatibility, migrations, exports, and Bun install guidance");
86479
88766
  releaseCompat.command("check").description("Build a local release compatibility report").option("--root <path>", "Package root", process.cwd()).option("--levels <csv>", "Comma-separated migration levels to simulate").option("--format <format>", "json or markdown", "json").action(async (opts) => {
86480
- const globalOpts = globalOptions4(program2);
88767
+ const globalOpts = globalOptions5(program2);
86481
88768
  try {
86482
88769
  const { createReleaseCompatibilityReport: createReleaseCompatibilityReport2, renderReleaseCompatibilityMarkdown: renderReleaseCompatibilityMarkdown2 } = await Promise.resolve().then(() => (init_release_compatibility(), exports_release_compatibility));
86483
88770
  const report = createReleaseCompatibilityReport2({
@@ -86513,7 +88800,7 @@ var exports_usage_ledger_commands = {};
86513
88800
  __export(exports_usage_ledger_commands, {
86514
88801
  registerUsageLedgerCommands: () => registerUsageLedgerCommands
86515
88802
  });
86516
- function globalOptions5(program2) {
88803
+ function globalOptions6(program2) {
86517
88804
  const command = program2;
86518
88805
  return command.optsWithGlobals?.() ?? program2.opts();
86519
88806
  }
@@ -86539,7 +88826,7 @@ function registerUsageLedgerCommands(program2) {
86539
88826
  const usage = program2.command("usage").description("Report local task, run, command, cost, duration, storage, and quota usage");
86540
88827
  usage.command("report").description("Build an aggregate local usage ledger").option("--project <id>", "Filter by project").option("--agent <name>", "Filter by agent").option("--since <iso>", "Only include records created or started at or after this timestamp").option("--until <iso>", "Only include records created or started at or before this timestamp").option("--max-tasks <n>", "Simulate a task quota").option("--max-projects <n>", "Simulate a project quota").option("--max-runs <n>", "Simulate a run quota").option("--max-commands <n>", "Simulate a command quota").option("--max-tokens <n>", "Simulate a token quota").option("--max-cost-usd <n>", "Simulate a USD cost quota").option("--max-storage-bytes <n>", "Simulate an evidence storage quota").option("--format <format>", "json or markdown", "json").option("-j, --json", "Output as JSON").action(async (opts) => {
86541
88828
  try {
86542
- const globalOpts = globalOptions5(program2);
88829
+ const globalOpts = globalOptions6(program2);
86543
88830
  const { createLocalUsageLedger: createLocalUsageLedger2, renderLocalUsageLedgerMarkdown: renderLocalUsageLedgerMarkdown2 } = await Promise.resolve().then(() => (init_usage_ledger(), exports_usage_ledger));
86544
88831
  const report = createLocalUsageLedger2({
86545
88832
  project_id: resolveProjectInput(opts.project || globalOpts.project),
@@ -86581,7 +88868,7 @@ __export(exports_local_backup_commands, {
86581
88868
  });
86582
88869
  import chalk28 from "chalk";
86583
88870
  import { resolve as resolve25 } from "path";
86584
- function globalOptions6(program2) {
88871
+ function globalOptions7(program2) {
86585
88872
  const command = program2;
86586
88873
  return command.optsWithGlobals?.() ?? program2.opts();
86587
88874
  }
@@ -86597,7 +88884,7 @@ function registerLocalBackupCommands(program2) {
86597
88884
  const backup = program2.command("backup").description("Create, verify, restore, and inspect local backup bundles");
86598
88885
  backup.command("create").description("Create a local backup bundle with a manifest and checksums").option("-o, --output <path>", "Write backup JSON to a file").option("--project-id <id>", "Project id to scope the backup. Defaults to auto-detected project when available.").option("-j, --json", "Output as JSON").action(async (opts) => {
86599
88886
  try {
86600
- const globalOpts = globalOptions6(program2);
88887
+ const globalOpts = globalOptions7(program2);
86601
88888
  const { createLocalBackup: createLocalBackup2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
86602
88889
  const projectId = opts.projectId ?? autoProject(globalOpts);
86603
88890
  const backupBundle = createLocalBackup2({
@@ -86619,7 +88906,7 @@ function registerLocalBackupCommands(program2) {
86619
88906
  });
86620
88907
  backup.command("verify <file>").description("Verify a local backup bundle checksum, manifest, bridge schema, and current SQLite integrity").option("-j, --json", "Output as JSON").action(async (file, opts) => {
86621
88908
  try {
86622
- const globalOpts = globalOptions6(program2);
88909
+ const globalOpts = globalOptions7(program2);
86623
88910
  const { readLocalBackupFile: readLocalBackupFile2, verifyLocalBackup: verifyLocalBackup2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
86624
88911
  const verification2 = verifyLocalBackup2(readLocalBackupFile2(file));
86625
88912
  if (opts.json || globalOpts.json) {
@@ -86639,7 +88926,7 @@ function registerLocalBackupCommands(program2) {
86639
88926
  });
86640
88927
  backup.command("restore <file>").description("Dry-run or apply a local backup restore. Dry-run is the default.").option("--apply", "Apply the restore. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge existing local tasks while preserving divergent fields").option("-j, --json", "Output as JSON").action(async (file, opts) => {
86641
88928
  try {
86642
- const globalOpts = globalOptions6(program2);
88929
+ const globalOpts = globalOptions7(program2);
86643
88930
  const { readLocalBackupFile: readLocalBackupFile2, restoreLocalBackup: restoreLocalBackup2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
86644
88931
  const result = restoreLocalBackup2(readLocalBackupFile2(file), {
86645
88932
  apply: Boolean(opts.apply),
@@ -86667,7 +88954,7 @@ function registerLocalBackupCommands(program2) {
86667
88954
  });
86668
88955
  backup.command("integrity").description("Check local SQLite, bridge, count, and orphan-row integrity").option("--project-id <id>", "Optional project id to scope bridge counts").option("-j, --json", "Output as JSON").action(async (opts) => {
86669
88956
  try {
86670
- const globalOpts = globalOptions6(program2);
88957
+ const globalOpts = globalOptions7(program2);
86671
88958
  const { checkLocalIntegrity: checkLocalIntegrity2 } = await Promise.resolve().then(() => (init_local_backups(), exports_local_backups));
86672
88959
  const report = checkLocalIntegrity2({
86673
88960
  project_id: opts.projectId ?? autoProject(globalOpts)
@@ -87186,7 +89473,7 @@ var init_hybrid = __esm(() => {
87186
89473
  });
87187
89474
 
87188
89475
  // src/storage/s3-artifacts.ts
87189
- import { createHash as createHash22, createHmac as createHmac2 } from "crypto";
89476
+ import { createHash as createHash23, createHmac as createHmac2 } from "crypto";
87190
89477
  function createTodosS3ArtifactStore(options) {
87191
89478
  const requestFetch = options.fetch ?? fetch;
87192
89479
  const now4 = options.now ?? (() => new Date);
@@ -87358,7 +89645,7 @@ function toAmzDate(date) {
87358
89645
  return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
87359
89646
  }
87360
89647
  function sha256Hex(value) {
87361
- return createHash22("sha256").update(value).digest("hex");
89648
+ return createHash23("sha256").update(value).digest("hex");
87362
89649
  }
87363
89650
  function hmac(key2, value) {
87364
89651
  return createHmac2("sha256", key2).update(value).digest();
@@ -87966,7 +90253,7 @@ __export(exports_storage_commands, {
87966
90253
  registerStorageCommands: () => registerStorageCommands
87967
90254
  });
87968
90255
  import chalk29 from "chalk";
87969
- function globalOptions7(program2) {
90256
+ function globalOptions8(program2) {
87970
90257
  const command = program2;
87971
90258
  return command.optsWithGlobals?.() ?? program2.opts();
87972
90259
  }
@@ -88102,7 +90389,7 @@ function registerStorageCommands(program2) {
88102
90389
  const storage = program2.command("storage").description("Inspect explicit native local and remote storage configuration");
88103
90390
  storage.command("status").description("Show redacted native storage configuration without opening network connections").option("-j, --json", "Output as JSON").action(async (opts) => {
88104
90391
  try {
88105
- const globalOpts = globalOptions7(program2);
90392
+ const globalOpts = globalOptions8(program2);
88106
90393
  const { getNativeStorageStatus: getNativeStorageStatus2 } = await Promise.resolve().then(() => (init_native_storage_status(), exports_native_storage_status));
88107
90394
  const nativeStatus = getNativeStorageStatus2();
88108
90395
  const remoteAuthority = getTodosRemoteAuthorityConfigStatus();
@@ -88160,7 +90447,7 @@ function registerStorageCommands(program2) {
88160
90447
  });
88161
90448
  storage.command("sync-plan").description("Preview native storage sync work without opening network connections").option("--schema-sql", "Include Postgres schema SQL in the dry-run output").option("-j, --json", "Output as JSON").action(async (opts) => {
88162
90449
  try {
88163
- const globalOpts = globalOptions7(program2);
90450
+ const globalOpts = globalOptions8(program2);
88164
90451
  const { getNativeStorageSyncPlan: getNativeStorageSyncPlan2 } = await Promise.resolve().then(() => (init_native_storage_status(), exports_native_storage_status));
88165
90452
  const plan = getNativeStorageSyncPlan2(process.env, {
88166
90453
  includeSchemaSql: Boolean(opts.schemaSql)
@@ -88177,7 +90464,7 @@ function registerStorageCommands(program2) {
88177
90464
  storage.command("shadow-status").description("Report dual-write shadow divergence: local vs cloud row counts and last mirror lag (opens a read-only DB connection)").option("-j, --json", "Output as JSON").action(async (opts) => {
88178
90465
  let cloud = null;
88179
90466
  try {
88180
- const globalOpts = globalOptions7(program2);
90467
+ const globalOpts = globalOptions8(program2);
88181
90468
  const asJson = Boolean(opts.json || globalOpts.json);
88182
90469
  const {
88183
90470
  createTodosCloudQueryClientFromEnv: createTodosCloudQueryClientFromEnv2,
@@ -88217,7 +90504,7 @@ function registerStorageCommands(program2) {
88217
90504
  });
88218
90505
  storage.command("shadow-drain").description("Drain the durable dual-write shadow outbox to cloud Postgres (one-way, write-only)").option("-j, --json", "Output as JSON").option("--timeout <ms>", "Max drain time in milliseconds", "30000").action(async (opts) => {
88219
90506
  try {
88220
- const globalOpts = globalOptions7(program2);
90507
+ const globalOpts = globalOptions8(program2);
88221
90508
  const asJson = Boolean(opts.json || globalOpts.json);
88222
90509
  const {
88223
90510
  isTodosShadowEnabled: isTodosShadowEnabled2,
@@ -88259,7 +90546,7 @@ function registerStorageCommands(program2) {
88259
90546
  const artifacts = storage.command("artifacts").description("Preview or apply native S3 sync for locally stored run artifacts");
88260
90547
  artifacts.command("upload").description("Upload locally stored run artifact bytes to configured S3. Dry-run by default.").option("--run-id <id>", "Limit to one run id").option("--task-id <id>", "Limit to one task id").option("--limit <n>", "Maximum artifacts to scan").option("--include-already-synced", "Include artifacts that already have a remote reference").option("--apply", "Perform S3 uploads. Defaults to dry-run.").option("-j, --json", "Output as JSON").action(async (opts) => {
88261
90548
  try {
88262
- const globalOpts = globalOptions7(program2);
90549
+ const globalOpts = globalOptions8(program2);
88263
90550
  const { planRunArtifactsS3Sync: planRunArtifactsS3Sync2, uploadRunArtifactsToS3: uploadRunArtifactsToS32 } = await Promise.resolve().then(() => (init_storage(), exports_storage));
88264
90551
  const filter = artifactFilter(opts);
88265
90552
  if (!opts.apply) {
@@ -88283,7 +90570,7 @@ function registerStorageCommands(program2) {
88283
90570
  });
88284
90571
  artifacts.command("download").description("Restore locally stored run artifact bytes from configured S3. Dry-run by default.").option("--run-id <id>", "Limit to one run id").option("--task-id <id>", "Limit to one task id").option("--limit <n>", "Maximum artifacts to scan").option("--force", "Download even when local stored content already verifies").option("--apply", "Perform S3 downloads. Defaults to dry-run.").option("-j, --json", "Output as JSON").action(async (opts) => {
88285
90572
  try {
88286
- const globalOpts = globalOptions7(program2);
90573
+ const globalOpts = globalOptions8(program2);
88287
90574
  const { downloadRunArtifactsFromS3: downloadRunArtifactsFromS32, planRunArtifactsS3Sync: planRunArtifactsS3Sync2 } = await Promise.resolve().then(() => (init_storage(), exports_storage));
88288
90575
  const filter = artifactFilter(opts);
88289
90576
  if (!opts.apply) {
@@ -88550,7 +90837,7 @@ __export(exports_scale_hardening_commands, {
88550
90837
  registerScaleHardeningCommands: () => registerScaleHardeningCommands
88551
90838
  });
88552
90839
  import chalk30 from "chalk";
88553
- function globalOptions8(program2) {
90840
+ function globalOptions9(program2) {
88554
90841
  const command = program2;
88555
90842
  return command.optsWithGlobals?.() ?? program2.opts();
88556
90843
  }
@@ -88567,7 +90854,7 @@ function registerScaleHardeningCommands(program2) {
88567
90854
  const scale = program2.command("scale").description("Benchmark local performance, archive readiness, compaction, and SQLite integrity");
88568
90855
  scale.command("report").description("Build a local scale hardening report without network access").option("--older-than-days <days>", "Archive-readiness window for terminal tasks", "30").option("--format <format>", "json or markdown", "markdown").option("-j, --json", "Output as JSON").action(async (opts) => {
88569
90856
  try {
88570
- const globalOpts = globalOptions8(program2);
90857
+ const globalOpts = globalOptions9(program2);
88571
90858
  const { createScalePerformanceReport: createScalePerformanceReport2, renderScalePerformanceReportMarkdown: renderScalePerformanceReportMarkdown2 } = await Promise.resolve().then(() => (init_scale_hardening(), exports_scale_hardening));
88572
90859
  const report = createScalePerformanceReport2({
88573
90860
  older_than_days: parsePositiveInteger2(opts.olderThanDays, 30)
@@ -88586,7 +90873,7 @@ function registerScaleHardeningCommands(program2) {
88586
90873
  });
88587
90874
  scale.command("compact").description("Preview or apply local SQLite optimization and VACUUM compaction").option("--apply", "Run PRAGMA optimize and VACUUM; dry-run by default").option("--format <format>", "json or markdown", "json").option("-j, --json", "Output as JSON").action(async (opts) => {
88588
90875
  try {
88589
- const globalOpts = globalOptions8(program2);
90876
+ const globalOpts = globalOptions9(program2);
88590
90877
  const { compactScaleStorage: compactScaleStorage2 } = await Promise.resolve().then(() => (init_scale_hardening(), exports_scale_hardening));
88591
90878
  const result = compactScaleStorage2({ apply: Boolean(opts.apply) });
88592
90879
  const format = opts.json || globalOpts.json ? "json" : opts.format || "json";
@@ -88617,7 +90904,7 @@ __export(exports_pr_group_commands, {
88617
90904
  registerPrGroupCommands: () => registerPrGroupCommands
88618
90905
  });
88619
90906
  import chalk31 from "chalk";
88620
- function globalOptions9(program2) {
90907
+ function globalOptions10(program2) {
88621
90908
  const command = program2;
88622
90909
  return command.optsWithGlobals?.() ?? program2.opts();
88623
90910
  }
@@ -88639,7 +90926,7 @@ function registerPrGroupCommands(program2) {
88639
90926
  try {
88640
90927
  const remote = getTodosCloudClient();
88641
90928
  const view = remote ? await cloudGetPrGroup(remote, groupId) : await createLocalPrGroupLedger(getDatabase()).get(groupId);
88642
- if (opts.json || globalOptions9(program2)["json"]) {
90929
+ if (opts.json || globalOptions10(program2)["json"]) {
88643
90930
  output(view, true);
88644
90931
  return;
88645
90932
  }
@@ -88657,7 +90944,7 @@ function registerPrGroupCommands(program2) {
88657
90944
  };
88658
90945
  const remote = getTodosCloudClient();
88659
90946
  const history = remote ? await cloudPrGroupEvents(remote, groupId, options) : await createLocalPrGroupLedger(getDatabase()).events(groupId, options);
88660
- if (opts.json || globalOptions9(program2)["json"]) {
90947
+ if (opts.json || globalOptions10(program2)["json"]) {
88661
90948
  output(history, true);
88662
90949
  return;
88663
90950
  }
@@ -88940,7 +91227,7 @@ var exports_help_commands = {};
88940
91227
  __export(exports_help_commands, {
88941
91228
  registerHelpCommands: () => registerHelpCommands
88942
91229
  });
88943
- function globalOptions10(program2) {
91230
+ function globalOptions11(program2) {
88944
91231
  const command = program2;
88945
91232
  return command.optsWithGlobals?.() ?? program2.opts();
88946
91233
  }
@@ -88949,19 +91236,19 @@ function parseShell(value) {
88949
91236
  return value;
88950
91237
  throw new Error(`Unsupported shell: ${value}. Expected one of: ${COMPLETION_SHELLS.join(", ")}`);
88951
91238
  }
88952
- function registerHelpCommands(program2, route = "local") {
91239
+ function registerHelpCommands(program2, route = "local", remoteCapabilities = new Set) {
88953
91240
  program2.command("completions").alias("completion").description("Generate shell completions for bash, zsh, or fish").argument("<shell>", "Shell to generate: bash, zsh, or fish").action((shell) => {
88954
91241
  try {
88955
- console.log(generateCompletionScript(program2, parseShell(shell), (command) => isTodosCliCommandVisibleForRoute(command, route)));
91242
+ console.log(generateCompletionScript(program2, parseShell(shell), (command) => isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities)));
88956
91243
  } catch (error2) {
88957
91244
  handleError(error2);
88958
91245
  }
88959
91246
  });
88960
91247
  program2.command("manual").description("Print the complete local CLI manual").option("--format <format>", "markdown or json", "markdown").option("-j, --json", "Output as JSON").action((opts) => {
88961
91248
  try {
88962
- const globalOpts = globalOptions10(program2);
91249
+ const globalOpts = globalOptions11(program2);
88963
91250
  const manual = createCliManual(program2, {
88964
- isCommandVisible: (command) => isTodosCliCommandVisibleForRoute(command, route),
91251
+ isCommandVisible: (command) => isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities),
88965
91252
  localOnly: route === "local"
88966
91253
  });
88967
91254
  const format = opts.json || globalOpts.json ? "json" : opts.format || "markdown";
@@ -88987,6 +91274,7 @@ var init_help_commands = __esm(() => {
88987
91274
  init_esm();
88988
91275
  init_package_version();
88989
91276
  init_stage_a();
91277
+ init_cloud_router();
88990
91278
  var program2 = new Command;
88991
91279
  function fallbackJsonRequested() {
88992
91280
  return program2.opts().json === true || process.argv.includes("--json");
@@ -89049,12 +91337,25 @@ try {
89049
91337
  console.error(error2 instanceof Error ? error2.message : String(error2));
89050
91338
  process.exit(1);
89051
91339
  }
91340
+ var remoteCommandCapabilities = new Set;
91341
+ var metadataRequested = authority.route === "remote-diagnostic";
91342
+ if (authority.route !== "local" && metadataRequested) {
91343
+ try {
91344
+ const client = getTodosCloudClient();
91345
+ if (client) {
91346
+ remoteCommandCapabilities = await getTodosRemoteCommandCapabilities(client);
91347
+ }
91348
+ } catch {
91349
+ remoteCommandCapabilities = new Set;
91350
+ }
91351
+ }
89052
91352
  var [
89053
91353
  { handleError: handleError2 },
89054
91354
  { registerTaskCommands: registerTaskCommands2 },
89055
91355
  { registerPlanTemplateCommands: registerPlanTemplateCommands2 },
89056
91356
  { registerProjectCommands: registerProjectCommands2 },
89057
91357
  { registerAgentCommands: registerAgentCommands2 },
91358
+ { registerAiCommands: registerAiCommands2 },
89058
91359
  { registerConfigServeCommands: registerConfigServeCommands2 },
89059
91360
  { registerQueryCommands: registerQueryCommands2 },
89060
91361
  { registerMcpHooksCommands: registerMcpHooksCommands2 },
@@ -89087,6 +91388,7 @@ var [
89087
91388
  Promise.resolve().then(() => (init_plan_template_commands(), exports_plan_template_commands)),
89088
91389
  Promise.resolve().then(() => (init_project_commands(), exports_project_commands)),
89089
91390
  Promise.resolve().then(() => (init_agent_commands(), exports_agent_commands)),
91391
+ Promise.resolve().then(() => (init_ai_commands(), exports_ai_commands)),
89090
91392
  Promise.resolve().then(() => (init_config_serve_commands(), exports_config_serve_commands)),
89091
91393
  Promise.resolve().then(() => (init_query_commands(), exports_query_commands)),
89092
91394
  Promise.resolve().then(() => (init_mcp_hooks_commands(), exports_mcp_hooks_commands)),
@@ -89118,6 +91420,7 @@ registerTaskCommands2(program2);
89118
91420
  registerPlanTemplateCommands2(program2);
89119
91421
  registerProjectCommands2(program2);
89120
91422
  registerAgentCommands2(program2);
91423
+ registerAiCommands2(program2);
89121
91424
  registerConfigServeCommands2(program2);
89122
91425
  registerQueryCommands2(program2);
89123
91426
  registerMcpHooksCommands2(program2);
@@ -89144,9 +91447,15 @@ registerStorageCommands2(program2);
89144
91447
  registerScaleHardeningCommands2(program2);
89145
91448
  registerPrGroupCommands2(program2);
89146
91449
  await registerOptionalEventsCommands(program2);
89147
- registerHelpCommands2(program2, authority.route);
89148
- applyTodosCliHelpVisibility(program2, authority.route);
91450
+ registerHelpCommands2(program2, authority.route, remoteCommandCapabilities);
91451
+ applyTodosCliHelpVisibility(program2, authority.route, remoteCommandCapabilities);
89149
91452
  try {
91453
+ if (metadataRequested) {
91454
+ const unavailableCommand = getUnavailableTodosCliRemoteMetadataCommand(authority.route, remoteCommandCapabilities, process.argv.slice(2));
91455
+ if (unavailableCommand) {
91456
+ throw new Error(`REMOTE_COMMAND_UNAVAILABLE: configured Todos authority does not advertise ${unavailableCommand}; help is unavailable for this command`);
91457
+ }
91458
+ }
89150
91459
  await program2.parseAsync();
89151
91460
  } catch (err) {
89152
91461
  handleError2(err);