@hasna/todos 0.15.35 → 0.15.46

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 (92) hide show
  1. package/dist/cli/cloud-router.d.ts +52 -18
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/agent-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  6. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  7. package/dist/cli/commands/task-subtree-transfer-commands.d.ts +3 -0
  8. package/dist/cli/commands/task-subtree-transfer-commands.d.ts.map +1 -0
  9. package/dist/cli/index.js +3430 -827
  10. package/dist/cli/stage-a.d.ts.map +1 -1
  11. package/dist/contracts.js +66 -58
  12. package/dist/db/plan-comments.d.ts +14 -0
  13. package/dist/db/plan-comments.d.ts.map +1 -0
  14. package/dist/db/schema.d.ts.map +1 -1
  15. package/dist/db/task-crud.d.ts.map +1 -1
  16. package/dist/db/task-graph.d.ts.map +1 -1
  17. package/dist/db/task-lifecycle.d.ts.map +1 -1
  18. package/dist/db/task-lists.d.ts.map +1 -1
  19. package/dist/db/task-relations.d.ts.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1897 -334
  23. package/dist/lib/comment-cursor.d.ts +19 -0
  24. package/dist/lib/comment-cursor.d.ts.map +1 -1
  25. package/dist/lib/import-export-bridge.d.ts.map +1 -1
  26. package/dist/lib/instant-compare.d.ts +35 -0
  27. package/dist/lib/instant-compare.d.ts.map +1 -0
  28. package/dist/mcp/index.js +2403 -501
  29. package/dist/mcp.js +8 -4
  30. package/dist/pr-groups/postgres.d.ts.map +1 -1
  31. package/dist/project-registration/postgres.d.ts.map +1 -1
  32. package/dist/project-registration.js +290 -81
  33. package/dist/registry.d.ts +1 -1
  34. package/dist/registry.d.ts.map +1 -1
  35. package/dist/registry.js +73 -58
  36. package/dist/release-provenance.json +5 -5
  37. package/dist/sdk/index.d.ts +1 -1
  38. package/dist/sdk/index.d.ts.map +1 -1
  39. package/dist/sdk/index.js +35 -0
  40. package/dist/sdk/v1.generated.d.ts +141 -0
  41. package/dist/sdk/v1.generated.d.ts.map +1 -1
  42. package/dist/server/cloud.d.ts +12 -2
  43. package/dist/server/cloud.d.ts.map +1 -1
  44. package/dist/server/index.js +5161 -8425
  45. package/dist/server/openapi.d.ts +2714 -1937
  46. package/dist/server/openapi.d.ts.map +1 -1
  47. package/dist/server/v1.d.ts +2 -1
  48. package/dist/server/v1.d.ts.map +1 -1
  49. package/dist/storage/config.d.ts +12 -30
  50. package/dist/storage/config.d.ts.map +1 -1
  51. package/dist/storage/index.d.ts +2 -2
  52. package/dist/storage/index.d.ts.map +1 -1
  53. package/dist/storage/interfaces.d.ts +13 -2
  54. package/dist/storage/interfaces.d.ts.map +1 -1
  55. package/dist/storage/local-sqlite.d.ts.map +1 -1
  56. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  57. package/dist/storage/timestamp-backfill.d.ts +62 -0
  58. package/dist/storage/timestamp-backfill.d.ts.map +1 -0
  59. package/dist/storage.d.ts +2 -2
  60. package/dist/storage.d.ts.map +1 -1
  61. package/dist/storage.js +268 -74
  62. package/dist/task-manifest/postgres.d.ts.map +1 -1
  63. package/dist/task-manifest.js +15 -6
  64. package/dist/task-subtree-transfer/authority.d.ts +27 -0
  65. package/dist/task-subtree-transfer/authority.d.ts.map +1 -0
  66. package/dist/task-subtree-transfer/backend.d.ts +9 -0
  67. package/dist/task-subtree-transfer/backend.d.ts.map +1 -0
  68. package/dist/task-subtree-transfer/canonical.d.ts +14 -0
  69. package/dist/task-subtree-transfer/canonical.d.ts.map +1 -0
  70. package/dist/task-subtree-transfer/http.d.ts +16 -0
  71. package/dist/task-subtree-transfer/http.d.ts.map +1 -0
  72. package/dist/task-subtree-transfer/index.d.ts +6 -0
  73. package/dist/task-subtree-transfer/index.d.ts.map +1 -0
  74. package/dist/task-subtree-transfer/planner.d.ts +24 -0
  75. package/dist/task-subtree-transfer/planner.d.ts.map +1 -0
  76. package/dist/task-subtree-transfer/postgres.d.ts +19 -0
  77. package/dist/task-subtree-transfer/postgres.d.ts.map +1 -0
  78. package/dist/task-subtree-transfer/schema-sql.d.ts +5 -0
  79. package/dist/task-subtree-transfer/schema-sql.d.ts.map +1 -0
  80. package/dist/task-subtree-transfer/sqlite.d.ts +16 -0
  81. package/dist/task-subtree-transfer/sqlite.d.ts.map +1 -0
  82. package/dist/task-subtree-transfer/types.d.ts +148 -0
  83. package/dist/task-subtree-transfer/types.d.ts.map +1 -0
  84. package/dist/task-subtree-transfer.d.ts +2 -0
  85. package/dist/task-subtree-transfer.d.ts.map +1 -0
  86. package/dist/task-subtree-transfer.js +1878 -0
  87. package/dist/testing.d.ts +41 -7
  88. package/dist/testing.d.ts.map +1 -1
  89. package/dist/testing.js +44 -1
  90. package/dist/types/index.d.ts +24 -0
  91. package/dist/types/index.d.ts.map +1 -1
  92. package/package.json +8 -4
package/dist/mcp/index.js CHANGED
@@ -2888,6 +2888,20 @@ function ensureSchema(db) {
2888
2888
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
2889
2889
  ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
2890
2890
  ensureColumn("task_comments", "progress_pct", "INTEGER");
2891
+ ensureTable("plan_comments", `
2892
+ CREATE TABLE plan_comments (
2893
+ id TEXT PRIMARY KEY,
2894
+ plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
2895
+ agent_id TEXT,
2896
+ session_id TEXT,
2897
+ content TEXT NOT NULL,
2898
+ type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note')),
2899
+ progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100)),
2900
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2901
+ machine_id TEXT,
2902
+ synced_at TEXT
2903
+ )`);
2904
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plan_comments_plan ON plan_comments(plan_id)");
2891
2905
  ensureColumn("projects", "machine_id", "TEXT");
2892
2906
  ensureColumn("projects", "synced_at", "TEXT");
2893
2907
  ensureColumn("tasks", "machine_id", "TEXT");
@@ -3884,6 +3898,7 @@ var init_machines = __esm(() => {
3884
3898
  "task_lists",
3885
3899
  "plans",
3886
3900
  "task_comments",
3901
+ "plan_comments",
3887
3902
  "sessions",
3888
3903
  "task_history",
3889
3904
  "webhooks",
@@ -4112,20 +4127,16 @@ var init_sync_utils = __esm(() => {
4112
4127
  // src/storage/config.ts
4113
4128
  var exports_config = {};
4114
4129
  __export(exports_config, {
4115
- parseStorageMode: () => parseStorageMode,
4116
4130
  parseStorageBackend: () => parseStorageBackend,
4117
4131
  loadTodosStorageConfig: () => loadTodosStorageConfig,
4118
4132
  loadStorageConfig: () => loadStorageConfig,
4119
4133
  isTodosShadowEnabled: () => isTodosShadowEnabled,
4120
- isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
4121
4134
  isTodosPostgresBackend: () => isTodosPostgresBackend,
4122
4135
  getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
4123
- getTodosStorageMode: () => getTodosStorageMode,
4124
4136
  getTodosStorageEnvName: () => getTodosStorageEnvName,
4125
4137
  getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
4126
4138
  getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
4127
4139
  getTodosStorageBackend: () => getTodosStorageBackend,
4128
- getStorageMode: () => getStorageMode,
4129
4140
  getStorageDatabaseUrl: () => getStorageDatabaseUrl,
4130
4141
  getStorageDatabaseEnv: () => getStorageDatabaseEnv,
4131
4142
  getCanonicalTodosRdsConfig: () => getCanonicalTodosRdsConfig,
@@ -4135,7 +4146,6 @@ __export(exports_config, {
4135
4146
  TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
4136
4147
  TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
4137
4148
  STORAGE_TABLES: () => STORAGE_TABLES,
4138
- REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
4139
4149
  CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
4140
4150
  CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
4141
4151
  CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
@@ -4190,9 +4200,6 @@ function loadStorageConfig(env = process.env) {
4190
4200
  function isTodosPostgresBackend(config) {
4191
4201
  return config.mode === "postgres";
4192
4202
  }
4193
- function isTodosRemoteStorageEnabled(config) {
4194
- return isTodosPostgresBackend(config);
4195
- }
4196
4203
  function assertTodosRemoteStorageConfig(config) {
4197
4204
  if (!isTodosPostgresBackend(config))
4198
4205
  return;
@@ -4208,32 +4215,15 @@ function parseStorageBackend(value) {
4208
4215
  return "sqlite";
4209
4216
  if (normalized === "postgres" || normalized === "postgresql")
4210
4217
  return "postgres";
4211
- if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
4212
- throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} selects the backend. Deployment modes no longer exist: ` + `delete the storage-mode variable and set ${TODOS_STORAGE_ENV.databaseUrl} to select the ` + `postgresql backend, or leave it unset for sqlite.`);
4213
- }
4214
4218
  throw new Error(`Storage backend must be sqlite or postgres`);
4215
4219
  }
4216
- function parseStorageMode(value) {
4217
- return parseStorageBackend(value);
4218
- }
4219
4220
  function getTodosStorageBackend(env = process.env) {
4220
- for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
4221
- if (Object.hasOwn(env, key) && env[key] !== undefined) {
4222
- throw new Error(`${key} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `Set ${TODOS_STORAGE_ENV.databaseUrl} to select the postgresql backend, ` + `or leave it unset for sqlite.`);
4223
- }
4224
- }
4225
4221
  if (!getTodosStorageDatabaseUrl(env))
4226
4222
  return "sqlite";
4227
4223
  if (isTodosShadowEnabled(env))
4228
4224
  return "sqlite";
4229
4225
  return "postgres";
4230
4226
  }
4231
- function getTodosStorageMode(env = process.env) {
4232
- return getTodosStorageBackend(env);
4233
- }
4234
- function getStorageMode(env = process.env) {
4235
- return getTodosStorageBackend(env);
4236
- }
4237
4227
  function isTodosShadowEnabled(env = process.env) {
4238
4228
  return parseBoolean(readStorageEnv(env, "shadow").value, false);
4239
4229
  }
@@ -4300,7 +4290,7 @@ function parsePositiveInteger(value, fallback) {
4300
4290
  }
4301
4291
  return parsed;
4302
4292
  }
4303
- var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, REMOVED_STORAGE_MODE_ENV_KEYS, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
4293
+ var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
4304
4294
  var init_config = __esm(() => {
4305
4295
  TODOS_STORAGE_TABLES = [
4306
4296
  "todos_sync_records",
@@ -4339,12 +4329,6 @@ var init_config = __esm(() => {
4339
4329
  syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
4340
4330
  syncDryRun: "TODOS_SYNC_DRY_RUN"
4341
4331
  };
4342
- REMOVED_STORAGE_MODE_ENV_KEYS = [
4343
- "HASNA_TODOS_STORAGE_MODE",
4344
- "HASNA_TODOS_MODE",
4345
- "TODOS_STORAGE_MODE",
4346
- "TODOS_MODE"
4347
- ];
4348
4332
  });
4349
4333
 
4350
4334
  // src/storage/shadow-outbox-schema.ts
@@ -11747,22 +11731,31 @@ function updateTaskList(id, input, db) {
11747
11731
  throw new TaskListNotFoundError(id);
11748
11732
  const sets = ["updated_at = ?"];
11749
11733
  const params = [now()];
11750
- if (input.slug !== undefined) {
11751
- const slug = slugify(input.slug);
11752
- if (!slug)
11753
- throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
11754
- const duplicate = existing.project_id ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(existing.project_id, slug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(slug, id);
11734
+ const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? existing.project_id : input.project_id;
11735
+ const scopeChanged = projectId !== existing.project_id;
11736
+ const effectiveSlug = input.slug !== undefined ? slugify(input.slug) : existing.slug;
11737
+ if (input.slug !== undefined && !effectiveSlug) {
11738
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
11739
+ }
11740
+ if (scopeChanged || input.slug !== undefined && effectiveSlug !== existing.slug) {
11741
+ const duplicate = projectId ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(projectId, effectiveSlug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(effectiveSlug, id);
11755
11742
  if (duplicate) {
11756
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
11743
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
11757
11744
  }
11758
- if (slug !== existing.slug) {
11745
+ if (scopeChanged || effectiveSlug !== existing.slug) {
11759
11746
  releaseCanonicalSlugClaims("task_list", id, d);
11760
- if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(existing.project_id), slug, id, d)) {
11761
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
11747
+ if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(projectId), effectiveSlug, id, d)) {
11748
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
11762
11749
  }
11763
11750
  }
11751
+ }
11752
+ if (input.slug !== undefined) {
11764
11753
  sets.push("slug = ?");
11765
- params.push(slug);
11754
+ params.push(effectiveSlug);
11755
+ }
11756
+ if (projectId !== existing.project_id) {
11757
+ sets.push("project_id = ?");
11758
+ params.push(projectId);
11766
11759
  }
11767
11760
  if (input.name !== undefined) {
11768
11761
  sets.push("name = ?");
@@ -13296,7 +13289,7 @@ function getTaskGraph(taskId, direction = "both", db) {
13296
13289
  const deps = getTaskDependencies(t.id, d);
13297
13290
  const hasUnfinishedDeps = deps.some((dep) => {
13298
13291
  const depTask = getTask(dep.depends_on, d);
13299
- return depTask && depTask.status !== "completed";
13292
+ return depTask && isBlockingDependencyStatus(depTask.status);
13300
13293
  });
13301
13294
  return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
13302
13295
  }
@@ -13556,7 +13549,7 @@ function getBlockingDeps(id, db) {
13556
13549
  const blocking = [];
13557
13550
  for (const dep of deps) {
13558
13551
  const task = getTask(dep.depends_on, d);
13559
- if (task && task.status !== "completed")
13552
+ if (task && isBlockingDependencyStatus(task.status))
13560
13553
  blocking.push(task);
13561
13554
  }
13562
13555
  return blocking;
@@ -13880,7 +13873,7 @@ function getNextTask(agentId, filters, db) {
13880
13873
  conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
13881
13874
  params.push(...filters.tags);
13882
13875
  }
13883
- conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
13876
+ conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status NOT IN ('completed', 'cancelled'))");
13884
13877
  const where = conditions.join(" AND ");
13885
13878
  let recentProjectIds = [];
13886
13879
  const assignedAliasParams = [];
@@ -13924,7 +13917,7 @@ function getActiveWork(filters, db) {
13924
13917
  }
13925
13918
  function getTasksChangedSince(since, filters, db) {
13926
13919
  const d = db || getDatabase();
13927
- const conditions = ["updated_at > ?"];
13920
+ const conditions = ["(julianday(updated_at) IS NULL OR julianday(updated_at) > julianday(?))"];
13928
13921
  const params = [since];
13929
13922
  if (filters?.project_id) {
13930
13923
  conditions.push("project_id = ?");
@@ -13959,8 +13952,8 @@ function failTask(id, agentId, reason, options, db) {
13959
13952
  const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
13960
13953
  const timestamp2 = now();
13961
13954
  const failTx = d.transaction(() => {
13962
- const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
13963
- WHERE id = ? AND version = ?`, [safeReason, JSON.stringify(safeMeta), timestamp2, id, task.version]);
13955
+ const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, completed_at = COALESCE(completed_at, ?), metadata = ?, version = version + 1, updated_at = ?
13956
+ WHERE id = ? AND version = ?`, [safeReason, timestamp2, JSON.stringify(safeMeta), timestamp2, id, task.version]);
13964
13957
  if (res.changes === 0) {
13965
13958
  const current = getTask(id, d);
13966
13959
  throw new VersionConflictError(id, task.version, current?.version ?? -1);
@@ -13972,6 +13965,7 @@ function failTask(id, agentId, reason, options, db) {
13972
13965
  status: "failed",
13973
13966
  locked_by: null,
13974
13967
  locked_at: null,
13968
+ completed_at: task.completed_at ?? timestamp2,
13975
13969
  reason: safeReason,
13976
13970
  metadata: safeMeta,
13977
13971
  version: task.version + 1,
@@ -14620,13 +14614,20 @@ function updateTaskStored(id, input, db) {
14620
14614
  if (isTerminalStatus(input.status)) {
14621
14615
  sets.push("locked_by = NULL");
14622
14616
  sets.push("locked_at = NULL");
14623
- }
14624
- if (input.status === "completed") {
14625
- sets.push("completed_at = ?");
14626
- params.push(completionTimestamp);
14617
+ if (input.status === "completed") {
14618
+ sets.push("completed_at = ?");
14619
+ params.push(completionTimestamp);
14620
+ } else {
14621
+ sets.push("completed_at = COALESCE(completed_at, ?)");
14622
+ params.push(completionTimestamp);
14623
+ }
14627
14624
  } else if (task.status === "completed" && input.completed_at === undefined) {
14628
14625
  sets.push("completed_at = NULL");
14629
14626
  }
14627
+ if (input.status === "in_progress") {
14628
+ sets.push("started_at = COALESCE(started_at, ?)");
14629
+ params.push(timestamp2);
14630
+ }
14630
14631
  }
14631
14632
  if (input.priority !== undefined) {
14632
14633
  sets.push("priority = ?");
@@ -14764,7 +14765,6 @@ function updateTaskStored(id, input, db) {
14764
14765
  if (input.approved_by !== undefined)
14765
14766
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
14766
14767
  const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
14767
- const completedNow = input.status === "completed";
14768
14768
  const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
14769
14769
  const updatedTask = {
14770
14770
  ...task,
@@ -14775,7 +14775,8 @@ function updateTaskStored(id, input, db) {
14775
14775
  updated_at: timestamp2,
14776
14776
  locked_by: terminalNow ? null : task.locked_by,
14777
14777
  locked_at: terminalNow ? null : task.locked_at,
14778
- completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
14778
+ completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : terminalNow ? task.completed_at ?? completionTimestamp : reopened ? null : task.completed_at,
14779
+ started_at: input.status === "in_progress" ? task.started_at ?? timestamp2 : task.started_at,
14779
14780
  sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
14780
14781
  actual_minutes: input.actual_minutes ?? task.actual_minutes,
14781
14782
  confidence: input.confidence !== undefined ? input.confidence : task.confidence,
@@ -14836,6 +14837,9 @@ function deleteTask(id, db) {
14836
14837
  version: row.version
14837
14838
  }, d);
14838
14839
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
14840
+ if (result.changes > 0) {
14841
+ d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
14842
+ }
14839
14843
  return result.changes > 0;
14840
14844
  }
14841
14845
  var init_task_crud = __esm(() => {
@@ -15100,8 +15104,10 @@ function bulkDeleteTasks(taskIds, force = false, db) {
15100
15104
  continue;
15101
15105
  }
15102
15106
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
15103
- if (result.changes > 0)
15107
+ if (result.changes > 0) {
15108
+ d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
15104
15109
  deleted++;
15110
+ }
15105
15111
  } catch (e) {
15106
15112
  failed.push({ id, error: e instanceof Error ? e.message : String(e) });
15107
15113
  }
@@ -18861,6 +18867,60 @@ var init_assignee_context = __esm(() => {
18861
18867
  init_assignee_validation();
18862
18868
  });
18863
18869
 
18870
+ // src/lib/instant-compare.ts
18871
+ function isLeapYear(year) {
18872
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
18873
+ }
18874
+ function sqliteJulianDay(value) {
18875
+ const m = SQLITE_STAMP.exec(value);
18876
+ if (!m)
18877
+ return null;
18878
+ const year = Number(m[1]);
18879
+ const month = Number(m[2]);
18880
+ const day = Number(m[3]);
18881
+ const hour = m[4] === undefined ? 0 : Number(m[4]);
18882
+ const minute = m[5] === undefined ? 0 : Number(m[5]);
18883
+ const second = m[6] === undefined ? 0 : Number(m[6]);
18884
+ const frac = m[7];
18885
+ const sign = m[8];
18886
+ const offsetHour = m[9];
18887
+ const offsetMinute = m[10];
18888
+ if (month < 1 || month > 12)
18889
+ return null;
18890
+ if (hour > 23 || minute > 59 || second > 59)
18891
+ return null;
18892
+ const maxDay = (DAYS_IN_MONTH[month - 1] ?? 0) + (month === 2 && isLeapYear(year) ? 1 : 0);
18893
+ if (day < 1 || day > maxDay)
18894
+ return null;
18895
+ let offsetMinutes = 0;
18896
+ if (sign !== undefined) {
18897
+ const oh = Number(offsetHour ?? "0");
18898
+ const om = Number(offsetMinute ?? "0");
18899
+ if (oh > 23 || om > 59)
18900
+ return null;
18901
+ offsetMinutes = oh * 60 + om;
18902
+ if (sign === "-")
18903
+ offsetMinutes = -offsetMinutes;
18904
+ }
18905
+ const micros = frac === undefined ? 0 : Number((frac + "000000").slice(0, 6));
18906
+ const ms = Date.UTC(year, month - 1, day, hour, minute, second);
18907
+ return (ms + micros / 1000) / 86400000 + 2440587.5 - offsetMinutes / 1440;
18908
+ }
18909
+ function changedSinceStampNewer(stamp, since) {
18910
+ const stampJd = sqliteJulianDay(stamp);
18911
+ if (stampJd === null)
18912
+ return true;
18913
+ const sinceJd = sqliteJulianDay(since);
18914
+ if (sinceJd === null)
18915
+ return false;
18916
+ return stampJd > sinceJd;
18917
+ }
18918
+ var SQLITE_STAMP, DAYS_IN_MONTH;
18919
+ var init_instant_compare = __esm(() => {
18920
+ SQLITE_STAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(?:Z|([+-])(\d{2}):(\d{2}))?)?$/;
18921
+ DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
18922
+ });
18923
+
18864
18924
  // src/lib/plan-project-link-contract.ts
18865
18925
  import { createHash as createHash3 } from "crypto";
18866
18926
  function canonicalPlanProjectLinkJson(value) {
@@ -21187,20 +21247,25 @@ var init_page_validation = __esm(() => {
21187
21247
  import { resolveStorageClient } from "@hasna/contracts/client/storage";
21188
21248
  import { randomUUID as randomUUID3 } from "crypto";
21189
21249
  import { resolve as resolvePath } from "path";
21190
- function assertNoLegacyStorageMode(env = process.env) {
21191
- for (const key of LEGACY_STORAGE_MODE_KEYS) {
21192
- if (Object.hasOwn(env, key) && env[key] !== undefined) {
21193
- throw new Error(`REMOTE_STORAGE_MODE_REMOVED: ${key} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the on-box SQLite store, or the HTTP API selected by ` + `HASNA_TODOS_API_URL + HASNA_TODOS_API_KEY. ` + `On the server, set HASNA_TODOS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
21194
- }
21195
- }
21250
+ function emitTodosLocalFallbackNotice(env) {
21251
+ if (todosLocalFallbackNoticeEmitted)
21252
+ return;
21253
+ todosLocalFallbackNoticeEmitted = true;
21254
+ const notice = {
21255
+ event: "todos-local-fallback",
21256
+ transport: "sqlite",
21257
+ source: "default",
21258
+ apiUrlPresent: Boolean(env.HASNA_TODOS_API_URL?.trim()),
21259
+ apiKeyPresent: Boolean(env.HASNA_TODOS_API_KEY?.trim()),
21260
+ notice: "No hosted API config (HASNA_TODOS_API_URL + HASNA_TODOS_API_KEY) is present; " + "using local SQLite. Hosted todos are NOT visible in this output."
21261
+ };
21262
+ console.error(JSON.stringify(notice));
21196
21263
  }
21197
- function resolveTodosCliStorageMode(env = process.env) {
21198
- assertNoLegacyStorageMode(env);
21264
+ function resolveTodosCliTransport(env = process.env) {
21199
21265
  const urlValue = env.HASNA_TODOS_API_URL?.trim();
21200
21266
  const keyValue = env.HASNA_TODOS_API_KEY?.trim();
21201
21267
  if (urlValue && keyValue) {
21202
21268
  return {
21203
- mode: "http",
21204
21269
  transport: "http",
21205
21270
  selected: true,
21206
21271
  source: "HASNA_TODOS_API_URL+HASNA_TODOS_API_KEY"
@@ -21212,15 +21277,15 @@ function resolveTodosCliStorageMode(env = process.env) {
21212
21277
  if (keyValue) {
21213
21278
  throw new Error("REMOTE_API_URL_MISSING: remote Todos storage requires HASNA_TODOS_API_URL; local SQLite fallback is disabled");
21214
21279
  }
21280
+ emitTodosLocalFallbackNotice(env);
21215
21281
  return {
21216
- mode: "sqlite",
21217
21282
  transport: "sqlite",
21218
21283
  selected: false,
21219
21284
  source: "default"
21220
21285
  };
21221
21286
  }
21222
21287
  function requestedTransport(env) {
21223
- return resolveTodosCliStorageMode(env).transport;
21288
+ return resolveTodosCliTransport(env).transport;
21224
21289
  }
21225
21290
  function normalizeRemoteAuthorityUrl(value) {
21226
21291
  const trimmed = value?.trim();
@@ -21254,13 +21319,13 @@ function normalizeRemoteAuthorityUrl(value) {
21254
21319
  function getTodosRemoteAuthorityConfigStatus(env = process.env) {
21255
21320
  let resolution;
21256
21321
  try {
21257
- resolution = resolveTodosCliStorageMode(env);
21322
+ resolution = resolveTodosCliTransport(env);
21258
21323
  } catch (error) {
21259
21324
  const issue = error instanceof Error ? error.message : String(error);
21260
21325
  return {
21261
21326
  selected: true,
21262
21327
  ok: false,
21263
- mode: "invalid",
21328
+ transport: "invalid",
21264
21329
  api_url_configured: Boolean(env.HASNA_TODOS_API_URL?.trim()),
21265
21330
  api_key_configured: Boolean(env.HASNA_TODOS_API_KEY?.trim()),
21266
21331
  v1_base_url: null,
@@ -21268,12 +21333,12 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
21268
21333
  local_fallback: false
21269
21334
  };
21270
21335
  }
21271
- const { mode, selected } = resolution;
21336
+ const { transport, selected } = resolution;
21272
21337
  if (!selected) {
21273
21338
  return {
21274
21339
  selected: false,
21275
21340
  ok: true,
21276
- mode,
21341
+ transport,
21277
21342
  api_url_configured: false,
21278
21343
  api_key_configured: false,
21279
21344
  v1_base_url: null,
@@ -21298,7 +21363,7 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
21298
21363
  return {
21299
21364
  selected: true,
21300
21365
  ok: issues.length === 0,
21301
- mode,
21366
+ transport,
21302
21367
  api_url_configured: apiUrl !== null,
21303
21368
  api_key_configured: apiKeyConfigured,
21304
21369
  v1_base_url: apiUrl ? `${apiUrl}/v1` : null,
@@ -21342,7 +21407,8 @@ function classifyRemoteRequestError(baseUrl, route, error) {
21342
21407
  }
21343
21408
  const message = error instanceof Error ? error.message : String(error);
21344
21409
  if (error instanceof Error && error.name === "AbortError" || /abort|timed?\s*out/i.test(message)) {
21345
- throw new Error(`REMOTE_API_TIMEOUT: configured Todos authority ${baseUrl} timed out for ${route}; local SQLite fallback is disabled`, { cause: error });
21410
+ const unboundedNote = route === "/tasks" ? "; this is likely an UNBOUNDED task read timing out (bounded task reads from this authority return in ~1 s) \u2014 retry with a bounded/count-specific shape such as todos list --limit; local SQLite fallback is disabled" : "; local SQLite fallback is disabled";
21411
+ throw new Error(`REMOTE_API_TIMEOUT: configured Todos authority ${baseUrl} timed out for ${route}${unboundedNote}`, { cause: error });
21346
21412
  }
21347
21413
  if (status === undefined) {
21348
21414
  throw new Error(`REMOTE_API_UNREACHABLE: configured Todos authority ${baseUrl} could not be reached for ${route}; ` + "local SQLite fallback is disabled", { cause: error });
@@ -21429,11 +21495,10 @@ function getTodosCloudClient(env = process.env, requestTimeoutMs = REMOTE_REQUES
21429
21495
  fetchImpl: (input, init) => globalThis.fetch(input, { ...init, redirect: "manual" }),
21430
21496
  timeoutMs: requestTimeoutMs
21431
21497
  });
21432
- if (resolved.transport === "cloud-http")
21433
- return protectRemoteClient(resolved.client, requestTimeoutMs);
21434
21498
  const transportName = resolved.transport;
21435
- if (transportName === "http")
21499
+ if (transportName === "cloud-http" || transportName === "http") {
21436
21500
  return protectRemoteClient(resolved.client, requestTimeoutMs);
21501
+ }
21437
21502
  return null;
21438
21503
  }
21439
21504
  function unwrapTask(raw) {
@@ -21905,10 +21970,11 @@ async function cloudResolveTaskListRef(client, ref, projectId) {
21905
21970
  return input.toLowerCase();
21906
21971
  return (await cloudResolveTaskList(client, ref, projectId)).id;
21907
21972
  }
21908
- var UUID_RE, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache, LEGACY_STORAGE_MODE_KEYS, REMOTE_REQUEST_TIMEOUT_MS = 1e4, PRIORITY_RANK, listTagsCapabilityCache;
21973
+ var UUID_RE, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache, todosLocalFallbackNoticeEmitted = false, REMOTE_REQUEST_TIMEOUT_MS = 1e4, PRIORITY_RANK, listTagsCapabilityCache;
21909
21974
  var init_cloud_router = __esm(() => {
21910
21975
  init_types();
21911
21976
  init_redaction();
21977
+ init_instant_compare();
21912
21978
  init_plan_project_link_contract();
21913
21979
  init_http_client();
21914
21980
  init_adoption_validation();
@@ -21919,12 +21985,6 @@ var init_cloud_router = __esm(() => {
21919
21985
  taskCreatorCapabilityCache = new Map;
21920
21986
  gitRefCapabilityCache = new Map;
21921
21987
  remoteCommandCapabilityCache = new Map;
21922
- LEGACY_STORAGE_MODE_KEYS = [
21923
- "HASNA_TODOS_STORAGE_MODE",
21924
- "HASNA_TODOS_MODE",
21925
- "TODOS_STORAGE_MODE",
21926
- "TODOS_MODE"
21927
- ];
21928
21988
  PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
21929
21989
  listTagsCapabilityCache = new Map;
21930
21990
  });
@@ -34719,7 +34779,7 @@ function scoreHealth(scope, scopeId, db) {
34719
34779
  const blockers = d.query(`SELECT dep.id, dep.short_id, dep.title, dep.status
34720
34780
  FROM task_dependencies td
34721
34781
  JOIN tasks dep ON dep.id = td.depends_on
34722
- WHERE td.task_id = ? AND dep.status != 'completed'`).all(task.id);
34782
+ WHERE td.task_id = ? AND dep.status NOT IN ('completed', 'cancelled')`).all(task.id);
34723
34783
  return { id: task.id, short_id: task.short_id, title: redactEvidenceText(task.title), blockers };
34724
34784
  }).filter((entry) => entry.blockers.length > 0);
34725
34785
  const overdue = tasks.filter((task) => activeTaskIds.has(task.id) && Boolean(task.due_at && task.due_at < generatedAt)).map((task) => ({ id: task.id, short_id: task.short_id, title: redactEvidenceText(task.title), due_at: task.due_at }));
@@ -36042,7 +36102,7 @@ var package_default;
36042
36102
  var init_package = __esm(() => {
36043
36103
  package_default = {
36044
36104
  name: "@hasna/todos",
36045
- version: "0.15.35",
36105
+ version: "0.15.46",
36046
36106
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
36047
36107
  type: "module",
36048
36108
  main: "dist/index.js",
@@ -36088,6 +36148,10 @@ var init_package = __esm(() => {
36088
36148
  "./task-manifest": {
36089
36149
  types: "./dist/task-manifest.d.ts",
36090
36150
  import: "./dist/task-manifest.js"
36151
+ },
36152
+ "./task-subtree-transfer": {
36153
+ types: "./dist/task-subtree-transfer.d.ts",
36154
+ import: "./dist/task-subtree-transfer.js"
36091
36155
  }
36092
36156
  },
36093
36157
  workspaces: [
@@ -36101,8 +36165,8 @@ var init_package = __esm(() => {
36101
36165
  "README.md"
36102
36166
  ],
36103
36167
  scripts: {
36104
- build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
36105
- "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
36168
+ build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
36169
+ "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
36106
36170
  migrate: "bun run src/server/index.ts migrate",
36107
36171
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
36108
36172
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -36156,7 +36220,7 @@ var init_package = __esm(() => {
36156
36220
  author: "Andrei Hasna <andrei@hasna.com>",
36157
36221
  license: "Apache-2.0",
36158
36222
  dependencies: {
36159
- "@hasna/contracts": "0.5.2",
36223
+ "@hasna/contracts": "0.13.4",
36160
36224
  "@hasna/events": "^0.1.11",
36161
36225
  "@modelcontextprotocol/sdk": "^1.12.1",
36162
36226
  chalk: "^5.4.1",
@@ -45433,6 +45497,75 @@ var init_cloud_client = __esm(() => {
45433
45497
  init_config();
45434
45498
  });
45435
45499
 
45500
+ // src/db/plan-comments.ts
45501
+ function addPlanComment(input, db) {
45502
+ const d = db || getDatabase();
45503
+ if (!getPlan(input.plan_id, d)) {
45504
+ throw new PlanNotFoundError(input.plan_id);
45505
+ }
45506
+ const id = uuid();
45507
+ const timestamp4 = now();
45508
+ d.run(`INSERT INTO plan_comments (id, plan_id, agent_id, session_id, content, type, progress_pct, created_at)
45509
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
45510
+ id,
45511
+ input.plan_id,
45512
+ input.agent_id || null,
45513
+ input.session_id || null,
45514
+ sanitizePreWriteText(input.content, "comment.content"),
45515
+ input.type || "comment",
45516
+ input.progress_pct ?? null,
45517
+ timestamp4
45518
+ ]);
45519
+ return getPlanComment(id, d);
45520
+ }
45521
+ function getPlanComment(id, db) {
45522
+ const d = db || getDatabase();
45523
+ return d.query("SELECT * FROM plan_comments WHERE id = ?").get(id);
45524
+ }
45525
+ function listPlanComments(planId, db) {
45526
+ const d = db || getDatabase();
45527
+ return d.query("SELECT * FROM plan_comments WHERE plan_id = ? ORDER BY created_at, rowid").all(planId);
45528
+ }
45529
+ var init_plan_comments = __esm(() => {
45530
+ init_types();
45531
+ init_database();
45532
+ init_plans();
45533
+ init_prewrite_secrets();
45534
+ });
45535
+
45536
+ // src/lib/comment-cursor.ts
45537
+ function encodeCommentCursor(comment) {
45538
+ return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
45539
+ }
45540
+ function decodeCommentCursor(value) {
45541
+ if (value.length > MAX_COMMENT_CURSOR_LENGTH)
45542
+ throw new Error("invalid comment cursor");
45543
+ let parsed;
45544
+ try {
45545
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
45546
+ } catch {
45547
+ throw new Error("invalid comment cursor");
45548
+ }
45549
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
45550
+ throw new Error("invalid comment cursor");
45551
+ const cursor = parsed;
45552
+ if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
45553
+ throw new Error("invalid comment cursor");
45554
+ }
45555
+ return { created_at: cursor["created_at"], id: cursor["id"] };
45556
+ }
45557
+ function isStrictlyOlder(comment, before) {
45558
+ return comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id;
45559
+ }
45560
+ function compareCommentKeyset(left, right) {
45561
+ if (isStrictlyOlder(left, right))
45562
+ return -1;
45563
+ if (isStrictlyOlder(right, left))
45564
+ return 1;
45565
+ return 0;
45566
+ }
45567
+ var MAX_COMMENT_CURSOR_LENGTH = 1024;
45568
+
45436
45569
  // src/db/plan-project-links.ts
45437
45570
  function getPlanProjectLinkReceipt(receiptId, db) {
45438
45571
  const d = db || getDatabase();
@@ -46163,7 +46296,7 @@ function matchesExtraFilters(task2, filter) {
46163
46296
  }
46164
46297
  if (filter.tags?.length) {
46165
46298
  const taskTags = new Set(task2.tags ?? []);
46166
- if (!filter.tags.every((tag) => taskTags.has(tag)))
46299
+ if (!filter.tags.some((tag) => taskTags.has(tag)))
46167
46300
  return false;
46168
46301
  }
46169
46302
  if (filter.include_subtasks !== true && filter.parent_id === undefined && task2.parent_id)
@@ -46239,7 +46372,22 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
46239
46372
  list: (projectId) => listPlans(projectId, database()),
46240
46373
  update: (id, input) => updatePlan(id, input, database()),
46241
46374
  completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
46242
- delete: (id) => deletePlan(id, database())
46375
+ delete: (id) => deletePlan(id, database()),
46376
+ addComment: (input) => addPlanComment(input, database()),
46377
+ getComments: (planId) => listPlanComments(planId, database()),
46378
+ getCommentsPage: (planId, options2) => {
46379
+ if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
46380
+ throw new Error("Plan comment limit must be an integer between 1 and 1001");
46381
+ }
46382
+ let comments = listPlanComments(planId, database());
46383
+ comments = comments.sort(compareCommentKeyset);
46384
+ if (options2?.before) {
46385
+ const before = options2.before;
46386
+ comments = comments.filter((comment) => isStrictlyOlder(comment, before));
46387
+ }
46388
+ const limit = options2?.limit ?? 100;
46389
+ return comments.slice(-limit);
46390
+ }
46243
46391
  },
46244
46392
  planProjectLinks: {
46245
46393
  apply: (input) => applyPlanProjectLinkSqlite(input, database()),
@@ -46280,10 +46428,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
46280
46428
  throw new Error("Comment limit must be an integer between 1 and 1001");
46281
46429
  }
46282
46430
  let comments = listComments(taskId, database());
46283
- comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
46431
+ comments = comments.sort(compareCommentKeyset);
46284
46432
  if (options2?.before) {
46285
46433
  const before = options2.before;
46286
- comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
46434
+ comments = comments.filter((comment) => isStrictlyOlder(comment, before));
46287
46435
  }
46288
46436
  if (options2?.limit !== undefined)
46289
46437
  comments = comments.slice(-options2.limit);
@@ -46314,6 +46462,7 @@ var init_local_sqlite = __esm(() => {
46314
46462
  init_tasks();
46315
46463
  init_projects();
46316
46464
  init_plans();
46465
+ init_plan_comments();
46317
46466
  init_agents();
46318
46467
  init_task_lists();
46319
46468
  init_templates();
@@ -47226,7 +47375,26 @@ function createPostgresTodosStorageAdapter(options) {
47226
47375
  list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
47227
47376
  update: (id, input) => updatePlan2(id, input, store),
47228
47377
  completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
47229
- delete: (id, context) => store.deletePlan(id, context)
47378
+ delete: (id, context) => store.deletePlan(id, context),
47379
+ addComment: (input, context) => addPlanComment2(input, store, context),
47380
+ getComments: async (planId) => {
47381
+ const pages = [];
47382
+ let before;
47383
+ while (true) {
47384
+ const page = await store.listPlanComments(planId, { limit: 1000, ...before ? { before } : {} });
47385
+ if (page.length === 0)
47386
+ break;
47387
+ pages.unshift(page);
47388
+ if (page.length < 1000)
47389
+ break;
47390
+ const oldest = page[0];
47391
+ before = { created_at: oldest.created_at, id: oldest.id };
47392
+ }
47393
+ return pages.flat().map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
47394
+ },
47395
+ getCommentsPage: async (planId, options2) => {
47396
+ return (await store.listPlanComments(planId, options2)).map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
47397
+ }
47230
47398
  },
47231
47399
  planProjectLinks: {
47232
47400
  apply: (input, context) => store.applyPlanProjectLink(input, context),
@@ -47320,11 +47488,16 @@ class PostgresJsonRecordStore {
47320
47488
  return context?.requestId ?? this.sourceMachineId ?? null;
47321
47489
  }
47322
47490
  async ensureSchema() {
47323
- this.schemaReady ??= (async () => {
47324
- for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
47325
- await this.options.client.query(sql);
47326
- }
47327
- })();
47491
+ if (!this.schemaReady) {
47492
+ this.schemaReady = (async () => {
47493
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
47494
+ await this.options.client.query(sql);
47495
+ }
47496
+ })().catch((error) => {
47497
+ this.schemaReady = null;
47498
+ throw error;
47499
+ });
47500
+ }
47328
47501
  await this.schemaReady;
47329
47502
  }
47330
47503
  async get(type, id) {
@@ -47444,6 +47617,27 @@ class PostgresJsonRecordStore {
47444
47617
  LIMIT $${params.length}`, params);
47445
47618
  return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
47446
47619
  }
47620
+ async listPlanComments(planId, options = {}) {
47621
+ await this.ensureSchema();
47622
+ const limit = options.limit ?? 100;
47623
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
47624
+ throw new Error("Postgres plan comment limit must be an integer between 1 and 1001");
47625
+ }
47626
+ const params = [this.service, planId];
47627
+ let cursorPredicate = "";
47628
+ if (options.before) {
47629
+ params.push(options.before.created_at, options.before.id);
47630
+ cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
47631
+ }
47632
+ params.push(limit);
47633
+ const result = await this.options.client.query(`/* todos:list-plan-comments */ SELECT payload FROM ${this.tableName}
47634
+ WHERE service = $1 AND object_type = 'plan_comments' AND deleted_at IS NULL
47635
+ AND payload->>'plan_id' = $2
47636
+ ${cursorPredicate}
47637
+ ORDER BY payload->>'created_at' DESC, object_id DESC
47638
+ LIMIT $${params.length}`, params);
47639
+ return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
47640
+ }
47447
47641
  async listRecords(type) {
47448
47642
  await this.ensureSchema();
47449
47643
  const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
@@ -48605,6 +48799,15 @@ class PostgresJsonRecordStore {
48605
48799
  WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
48606
48800
  AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
48607
48801
  RETURNING 1
48802
+ ), deleted_plan_comments AS (
48803
+ UPDATE ${this.tableName} r SET
48804
+ deleted_at = $3::timestamptz,
48805
+ updated_at = $3::timestamptz,
48806
+ source_machine_id = COALESCE($4, r.source_machine_id),
48807
+ version = COALESCE(r.version, 0) + 1
48808
+ WHERE r.service = $1 AND r.object_type = 'plan_comments' AND r.deleted_at IS NULL
48809
+ AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
48810
+ RETURNING 1
48608
48811
  ), deleted_plan AS (
48609
48812
  UPDATE ${this.tableName} r SET
48610
48813
  deleted_at = $3::timestamptz,
@@ -48780,7 +48983,8 @@ async function updateTask2(id, input, store, context) {
48780
48983
  task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
48781
48984
  parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
48782
48985
  created_by: existing.created_by,
48783
- completed_at: reopened ? null : input.completed_at !== undefined ? input.completed_at : existing.completed_at
48986
+ completed_at: input.status === "completed" ? input.completed_at ?? new Date().toISOString() : input.completed_at !== undefined ? input.completed_at : input.status !== undefined && isTerminalStatus(input.status) ? existing.completed_at ?? new Date().toISOString() : reopened ? null : existing.completed_at,
48987
+ started_at: input.status === "in_progress" ? existing.started_at ?? new Date().toISOString() : existing.started_at
48784
48988
  };
48785
48989
  const storedTask = await store.upsertTaskWithPlanMembershipGuard(task2, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
48786
48990
  operation: "update",
@@ -48825,7 +49029,8 @@ async function failTask2(id, agentId, reason, options, store) {
48825
49029
  status: "failed",
48826
49030
  assigned_to: task2.assigned_to ?? agentId ?? null,
48827
49031
  reason: reason ?? task2.reason,
48828
- retry_after: options?.retry_after ?? task2.retry_after
49032
+ retry_after: options?.retry_after ?? task2.retry_after,
49033
+ completed_at: task2.completed_at ?? new Date().toISOString()
48829
49034
  }, store);
48830
49035
  if (!options?.retry)
48831
49036
  return { task: failed };
@@ -49054,7 +49259,7 @@ async function getActiveWork2(filters, store) {
49054
49259
  }));
49055
49260
  }
49056
49261
  async function getChangedSince(since, filters, store) {
49057
- return (await listTasks3(filters ?? {}, store)).filter((task2) => task2.updated_at > since);
49262
+ return (await listTasks3(filters ?? {}, store)).filter((task2) => changedSinceStampNewer(task2.updated_at ?? "", since));
49058
49263
  }
49059
49264
  async function createProject2(input, store, context) {
49060
49265
  const timestamp4 = new Date().toISOString();
@@ -49270,16 +49475,22 @@ async function createTaskList2(input, store, context) {
49270
49475
  async function updateTaskList2(id, input, store) {
49271
49476
  const list = await requireRecord("task_lists", id, store);
49272
49477
  const patch = definedPatch(input);
49478
+ const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? list.project_id : input.project_id;
49273
49479
  if (input.slug !== undefined) {
49274
49480
  const slug = slugifyRaw(input.slug);
49275
49481
  if (!slug)
49276
49482
  throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
49277
- const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === list.project_id && candidate.slug === slug);
49483
+ patch.slug = slug;
49484
+ }
49485
+ const effectiveSlug = patch.slug ?? list.slug;
49486
+ if (projectId !== list.project_id || effectiveSlug !== list.slug) {
49487
+ const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === projectId && candidate.slug === effectiveSlug);
49278
49488
  if (duplicate) {
49279
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
49489
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
49280
49490
  }
49281
- patch.slug = slug;
49282
49491
  }
49492
+ if (projectId !== list.project_id)
49493
+ patch.project_id = projectId;
49283
49494
  return store.upsert("task_lists", {
49284
49495
  ...list,
49285
49496
  ...patch,
@@ -49372,6 +49583,22 @@ async function addComment2(input, store, context) {
49372
49583
  function redactComment2(comment) {
49373
49584
  return { ...comment, content: redactEvidenceText(comment.content) };
49374
49585
  }
49586
+ async function addPlanComment2(input, store, context) {
49587
+ const comment = {
49588
+ id: randomUUID4(),
49589
+ plan_id: input.plan_id,
49590
+ agent_id: input.agent_id ?? context?.agentId ?? null,
49591
+ session_id: input.session_id ?? context?.sessionId ?? null,
49592
+ content: redactEvidenceText(input.content),
49593
+ type: input.type ?? "comment",
49594
+ progress_pct: input.progress_pct ?? null,
49595
+ created_at: new Date().toISOString()
49596
+ };
49597
+ return store.upsert("plan_comments", comment, context);
49598
+ }
49599
+ function redactPlanComment(comment) {
49600
+ return { ...comment, content: redactEvidenceText(comment.content) };
49601
+ }
49375
49602
  async function exportSnapshot(store) {
49376
49603
  return {
49377
49604
  exportedAt: new Date().toISOString(),
@@ -49591,6 +49818,7 @@ var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_TIEBREAK = "CASE payload->>'prior
49591
49818
  var init_postgres_adapter = __esm(() => {
49592
49819
  init_types();
49593
49820
  init_creator_identity();
49821
+ init_instant_compare();
49594
49822
  init_plan_project_link_contract();
49595
49823
  init_stale_lock_handoff();
49596
49824
  init_postgres_sync();
@@ -50021,10 +50249,19 @@ class PostgresPrGroupLedgerPersistence {
50021
50249
  this.client = client;
50022
50250
  }
50023
50251
  async ensureSchema() {
50024
- this.schemaReady ??= (async () => {
50025
- for (const statement of postgresPrGroupSchemaSql())
50026
- await this.client.query(statement);
50027
- })();
50252
+ if (this.schemaReady === null) {
50253
+ const attempt = (async () => {
50254
+ for (const statement of postgresPrGroupSchemaSql())
50255
+ await this.client.query(statement);
50256
+ })();
50257
+ this.schemaReady = attempt;
50258
+ try {
50259
+ await attempt;
50260
+ } catch (error) {
50261
+ this.schemaReady = null;
50262
+ throw error;
50263
+ }
50264
+ }
50028
50265
  return this.schemaReady;
50029
50266
  }
50030
50267
  async transaction(fn) {
@@ -50442,14 +50679,23 @@ class PostgresTodosProjectRegistrationBackend {
50442
50679
  this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
50443
50680
  }
50444
50681
  async ensureSchema() {
50445
- this.schemaReady ??= (async () => {
50446
- for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
50447
- await this.client.query(statement);
50448
- }
50449
- for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
50450
- await this.client.query(statement);
50682
+ if (this.schemaReady === null) {
50683
+ const attempt = (async () => {
50684
+ for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
50685
+ await this.client.query(statement);
50686
+ }
50687
+ for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
50688
+ await this.client.query(statement);
50689
+ }
50690
+ })();
50691
+ this.schemaReady = attempt;
50692
+ try {
50693
+ await attempt;
50694
+ } catch (error) {
50695
+ this.schemaReady = null;
50696
+ throw error;
50451
50697
  }
50452
- })();
50698
+ }
50453
50699
  await this.schemaReady;
50454
50700
  }
50455
50701
  async transaction(fn) {
@@ -53159,12 +53405,21 @@ class PostgresTodosTaskManifestBackend {
53159
53405
  this.tenantId = options.tenantId ?? "default";
53160
53406
  }
53161
53407
  async ensureSchema() {
53162
- this.schemaReady ??= (async () => {
53163
- for (const sql of postgresTodosSyncSchemaSql(this.tableName))
53164
- await this.client.query(sql);
53165
- for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
53166
- await this.client.query(sql);
53167
- })();
53408
+ if (this.schemaReady === null) {
53409
+ const attempt = (async () => {
53410
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName))
53411
+ await this.client.query(sql);
53412
+ for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
53413
+ await this.client.query(sql);
53414
+ })();
53415
+ this.schemaReady = attempt;
53416
+ try {
53417
+ await attempt;
53418
+ } catch (error) {
53419
+ this.schemaReady = null;
53420
+ throw error;
53421
+ }
53422
+ }
53168
53423
  await this.schemaReady;
53169
53424
  }
53170
53425
  async insertSync(tx, objectType2, objectId, payload, now4) {
@@ -54191,6 +54446,933 @@ var init_task_manifest = __esm(() => {
54191
54446
  init_types5();
54192
54447
  });
54193
54448
 
54449
+ // src/task-subtree-transfer/types.ts
54450
+ var TODOS_TASK_SUBTREE_TRANSFER_ROUTE = "todos.task-subtree-transfer.v1", TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION = 1, TodosTaskSubtreeTransferError;
54451
+ var init_types6 = __esm(() => {
54452
+ TodosTaskSubtreeTransferError = class TodosTaskSubtreeTransferError extends Error {
54453
+ code;
54454
+ details;
54455
+ constructor(code, message, details = {}) {
54456
+ super(message);
54457
+ this.code = code;
54458
+ this.details = details;
54459
+ this.name = "TodosTaskSubtreeTransferError";
54460
+ }
54461
+ };
54462
+ });
54463
+
54464
+ // src/task-subtree-transfer/canonical.ts
54465
+ function taskSubtreeTransferRequestDigest(input) {
54466
+ const { idempotency_key: _idempotencyKey, ...request } = input;
54467
+ return canonicalDigest(request);
54468
+ }
54469
+ function taskSubtreeTransferRollbackRequestDigest(input) {
54470
+ const { idempotency_key: _idempotencyKey, ...request } = input;
54471
+ return canonicalDigest(request);
54472
+ }
54473
+ function deriveTodosTaskSubtreeTransferIdempotencyKey(input) {
54474
+ return `tstk_${canonicalDigest({
54475
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
54476
+ ...input
54477
+ }).slice(0, 48)}`;
54478
+ }
54479
+ var init_canonical2 = __esm(() => {
54480
+ init_canonical();
54481
+ init_types6();
54482
+ });
54483
+
54484
+ // src/task-subtree-transfer/planner.ts
54485
+ function sortedTaskImage(task3) {
54486
+ return {
54487
+ task_id: task3.task_id,
54488
+ project_id: task3.project_id,
54489
+ parent_id: task3.parent_id,
54490
+ plan_id: task3.plan_id,
54491
+ task_list_id: task3.task_list_id,
54492
+ version: task3.version,
54493
+ updated_at: task3.updated_at
54494
+ };
54495
+ }
54496
+ function sourcePopulationDigest(tasks) {
54497
+ return canonicalDigest(tasks.map((task3) => ({
54498
+ ...sortedTaskImage(task3),
54499
+ archived_at: task3.archived_at ?? null
54500
+ })).sort((left, right) => left.task_id.localeCompare(right.task_id)));
54501
+ }
54502
+ function closure(tasks, rootTaskId) {
54503
+ const byId = new Map(tasks.map((task3) => [task3.task_id, task3]));
54504
+ if (!byId.has(rootTaskId)) {
54505
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Root task not found in the exact source project: ${rootTaskId}`, { root_task_id: rootTaskId });
54506
+ }
54507
+ const children = new Map;
54508
+ for (const task3 of tasks) {
54509
+ if (!task3.parent_id)
54510
+ continue;
54511
+ const siblings = children.get(task3.parent_id) ?? [];
54512
+ siblings.push(task3.task_id);
54513
+ children.set(task3.parent_id, siblings);
54514
+ }
54515
+ for (const ids2 of children.values())
54516
+ ids2.sort();
54517
+ const visiting = new Set;
54518
+ const visited = new Set;
54519
+ const result = [];
54520
+ const visit = (id) => {
54521
+ if (visiting.has(id)) {
54522
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE", "Task hierarchy contains a cycle in the requested descendant closure", { task_id: id });
54523
+ }
54524
+ if (visited.has(id))
54525
+ return;
54526
+ const task3 = byId.get(id);
54527
+ if (!task3) {
54528
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE", "Task hierarchy references a parent outside the exact source population", { task_id: id });
54529
+ }
54530
+ visiting.add(id);
54531
+ result.push(task3);
54532
+ for (const child of children.get(id) ?? [])
54533
+ visit(child);
54534
+ visiting.delete(id);
54535
+ visited.add(id);
54536
+ };
54537
+ visit(rootTaskId);
54538
+ return result.sort((left, right) => left.task_id.localeCompare(right.task_id));
54539
+ }
54540
+ function prepareTransfer(snapshot, input) {
54541
+ if (!snapshot.destination_project_found || !snapshot.destination_task_list_found || !snapshot.destination_parent_found) {
54542
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE", "Destination project, task list, or parent does not match the exact requested target");
54543
+ }
54544
+ const moved = closure(snapshot.source_tasks, input.root_task_id);
54545
+ const movedIds = new Set(moved.map((task3) => task3.task_id));
54546
+ if (input.destination_parent_id && movedIds.has(input.destination_parent_id)) {
54547
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE", "Destination parent cannot be inside the transferred subtree", { destination_parent_id: input.destination_parent_id });
54548
+ }
54549
+ const planIds = [...new Set(moved.map((task3) => task3.plan_id).filter((id) => Boolean(id)))].sort();
54550
+ const allPlanTasks = new Map;
54551
+ for (const task3 of snapshot.plan_tasks) {
54552
+ if (!task3.plan_id)
54553
+ continue;
54554
+ const rows = allPlanTasks.get(task3.plan_id) ?? [];
54555
+ rows.push(task3);
54556
+ allPlanTasks.set(task3.plan_id, rows);
54557
+ }
54558
+ const contained = [];
54559
+ const shared = [];
54560
+ const planById = new Map(snapshot.plans.map((plan) => [plan.plan_id, plan]));
54561
+ for (const planId of planIds) {
54562
+ const plan = planById.get(planId);
54563
+ if (!plan || plan.project_id !== input.source_project_id) {
54564
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "A moved task references a plan absent from the exact source project", { plan_id: planId });
54565
+ }
54566
+ const members = allPlanTasks.get(planId) ?? [];
54567
+ if (members.length === 0) {
54568
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "A moved task references a plan with no authoritative task membership", { plan_id: planId });
54569
+ }
54570
+ if (members.every((task3) => movedIds.has(task3.task_id)))
54571
+ contained.push(planId);
54572
+ else
54573
+ shared.push(planId);
54574
+ }
54575
+ const inspection = {
54576
+ source_project_id: input.source_project_id,
54577
+ destination_project_id: input.destination_project_id,
54578
+ destination_task_list_id: input.destination_task_list_id,
54579
+ root_task_id: input.root_task_id,
54580
+ destination_parent_id: input.destination_parent_id,
54581
+ expected_root_parent_id: moved.find((task3) => task3.task_id === input.root_task_id).parent_id,
54582
+ source_population_digest: sourcePopulationDigest(snapshot.source_tasks),
54583
+ expected_tasks: moved.map((task3) => ({ task_id: task3.task_id, version: task3.version })),
54584
+ contained_plan_ids: contained,
54585
+ shared_plan_ids: shared,
54586
+ complete: true
54587
+ };
54588
+ return {
54589
+ inspection,
54590
+ prior_tasks: moved.map(sortedTaskImage),
54591
+ prior_plans: contained.map((id) => planById.get(id)),
54592
+ task_plan_targets: new Map(moved.map((task3) => [task3.task_id, task3.plan_id]))
54593
+ };
54594
+ }
54595
+ function validateApplySnapshot(prepared, request, plans) {
54596
+ if (request.expected_root_parent_id !== prepared.inspection.expected_root_parent_id) {
54597
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Root parent changed since inspection");
54598
+ }
54599
+ if (request.source_population_digest !== prepared.inspection.source_population_digest) {
54600
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_POPULATION_DRIFT", "Complete source-project task population changed since inspection");
54601
+ }
54602
+ const expected = request.expected_tasks.map((task3) => ({ task_id: task3.task_id, version: task3.version })).sort((left, right) => left.task_id.localeCompare(right.task_id));
54603
+ if (JSON.stringify(expected) !== JSON.stringify(prepared.inspection.expected_tasks)) {
54604
+ const actualById = new Map(prepared.inspection.expected_tasks.map((task3) => [task3.task_id, task3.version]));
54605
+ const sameIds = expected.length === prepared.inspection.expected_tasks.length && expected.every((task3) => actualById.has(task3.task_id));
54606
+ throw new TodosTaskSubtreeTransferError(sameIds ? "TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT" : "TODOS_TASK_SUBTREE_TRANSFER_CLOSURE_DRIFT", sameIds ? "At least one exact task version changed since inspection" : "Exact descendant closure changed since inspection");
54607
+ }
54608
+ const requestedSources = request.shared_plan_splits.map((mapping) => mapping.source_plan_id).sort();
54609
+ if (JSON.stringify(requestedSources) !== JSON.stringify(prepared.inspection.shared_plan_ids)) {
54610
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PARTIAL_PLAN", "Shared plans require one exact explicit destination-plan mapping each", {
54611
+ expected_shared_plan_ids: prepared.inspection.shared_plan_ids,
54612
+ received_shared_plan_ids: requestedSources
54613
+ });
54614
+ }
54615
+ const planById = new Map(plans.map((plan) => [plan.plan_id, plan]));
54616
+ const destinationIds = new Set;
54617
+ for (const mapping of request.shared_plan_splits) {
54618
+ if (destinationIds.has(mapping.destination_plan_id)) {
54619
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "Two shared source plans cannot collapse into one destination plan");
54620
+ }
54621
+ destinationIds.add(mapping.destination_plan_id);
54622
+ const destination = planById.get(mapping.destination_plan_id);
54623
+ if (!destination || destination.project_id !== request.destination_project_id || destination.task_list_id !== request.destination_task_list_id) {
54624
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "Shared-plan destination does not match the exact destination project and task list", { destination_plan_id: mapping.destination_plan_id });
54625
+ }
54626
+ for (const [taskId, planId] of prepared.task_plan_targets) {
54627
+ if (planId === mapping.source_plan_id) {
54628
+ prepared.task_plan_targets.set(taskId, mapping.destination_plan_id);
54629
+ }
54630
+ }
54631
+ }
54632
+ }
54633
+ var init_planner = __esm(() => {
54634
+ init_canonical2();
54635
+ init_types6();
54636
+ });
54637
+
54638
+ // src/task-subtree-transfer/schema-sql.ts
54639
+ function postgresTodosTaskSubtreeTransferSchemaSql() {
54640
+ return [
54641
+ `CREATE TABLE IF NOT EXISTS todos_task_subtree_transfer_receipts (
54642
+ receipt_id text PRIMARY KEY,
54643
+ tenant_id text NOT NULL,
54644
+ kind text NOT NULL CHECK(kind IN ('apply', 'rollback')),
54645
+ operation_id text NOT NULL,
54646
+ step_id text NOT NULL,
54647
+ idempotency_key text NOT NULL,
54648
+ request_digest text NOT NULL,
54649
+ precondition_digest text NOT NULL,
54650
+ result_digest text NOT NULL,
54651
+ apply_receipt_id text,
54652
+ request_json jsonb NOT NULL,
54653
+ result_json jsonb NOT NULL,
54654
+ created_at timestamptz NOT NULL,
54655
+ UNIQUE(tenant_id, kind, operation_id, step_id),
54656
+ UNIQUE(tenant_id, kind, idempotency_key)
54657
+ )`,
54658
+ `CREATE OR REPLACE FUNCTION reject_todos_task_subtree_transfer_receipt_mutation()
54659
+ RETURNS trigger LANGUAGE plpgsql AS $$
54660
+ BEGIN
54661
+ RAISE EXCEPTION 'todos task subtree transfer receipts are immutable';
54662
+ END $$`,
54663
+ `DROP TRIGGER IF EXISTS todos_task_subtree_transfer_receipts_immutable_update
54664
+ ON todos_task_subtree_transfer_receipts`,
54665
+ `CREATE TRIGGER todos_task_subtree_transfer_receipts_immutable_update
54666
+ BEFORE UPDATE OR DELETE ON todos_task_subtree_transfer_receipts
54667
+ FOR EACH ROW EXECUTE FUNCTION reject_todos_task_subtree_transfer_receipt_mutation()`
54668
+ ];
54669
+ }
54670
+
54671
+ // src/task-subtree-transfer/postgres.ts
54672
+ function safeIdentifier3(value, fallback) {
54673
+ const candidate = value ?? fallback;
54674
+ if (!/^[a-z_][a-z0-9_]*$/.test(candidate)) {
54675
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "PostgreSQL table name must be a safe identifier");
54676
+ }
54677
+ return candidate;
54678
+ }
54679
+ function timestamp5(value) {
54680
+ return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
54681
+ }
54682
+ function payloadOf(row) {
54683
+ return typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload;
54684
+ }
54685
+ function taskRecord(row) {
54686
+ const payload = payloadOf(row);
54687
+ return {
54688
+ task_id: String(payload.id ?? row.object_id),
54689
+ project_id: payload.project_id == null ? null : String(payload.project_id),
54690
+ parent_id: payload.parent_id == null ? null : String(payload.parent_id),
54691
+ plan_id: payload.plan_id == null ? null : String(payload.plan_id),
54692
+ task_list_id: payload.task_list_id == null ? null : String(payload.task_list_id),
54693
+ version: Number(payload.version ?? row.version ?? 1),
54694
+ updated_at: timestamp5(payload.updated_at ?? row.updated_at),
54695
+ archived_at: payload.archived_at == null ? null : String(payload.archived_at)
54696
+ };
54697
+ }
54698
+ function planRecord(row) {
54699
+ const payload = payloadOf(row);
54700
+ return {
54701
+ plan_id: String(payload.id ?? row.object_id),
54702
+ project_id: payload.project_id == null ? null : String(payload.project_id),
54703
+ task_list_id: payload.task_list_id == null ? null : String(payload.task_list_id),
54704
+ updated_at: timestamp5(payload.updated_at ?? row.updated_at)
54705
+ };
54706
+ }
54707
+
54708
+ class PostgresTodosTaskSubtreeTransferBackend {
54709
+ client;
54710
+ kind = "postgresql";
54711
+ service;
54712
+ tableName;
54713
+ tenantId;
54714
+ schemaReady = null;
54715
+ constructor(client, options = {}) {
54716
+ this.client = client;
54717
+ this.service = options.service ?? "todos";
54718
+ this.tableName = safeIdentifier3(options.tableName, DEFAULT_TODOS_POSTGRES_SYNC_TABLE);
54719
+ this.tenantId = options.tenantId ?? "default";
54720
+ }
54721
+ async ensureSchema() {
54722
+ if (this.schemaReady === null) {
54723
+ const attempt = (async () => {
54724
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName))
54725
+ await this.client.query(sql);
54726
+ for (const sql of postgresTodosTaskSubtreeTransferSchemaSql())
54727
+ await this.client.query(sql);
54728
+ })();
54729
+ this.schemaReady = attempt;
54730
+ try {
54731
+ await attempt;
54732
+ } catch (error) {
54733
+ this.schemaReady = null;
54734
+ throw error;
54735
+ }
54736
+ }
54737
+ await this.schemaReady;
54738
+ }
54739
+ async snapshot(client, input, forUpdate = false) {
54740
+ const lock = forUpdate ? " FOR UPDATE" : "";
54741
+ const source3 = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
54742
+ WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL
54743
+ AND payload->>'project_id' = $2 ORDER BY object_id${lock}`, [this.service, input.source_project_id]);
54744
+ const allTasks = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
54745
+ WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL ORDER BY object_id${lock}`, [this.service]);
54746
+ const plans = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
54747
+ WHERE service = $1 AND object_type = 'plans' AND deleted_at IS NULL ORDER BY object_id${lock}`, [this.service]);
54748
+ const destinationProject = await client.query(`SELECT 1 FROM ${this.tableName}
54749
+ WHERE service = $1 AND object_type = 'projects' AND object_id = $2
54750
+ AND deleted_at IS NULL LIMIT 1${lock}`, [this.service, input.destination_project_id]);
54751
+ const destinationList = await client.query(`SELECT 1 FROM ${this.tableName}
54752
+ WHERE service = $1 AND object_type = 'task_lists' AND object_id = $2
54753
+ AND deleted_at IS NULL AND payload->>'project_id' = $3 LIMIT 1`, [this.service, input.destination_task_list_id, input.destination_project_id]);
54754
+ const destinationParent = input.destination_parent_id === null ? true : (await client.query(`SELECT 1 FROM ${this.tableName}
54755
+ WHERE service = $1 AND object_type = 'tasks' AND object_id = $2
54756
+ AND deleted_at IS NULL AND payload->>'project_id' = $3 LIMIT 1`, [this.service, input.destination_parent_id, input.destination_project_id])).rows.length === 1;
54757
+ return {
54758
+ source_tasks: source3.rows.map(taskRecord),
54759
+ plan_tasks: allTasks.rows.map(taskRecord),
54760
+ plans: plans.rows.map(planRecord),
54761
+ destination_project_found: destinationProject.rows.length === 1,
54762
+ destination_task_list_found: destinationList.rows.length === 1,
54763
+ destination_parent_found: destinationParent
54764
+ };
54765
+ }
54766
+ async inspect(input) {
54767
+ await this.ensureSchema();
54768
+ return prepareTransfer(await this.snapshot(this.client, input), input).inspection;
54769
+ }
54770
+ async insertPayload(client, task3, update, now4) {
54771
+ const current = await client.query(`SELECT payload FROM ${this.tableName}
54772
+ WHERE service = $1 AND object_type = 'tasks' AND object_id = $2
54773
+ AND deleted_at IS NULL FOR UPDATE`, [this.service, task3.task_id]);
54774
+ const row = current.rows[0];
54775
+ if (!row)
54776
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Task not found: ${task3.task_id}`);
54777
+ const payload = payloadOf(row);
54778
+ if (Number(payload.version ?? 1) !== task3.version) {
54779
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Task changed during transfer", { task_id: task3.task_id });
54780
+ }
54781
+ const next = {
54782
+ ...payload,
54783
+ ...update,
54784
+ version: task3.version + 1,
54785
+ updated_at: now4
54786
+ };
54787
+ const result = await client.query(`UPDATE ${this.tableName} SET payload = $1::jsonb, updated_at = $2, version = $3
54788
+ WHERE service = $4 AND object_type = 'tasks' AND object_id = $5
54789
+ AND deleted_at IS NULL AND version = $6
54790
+ RETURNING object_id`, [canonicalJson(next), now4, task3.version + 1, this.service, task3.task_id, task3.version]);
54791
+ if (result.rows.length !== 1) {
54792
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Task CAS failed", { task_id: task3.task_id });
54793
+ }
54794
+ }
54795
+ async apply(input, options) {
54796
+ await this.ensureSchema();
54797
+ if (!this.client.transaction) {
54798
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE", "PostgreSQL subtree transfer requires an authoritative transaction-capable client");
54799
+ }
54800
+ return this.client.transaction(async (tx) => {
54801
+ await tx.query("SELECT pg_advisory_xact_lock(hashtextextended('todos:task-parent-integrity', 0))");
54802
+ const requestDigest = taskSubtreeTransferRequestDigest(input);
54803
+ const existing = await tx.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
54804
+ FROM todos_task_subtree_transfer_receipts
54805
+ WHERE tenant_id = $1 AND kind = 'apply'
54806
+ AND (idempotency_key = $2 OR (operation_id = $3 AND step_id = $4))
54807
+ ORDER BY created_at ASC LIMIT 1`, [this.tenantId, input.idempotency_key, input.operation_id, input.step_id]);
54808
+ if (existing.rows[0]) {
54809
+ const row = existing.rows[0];
54810
+ if (row.request_digest !== requestDigest || row.precondition_digest !== input.precondition_digest || row.idempotency_key !== input.idempotency_key) {
54811
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The operation was already accepted for a different request");
54812
+ }
54813
+ return { ...typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json, duplicate: true };
54814
+ }
54815
+ const snapshot = await this.snapshot(tx, input, true);
54816
+ const prepared = prepareTransfer(snapshot, input);
54817
+ validateApplySnapshot(prepared, input, snapshot.plans);
54818
+ const now4 = options.now?.() ?? new Date().toISOString();
54819
+ const priorImage = {
54820
+ tasks: prepared.prior_tasks,
54821
+ plans: prepared.prior_plans
54822
+ };
54823
+ for (const task3 of prepared.prior_tasks) {
54824
+ await this.insertPayload(tx, task3, {
54825
+ project_id: input.destination_project_id,
54826
+ task_list_id: input.destination_task_list_id,
54827
+ plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
54828
+ parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id
54829
+ }, now4);
54830
+ }
54831
+ if (options.faultInjector && await options.faultInjector("after_task_writes"))
54832
+ throw new Error("Injected task-subtree-transfer fault at after_task_writes");
54833
+ for (const plan of prepared.prior_plans) {
54834
+ const row = await tx.query(`SELECT payload FROM ${this.tableName}
54835
+ WHERE service = $1 AND object_type = 'plans' AND object_id = $2
54836
+ AND deleted_at IS NULL FOR UPDATE`, [this.service, plan.plan_id]);
54837
+ const current = row.rows[0] ? payloadOf(row.rows[0]) : null;
54838
+ if (!current || String(current.project_id ?? "") !== String(plan.project_id ?? "") || (current.task_list_id ?? null) !== plan.task_list_id || timestamp5(current.updated_at ?? row.rows[0]?.updated_at) !== plan.updated_at) {
54839
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Contained plan changed during transfer", { plan_id: plan.plan_id });
54840
+ }
54841
+ const result2 = await tx.query(`UPDATE ${this.tableName} SET payload = jsonb_set(jsonb_set(jsonb_set(payload, '{project_id}', to_jsonb($1::text)), '{task_list_id}', to_jsonb($2::text)), '{updated_at}', to_jsonb($3::text)), updated_at = $3
54842
+ WHERE service = $4 AND object_type = 'plans' AND object_id = $5 AND deleted_at IS NULL
54843
+ RETURNING object_id`, [input.destination_project_id, input.destination_task_list_id, now4, this.service, plan.plan_id]);
54844
+ if (result2.rows.length !== 1) {
54845
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Contained plan update did not affect exactly one row", { plan_id: plan.plan_id });
54846
+ }
54847
+ }
54848
+ if (options.faultInjector && await options.faultInjector("after_plan_writes"))
54849
+ throw new Error("Injected task-subtree-transfer fault at after_plan_writes");
54850
+ const postImage = {
54851
+ tasks: prepared.prior_tasks.map((task3) => ({
54852
+ ...task3,
54853
+ project_id: input.destination_project_id,
54854
+ task_list_id: input.destination_task_list_id,
54855
+ plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
54856
+ parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id,
54857
+ version: task3.version + 1,
54858
+ updated_at: now4
54859
+ })),
54860
+ plans: prepared.prior_plans.map((plan) => ({ ...plan, project_id: input.destination_project_id, task_list_id: input.destination_task_list_id, updated_at: now4 }))
54861
+ };
54862
+ const resultDigest = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, request_digest: requestDigest, prior_image: priorImage, post_image: postImage });
54863
+ const receipt = {
54864
+ receipt_id: deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "apply", input.operation_id, input.step_id, input.idempotency_key, requestDigest),
54865
+ authority: "todos",
54866
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
54867
+ schema_version: 1,
54868
+ kind: "apply",
54869
+ operation_id: input.operation_id,
54870
+ step_id: input.step_id,
54871
+ idempotency_key: input.idempotency_key,
54872
+ request_digest: requestDigest,
54873
+ precondition_digest: input.precondition_digest,
54874
+ result_digest: resultDigest,
54875
+ apply_receipt_id: null,
54876
+ source_project_id: input.source_project_id,
54877
+ destination_project_id: input.destination_project_id,
54878
+ destination_task_list_id: input.destination_task_list_id,
54879
+ root_task_id: input.root_task_id,
54880
+ source_population_digest: input.source_population_digest,
54881
+ prior_image: priorImage,
54882
+ post_image: postImage,
54883
+ shared_plan_splits: input.shared_plan_splits,
54884
+ created_at: now4
54885
+ };
54886
+ const result = { duplicate: false, receipt, moved_task_ids: prepared.prior_tasks.map((task3) => task3.task_id), moved_plan_ids: prepared.prior_plans.map((plan) => plan.plan_id), complete: true };
54887
+ await tx.query(`INSERT INTO todos_task_subtree_transfer_receipts (
54888
+ receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
54889
+ request_digest, precondition_digest, result_digest, apply_receipt_id,
54890
+ request_json, result_json, created_at
54891
+ ) VALUES ($1, $2, 'apply', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10::jsonb, $11)`, [receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, canonicalJson(input), canonicalJson(result), now4]);
54892
+ if (options.faultInjector && await options.faultInjector("after_receipt_write"))
54893
+ throw new Error("Injected task-subtree-transfer fault at after_receipt_write");
54894
+ return result;
54895
+ });
54896
+ }
54897
+ async readExact(receiptId2) {
54898
+ await this.ensureSchema();
54899
+ const row = await this.client.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts WHERE tenant_id = $1 AND receipt_id = $2 LIMIT 1`, [this.tenantId, receiptId2]);
54900
+ if (!row.rows[0])
54901
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Transfer receipt not found: ${receiptId2}`);
54902
+ return { ...typeof row.rows[0].result_json === "string" ? JSON.parse(String(row.rows[0].result_json)) : row.rows[0].result_json, duplicate: false };
54903
+ }
54904
+ async rollback(input, options) {
54905
+ await this.ensureSchema();
54906
+ if (!this.client.transaction)
54907
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE", "PostgreSQL subtree rollback requires a transaction-capable client");
54908
+ return this.client.transaction(async (tx) => {
54909
+ await tx.query("SELECT pg_advisory_xact_lock(hashtextextended('todos:task-parent-integrity', 0))");
54910
+ const requestDigest = taskSubtreeTransferRollbackRequestDigest(input);
54911
+ const existing = await tx.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
54912
+ FROM todos_task_subtree_transfer_receipts
54913
+ WHERE tenant_id = $1 AND kind = 'rollback'
54914
+ AND (idempotency_key = $2 OR (operation_id = $3 AND step_id = $4))
54915
+ ORDER BY created_at ASC LIMIT 1`, [this.tenantId, input.idempotency_key, input.operation_id, input.step_id]);
54916
+ if (existing.rows[0]) {
54917
+ const row = existing.rows[0];
54918
+ if (row.request_digest !== requestDigest || row.precondition_digest !== input.precondition_digest || row.idempotency_key !== input.idempotency_key)
54919
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The rollback operation was already accepted for a different request");
54920
+ return { ...typeof row.result_json === "string" ? JSON.parse(String(row.result_json)) : row.result_json, duplicate: true };
54921
+ }
54922
+ const applyRow = await tx.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts WHERE tenant_id = $1 AND kind = 'apply' AND receipt_id = $2 LIMIT 1`, [this.tenantId, input.receipt_id]);
54923
+ if (!applyRow.rows[0])
54924
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Apply receipt not found: ${input.receipt_id}`);
54925
+ const applied = typeof applyRow.rows[0].result_json === "string" ? JSON.parse(String(applyRow.rows[0].result_json)) : applyRow.rows[0].result_json;
54926
+ if (applied.receipt.operation_id !== input.operation_id) {
54927
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback operation does not match the apply receipt");
54928
+ }
54929
+ const expectedPrecondition = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, direction: "rollback", operation_id: input.operation_id, step_id: input.step_id, apply_receipt_id: input.receipt_id, apply_result_digest: applied.receipt.result_digest });
54930
+ if (input.precondition_digest !== expectedPrecondition)
54931
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "Rollback precondition digest does not match the exact apply receipt", { expected_precondition_digest: expectedPrecondition });
54932
+ const now4 = options.now?.() ?? new Date().toISOString();
54933
+ for (const task3 of applied.receipt.post_image.tasks) {
54934
+ const row = await tx.query(`SELECT payload FROM ${this.tableName} WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL FOR UPDATE`, [this.service, task3.task_id]);
54935
+ if (!row.rows[0])
54936
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Transferred task disappeared", { task_id: task3.task_id });
54937
+ const payload = payloadOf(row.rows[0]);
54938
+ if (Number(payload.version ?? 1) !== task3.version || String(payload.project_id ?? "") !== String(task3.project_id ?? "") || (payload.parent_id ?? null) !== task3.parent_id || (payload.plan_id ?? null) !== task3.plan_id || (payload.task_list_id ?? null) !== task3.task_list_id)
54939
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Transferred task changed after apply", { task_id: task3.task_id });
54940
+ }
54941
+ for (const task3 of applied.receipt.prior_image.tasks) {
54942
+ const row = await tx.query(`SELECT payload FROM ${this.tableName} WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL`, [this.service, task3.task_id]);
54943
+ const payload = row.rows[0] ? payloadOf(row.rows[0]) : null;
54944
+ if (!payload)
54945
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Task disappeared during rollback", { task_id: task3.task_id });
54946
+ const next = { ...payload, project_id: task3.project_id, task_list_id: task3.task_list_id, plan_id: task3.plan_id, parent_id: task3.parent_id, version: task3.version + 2, updated_at: now4 };
54947
+ const result2 = await tx.query(`UPDATE ${this.tableName} SET payload = $1::jsonb, updated_at = $2, version = $3
54948
+ WHERE service = $4 AND object_type = 'tasks' AND object_id = $5 AND deleted_at IS NULL AND version = $6
54949
+ RETURNING object_id`, [canonicalJson(next), now4, task3.version + 2, this.service, task3.task_id, task3.version + 1]);
54950
+ if (result2.rows.length !== 1) {
54951
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Task rollback did not affect exactly one row", { task_id: task3.task_id });
54952
+ }
54953
+ }
54954
+ if (options.faultInjector && await options.faultInjector("after_rollback_task_writes"))
54955
+ throw new Error("Injected task-subtree-transfer fault at after_rollback_task_writes");
54956
+ for (const plan of applied.receipt.prior_image.plans) {
54957
+ const post = applied.receipt.post_image.plans.find((candidate) => candidate.plan_id === plan.plan_id);
54958
+ const result2 = await tx.query(`UPDATE ${this.tableName}
54959
+ SET payload = jsonb_set(
54960
+ jsonb_set(
54961
+ jsonb_set(payload, '{project_id}', to_jsonb($1::text)),
54962
+ '{task_list_id}',
54963
+ COALESCE(to_jsonb($2::text), 'null'::jsonb)
54964
+ ),
54965
+ '{updated_at}',
54966
+ to_jsonb($3::text)
54967
+ ), updated_at = $3
54968
+ WHERE service = $4 AND object_type = 'plans' AND object_id = $5 AND deleted_at IS NULL
54969
+ AND payload->>'project_id' = $6 AND COALESCE(payload->>'task_list_id', '') = COALESCE($7, '') AND payload->>'updated_at' = $8
54970
+ RETURNING object_id`, [plan.project_id, plan.task_list_id, now4, this.service, plan.plan_id, post.project_id, post.task_list_id, post.updated_at]);
54971
+ if (result2.rows.length !== 1) {
54972
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Contained plan rollback did not affect exactly one row", { plan_id: plan.plan_id });
54973
+ }
54974
+ }
54975
+ if (options.faultInjector && await options.faultInjector("after_rollback_plan_writes"))
54976
+ throw new Error("Injected task-subtree-transfer fault at after_rollback_plan_writes");
54977
+ const restored = {
54978
+ tasks: applied.receipt.prior_image.tasks.map((task3) => ({ ...task3, version: task3.version + 2, updated_at: now4 })),
54979
+ plans: applied.receipt.prior_image.plans.map((plan) => ({ ...plan, updated_at: now4 }))
54980
+ };
54981
+ const resultDigest = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, direction: "rollback", apply_receipt_id: input.receipt_id, prior_image: applied.receipt.post_image, post_image: restored });
54982
+ const receipt = { ...applied.receipt, receipt_id: deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "rollback", input.operation_id, input.step_id, input.idempotency_key, input.receipt_id), kind: "rollback", operation_id: input.operation_id, step_id: input.step_id, idempotency_key: input.idempotency_key, request_digest: requestDigest, precondition_digest: input.precondition_digest, result_digest: resultDigest, apply_receipt_id: input.receipt_id, prior_image: applied.receipt.post_image, post_image: restored, created_at: now4 };
54983
+ const result = { duplicate: false, receipt, moved_task_ids: applied.moved_task_ids, moved_plan_ids: applied.moved_plan_ids, complete: true };
54984
+ await tx.query(`INSERT INTO todos_task_subtree_transfer_receipts (
54985
+ receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
54986
+ request_digest, precondition_digest, result_digest, apply_receipt_id,
54987
+ request_json, result_json, created_at
54988
+ ) VALUES ($1, $2, 'rollback', $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)`, [receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, receipt.apply_receipt_id, canonicalJson(input), canonicalJson(result), now4]);
54989
+ if (options.faultInjector && await options.faultInjector("after_rollback_receipt_write"))
54990
+ throw new Error("Injected task-subtree-transfer fault at after_rollback_receipt_write");
54991
+ return result;
54992
+ });
54993
+ }
54994
+ }
54995
+ var init_postgres4 = __esm(() => {
54996
+ init_postgres_sync();
54997
+ init_canonical2();
54998
+ init_planner();
54999
+ init_types6();
55000
+ });
55001
+
55002
+ // src/task-subtree-transfer/sqlite.ts
55003
+ var sqliteTails2;
55004
+ var init_sqlite3 = __esm(() => {
55005
+ init_canonical2();
55006
+ init_planner();
55007
+ init_types6();
55008
+ sqliteTails2 = new WeakMap;
55009
+ });
55010
+
55011
+ // src/task-subtree-transfer/authority.ts
55012
+ function boundedString(value, field, max = 200) {
55013
+ if (typeof value !== "string" || value.length === 0 || value.length > max) {
55014
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field} must be a non-empty bounded string`);
55015
+ }
55016
+ return value;
55017
+ }
55018
+ function uuid3(value, field) {
55019
+ const parsed = boundedString(value, field, 80);
55020
+ if (!UUID.test(parsed)) {
55021
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field} must be a UUID`);
55022
+ }
55023
+ return parsed;
55024
+ }
55025
+ function digest2(value, field) {
55026
+ const parsed = boundedString(value, field, 64);
55027
+ if (!DIGEST.test(parsed)) {
55028
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field} must be a SHA-256 digest`);
55029
+ }
55030
+ return parsed.toLowerCase();
55031
+ }
55032
+ function nullableUuid(value, field) {
55033
+ if (value === null)
55034
+ return null;
55035
+ return uuid3(value, field);
55036
+ }
55037
+ function strictObject(input, fields) {
55038
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
55039
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Request must be a JSON object");
55040
+ }
55041
+ const object = input;
55042
+ const allowed = new Set(fields);
55043
+ for (const key2 of Object.keys(object)) {
55044
+ if (!allowed.has(key2)) {
55045
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `Unknown field: ${key2}`);
55046
+ }
55047
+ }
55048
+ return object;
55049
+ }
55050
+ function parseTodosTaskSubtreeTransferInspect(input) {
55051
+ const value = strictObject(input, INSPECT_FIELDS);
55052
+ const request = {
55053
+ source_project_id: uuid3(value.source_project_id, "source_project_id"),
55054
+ destination_project_id: uuid3(value.destination_project_id, "destination_project_id"),
55055
+ destination_task_list_id: uuid3(value.destination_task_list_id, "destination_task_list_id"),
55056
+ root_task_id: uuid3(value.root_task_id, "root_task_id"),
55057
+ destination_parent_id: nullableUuid(value.destination_parent_id, "destination_parent_id")
55058
+ };
55059
+ if (request.source_project_id === request.destination_project_id) {
55060
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "source_project_id and destination_project_id must identify different Projects");
55061
+ }
55062
+ return request;
55063
+ }
55064
+ function parseTodosTaskSubtreeTransferApply(input) {
55065
+ const value = strictObject(input, APPLY_FIELDS);
55066
+ if (value.version !== 1) {
55067
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "version must be 1");
55068
+ }
55069
+ if (!Array.isArray(value.expected_tasks) || value.expected_tasks.length === 0) {
55070
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "expected_tasks must be a non-empty array");
55071
+ }
55072
+ const expected_tasks = value.expected_tasks.map((item, index) => {
55073
+ const row = strictObject(item, ["task_id", "version"]);
55074
+ const version = row.version;
55075
+ if (!Number.isSafeInteger(version) || Number(version) < 1) {
55076
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `expected_tasks.${index}.version must be a positive integer`);
55077
+ }
55078
+ return { task_id: uuid3(row.task_id, `expected_tasks.${index}.task_id`), version: Number(version) };
55079
+ });
55080
+ const ids2 = new Set;
55081
+ for (const row of expected_tasks) {
55082
+ if (ids2.has(row.task_id)) {
55083
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "expected_tasks must not contain duplicate task IDs");
55084
+ }
55085
+ ids2.add(row.task_id);
55086
+ }
55087
+ if (!Array.isArray(value.shared_plan_splits)) {
55088
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "shared_plan_splits must be an array");
55089
+ }
55090
+ const shared_plan_splits = value.shared_plan_splits.map((item, index) => {
55091
+ const row = strictObject(item, ["source_plan_id", "destination_plan_id"]);
55092
+ return {
55093
+ source_plan_id: uuid3(row.source_plan_id, `shared_plan_splits.${index}.source_plan_id`),
55094
+ destination_plan_id: uuid3(row.destination_plan_id, `shared_plan_splits.${index}.destination_plan_id`)
55095
+ };
55096
+ });
55097
+ return {
55098
+ ...parseTodosTaskSubtreeTransferInspect({
55099
+ source_project_id: value.source_project_id,
55100
+ destination_project_id: value.destination_project_id,
55101
+ destination_task_list_id: value.destination_task_list_id,
55102
+ root_task_id: value.root_task_id,
55103
+ destination_parent_id: value.destination_parent_id
55104
+ }),
55105
+ version: 1,
55106
+ operation_id: boundedString(value.operation_id, "operation_id"),
55107
+ step_id: boundedString(value.step_id, "step_id"),
55108
+ idempotency_key: boundedString(value.idempotency_key, "idempotency_key", 240),
55109
+ precondition_digest: digest2(value.precondition_digest, "precondition_digest"),
55110
+ expected_root_parent_id: nullableUuid(value.expected_root_parent_id, "expected_root_parent_id"),
55111
+ source_population_digest: digest2(value.source_population_digest, "source_population_digest"),
55112
+ expected_tasks,
55113
+ shared_plan_splits
55114
+ };
55115
+ }
55116
+ function parseTodosTaskSubtreeTransferRollback(input) {
55117
+ const value = strictObject(input, [
55118
+ "receipt_id",
55119
+ "operation_id",
55120
+ "step_id",
55121
+ "idempotency_key",
55122
+ "precondition_digest"
55123
+ ]);
55124
+ return {
55125
+ receipt_id: uuid3(value.receipt_id, "receipt_id"),
55126
+ operation_id: boundedString(value.operation_id, "operation_id"),
55127
+ step_id: boundedString(value.step_id, "step_id"),
55128
+ idempotency_key: boundedString(value.idempotency_key, "idempotency_key", 240),
55129
+ precondition_digest: digest2(value.precondition_digest, "precondition_digest")
55130
+ };
55131
+ }
55132
+ function deriveTodosTaskSubtreeTransferApplyPreconditionDigest(input) {
55133
+ const {
55134
+ source_project_id,
55135
+ destination_project_id,
55136
+ destination_task_list_id,
55137
+ root_task_id,
55138
+ expected_root_parent_id,
55139
+ destination_parent_id,
55140
+ source_population_digest,
55141
+ expected_tasks,
55142
+ shared_plan_splits
55143
+ } = input;
55144
+ return canonicalDigest({
55145
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
55146
+ direction: "apply",
55147
+ source_project_id,
55148
+ destination_project_id,
55149
+ destination_task_list_id,
55150
+ root_task_id,
55151
+ expected_root_parent_id,
55152
+ destination_parent_id,
55153
+ source_population_digest,
55154
+ expected_tasks,
55155
+ shared_plan_splits
55156
+ });
55157
+ }
55158
+ function resolveTenantId2(value) {
55159
+ const tenant = value ?? "default";
55160
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenant)) {
55161
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
55162
+ }
55163
+ return tenant;
55164
+ }
55165
+
55166
+ class PackageOwnedTodosTaskSubtreeTransferAuthority {
55167
+ backend;
55168
+ tenantId;
55169
+ backendOptions;
55170
+ constructor(backend, options = {}) {
55171
+ this.backend = backend;
55172
+ this.tenantId = resolveTenantId2(options.tenantId);
55173
+ this.backendOptions = { ...options, tenantId: this.tenantId };
55174
+ }
55175
+ async capability() {
55176
+ return {
55177
+ authority: "todos",
55178
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
55179
+ schema_version: TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION,
55180
+ tenant_id: this.tenantId,
55181
+ backend: this.backend.kind,
55182
+ exact_descendant_closure: true,
55183
+ complete_source_population_digest: true,
55184
+ per_task_version_cas: true,
55185
+ explicit_shared_plan_splits: true,
55186
+ atomic_apply: true,
55187
+ immutable_forward_inverse_receipts: true,
55188
+ prior_image_receipts: true,
55189
+ cas_protected_rollback: true,
55190
+ preserves_descendant_parent_ids: true,
55191
+ preserves_task_and_relation_identities: true
55192
+ };
55193
+ }
55194
+ async inspect(input) {
55195
+ return this.backend.inspect(parseTodosTaskSubtreeTransferInspect(input));
55196
+ }
55197
+ async apply(input) {
55198
+ const request = parseTodosTaskSubtreeTransferApply(input);
55199
+ const expected = deriveTodosTaskSubtreeTransferApplyPreconditionDigest(request);
55200
+ if (request.precondition_digest !== expected) {
55201
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "precondition_digest does not match the exact transfer target and source snapshot", { expected_precondition_digest: expected });
55202
+ }
55203
+ const requestDigest = taskSubtreeTransferRequestDigest(request);
55204
+ const expectedKey = deriveTodosTaskSubtreeTransferIdempotencyKey({
55205
+ operation_id: request.operation_id,
55206
+ step_id: request.step_id,
55207
+ direction: "apply",
55208
+ target_selector: request.root_task_id,
55209
+ request_digest: requestDigest,
55210
+ precondition_digest: request.precondition_digest
55211
+ });
55212
+ if (request.idempotency_key !== expectedKey) {
55213
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the exact transfer request", { expected_idempotency_key: expectedKey });
55214
+ }
55215
+ return this.backend.apply(request, this.backendOptions);
55216
+ }
55217
+ async readExact(receiptIdValue) {
55218
+ return this.backend.readExact(uuid3(receiptIdValue, "receipt_id"));
55219
+ }
55220
+ async rollback(input) {
55221
+ const request = parseTodosTaskSubtreeTransferRollback(input);
55222
+ const requestDigest = taskSubtreeTransferRollbackRequestDigest(request);
55223
+ const expectedKey = deriveTodosTaskSubtreeTransferIdempotencyKey({
55224
+ operation_id: request.operation_id,
55225
+ step_id: request.step_id,
55226
+ direction: "rollback",
55227
+ target_selector: request.receipt_id,
55228
+ request_digest: requestDigest,
55229
+ precondition_digest: request.precondition_digest
55230
+ });
55231
+ if (request.idempotency_key !== expectedKey) {
55232
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_MISMATCH", "rollback idempotency_key does not match the exact rollback request", { expected_idempotency_key: expectedKey });
55233
+ }
55234
+ return this.backend.rollback(request, this.backendOptions);
55235
+ }
55236
+ }
55237
+ function createPostgresTodosTaskSubtreeTransferAuthority(client, options = {}) {
55238
+ return new PackageOwnedTodosTaskSubtreeTransferAuthority(new PostgresTodosTaskSubtreeTransferBackend(client, options), options);
55239
+ }
55240
+ var UUID, DIGEST, INSPECT_FIELDS, APPLY_FIELDS;
55241
+ var init_authority3 = __esm(() => {
55242
+ init_canonical2();
55243
+ init_postgres4();
55244
+ init_sqlite3();
55245
+ init_types6();
55246
+ init_canonical2();
55247
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
55248
+ DIGEST = /^[0-9a-f]{64}$/i;
55249
+ INSPECT_FIELDS = [
55250
+ "source_project_id",
55251
+ "destination_project_id",
55252
+ "destination_task_list_id",
55253
+ "root_task_id",
55254
+ "destination_parent_id"
55255
+ ];
55256
+ APPLY_FIELDS = [
55257
+ ...INSPECT_FIELDS,
55258
+ "version",
55259
+ "operation_id",
55260
+ "step_id",
55261
+ "idempotency_key",
55262
+ "precondition_digest",
55263
+ "expected_root_parent_id",
55264
+ "source_population_digest",
55265
+ "expected_tasks",
55266
+ "shared_plan_splits"
55267
+ ];
55268
+ });
55269
+
55270
+ // src/task-subtree-transfer/http.ts
55271
+ function json3(body2, status2 = 200) {
55272
+ return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS3 });
55273
+ }
55274
+ function status2(error) {
55275
+ switch (error.code) {
55276
+ case "TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT":
55277
+ case "TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE":
55278
+ case "TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE":
55279
+ return 400;
55280
+ case "TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND":
55281
+ case "TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND":
55282
+ return 404;
55283
+ case "TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE":
55284
+ return 503;
55285
+ default:
55286
+ return 409;
55287
+ }
55288
+ }
55289
+ async function boundedText2(message, limit) {
55290
+ if (!message.body)
55291
+ return "";
55292
+ const reader = message.body.getReader();
55293
+ const decoder = new TextDecoder;
55294
+ let bytes = 0;
55295
+ let text2 = "";
55296
+ try {
55297
+ while (true) {
55298
+ const { done, value } = await reader.read();
55299
+ if (done)
55300
+ break;
55301
+ bytes += value.byteLength;
55302
+ if (bytes > limit) {
55303
+ await reader.cancel();
55304
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Task-subtree-transfer HTTP body exceeds the byte bound");
55305
+ }
55306
+ text2 += decoder.decode(value, { stream: true });
55307
+ }
55308
+ return text2 + decoder.decode();
55309
+ } finally {
55310
+ reader.releaseLock();
55311
+ }
55312
+ }
55313
+ async function readBody(request) {
55314
+ const declaredLength = Number(request.headers.get("content-length") ?? 0);
55315
+ if (Number.isFinite(declaredLength) && declaredLength > REQUEST_BYTES) {
55316
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Task-subtree-transfer request exceeds the byte bound");
55317
+ }
55318
+ const text2 = await boundedText2(request, REQUEST_BYTES);
55319
+ try {
55320
+ return JSON.parse(text2);
55321
+ } catch {
55322
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Invalid JSON body");
55323
+ }
55324
+ }
55325
+ async function handleTodosTaskSubtreeTransferHttpRequest(request, url, authority, basePath = "/v1/task-subtree-transfer") {
55326
+ if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`))
55327
+ return null;
55328
+ const action = url.pathname.slice(basePath.length).split("/").filter(Boolean).join("/");
55329
+ try {
55330
+ if ((action === "" || action === "capability") && request.method === "GET") {
55331
+ return json3({ capability: await authority.capability() });
55332
+ }
55333
+ if (request.method !== "POST")
55334
+ return json3({ error: "method not allowed" }, 405);
55335
+ if (action === "inspect")
55336
+ return json3({ inspection: await authority.inspect(await readBody(request)) });
55337
+ if (action === "apply")
55338
+ return json3({ result: await authority.apply(await readBody(request)) }, 201);
55339
+ if (action === "rollback")
55340
+ return json3({ result: await authority.rollback(await readBody(request)) }, 201);
55341
+ if (action === "read-exact") {
55342
+ const input = await readBody(request);
55343
+ if (!input || typeof input.receipt_id !== "string") {
55344
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "receipt_id is required");
55345
+ }
55346
+ return json3({ result: await authority.readExact(input.receipt_id) });
55347
+ }
55348
+ return json3({ error: "unknown task-subtree-transfer route" }, 404);
55349
+ } catch (cause) {
55350
+ if (cause instanceof TodosTaskSubtreeTransferError) {
55351
+ return json3({
55352
+ error: cause.message,
55353
+ code: cause.code,
55354
+ details: cause.details,
55355
+ authoritative: true
55356
+ }, status2(cause));
55357
+ }
55358
+ return json3({ error: "internal task-subtree-transfer error" }, 500);
55359
+ }
55360
+ }
55361
+ var JSON_HEADERS3, REQUEST_BYTES, RESPONSE_BYTES;
55362
+ var init_http4 = __esm(() => {
55363
+ init_types6();
55364
+ JSON_HEADERS3 = { "Content-Type": "application/json" };
55365
+ REQUEST_BYTES = 2 * 1024 * 1024;
55366
+ RESPONSE_BYTES = 8 * 1024 * 1024;
55367
+ });
55368
+
55369
+ // src/task-subtree-transfer/index.ts
55370
+ var init_task_subtree_transfer = __esm(() => {
55371
+ init_authority3();
55372
+ init_http4();
55373
+ init_types6();
55374
+ });
55375
+
54194
55376
  // src/storage/comment-redaction-backfill.ts
54195
55377
  function assertSafeIdentifier2(value) {
54196
55378
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
@@ -54288,6 +55470,207 @@ var init_comment_redaction_backfill = __esm(() => {
54288
55470
  init_postgres_sync();
54289
55471
  });
54290
55472
 
55473
+ // src/storage/timestamp-backfill.ts
55474
+ function toIso(value) {
55475
+ if (!value || value === "null")
55476
+ return null;
55477
+ const parsed = new Date(value);
55478
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
55479
+ }
55480
+ async function backfillMissingTimestamps(client, options = {}) {
55481
+ const service = options.service ?? "todos";
55482
+ const tableName = options.tableName ?? "todos_sync_records";
55483
+ const batchSize = Math.min(Math.max(options.batchSize ?? 100, 1), 500);
55484
+ const apply = Boolean(options.apply);
55485
+ if (apply && options.confirmation !== TIMESTAMP_BACKFILL_CONFIRMATION) {
55486
+ throw new Error(`timestamp-backfill: --apply requires --confirm=${TIMESTAMP_BACKFILL_CONFIRMATION}`);
55487
+ }
55488
+ if (apply && !options.evidencePath) {
55489
+ throw new Error("timestamp-backfill: --apply requires --evidence-path so the pre-change state is preserved before mutation");
55490
+ }
55491
+ let scanned = 0;
55492
+ let candidates = 0;
55493
+ let completedAtBackfilled = 0;
55494
+ let startedAtBackfilled = 0;
55495
+ let remainingAfterApply = 0;
55496
+ let batches = 0;
55497
+ let cursor = null;
55498
+ for (;; ) {
55499
+ const pageResult = await client.query(`/* todos:timestamp-backfill-candidates */ SELECT object_id,
55500
+ payload->>'status' AS status,
55501
+ payload->>'started_at' AS started_at,
55502
+ payload->>'completed_at' AS completed_at,
55503
+ payload->>'updated_at' AS updated_at,
55504
+ payload->'metadata'->'_failure'->>'failed_at' AS failed_at
55505
+ FROM ${tableName}
55506
+ WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL
55507
+ AND ((payload->>'status' = 'completed'
55508
+ AND ((payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null'))
55509
+ OR (payload->>'status' = 'failed'
55510
+ AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null'
55511
+ OR (payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null'))
55512
+ OR (payload->>'status' = 'cancelled'
55513
+ AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null'
55514
+ OR (payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null')))
55515
+ AND ($2::text IS NULL OR object_id > $2)
55516
+ ORDER BY object_id
55517
+ LIMIT $3`, [service, cursor, batchSize]);
55518
+ const page = pageResult.rows;
55519
+ batches += 1;
55520
+ scanned += page.length;
55521
+ if (page.length === 0)
55522
+ break;
55523
+ const batch = page.map((row) => ({
55524
+ ...row,
55525
+ started_at: toIso(row.started_at),
55526
+ completed_at: toIso(row.completed_at),
55527
+ updated_at: toIso(row.updated_at),
55528
+ failed_at: toIso(row.failed_at)
55529
+ }));
55530
+ const taskIds = batch.map((row) => row.object_id);
55531
+ const historyResult = await client.query(`/* todos:timestamp-backfill-history */ SELECT
55532
+ payload->>'task_id' AS task_id,
55533
+ payload->>'action' AS action,
55534
+ payload->>'new_value' AS new_value,
55535
+ payload->>'created_at' AS created_at
55536
+ FROM ${tableName}
55537
+ WHERE service = $1 AND object_type = 'audit_history' AND deleted_at IS NULL
55538
+ AND payload->>'action' IN ('start', 'complete', 'fail')
55539
+ AND payload->>'task_id' = ANY($2::text[])`, [service, taskIds]);
55540
+ const historyRows = historyResult.rows;
55541
+ const byTask = new Map;
55542
+ for (const row of historyRows) {
55543
+ if (!row.task_id)
55544
+ continue;
55545
+ const list = byTask.get(row.task_id) ?? [];
55546
+ list.push(row);
55547
+ byTask.set(row.task_id, list);
55548
+ }
55549
+ for (const row of batch) {
55550
+ candidates += 1;
55551
+ const entries = (byTask.get(row.object_id) ?? []).sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""));
55552
+ let derivedStartedAt = null;
55553
+ let startedSource = null;
55554
+ if (!row.started_at) {
55555
+ const start = entries.find((entry2) => entry2.action === "start");
55556
+ const candidate = toIso(start?.created_at);
55557
+ if (candidate) {
55558
+ derivedStartedAt = candidate;
55559
+ startedSource = "audit_history";
55560
+ }
55561
+ }
55562
+ let derivedCompletedAt = null;
55563
+ let completedSource = null;
55564
+ if (!row.completed_at) {
55565
+ const terminal = entries.filter((entry2) => entry2.action === "complete" || entry2.action === "fail");
55566
+ if (terminal.length > 0) {
55567
+ const latest = terminal[terminal.length - 1];
55568
+ const candidate = toIso(latest.created_at);
55569
+ if (candidate) {
55570
+ derivedCompletedAt = candidate;
55571
+ completedSource = "audit_history";
55572
+ }
55573
+ }
55574
+ if (!derivedCompletedAt && row.failed_at && row.status === "failed") {
55575
+ const candidate = toIso(row.failed_at);
55576
+ if (candidate) {
55577
+ derivedCompletedAt = candidate;
55578
+ completedSource = "failure_metadata";
55579
+ }
55580
+ }
55581
+ if (!derivedCompletedAt) {
55582
+ const candidate = toIso(row.updated_at);
55583
+ if (candidate) {
55584
+ derivedCompletedAt = candidate;
55585
+ completedSource = "updated_at";
55586
+ }
55587
+ }
55588
+ }
55589
+ const evidenceRow = {
55590
+ object_id: row.object_id,
55591
+ status: row.status,
55592
+ started_at: row.started_at,
55593
+ completed_at: row.completed_at,
55594
+ updated_at: row.updated_at,
55595
+ derived_started_at: derivedStartedAt,
55596
+ derived_completed_at: derivedCompletedAt,
55597
+ started_source: startedSource,
55598
+ completed_source: completedSource
55599
+ };
55600
+ if (apply && options.evidencePath) {
55601
+ const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync8 } = await import("fs");
55602
+ const { dirname: dirname9 } = await import("path");
55603
+ mkdirSync8(dirname9(options.evidencePath), { recursive: true });
55604
+ appendFileSync2(options.evidencePath, JSON.stringify(evidenceRow) + `
55605
+ `);
55606
+ }
55607
+ let completedAtLanded = false;
55608
+ let startedAtLanded = false;
55609
+ if (apply) {
55610
+ const willFillStarted = row.started_at === null && derivedStartedAt !== null;
55611
+ const willFillCompleted = row.completed_at === null && derivedCompletedAt !== null;
55612
+ if (willFillStarted || willFillCompleted) {
55613
+ const result = await client.query(`/* todos:timestamp-backfill-apply */ UPDATE ${tableName} SET
55614
+ payload = jsonb_set(
55615
+ jsonb_set(
55616
+ payload,
55617
+ '{completed_at}',
55618
+ CASE WHEN $3::text IS NOT NULL
55619
+ AND ((payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null')
55620
+ THEN to_jsonb($3::text) ELSE payload->'completed_at' END,
55621
+ true
55622
+ ),
55623
+ '{started_at}',
55624
+ CASE WHEN $4::text IS NOT NULL
55625
+ AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null')
55626
+ THEN to_jsonb($4::text) ELSE payload->'started_at' END,
55627
+ true
55628
+ )
55629
+ WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL
55630
+ AND (($3::text IS NOT NULL
55631
+ AND ((payload->>'completed_at') IS NULL OR (payload->>'completed_at') = 'null'))
55632
+ OR ($4::text IS NOT NULL
55633
+ AND ((payload->>'started_at') IS NULL OR (payload->>'started_at') = 'null')))
55634
+ RETURNING object_id,
55635
+ ($3::text IS NOT NULL AND payload->>'completed_at' = $3::text) AS completed_written,
55636
+ ($4::text IS NOT NULL AND payload->>'started_at' = $4::text) AS started_written`, [service, row.object_id, derivedCompletedAt, derivedStartedAt]);
55637
+ if (result.rows.length > 0) {
55638
+ const landed = result.rows[0];
55639
+ completedAtLanded = willFillCompleted && landed.completed_written;
55640
+ startedAtLanded = willFillStarted && landed.started_written;
55641
+ if (completedAtLanded)
55642
+ completedAtBackfilled += 1;
55643
+ if (startedAtLanded)
55644
+ startedAtBackfilled += 1;
55645
+ }
55646
+ }
55647
+ }
55648
+ if (row.completed_at === null && !completedAtLanded) {
55649
+ remainingAfterApply += 1;
55650
+ } else if ((row.status === "failed" || row.status === "cancelled") && row.started_at === null && !startedAtLanded) {
55651
+ remainingAfterApply += 1;
55652
+ }
55653
+ }
55654
+ const last = batch[batch.length - 1];
55655
+ if (last.object_id === cursor)
55656
+ break;
55657
+ cursor = last.object_id;
55658
+ if (page.length < batchSize)
55659
+ break;
55660
+ }
55661
+ return {
55662
+ dry_run: !apply,
55663
+ scanned,
55664
+ candidates,
55665
+ completed_at_backfilled: apply ? completedAtBackfilled : 0,
55666
+ started_at_backfilled: apply ? startedAtBackfilled : 0,
55667
+ batches,
55668
+ remaining_candidates: apply ? remainingAfterApply : candidates,
55669
+ evidence_path: options.evidencePath ?? null
55670
+ };
55671
+ }
55672
+ var TIMESTAMP_BACKFILL_CONFIRMATION = "BACKFILL_TODOS_TERMINAL_TIMESTAMPS";
55673
+
54291
55674
  // src/server/cloud.ts
54292
55675
  var exports_cloud = {};
54293
55676
  __export(exports_cloud, {
@@ -54296,8 +55679,8 @@ __export(exports_cloud, {
54296
55679
  pingCloud: () => pingCloud,
54297
55680
  normalizeCloudPayloads: () => normalizeCloudPayloads,
54298
55681
  isPostgresBackendConfigured: () => isPostgresBackendConfigured,
54299
- isCloudModeEnabled: () => isCloudModeEnabled,
54300
55682
  getCloudVerifier: () => getCloudVerifier,
55683
+ getCloudTaskSubtreeTransferAuthority: () => getCloudTaskSubtreeTransferAuthority,
54301
55684
  getCloudTaskManifestAuthority: () => getCloudTaskManifestAuthority,
54302
55685
  getCloudStorageAdapter: () => getCloudStorageAdapter,
54303
55686
  getCloudProjectRegistrationAuthority: () => getCloudProjectRegistrationAuthority,
@@ -54309,6 +55692,7 @@ __export(exports_cloud, {
54309
55692
  ensureCloudSchema: () => ensureCloudSchema,
54310
55693
  ensureCloudCommentCursorIndex: () => ensureCloudCommentCursorIndex,
54311
55694
  closeCloud: () => closeCloud,
55695
+ backfillCloudTimestamps: () => backfillCloudTimestamps,
54312
55696
  backfillCloudCommentRedaction: () => backfillCloudCommentRedaction,
54313
55697
  TODOS_APP_SLUG: () => TODOS_APP_SLUG
54314
55698
  });
@@ -54323,9 +55707,6 @@ function resolveSigningSecret(env = process.env) {
54323
55707
  function isPostgresBackendConfigured(env = process.env) {
54324
55708
  return Boolean(resolveCloudDatabaseUrl(env));
54325
55709
  }
54326
- function isCloudModeEnabled(env = process.env) {
54327
- return isPostgresBackendConfigured(env);
54328
- }
54329
55710
  function getCloudTenantId() {
54330
55711
  return process.env.HASNA_TODOS_TENANT_ID ?? "default";
54331
55712
  }
@@ -54372,6 +55753,15 @@ function getCloudTaskManifestAuthority() {
54372
55753
  });
54373
55754
  return cachedTaskManifestAuthority;
54374
55755
  }
55756
+ function getCloudTaskSubtreeTransferAuthority() {
55757
+ if (cachedTaskSubtreeTransferAuthority)
55758
+ return cachedTaskSubtreeTransferAuthority;
55759
+ cachedTaskSubtreeTransferAuthority = createPostgresTodosTaskSubtreeTransferAuthority(getClient(), {
55760
+ service: TODOS_APP_SLUG,
55761
+ tenantId: getCloudTenantId()
55762
+ });
55763
+ return cachedTaskSubtreeTransferAuthority;
55764
+ }
54375
55765
  function authClient() {
54376
55766
  const client = getClient();
54377
55767
  return {
@@ -54405,7 +55795,7 @@ function getCloudVerifier() {
54405
55795
  cachedVerifier = verifyApiKey2({
54406
55796
  app: TODOS_APP_SLUG,
54407
55797
  signingSecret,
54408
- isRevoked: store.isRevoked
55798
+ keyStatus: store.keyStatus
54409
55799
  });
54410
55800
  return cachedVerifier;
54411
55801
  }
@@ -54426,8 +55816,14 @@ async function ensureCloudSchema() {
54426
55816
  for (const sql of postgresTodosTaskManifestSchemaSql(getCloudTenantId())) {
54427
55817
  await client.query(sql);
54428
55818
  }
55819
+ for (const sql of postgresTodosTaskSubtreeTransferSchemaSql()) {
55820
+ await client.query(sql);
55821
+ }
54429
55822
  await getApiKeyStore().ensureSchema();
54430
- })();
55823
+ })().catch((error) => {
55824
+ schemaEnsured = null;
55825
+ throw error;
55826
+ });
54431
55827
  return schemaEnsured;
54432
55828
  }
54433
55829
  async function ensureCloudCommentCursorIndex() {
@@ -54453,6 +55849,9 @@ async function normalizeCloudPayloads() {
54453
55849
  function backfillCloudCommentRedaction(options = {}) {
54454
55850
  return backfillPostgresCommentRedaction(getClient(), { ...options, service: TODOS_APP_SLUG });
54455
55851
  }
55852
+ function backfillCloudTimestamps(options = {}) {
55853
+ return backfillMissingTimestamps(getClient(), { ...options, service: TODOS_APP_SLUG });
55854
+ }
54456
55855
  async function pingCloud() {
54457
55856
  const client = getClient();
54458
55857
  const res = await client.query("select 1 as ok");
@@ -54468,9 +55867,10 @@ async function closeCloud() {
54468
55867
  cachedPrGroupLedger = null;
54469
55868
  cachedProjectRegistrationAuthority = null;
54470
55869
  cachedTaskManifestAuthority = null;
55870
+ cachedTaskSubtreeTransferAuthority = null;
54471
55871
  schemaEnsured = null;
54472
55872
  }
54473
- var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, cachedTaskManifestAuthority = null, schemaEnsured = null;
55873
+ var TODOS_APP_SLUG = "todos", cachedClient = null, cachedAdapter = null, cachedStore = null, cachedVerifier = null, cachedPrGroupLedger = null, cachedProjectRegistrationAuthority = null, cachedTaskManifestAuthority = null, cachedTaskSubtreeTransferAuthority = null, schemaEnsured = null;
54474
55874
  var init_cloud = __esm(() => {
54475
55875
  init_cloud_client();
54476
55876
  init_postgres_adapter();
@@ -54478,6 +55878,7 @@ var init_cloud = __esm(() => {
54478
55878
  init_postgres();
54479
55879
  init_project_registration();
54480
55880
  init_task_manifest();
55881
+ init_task_subtree_transfer();
54481
55882
  init_postgres_sync();
54482
55883
  init_comment_redaction_backfill();
54483
55884
  });
@@ -54691,8 +56092,8 @@ function rowToOrg(row) {
54691
56092
  function createOrg(input, db) {
54692
56093
  const d = db || getDatabase();
54693
56094
  const id = uuid();
54694
- const timestamp5 = now();
54695
- d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp5, timestamp5]);
56095
+ const timestamp6 = now();
56096
+ d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp6, timestamp6]);
54696
56097
  return getOrg(id, d);
54697
56098
  }
54698
56099
  function getOrg(id, db) {
@@ -54836,9 +56237,9 @@ function parseBoundedLimit(value, fallback, max) {
54836
56237
  return fallback;
54837
56238
  return Math.min(parsed, max);
54838
56239
  }
54839
- function mapTaskError(e, json4) {
56240
+ function mapTaskError(e, json5) {
54840
56241
  if (e instanceof VersionConflictError) {
54841
- return json4({
56242
+ return json5({
54842
56243
  error: e.message,
54843
56244
  code: VersionConflictError.code,
54844
56245
  expected_version: e.expectedVersion,
@@ -54846,23 +56247,23 @@ function mapTaskError(e, json4) {
54846
56247
  }, 409);
54847
56248
  }
54848
56249
  if (e instanceof TaskNotFoundError) {
54849
- return json4({ error: e.message, code: TaskNotFoundError.code }, 404);
56250
+ return json5({ error: e.message, code: TaskNotFoundError.code }, 404);
54850
56251
  }
54851
56252
  if (e instanceof LockError) {
54852
- return json4({ error: e.message, code: LockError.code }, 409);
56253
+ return json5({ error: e.message, code: LockError.code }, 409);
54853
56254
  }
54854
56255
  if (e instanceof CompletionGuardError) {
54855
- return json4({
56256
+ return json5({
54856
56257
  error: e.message,
54857
56258
  code: CompletionGuardError.code,
54858
56259
  retry_after: e.retryAfterSeconds ?? null
54859
56260
  }, 409);
54860
56261
  }
54861
56262
  if (e instanceof TaskNotStartableError) {
54862
- return json4({ error: e.message, code: TaskNotStartableError.code }, 409);
56263
+ return json5({ error: e.message, code: TaskNotStartableError.code }, 409);
54863
56264
  }
54864
56265
  if (e instanceof Error && / is blocked by /.test(e.message)) {
54865
- return json4({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
56266
+ return json5({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
54866
56267
  }
54867
56268
  return null;
54868
56269
  }
@@ -54947,11 +56348,11 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
54947
56348
  }
54948
56349
  });
54949
56350
  }
54950
- function handleHealth(_ctx, json4) {
56351
+ function handleHealth(_ctx, json5) {
54951
56352
  const stats2 = getTaskStats();
54952
56353
  const staleCount = getStaleTasks(30).length;
54953
56354
  const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
54954
- return json4({
56355
+ return json5({
54955
56356
  status: staleCount === 0 && overdueRecurring === 0 ? "ok" : "warn",
54956
56357
  tasks: stats2.total,
54957
56358
  stale: staleCount,
@@ -54959,18 +56360,18 @@ function handleHealth(_ctx, json4) {
54959
56360
  timestamp: new Date().toISOString()
54960
56361
  });
54961
56362
  }
54962
- function handleHeadlessBoundary(_ctx, json4) {
56363
+ function handleHeadlessBoundary(_ctx, json5) {
54963
56364
  const { getHeadlessBoundaryManifest: getHeadlessBoundaryManifest2 } = (init_headless_boundaries(), __toCommonJS(exports_headless_boundaries));
54964
- return json4(getHeadlessBoundaryManifest2());
56365
+ return json5(getHeadlessBoundaryManifest2());
54965
56366
  }
54966
- function handleStats(_ctx, json4) {
56367
+ function handleStats(_ctx, json5) {
54967
56368
  const stats2 = getTaskStats();
54968
56369
  const byStatus = stats2.by_status;
54969
56370
  const projects = listProjects();
54970
56371
  const agents = listAgents();
54971
56372
  const staleCount = getStaleTasks(30).length;
54972
56373
  const overdueRecurring = getOverdueTasks().filter((t) => t.recurrence_rule && t.status === "pending").length;
54973
- return json4({
56374
+ return json5({
54974
56375
  total_tasks: stats2.total,
54975
56376
  pending: byStatus["pending"] ?? 0,
54976
56377
  in_progress: byStatus["in_progress"] ?? 0,
@@ -54993,10 +56394,10 @@ function taskStatusQueryParam(url) {
54993
56394
  return { ok: false, message: result.message };
54994
56395
  return { ok: true, value: collapseEnumValues(result.values) };
54995
56396
  }
54996
- async function handleListTasks(_req, url, _ctx, json4, taskToSummary2) {
56397
+ async function handleListTasks(_req, url, _ctx, json5, taskToSummary2) {
54997
56398
  const statusParam = taskStatusQueryParam(url);
54998
56399
  if (!statusParam.ok)
54999
- return json4({ error: statusParam.message }, 400);
56400
+ return json5({ error: statusParam.message }, 400);
55000
56401
  const projectId = url.searchParams.get("project_id") || undefined;
55001
56402
  const sessionId = url.searchParams.get("session_id") || undefined;
55002
56403
  const agentId = url.searchParams.get("agent_id") || undefined;
@@ -55011,13 +56412,13 @@ async function handleListTasks(_req, url, _ctx, json4, taskToSummary2) {
55011
56412
  limit: limitParam ? parseInt(limitParam, 10) : undefined,
55012
56413
  offset: offsetParam ? parseInt(offsetParam, 10) : undefined
55013
56414
  });
55014
- return json4(tasks.map((t) => taskToSummary2(t, fields)));
56415
+ return json5(tasks.map((t) => taskToSummary2(t, fields)));
55015
56416
  }
55016
- async function handleCreateTask(req, ctx, json4, taskToSummary2) {
56417
+ async function handleCreateTask(req, ctx, json5, taskToSummary2) {
55017
56418
  try {
55018
56419
  const body2 = await req.json();
55019
56420
  if (!body2.title)
55020
- return json4({ error: "Missing 'title'" }, 400);
56421
+ return json5({ error: "Missing 'title'" }, 400);
55021
56422
  const createdBy = body2.created_by ?? body2.agent_id ?? "dashboard";
55022
56423
  const task3 = createTask({
55023
56424
  title: body2.title,
@@ -55029,19 +56430,19 @@ async function handleCreateTask(req, ctx, json4, taskToSummary2) {
55029
56430
  ...body2.assigned_to ? { assigned_to: body2.assigned_to } : {}
55030
56431
  });
55031
56432
  ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "created", agent_id: task3.agent_id, project_id: task3.project_id });
55032
- return json4(taskToSummary2(task3), 201);
56433
+ return json5(taskToSummary2(task3), 201);
55033
56434
  } catch (e) {
55034
- return json4({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
56435
+ return json5({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
55035
56436
  }
55036
56437
  }
55037
- async function handleUpsertTask(req, ctx, json4, taskToSummary2) {
56438
+ async function handleUpsertTask(req, ctx, json5, taskToSummary2) {
55038
56439
  try {
55039
56440
  const body2 = await req.json();
55040
56441
  if (typeof body2["fingerprint"] !== "string" || body2["fingerprint"].trim() === "") {
55041
- return json4({ error: "Missing 'fingerprint'" }, 400);
56442
+ return json5({ error: "Missing 'fingerprint'" }, 400);
55042
56443
  }
55043
56444
  if (typeof body2["title"] !== "string" || body2["title"].trim() === "") {
55044
- return json4({ error: "Missing 'title'" }, 400);
56445
+ return json5({ error: "Missing 'title'" }, 400);
55045
56446
  }
55046
56447
  const metadata = body2["metadata"] && typeof body2["metadata"] === "object" && !Array.isArray(body2["metadata"]) ? { ...body2["metadata"] } : {};
55047
56448
  for (const key2 of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
@@ -55062,9 +56463,9 @@ async function handleUpsertTask(req, ctx, json4, taskToSummary2) {
55062
56463
  metadata
55063
56464
  });
55064
56465
  ctx.broadcastEvent({ type: "task", task_id: result.task.id, action: result.created ? "created" : "updated", agent_id: result.task.agent_id, project_id: result.task.project_id });
55065
- return json4({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
56466
+ return json5({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
55066
56467
  } catch (e) {
55067
- return json4({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
56468
+ return json5({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
55068
56469
  }
55069
56470
  }
55070
56471
  function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
@@ -55116,11 +56517,11 @@ function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
55116
56517
  }
55117
56518
  });
55118
56519
  }
55119
- async function handleTasksBulk(req, _ctx, json4) {
56520
+ async function handleTasksBulk(req, _ctx, json5) {
55120
56521
  try {
55121
56522
  const body2 = await req.json();
55122
56523
  if (!body2.ids?.length || !body2.action)
55123
- return json4({ error: "Missing ids or action" }, 400);
56524
+ return json5({ error: "Missing ids or action" }, 400);
55124
56525
  const results = [];
55125
56526
  for (const id of body2.ids) {
55126
56527
  try {
@@ -55138,84 +56539,84 @@ async function handleTasksBulk(req, _ctx, json4) {
55138
56539
  results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
55139
56540
  }
55140
56541
  }
55141
- return json4({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
56542
+ return json5({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length });
55142
56543
  } catch (e) {
55143
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56544
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55144
56545
  }
55145
56546
  }
55146
- function handleTasksStatus(_req, url, _ctx, json4) {
56547
+ function handleTasksStatus(_req, url, _ctx, json5) {
55147
56548
  try {
55148
56549
  const projectId = url.searchParams.get("project_id") || undefined;
55149
56550
  const agentId = url.searchParams.get("agent_id") || undefined;
55150
- const status2 = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
55151
- return json4(status2);
56551
+ const status3 = getStatus(projectId ? { project_id: projectId } : undefined, agentId);
56552
+ return json5(status3);
55152
56553
  } catch (e) {
55153
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56554
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55154
56555
  }
55155
56556
  }
55156
- function handleTasksNext(_req, url, _ctx, json4, taskToSummary2) {
56557
+ function handleTasksNext(_req, url, _ctx, json5, taskToSummary2) {
55157
56558
  try {
55158
56559
  const projectId = url.searchParams.get("project_id") || undefined;
55159
56560
  const agentId = url.searchParams.get("agent_id") || undefined;
55160
56561
  const fields = parseFieldsParam(url);
55161
56562
  const task3 = getNextTask(agentId, projectId ? { project_id: projectId } : undefined);
55162
- return json4({ task: task3 ? taskToSummary2(task3, fields) : null });
56563
+ return json5({ task: task3 ? taskToSummary2(task3, fields) : null });
55163
56564
  } catch (e) {
55164
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56565
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55165
56566
  }
55166
56567
  }
55167
- function handleTasksActive(_req, url, _ctx, json4) {
56568
+ function handleTasksActive(_req, url, _ctx, json5) {
55168
56569
  try {
55169
56570
  const projectId = url.searchParams.get("project_id") || undefined;
55170
56571
  const work = getActiveWork(projectId ? { project_id: projectId } : undefined);
55171
- return json4({ active: work, count: work.length });
56572
+ return json5({ active: work, count: work.length });
55172
56573
  } catch (e) {
55173
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56574
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55174
56575
  }
55175
56576
  }
55176
- function handleTasksStale(_req, url, _ctx, json4, taskToSummary2) {
56577
+ function handleTasksStale(_req, url, _ctx, json5, taskToSummary2) {
55177
56578
  try {
55178
56579
  const projectId = url.searchParams.get("project_id") || undefined;
55179
56580
  const minutes2 = parseInt(url.searchParams.get("minutes") || "30", 10);
55180
56581
  const fields = parseFieldsParam(url);
55181
56582
  const tasks = getStaleTasks(minutes2, projectId ? { project_id: projectId } : undefined);
55182
- return json4({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
56583
+ return json5({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length });
55183
56584
  } catch (e) {
55184
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56585
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55185
56586
  }
55186
56587
  }
55187
- function handleTasksChanged(_req, url, _ctx, json4, taskToSummary2) {
56588
+ function handleTasksChanged(_req, url, _ctx, json5, taskToSummary2) {
55188
56589
  try {
55189
56590
  const since = url.searchParams.get("since");
55190
56591
  if (!since)
55191
- return json4({ error: "since parameter required (ISO date string)" }, 400);
56592
+ return json5({ error: "since parameter required (ISO date string)" }, 400);
55192
56593
  const projectId = url.searchParams.get("project_id") || undefined;
55193
56594
  const fields = parseFieldsParam(url);
55194
56595
  const tasks = getTasksChangedSince(since, projectId ? { project_id: projectId } : undefined);
55195
- return json4({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
56596
+ return json5({ tasks: tasks.map((t) => taskToSummary2(t, fields)), count: tasks.length, since });
55196
56597
  } catch (e) {
55197
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56598
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55198
56599
  }
55199
56600
  }
55200
- function handleTasksContext(_req, url, _ctx, json4, taskToSummary2) {
56601
+ function handleTasksContext(_req, url, _ctx, json5, taskToSummary2) {
55201
56602
  const agentId = url.searchParams.get("agent_id") || undefined;
55202
56603
  const projectId = url.searchParams.get("project_id") || undefined;
55203
56604
  const format = url.searchParams.get("format") || "text";
55204
56605
  const fields = parseFieldsParam(url);
55205
56606
  const filters = projectId ? { project_id: projectId } : undefined;
55206
- const status2 = getStatus(filters, agentId);
56607
+ const status3 = getStatus(filters, agentId);
55207
56608
  const next = getNextTask(agentId, filters);
55208
56609
  if (format === "json") {
55209
- return json4({ status: status2, next_task: next ? taskToSummary2(next, fields) : null });
56610
+ return json5({ status: status3, next_task: next ? taskToSummary2(next, fields) : null });
55210
56611
  }
55211
56612
  const lines = [];
55212
- lines.push(`Tasks: ${status2.pending} pending | ${status2.in_progress} active | ${status2.completed} done`);
55213
- if (status2.stale_count > 0)
55214
- lines.push(`${status2.stale_count} stale tasks stuck in-progress`);
55215
- if (status2.overdue_recurring > 0)
55216
- lines.push(`${status2.overdue_recurring} overdue recurring tasks`);
55217
- if (status2.active_work.length > 0) {
55218
- lines.push(`Active: ${status2.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
56613
+ lines.push(`Tasks: ${status3.pending} pending | ${status3.in_progress} active | ${status3.completed} done`);
56614
+ if (status3.stale_count > 0)
56615
+ lines.push(`${status3.stale_count} stale tasks stuck in-progress`);
56616
+ if (status3.overdue_recurring > 0)
56617
+ lines.push(`${status3.overdue_recurring} overdue recurring tasks`);
56618
+ if (status3.active_work.length > 0) {
56619
+ lines.push(`Active: ${status3.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
55219
56620
  }
55220
56621
  if (next)
55221
56622
  lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
@@ -55223,18 +56624,18 @@ function handleTasksContext(_req, url, _ctx, json4, taskToSummary2) {
55223
56624
  `);
55224
56625
  return new Response(text2, { headers: { "Content-Type": "text/plain" } });
55225
56626
  }
55226
- function handleTaskAttachments(id, _ctx, json4) {
56627
+ function handleTaskAttachments(id, _ctx, json5) {
55227
56628
  const task3 = getTask(id);
55228
56629
  if (!task3)
55229
- return json4({ error: "Task not found" }, 404);
56630
+ return json5({ error: "Task not found" }, 404);
55230
56631
  const evidence = task3.metadata?._evidence || {};
55231
56632
  const attachmentIds = evidence.attachments || [];
55232
- return json4({ task_id: id, short_id: task3.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
56633
+ return json5({ task_id: id, short_id: task3.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
55233
56634
  }
55234
- async function handleTaskProgress(id, req, method, _ctx, json4, url) {
56635
+ async function handleTaskProgress(id, req, method, _ctx, json5, url) {
55235
56636
  const task3 = getTask(id);
55236
56637
  if (!task3)
55237
- return json4({ error: "Task not found" }, 404);
56638
+ return json5({ error: "Task not found" }, 404);
55238
56639
  if (method === "GET") {
55239
56640
  const all = listComments(id);
55240
56641
  const progress = all.filter((c) => c.type === "progress");
@@ -55242,7 +56643,7 @@ async function handleTaskProgress(id, req, method, _ctx, json4, url) {
55242
56643
  const format = url?.searchParams.get("format") || "compact";
55243
56644
  const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 200);
55244
56645
  const progressEntries = format === "full" ? progress : progress.slice(-limit);
55245
- return json4({
56646
+ return json5({
55246
56647
  task_id: id,
55247
56648
  progress_entries: progressEntries,
55248
56649
  latest,
@@ -55259,27 +56660,27 @@ async function handleTaskProgress(id, req, method, _ctx, json4, url) {
55259
56660
  try {
55260
56661
  const body2 = await req.json();
55261
56662
  if (!body2.message)
55262
- return json4({ error: "message required" }, 400);
56663
+ return json5({ error: "message required" }, 400);
55263
56664
  const comment2 = logProgress(id, body2.message, body2.pct_complete, body2.agent_id);
55264
- return json4(comment2, 201);
56665
+ return json5(comment2, 201);
55265
56666
  } catch (e) {
55266
- return json4({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
56667
+ return json5({ error: e instanceof Error ? e.message : "Failed to log progress" }, 500);
55267
56668
  }
55268
56669
  }
55269
56670
  return null;
55270
56671
  }
55271
- function handleGetTask(id, _ctx, json4, taskToSummary2, url) {
56672
+ function handleGetTask(id, _ctx, json5, taskToSummary2, url) {
55272
56673
  const task3 = getTask(id);
55273
56674
  if (!task3)
55274
- return json4({ error: "Task not found" }, 404);
55275
- return json4(taskToSummary2(task3, url ? parseFieldsParam(url) : undefined));
56675
+ return json5({ error: "Task not found" }, 404);
56676
+ return json5(taskToSummary2(task3, url ? parseFieldsParam(url) : undefined));
55276
56677
  }
55277
- async function handlePatchTask(id, req, _ctx, json4, taskToSummary2) {
56678
+ async function handlePatchTask(id, req, _ctx, json5, taskToSummary2) {
55278
56679
  try {
55279
56680
  const body2 = await req.json();
55280
56681
  const task3 = getTask(id);
55281
56682
  if (!task3)
55282
- return json4({ error: "Task not found" }, 404);
56683
+ return json5({ error: "Task not found" }, 404);
55283
56684
  const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "actual_minutes", "confidence", "retry_count", "max_retries", "retry_after", "task_type"]);
55284
56685
  const safeBody = {};
55285
56686
  for (const [key2, value] of Object.entries(body2)) {
@@ -55291,85 +56692,85 @@ async function handlePatchTask(id, req, _ctx, json4, taskToSummary2) {
55291
56692
  ...safeBody,
55292
56693
  version: clientVersion
55293
56694
  });
55294
- return json4(taskToSummary2(updated));
56695
+ return json5(taskToSummary2(updated));
55295
56696
  } catch (e) {
55296
- const mapped = mapTaskError(e, json4);
56697
+ const mapped = mapTaskError(e, json5);
55297
56698
  if (mapped)
55298
56699
  return mapped;
55299
- return json4({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
56700
+ return json5({ error: e instanceof Error ? e.message : "Failed to update task" }, 500);
55300
56701
  }
55301
56702
  }
55302
- function handleDeleteTask(id, _ctx, json4) {
56703
+ function handleDeleteTask(id, _ctx, json5) {
55303
56704
  const deleted = deleteTask(id);
55304
56705
  if (!deleted)
55305
- return json4({ error: "Task not found" }, 404);
55306
- return json4({ success: true });
56706
+ return json5({ error: "Task not found" }, 404);
56707
+ return json5({ success: true });
55307
56708
  }
55308
- function handleStartTask(id, ctx, json4, taskToSummary2) {
56709
+ function handleStartTask(id, ctx, json5, taskToSummary2) {
55309
56710
  try {
55310
56711
  const task3 = startTask(id, "dashboard");
55311
56712
  ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "started", agent_id: "dashboard", project_id: task3.project_id });
55312
- return json4(taskToSummary2(task3));
56713
+ return json5(taskToSummary2(task3));
55313
56714
  } catch (e) {
55314
- const mapped = mapTaskError(e, json4);
56715
+ const mapped = mapTaskError(e, json5);
55315
56716
  if (mapped)
55316
56717
  return mapped;
55317
- return json4({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
56718
+ return json5({ error: e instanceof Error ? e.message : "Failed to start task" }, 500);
55318
56719
  }
55319
56720
  }
55320
- async function handleFailTask(id, req, ctx, json4, taskToSummary2) {
56721
+ async function handleFailTask(id, req, ctx, json5, taskToSummary2) {
55321
56722
  try {
55322
56723
  const body2 = await req.json().catch(() => ({}));
55323
56724
  const result = failTask(id, body2.agent_id, body2.reason, { retry: body2.retry, error_code: body2.error_code });
55324
56725
  ctx.broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body2.agent_id || null, project_id: result.task.project_id });
55325
- return json4({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
56726
+ return json5({ task: taskToSummary2(result.task), retry_task: result.retryTask ? taskToSummary2(result.retryTask) : null });
55326
56727
  } catch (e) {
55327
- return json4({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
56728
+ return json5({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500);
55328
56729
  }
55329
56730
  }
55330
- function handleCompleteTask(id, ctx, json4, taskToSummary2) {
56731
+ function handleCompleteTask(id, ctx, json5, taskToSummary2) {
55331
56732
  try {
55332
56733
  const task3 = completeTask(id, "dashboard");
55333
56734
  ctx.broadcastEvent({ type: "task", task_id: task3.id, action: "completed", agent_id: "dashboard", project_id: task3.project_id });
55334
- return json4(taskToSummary2(task3));
56735
+ return json5(taskToSummary2(task3));
55335
56736
  } catch (e) {
55336
- const mapped = mapTaskError(e, json4);
56737
+ const mapped = mapTaskError(e, json5);
55337
56738
  if (mapped)
55338
56739
  return mapped;
55339
- return json4({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
56740
+ return json5({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500);
55340
56741
  }
55341
56742
  }
55342
- function handleListProjects(url, _ctx, json4) {
56743
+ function handleListProjects(url, _ctx, json5) {
55343
56744
  const pFieldsParam = url.searchParams.get("fields");
55344
56745
  const pFields = pFieldsParam ? pFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
55345
56746
  const projects = listProjects();
55346
- return json4(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
56747
+ return json5(pFields ? projects.map((p) => Object.fromEntries(pFields.map((f) => [f, p[f] ?? null]))) : projects);
55347
56748
  }
55348
- async function handleCreateProject(req, _ctx, json4) {
56749
+ async function handleCreateProject(req, _ctx, json5) {
55349
56750
  try {
55350
56751
  const body2 = await req.json();
55351
56752
  if (!body2.name || !body2.path)
55352
- return json4({ error: "Missing name or path" }, 400);
56753
+ return json5({ error: "Missing name or path" }, 400);
55353
56754
  const project = createProject({ name: body2.name, path: body2.path, description: body2.description });
55354
- return json4(project, 201);
56755
+ return json5(project, 201);
55355
56756
  } catch (e) {
55356
- return json4({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
56757
+ return json5({ error: e instanceof Error ? e.message : "Failed to create project" }, 500);
55357
56758
  }
55358
56759
  }
55359
- function handleDeleteProject(id, _ctx, json4) {
56760
+ function handleDeleteProject(id, _ctx, json5) {
55360
56761
  const deleted = deleteProject(id);
55361
56762
  if (!deleted)
55362
- return json4({ error: "Project not found" }, 404);
55363
- return json4({ success: true });
56763
+ return json5({ error: "Project not found" }, 404);
56764
+ return json5({ success: true });
55364
56765
  }
55365
- async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
56766
+ async function handleAgentMe(_req, url, _ctx, json5, taskToSummary2) {
55366
56767
  try {
55367
56768
  const name = url.searchParams.get("name");
55368
56769
  if (!name)
55369
- return json4({ error: "Missing name param" }, 400);
56770
+ return json5({ error: "Missing name param" }, 400);
55370
56771
  const agentResult = registerAgent({ name });
55371
56772
  if (isAgentConflict(agentResult))
55372
- return json4({ error: agentResult.message, conflict: true }, 409);
56773
+ return json5({ error: agentResult.message, conflict: true }, 409);
55373
56774
  const agent = agentResult;
55374
56775
  const tasks = listTasks({ assigned_to: agent.name });
55375
56776
  const agentIdTasks = listTasks({ agent_id: agent.id });
@@ -55377,7 +56778,7 @@ async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
55377
56778
  const pending = allTasks.filter((t) => t.status === "pending");
55378
56779
  const inProgress = allTasks.filter((t) => t.status === "in_progress");
55379
56780
  const completed = allTasks.filter((t) => t.status === "completed");
55380
- return json4({
56781
+ return json5({
55381
56782
  agent,
55382
56783
  pending_tasks: pending.map((t) => taskToSummary2(t)),
55383
56784
  in_progress_tasks: inProgress.map((t) => taskToSummary2(t)),
@@ -55391,132 +56792,132 @@ async function handleAgentMe(_req, url, _ctx, json4, taskToSummary2) {
55391
56792
  });
55392
56793
  } catch (e) {
55393
56794
  if (e instanceof InvalidAgentNameError)
55394
- return json4({ error: e.message, suggestions: e.suggestions }, 400);
55395
- return json4({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
56795
+ return json5({ error: e.message, suggestions: e.suggestions }, 400);
56796
+ return json5({ error: e instanceof Error ? e.message : "Failed to get agent profile" }, 500);
55396
56797
  }
55397
56798
  }
55398
- function handleAgentQueue(agentId, _ctx, json4, taskToSummary2) {
56799
+ function handleAgentQueue(agentId, _ctx, json5, taskToSummary2) {
55399
56800
  const aliasSet = assignedToAliasSet(getDatabase(), agentId);
55400
56801
  const pending = listTasks({ status: "pending" });
55401
56802
  const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
55402
56803
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
55403
56804
  queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
55404
- return json4(queue.map((t) => taskToSummary2(t)));
56805
+ return json5(queue.map((t) => taskToSummary2(t)));
55405
56806
  }
55406
- async function handleClaimTask(req, _ctx, json4, taskToSummary2) {
56807
+ async function handleClaimTask(req, _ctx, json5, taskToSummary2) {
55407
56808
  try {
55408
56809
  const body2 = await req.json();
55409
56810
  const agentId = body2.agent_id || "anonymous";
55410
56811
  const task3 = claimNextTask(agentId, body2.project_id ? { project_id: body2.project_id } : undefined);
55411
- return json4({ task: task3 ? taskToSummary2(task3) : null });
56812
+ return json5({ task: task3 ? taskToSummary2(task3) : null });
55412
56813
  } catch (e) {
55413
- return json4({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
56814
+ return json5({ error: e instanceof Error ? e.message : "Failed to claim" }, 500);
55414
56815
  }
55415
56816
  }
55416
- function handleListOrgs(_ctx, json4) {
55417
- return json4(listOrgs());
56817
+ function handleListOrgs(_ctx, json5) {
56818
+ return json5(listOrgs());
55418
56819
  }
55419
- async function handleCreateOrg(req, _ctx, json4) {
56820
+ async function handleCreateOrg(req, _ctx, json5) {
55420
56821
  try {
55421
56822
  const body2 = await req.json();
55422
56823
  if (!body2.name)
55423
- return json4({ error: "Missing name" }, 400);
55424
- return json4(createOrg(body2), 201);
56824
+ return json5({ error: "Missing name" }, 400);
56825
+ return json5(createOrg(body2), 201);
55425
56826
  } catch (e) {
55426
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56827
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55427
56828
  }
55428
56829
  }
55429
- async function handleUpdateOrg(id, req, _ctx, json4) {
56830
+ async function handleUpdateOrg(id, req, _ctx, json5) {
55430
56831
  try {
55431
56832
  const body2 = await req.json();
55432
- return json4(updateOrg(id, body2));
56833
+ return json5(updateOrg(id, body2));
55433
56834
  } catch (e) {
55434
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56835
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55435
56836
  }
55436
56837
  }
55437
- function handleDeleteOrg(id, _ctx, json4) {
56838
+ function handleDeleteOrg(id, _ctx, json5) {
55438
56839
  const deleted = deleteOrg(id);
55439
- return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
56840
+ return json5(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
55440
56841
  }
55441
- function handleOrgChart(_ctx, json4) {
55442
- return json4(getOrgChart());
56842
+ function handleOrgChart(_ctx, json5) {
56843
+ return json5(getOrgChart());
55443
56844
  }
55444
- function handleAgentTeam(agentId, _ctx, json4) {
55445
- return json4(getDirectReports(decodeURIComponent(agentId)));
56845
+ function handleAgentTeam(agentId, _ctx, json5) {
56846
+ return json5(getDirectReports(decodeURIComponent(agentId)));
55446
56847
  }
55447
- function handleListAgents(url, _ctx, json4) {
56848
+ function handleListAgents(url, _ctx, json5) {
55448
56849
  const aFieldsParam = url.searchParams.get("fields");
55449
56850
  const aFields = aFieldsParam ? aFieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
55450
56851
  const agents = listAgents();
55451
- return json4(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
56852
+ return json5(aFields ? agents.map((a) => Object.fromEntries(aFields.map((f) => [f, a[f] ?? null]))) : agents);
55452
56853
  }
55453
- async function handleRegisterAgent(req, _ctx, json4) {
56854
+ async function handleRegisterAgent(req, _ctx, json5) {
55454
56855
  try {
55455
56856
  const body2 = await req.json();
55456
56857
  if (!body2.name)
55457
- return json4({ error: "Missing name" }, 400);
56858
+ return json5({ error: "Missing name" }, 400);
55458
56859
  const result = registerAgent({ name: body2.name, description: body2.description, session_id: body2.session_id, working_dir: body2.working_dir });
55459
56860
  if (isAgentConflict(result))
55460
- return json4({ error: result.message, conflict: true }, 409);
55461
- return json4(result, 201);
56861
+ return json5({ error: result.message, conflict: true }, 409);
56862
+ return json5(result, 201);
55462
56863
  } catch (e) {
55463
56864
  if (e instanceof InvalidAgentNameError)
55464
- return json4({ error: e.message, suggestions: e.suggestions }, 400);
55465
- return json4({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
56865
+ return json5({ error: e.message, suggestions: e.suggestions }, 400);
56866
+ return json5({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500);
55466
56867
  }
55467
56868
  }
55468
- async function handleUpdateAgent(id, req, _ctx, json4) {
56869
+ async function handleUpdateAgent(id, req, _ctx, json5) {
55469
56870
  try {
55470
56871
  const body2 = await req.json();
55471
56872
  const agent = updateAgent(id, body2);
55472
- return json4(agent);
56873
+ return json5(agent);
55473
56874
  } catch (e) {
55474
56875
  if (e instanceof InvalidAgentNameError)
55475
- return json4({ error: e.message, suggestions: e.suggestions }, 400);
55476
- return json4({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
56876
+ return json5({ error: e.message, suggestions: e.suggestions }, 400);
56877
+ return json5({ error: e instanceof Error ? e.message : "Failed to update agent" }, 500);
55477
56878
  }
55478
56879
  }
55479
- function handleDeleteAgent(id, _ctx, json4) {
56880
+ function handleDeleteAgent(id, _ctx, json5) {
55480
56881
  const deleted = deleteAgent(id);
55481
56882
  if (!deleted)
55482
- return json4({ error: "Agent not found" }, 404);
55483
- return json4({ success: true });
56883
+ return json5({ error: "Agent not found" }, 404);
56884
+ return json5({ success: true });
55484
56885
  }
55485
- async function handleBulkDeleteAgents(req, _ctx, json4) {
56886
+ async function handleBulkDeleteAgents(req, _ctx, json5) {
55486
56887
  try {
55487
56888
  const body2 = await req.json();
55488
56889
  if (!body2.ids?.length || body2.action !== "delete")
55489
- return json4({ error: "Missing ids or invalid action" }, 400);
56890
+ return json5({ error: "Missing ids or invalid action" }, 400);
55490
56891
  let succeeded = 0;
55491
56892
  for (const id of body2.ids) {
55492
56893
  if (deleteAgent(id))
55493
56894
  succeeded++;
55494
56895
  }
55495
- return json4({ succeeded, failed: body2.ids.length - succeeded });
56896
+ return json5({ succeeded, failed: body2.ids.length - succeeded });
55496
56897
  } catch (e) {
55497
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56898
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55498
56899
  }
55499
56900
  }
55500
- async function handleBulkDeleteProjects(req, _ctx, json4) {
56901
+ async function handleBulkDeleteProjects(req, _ctx, json5) {
55501
56902
  try {
55502
56903
  const body2 = await req.json();
55503
56904
  if (!body2.ids?.length || body2.action !== "delete")
55504
- return json4({ error: "Missing ids or invalid action" }, 400);
56905
+ return json5({ error: "Missing ids or invalid action" }, 400);
55505
56906
  let succeeded = 0;
55506
56907
  for (const id of body2.ids) {
55507
56908
  if (deleteProject(id))
55508
56909
  succeeded++;
55509
56910
  }
55510
- return json4({ succeeded, failed: body2.ids.length - succeeded });
56911
+ return json5({ succeeded, failed: body2.ids.length - succeeded });
55511
56912
  } catch (e) {
55512
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56913
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55513
56914
  }
55514
56915
  }
55515
- function handleDoctor(_ctx, json4) {
56916
+ function handleDoctor(_ctx, json5) {
55516
56917
  const { runTodosDoctor: runTodosDoctor2 } = (init_doctor(), __toCommonJS(exports_doctor));
55517
- return json4(runTodosDoctor2({ apply: false }));
56918
+ return json5(runTodosDoctor2({ apply: false }));
55518
56919
  }
55519
- function handleReport(_req, url, _ctx, json4) {
56920
+ function handleReport(_req, url, _ctx, json5) {
55520
56921
  const days = parseInt(url.searchParams.get("days") || "7", 10);
55521
56922
  const projectId = url.searchParams.get("project_id") || undefined;
55522
56923
  const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
@@ -55532,62 +56933,62 @@ function handleReport(_req, url, _ctx, json4) {
55532
56933
  byDay[day] = (byDay[day] || 0) + 1;
55533
56934
  }
55534
56935
  const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
55535
- return json4({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
56936
+ return json5({ days, period_since: since, total: all.length, stats: stats2, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, by_day: byDay });
55536
56937
  }
55537
- function handleActivity(_req, url, _ctx, json4) {
56938
+ function handleActivity(_req, url, _ctx, json5) {
55538
56939
  const limit = parseInt(url.searchParams.get("limit") || "50", 10);
55539
- return json4(getRecentActivity(limit));
56940
+ return json5(getRecentActivity(limit));
55540
56941
  }
55541
- function handleTaskHistory(id, _ctx, json4, url) {
56942
+ function handleTaskHistory(id, _ctx, json5, url) {
55542
56943
  const history = getTaskHistory(id);
55543
56944
  const format = url?.searchParams.get("format") || "compact";
55544
56945
  const limit = parseBoundedLimit(url?.searchParams.get("limit") || null, 20, 500);
55545
- return json4(format === "full" ? history : history.slice(0, limit));
56946
+ return json5(format === "full" ? history : history.slice(0, limit));
55546
56947
  }
55547
- function handleListWebhooks(_ctx, json4) {
55548
- return json4(listWebhooks());
56948
+ function handleListWebhooks(_ctx, json5) {
56949
+ return json5(listWebhooks());
55549
56950
  }
55550
- async function handleCreateWebhook(req, _ctx, json4) {
56951
+ async function handleCreateWebhook(req, _ctx, json5) {
55551
56952
  try {
55552
56953
  const body2 = await req.json();
55553
56954
  if (!body2.url)
55554
- return json4({ error: "Missing url" }, 400);
55555
- return json4(createWebhook(body2), 201);
56955
+ return json5({ error: "Missing url" }, 400);
56956
+ return json5(createWebhook(body2), 201);
55556
56957
  } catch (e) {
55557
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56958
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55558
56959
  }
55559
56960
  }
55560
- function handleDeleteWebhook(id, _ctx, json4) {
56961
+ function handleDeleteWebhook(id, _ctx, json5) {
55561
56962
  const deleted = deleteWebhook(id);
55562
- return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
56963
+ return json5(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
55563
56964
  }
55564
- function handleListTemplates(_ctx, json4) {
55565
- return json4(listTemplates());
56965
+ function handleListTemplates(_ctx, json5) {
56966
+ return json5(listTemplates());
55566
56967
  }
55567
- async function handleCreateTemplate(req, _ctx, json4) {
56968
+ async function handleCreateTemplate(req, _ctx, json5) {
55568
56969
  try {
55569
56970
  const body2 = await req.json();
55570
56971
  if (!body2.name || !body2.title_pattern)
55571
- return json4({ error: "Missing name or title_pattern" }, 400);
55572
- return json4(createTemplate(body2), 201);
56972
+ return json5({ error: "Missing name or title_pattern" }, 400);
56973
+ return json5(createTemplate(body2), 201);
55573
56974
  } catch (e) {
55574
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
56975
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55575
56976
  }
55576
56977
  }
55577
- function handleDeleteTemplate(id, _ctx, json4) {
56978
+ function handleDeleteTemplate(id, _ctx, json5) {
55578
56979
  const deleted = deleteTemplate(id);
55579
- return json4(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
56980
+ return json5(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404);
55580
56981
  }
55581
- function handleListPlans(url, _ctx, json4) {
56982
+ function handleListPlans(url, _ctx, json5) {
55582
56983
  const projectId = url.searchParams.get("project_id") || undefined;
55583
56984
  const plans = listPlans(projectId);
55584
- return json4(plans);
56985
+ return json5(plans);
55585
56986
  }
55586
- async function handleCreatePlan(req, _ctx, json4) {
56987
+ async function handleCreatePlan(req, _ctx, json5) {
55587
56988
  try {
55588
56989
  const body2 = await req.json();
55589
56990
  if (!body2.name)
55590
- return json4({ error: "Missing 'name'" }, 400);
56991
+ return json5({ error: "Missing 'name'" }, 400);
55591
56992
  const plan = createPlan({
55592
56993
  name: body2.name,
55593
56994
  slug: body2.slug,
@@ -55597,49 +56998,49 @@ async function handleCreatePlan(req, _ctx, json4) {
55597
56998
  agent_id: body2.agent_id,
55598
56999
  status: body2.status
55599
57000
  });
55600
- return json4(plan, 201);
57001
+ return json5(plan, 201);
55601
57002
  } catch (e) {
55602
- return json4({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
57003
+ return json5({ error: e instanceof Error ? e.message : "Failed to create plan" }, 500);
55603
57004
  }
55604
57005
  }
55605
- async function handleBulkDeletePlans(req, _ctx, json4) {
57006
+ async function handleBulkDeletePlans(req, _ctx, json5) {
55606
57007
  try {
55607
57008
  const body2 = await req.json();
55608
57009
  if (!body2.ids?.length || body2.action !== "delete")
55609
- return json4({ error: "Missing ids or invalid action" }, 400);
57010
+ return json5({ error: "Missing ids or invalid action" }, 400);
55610
57011
  let succeeded = 0;
55611
57012
  for (const id of body2.ids) {
55612
57013
  if (deletePlan(id))
55613
57014
  succeeded++;
55614
57015
  }
55615
- return json4({ succeeded, failed: body2.ids.length - succeeded });
57016
+ return json5({ succeeded, failed: body2.ids.length - succeeded });
55616
57017
  } catch (e) {
55617
- return json4({ error: e instanceof Error ? e.message : "Failed" }, 500);
57018
+ return json5({ error: e instanceof Error ? e.message : "Failed" }, 500);
55618
57019
  }
55619
57020
  }
55620
- function handleGetPlan(id, _ctx, json4, taskToSummary2) {
57021
+ function handleGetPlan(id, _ctx, json5, taskToSummary2) {
55621
57022
  const plan = getPlan(id);
55622
57023
  if (!plan)
55623
- return json4({ error: "Plan not found" }, 404);
57024
+ return json5({ error: "Plan not found" }, 404);
55624
57025
  const tasks = listTasks({ plan_id: id });
55625
- return json4({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
57026
+ return json5({ ...plan, tasks: tasks.map((t) => taskToSummary2(t)) });
55626
57027
  }
55627
- async function handleUpdatePlan(id, req, _ctx, json4) {
57028
+ async function handleUpdatePlan(id, req, _ctx, json5) {
55628
57029
  try {
55629
57030
  const body2 = await req.json();
55630
57031
  const plan = updatePlan(id, body2);
55631
- return json4(plan);
57032
+ return json5(plan);
55632
57033
  } catch (e) {
55633
- return json4({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
57034
+ return json5({ error: e instanceof Error ? e.message : "Failed to update plan" }, 500);
55634
57035
  }
55635
57036
  }
55636
- function handleDeletePlan(id, _ctx, json4) {
57037
+ function handleDeletePlan(id, _ctx, json5) {
55637
57038
  const deleted = deletePlan(id);
55638
57039
  if (!deleted)
55639
- return json4({ error: "Plan not found" }, 404);
55640
- return json4({ success: true });
57040
+ return json5({ error: "Plan not found" }, 404);
57041
+ return json5({ success: true });
55641
57042
  }
55642
- function handleStaticFiles(path, method, ctx, json4, serveStaticFile2) {
57043
+ function handleStaticFiles(path, method, ctx, json5, serveStaticFile2) {
55643
57044
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
55644
57045
  return null;
55645
57046
  if (path !== "/") {
@@ -55647,7 +57048,7 @@ function handleStaticFiles(path, method, ctx, json4, serveStaticFile2) {
55647
57048
  const resolvedFile = resolve16(filePath);
55648
57049
  const resolvedBase = resolve16(ctx.dashboardDir);
55649
57050
  if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
55650
- return json4({ error: "Forbidden" }, 403);
57051
+ return json5({ error: "Forbidden" }, 403);
55651
57052
  }
55652
57053
  const res2 = serveStaticFile2(filePath);
55653
57054
  if (res2)
@@ -55703,6 +57104,19 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
55703
57104
  TaskManifestReceipt: taskManifestReceiptSchema,
55704
57105
  TaskManifestApplyResult: taskManifestApplyResultSchema,
55705
57106
  TaskManifestApplyResponse: taskManifestApplyResponseSchema,
57107
+ TaskSubtreeTransferExpectedTask: taskSubtreeTransferExpectedTaskSchema,
57108
+ TaskSubtreeTransferSharedPlanSplit: taskSubtreeTransferSharedPlanSplitSchema,
57109
+ TaskSubtreeTransferInspectRequest: taskSubtreeTransferInspectRequestSchema,
57110
+ TaskSubtreeTransferInspection: taskSubtreeTransferInspectionSchema,
57111
+ TaskSubtreeTransferApplyRequest: taskSubtreeTransferApplyRequestSchema,
57112
+ TaskSubtreeTransferTaskImage: taskSubtreeTransferTaskImageSchema,
57113
+ TaskSubtreeTransferPlanImage: taskSubtreeTransferPlanImageSchema,
57114
+ TaskSubtreeTransferImage: taskSubtreeTransferImageSchema,
57115
+ TaskSubtreeTransferReceipt: taskSubtreeTransferReceiptSchema,
57116
+ TaskSubtreeTransferResult: taskSubtreeTransferResultSchema,
57117
+ TaskSubtreeTransferCapability: taskSubtreeTransferCapabilitySchema,
57118
+ TaskSubtreeTransferRollbackRequest: taskSubtreeTransferRollbackRequestSchema,
57119
+ TaskSubtreeTransferReadExactRequest: taskSubtreeTransferReadExactRequestSchema,
55706
57120
  TaskManifestCompensateRequest: taskManifestCompensateRequestSchema,
55707
57121
  TaskManifestCompensationResult: taskManifestCompensationResultSchema,
55708
57122
  TaskManifestCompensateResponse: taskManifestCompensateResponseSchema,
@@ -55907,7 +57321,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
55907
57321
  slug: { type: "string", minLength: 1, pattern: ".*[A-Za-z0-9].*" },
55908
57322
  name: { type: "string" },
55909
57323
  description: { type: "string" },
55910
- metadata: { type: "object", additionalProperties: true }
57324
+ metadata: { type: "object", additionalProperties: true },
57325
+ project_id: { type: "string", nullable: true }
55911
57326
  }
55912
57327
  },
55913
57328
  CreateTaskCommentInput: {
@@ -57051,6 +58466,160 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
57051
58466
  }
57052
58467
  }
57053
58468
  },
58469
+ "/v1/task-subtree-transfer/capability": {
58470
+ get: {
58471
+ operationId: "getTaskSubtreeTransferCapability",
58472
+ summary: "Read the current task-subtree-transfer authority capability and tenant",
58473
+ responses: {
58474
+ "200": {
58475
+ content: {
58476
+ "application/json": {
58477
+ schema: {
58478
+ type: "object",
58479
+ additionalProperties: false,
58480
+ required: ["capability"],
58481
+ properties: {
58482
+ capability: { $ref: "#/components/schemas/TaskSubtreeTransferCapability" }
58483
+ }
58484
+ }
58485
+ }
58486
+ }
58487
+ },
58488
+ "401": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58489
+ "503": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
58490
+ }
58491
+ }
58492
+ },
58493
+ "/v1/task-subtree-transfer/inspect": {
58494
+ post: {
58495
+ operationId: "inspectTaskSubtreeTransfer",
58496
+ summary: "Inspect one exact task subtree and complete source population snapshot",
58497
+ requestBody: {
58498
+ required: true,
58499
+ content: {
58500
+ "application/json": {
58501
+ schema: { $ref: "#/components/schemas/TaskSubtreeTransferInspectRequest" }
58502
+ }
58503
+ }
58504
+ },
58505
+ responses: {
58506
+ "200": {
58507
+ content: {
58508
+ "application/json": {
58509
+ schema: {
58510
+ type: "object",
58511
+ additionalProperties: false,
58512
+ required: ["inspection"],
58513
+ properties: {
58514
+ inspection: { $ref: "#/components/schemas/TaskSubtreeTransferInspection" }
58515
+ }
58516
+ }
58517
+ }
58518
+ }
58519
+ },
58520
+ "400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58521
+ "404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58522
+ "409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
58523
+ }
58524
+ }
58525
+ },
58526
+ "/v1/task-subtree-transfer/apply": {
58527
+ post: {
58528
+ operationId: "applyTaskSubtreeTransfer",
58529
+ summary: "Apply one exact task subtree transfer atomically with CAS and immutable receipt",
58530
+ requestBody: {
58531
+ required: true,
58532
+ content: {
58533
+ "application/json": {
58534
+ schema: { $ref: "#/components/schemas/TaskSubtreeTransferApplyRequest" }
58535
+ }
58536
+ }
58537
+ },
58538
+ responses: {
58539
+ "201": {
58540
+ content: {
58541
+ "application/json": {
58542
+ schema: {
58543
+ type: "object",
58544
+ additionalProperties: false,
58545
+ required: ["result"],
58546
+ properties: {
58547
+ result: { $ref: "#/components/schemas/TaskSubtreeTransferResult" }
58548
+ }
58549
+ }
58550
+ }
58551
+ }
58552
+ },
58553
+ "400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58554
+ "409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58555
+ "503": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
58556
+ }
58557
+ }
58558
+ },
58559
+ "/v1/task-subtree-transfer/read-exact": {
58560
+ post: {
58561
+ operationId: "readExactTaskSubtreeTransfer",
58562
+ summary: "Read one exact immutable task-subtree-transfer receipt",
58563
+ requestBody: {
58564
+ required: true,
58565
+ content: {
58566
+ "application/json": {
58567
+ schema: { $ref: "#/components/schemas/TaskSubtreeTransferReadExactRequest" }
58568
+ }
58569
+ }
58570
+ },
58571
+ responses: {
58572
+ "200": {
58573
+ content: {
58574
+ "application/json": {
58575
+ schema: {
58576
+ type: "object",
58577
+ additionalProperties: false,
58578
+ required: ["result"],
58579
+ properties: {
58580
+ result: { $ref: "#/components/schemas/TaskSubtreeTransferResult" }
58581
+ }
58582
+ }
58583
+ }
58584
+ }
58585
+ },
58586
+ "404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
58587
+ }
58588
+ }
58589
+ },
58590
+ "/v1/task-subtree-transfer/rollback": {
58591
+ post: {
58592
+ operationId: "rollbackTaskSubtreeTransfer",
58593
+ summary: "Rollback one exact task-subtree-transfer receipt with CAS protection",
58594
+ requestBody: {
58595
+ required: true,
58596
+ content: {
58597
+ "application/json": {
58598
+ schema: { $ref: "#/components/schemas/TaskSubtreeTransferRollbackRequest" }
58599
+ }
58600
+ }
58601
+ },
58602
+ responses: {
58603
+ "201": {
58604
+ content: {
58605
+ "application/json": {
58606
+ schema: {
58607
+ type: "object",
58608
+ additionalProperties: false,
58609
+ required: ["result"],
58610
+ properties: {
58611
+ result: { $ref: "#/components/schemas/TaskSubtreeTransferResult" }
58612
+ }
58613
+ }
58614
+ }
58615
+ }
58616
+ },
58617
+ "400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58618
+ "404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
58619
+ "409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
58620
+ }
58621
+ }
58622
+ },
57054
58623
  "/v1/task-manifest/capability": {
57055
58624
  get: {
57056
58625
  operationId: "getTaskManifestCapability",
@@ -58004,7 +59573,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
58004
59573
  }
58005
59574
  });
58006
59575
  }
58007
- var taskSchema, taskManifestBoundsSchema, taskManifestCapabilitySchema, taskManifestCapabilityResponseSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestSchema, taskManifestReceiptSchema, taskManifestApplyResultSchema, taskManifestApplyResponseSchema, taskManifestCompensateRequestSchema, taskManifestCompensationResultSchema, taskManifestCompensateResponseSchema, taskManifestReadExactRequestSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema, projectRegistrationBoundsProperties, projectRegistrationReceiptSchema, projectRegistrationCapabilitySchema, projectRegistrationRequestSchema, projectRegistrationLookupRequestSchema, priorRegistrationAdoptionValidationSchema, projectResourceSchema, projectResourcePageSchema;
59576
+ var taskSchema, taskManifestBoundsSchema, taskManifestCapabilitySchema, taskManifestCapabilityResponseSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestSchema, taskManifestReceiptSchema, taskManifestApplyResultSchema, taskManifestApplyResponseSchema, taskSubtreeTransferExpectedTaskSchema, taskSubtreeTransferSharedPlanSplitSchema, taskSubtreeTransferInspectRequestSchema, taskSubtreeTransferInspectionSchema, taskSubtreeTransferApplyRequestSchema, taskSubtreeTransferTaskImageSchema, taskSubtreeTransferPlanImageSchema, taskSubtreeTransferImageSchema, taskSubtreeTransferReceiptSchema, taskSubtreeTransferResultSchema, taskSubtreeTransferCapabilitySchema, taskSubtreeTransferRollbackRequestSchema, taskSubtreeTransferReadExactRequestSchema, taskManifestCompensateRequestSchema, taskManifestCompensationResultSchema, taskManifestCompensateResponseSchema, taskManifestReadExactRequestSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema, projectRegistrationBoundsProperties, projectRegistrationReceiptSchema, projectRegistrationCapabilitySchema, projectRegistrationRequestSchema, projectRegistrationLookupRequestSchema, priorRegistrationAdoptionValidationSchema, projectResourceSchema, projectResourcePageSchema;
58008
59577
  var init_openapi = __esm(() => {
58009
59578
  init_package_version();
58010
59579
  init_types();
@@ -58282,6 +59851,288 @@ var init_openapi = __esm(() => {
58282
59851
  required: ["result"],
58283
59852
  properties: { result: { $ref: "#/components/schemas/TaskManifestApplyResult" } }
58284
59853
  };
59854
+ taskSubtreeTransferExpectedTaskSchema = {
59855
+ type: "object",
59856
+ additionalProperties: false,
59857
+ required: ["task_id", "version"],
59858
+ properties: {
59859
+ task_id: { type: "string", format: "uuid" },
59860
+ version: { type: "integer", minimum: 1 }
59861
+ }
59862
+ };
59863
+ taskSubtreeTransferSharedPlanSplitSchema = {
59864
+ type: "object",
59865
+ additionalProperties: false,
59866
+ required: ["source_plan_id", "destination_plan_id"],
59867
+ properties: {
59868
+ source_plan_id: { type: "string", format: "uuid" },
59869
+ destination_plan_id: { type: "string", format: "uuid" }
59870
+ }
59871
+ };
59872
+ taskSubtreeTransferInspectRequestSchema = {
59873
+ type: "object",
59874
+ additionalProperties: false,
59875
+ required: [
59876
+ "source_project_id",
59877
+ "destination_project_id",
59878
+ "destination_task_list_id",
59879
+ "root_task_id",
59880
+ "destination_parent_id"
59881
+ ],
59882
+ properties: {
59883
+ source_project_id: { type: "string", format: "uuid" },
59884
+ destination_project_id: { type: "string", format: "uuid" },
59885
+ destination_task_list_id: { type: "string", format: "uuid" },
59886
+ root_task_id: { type: "string", format: "uuid" },
59887
+ destination_parent_id: { type: "string", format: "uuid", nullable: true }
59888
+ }
59889
+ };
59890
+ taskSubtreeTransferInspectionSchema = {
59891
+ type: "object",
59892
+ additionalProperties: false,
59893
+ required: [
59894
+ "source_project_id",
59895
+ "destination_project_id",
59896
+ "destination_task_list_id",
59897
+ "root_task_id",
59898
+ "destination_parent_id",
59899
+ "expected_root_parent_id",
59900
+ "source_population_digest",
59901
+ "expected_tasks",
59902
+ "contained_plan_ids",
59903
+ "shared_plan_ids",
59904
+ "complete"
59905
+ ],
59906
+ properties: {
59907
+ ...taskSubtreeTransferInspectRequestSchema.properties,
59908
+ expected_root_parent_id: { type: "string", format: "uuid", nullable: true },
59909
+ source_population_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
59910
+ expected_tasks: {
59911
+ type: "array",
59912
+ minItems: 1,
59913
+ items: { $ref: "#/components/schemas/TaskSubtreeTransferExpectedTask" }
59914
+ },
59915
+ contained_plan_ids: {
59916
+ type: "array",
59917
+ items: { type: "string", format: "uuid" }
59918
+ },
59919
+ shared_plan_ids: {
59920
+ type: "array",
59921
+ items: { type: "string", format: "uuid" }
59922
+ },
59923
+ complete: { type: "boolean", enum: [true] }
59924
+ }
59925
+ };
59926
+ taskSubtreeTransferApplyRequestSchema = {
59927
+ type: "object",
59928
+ additionalProperties: false,
59929
+ required: [
59930
+ "version",
59931
+ "operation_id",
59932
+ "step_id",
59933
+ "idempotency_key",
59934
+ "precondition_digest",
59935
+ "source_project_id",
59936
+ "destination_project_id",
59937
+ "destination_task_list_id",
59938
+ "root_task_id",
59939
+ "destination_parent_id",
59940
+ "expected_root_parent_id",
59941
+ "source_population_digest",
59942
+ "expected_tasks",
59943
+ "shared_plan_splits"
59944
+ ],
59945
+ properties: {
59946
+ ...taskSubtreeTransferInspectRequestSchema.properties,
59947
+ version: { type: "integer", enum: [1] },
59948
+ operation_id: { type: "string", minLength: 1, maxLength: 200 },
59949
+ step_id: { type: "string", minLength: 1, maxLength: 200 },
59950
+ idempotency_key: { type: "string", minLength: 1, maxLength: 240 },
59951
+ precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
59952
+ expected_root_parent_id: { type: "string", format: "uuid", nullable: true },
59953
+ source_population_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
59954
+ expected_tasks: {
59955
+ type: "array",
59956
+ minItems: 1,
59957
+ items: { $ref: "#/components/schemas/TaskSubtreeTransferExpectedTask" }
59958
+ },
59959
+ shared_plan_splits: {
59960
+ type: "array",
59961
+ items: { $ref: "#/components/schemas/TaskSubtreeTransferSharedPlanSplit" }
59962
+ }
59963
+ }
59964
+ };
59965
+ taskSubtreeTransferTaskImageSchema = {
59966
+ type: "object",
59967
+ additionalProperties: false,
59968
+ required: [
59969
+ "task_id",
59970
+ "project_id",
59971
+ "parent_id",
59972
+ "plan_id",
59973
+ "task_list_id",
59974
+ "version",
59975
+ "updated_at"
59976
+ ],
59977
+ properties: {
59978
+ task_id: { type: "string", format: "uuid" },
59979
+ project_id: { type: "string", format: "uuid", nullable: true },
59980
+ parent_id: { type: "string", format: "uuid", nullable: true },
59981
+ plan_id: { type: "string", format: "uuid", nullable: true },
59982
+ task_list_id: { type: "string", format: "uuid", nullable: true },
59983
+ version: { type: "integer", minimum: 1 },
59984
+ updated_at: { type: "string", format: "date-time" }
59985
+ }
59986
+ };
59987
+ taskSubtreeTransferPlanImageSchema = {
59988
+ type: "object",
59989
+ additionalProperties: false,
59990
+ required: ["plan_id", "project_id", "task_list_id", "updated_at"],
59991
+ properties: {
59992
+ plan_id: { type: "string", format: "uuid" },
59993
+ project_id: { type: "string", format: "uuid", nullable: true },
59994
+ task_list_id: { type: "string", format: "uuid", nullable: true },
59995
+ updated_at: { type: "string", format: "date-time" }
59996
+ }
59997
+ };
59998
+ taskSubtreeTransferImageSchema = {
59999
+ type: "object",
60000
+ additionalProperties: false,
60001
+ required: ["tasks", "plans"],
60002
+ properties: {
60003
+ tasks: {
60004
+ type: "array",
60005
+ items: { $ref: "#/components/schemas/TaskSubtreeTransferTaskImage" }
60006
+ },
60007
+ plans: {
60008
+ type: "array",
60009
+ items: { $ref: "#/components/schemas/TaskSubtreeTransferPlanImage" }
60010
+ }
60011
+ }
60012
+ };
60013
+ taskSubtreeTransferReceiptSchema = {
60014
+ type: "object",
60015
+ additionalProperties: false,
60016
+ required: [
60017
+ "receipt_id",
60018
+ "authority",
60019
+ "route",
60020
+ "schema_version",
60021
+ "kind",
60022
+ "operation_id",
60023
+ "step_id",
60024
+ "idempotency_key",
60025
+ "request_digest",
60026
+ "precondition_digest",
60027
+ "result_digest",
60028
+ "apply_receipt_id",
60029
+ "source_project_id",
60030
+ "destination_project_id",
60031
+ "destination_task_list_id",
60032
+ "root_task_id",
60033
+ "source_population_digest",
60034
+ "prior_image",
60035
+ "post_image",
60036
+ "shared_plan_splits",
60037
+ "created_at"
60038
+ ],
60039
+ properties: {
60040
+ receipt_id: { type: "string", format: "uuid" },
60041
+ authority: { type: "string", enum: ["todos"] },
60042
+ route: { type: "string", enum: ["todos.task-subtree-transfer.v1"] },
60043
+ schema_version: { type: "integer", enum: [1] },
60044
+ kind: { type: "string", enum: ["apply", "rollback"] },
60045
+ operation_id: { type: "string" },
60046
+ step_id: { type: "string" },
60047
+ idempotency_key: { type: "string" },
60048
+ request_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
60049
+ precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
60050
+ result_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
60051
+ apply_receipt_id: { type: "string", format: "uuid", nullable: true },
60052
+ source_project_id: { type: "string", format: "uuid" },
60053
+ destination_project_id: { type: "string", format: "uuid" },
60054
+ destination_task_list_id: { type: "string", format: "uuid" },
60055
+ root_task_id: { type: "string", format: "uuid" },
60056
+ source_population_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
60057
+ prior_image: { $ref: "#/components/schemas/TaskSubtreeTransferImage" },
60058
+ post_image: { $ref: "#/components/schemas/TaskSubtreeTransferImage" },
60059
+ shared_plan_splits: {
60060
+ type: "array",
60061
+ items: { $ref: "#/components/schemas/TaskSubtreeTransferSharedPlanSplit" }
60062
+ },
60063
+ created_at: { type: "string", format: "date-time" }
60064
+ }
60065
+ };
60066
+ taskSubtreeTransferResultSchema = {
60067
+ type: "object",
60068
+ additionalProperties: false,
60069
+ required: ["duplicate", "receipt", "moved_task_ids", "moved_plan_ids", "complete"],
60070
+ properties: {
60071
+ duplicate: { type: "boolean" },
60072
+ receipt: { $ref: "#/components/schemas/TaskSubtreeTransferReceipt" },
60073
+ moved_task_ids: { type: "array", items: { type: "string", format: "uuid" } },
60074
+ moved_plan_ids: { type: "array", items: { type: "string", format: "uuid" } },
60075
+ complete: { type: "boolean", enum: [true] }
60076
+ }
60077
+ };
60078
+ taskSubtreeTransferCapabilitySchema = {
60079
+ type: "object",
60080
+ additionalProperties: false,
60081
+ required: [
60082
+ "authority",
60083
+ "route",
60084
+ "schema_version",
60085
+ "tenant_id",
60086
+ "backend",
60087
+ "exact_descendant_closure",
60088
+ "complete_source_population_digest",
60089
+ "per_task_version_cas",
60090
+ "explicit_shared_plan_splits",
60091
+ "atomic_apply",
60092
+ "immutable_forward_inverse_receipts",
60093
+ "prior_image_receipts",
60094
+ "cas_protected_rollback",
60095
+ "preserves_descendant_parent_ids",
60096
+ "preserves_task_and_relation_identities"
60097
+ ],
60098
+ properties: {
60099
+ authority: { type: "string", enum: ["todos"] },
60100
+ route: { type: "string", enum: ["todos.task-subtree-transfer.v1"] },
60101
+ schema_version: { type: "integer", enum: [1] },
60102
+ tenant_id: { type: "string", minLength: 1, maxLength: 200 },
60103
+ backend: { type: "string", enum: ["sqlite", "postgresql", "http"] },
60104
+ exact_descendant_closure: { type: "boolean", enum: [true] },
60105
+ complete_source_population_digest: { type: "boolean", enum: [true] },
60106
+ per_task_version_cas: { type: "boolean", enum: [true] },
60107
+ explicit_shared_plan_splits: { type: "boolean", enum: [true] },
60108
+ atomic_apply: { type: "boolean", enum: [true] },
60109
+ immutable_forward_inverse_receipts: { type: "boolean", enum: [true] },
60110
+ prior_image_receipts: { type: "boolean", enum: [true] },
60111
+ cas_protected_rollback: { type: "boolean", enum: [true] },
60112
+ preserves_descendant_parent_ids: { type: "boolean", enum: [true] },
60113
+ preserves_task_and_relation_identities: { type: "boolean", enum: [true] }
60114
+ }
60115
+ };
60116
+ taskSubtreeTransferRollbackRequestSchema = {
60117
+ type: "object",
60118
+ additionalProperties: false,
60119
+ required: ["receipt_id", "operation_id", "step_id", "idempotency_key", "precondition_digest"],
60120
+ properties: {
60121
+ receipt_id: { type: "string", format: "uuid" },
60122
+ operation_id: { type: "string", minLength: 1, maxLength: 200 },
60123
+ step_id: { type: "string", minLength: 1, maxLength: 200 },
60124
+ idempotency_key: { type: "string", minLength: 1, maxLength: 240 },
60125
+ precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" }
60126
+ }
60127
+ };
60128
+ taskSubtreeTransferReadExactRequestSchema = {
60129
+ type: "object",
60130
+ additionalProperties: false,
60131
+ required: ["receipt_id"],
60132
+ properties: {
60133
+ receipt_id: { type: "string", format: "uuid" }
60134
+ }
60135
+ };
58285
60136
  taskManifestCompensateRequestSchema = {
58286
60137
  type: "object",
58287
60138
  additionalProperties: false,
@@ -58960,8 +60811,8 @@ var exports_pr_groups = {};
58960
60811
  __export(exports_pr_groups, {
58961
60812
  handlePrGroupHttpRequest: () => handlePrGroupHttpRequest
58962
60813
  });
58963
- function json4(body2, status2 = 200) {
58964
- return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS3 });
60814
+ function json5(body2, status3 = 200) {
60815
+ return new Response(JSON.stringify(body2), { status: status3, headers: JSON_HEADERS4 });
58965
60816
  }
58966
60817
  function errorStatus2(error) {
58967
60818
  switch (error.code) {
@@ -58995,26 +60846,26 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
58995
60846
  const method = req.method.toUpperCase();
58996
60847
  try {
58997
60848
  if (!groupId && method === "POST" && action === undefined) {
58998
- return json4({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
60849
+ return json5({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
58999
60850
  }
59000
60851
  if (groupId === "admit" && !action) {
59001
60852
  if (method !== "POST")
59002
- return json4({ error: "method not allowed" }, 405);
60853
+ return json5({ error: "method not allowed" }, 405);
59003
60854
  const body2 = await readJson2(req);
59004
60855
  if (!body2)
59005
- return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
59006
- return json4(await ledger.admit(body2), 201);
60856
+ return json5({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
60857
+ return json5(await ledger.admit(body2), 201);
59007
60858
  }
59008
60859
  if (!groupId)
59009
- return json4({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
60860
+ return json5({ error: "PR group id is required", code: "PR_GROUP_INVALID_INPUT" }, 400);
59010
60861
  if (!action && method === "GET") {
59011
- return json4({ view: await ledger.get(groupId) });
60862
+ return json5({ view: await ledger.get(groupId) });
59012
60863
  }
59013
60864
  if (action === "events") {
59014
60865
  if (method === "GET") {
59015
60866
  const limit = url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined;
59016
60867
  const afterSequence = url.searchParams.has("after_sequence") ? Number(url.searchParams.get("after_sequence")) : undefined;
59017
- return json4({
60868
+ return json5({
59018
60869
  history: await ledger.events(groupId, {
59019
60870
  ...limit !== undefined ? { limit } : {},
59020
60871
  ...afterSequence !== undefined ? { after_sequence: afterSequence } : {}
@@ -59024,72 +60875,49 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
59024
60875
  if (method === "POST") {
59025
60876
  const body2 = await readJson2(req);
59026
60877
  if (!body2)
59027
- return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
59028
- return json4(await ledger.append({
60878
+ return json5({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
60879
+ return json5(await ledger.append({
59029
60880
  ...body2,
59030
60881
  group_id: groupId,
59031
60882
  authenticated_actor_id: principal?.actor_id ?? undefined,
59032
60883
  authenticated_actor_run_id: principal?.actor_run_id ?? undefined
59033
60884
  }), 201);
59034
60885
  }
59035
- return json4({ error: "method not allowed" }, 405);
60886
+ return json5({ error: "method not allowed" }, 405);
59036
60887
  }
59037
60888
  if (action === "recover") {
59038
60889
  if (method !== "POST")
59039
- return json4({ error: "method not allowed" }, 405);
60890
+ return json5({ error: "method not allowed" }, 405);
59040
60891
  const body2 = await readJson2(req);
59041
60892
  if (!body2)
59042
- return json4({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
59043
- return json4(await ledger.recover({
60893
+ return json5({ error: "invalid JSON body", code: "PR_GROUP_INVALID_INPUT" }, 400);
60894
+ return json5(await ledger.recover({
59044
60895
  ...body2,
59045
60896
  group_id: groupId
59046
60897
  }), 201);
59047
60898
  }
59048
- return json4({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
60899
+ return json5({ error: "unknown PR-group route", code: "PR_GROUP_NOT_FOUND" }, 404);
59049
60900
  } catch (cause) {
59050
60901
  if (cause instanceof PrGroupLedgerError) {
59051
- return json4({
60902
+ return json5({
59052
60903
  error: cause.message,
59053
60904
  code: cause.code,
59054
60905
  details: cause.details,
59055
60906
  authoritative: true
59056
60907
  }, errorStatus2(cause));
59057
60908
  }
59058
- return json4({
60909
+ return json5({
59059
60910
  error: cause instanceof Error ? cause.message : "internal PR-group error",
59060
60911
  code: "PR_GROUP_ATOMICITY_UNAVAILABLE"
59061
60912
  }, 500);
59062
60913
  }
59063
60914
  }
59064
- var JSON_HEADERS3;
60915
+ var JSON_HEADERS4;
59065
60916
  var init_pr_groups = __esm(() => {
59066
60917
  init_types3();
59067
- JSON_HEADERS3 = { "Content-Type": "application/json" };
60918
+ JSON_HEADERS4 = { "Content-Type": "application/json" };
59068
60919
  });
59069
60920
 
59070
- // src/lib/comment-cursor.ts
59071
- function encodeCommentCursor(comment2) {
59072
- return Buffer.from(JSON.stringify({ created_at: comment2.created_at, id: comment2.id }), "utf8").toString("base64url");
59073
- }
59074
- function decodeCommentCursor(value) {
59075
- if (value.length > MAX_COMMENT_CURSOR_LENGTH)
59076
- throw new Error("invalid comment cursor");
59077
- let parsed;
59078
- try {
59079
- parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
59080
- } catch {
59081
- throw new Error("invalid comment cursor");
59082
- }
59083
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
59084
- throw new Error("invalid comment cursor");
59085
- const cursor = parsed;
59086
- if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
59087
- throw new Error("invalid comment cursor");
59088
- }
59089
- return { created_at: cursor["created_at"], id: cursor["id"] };
59090
- }
59091
- var MAX_COMMENT_CURSOR_LENGTH = 1024;
59092
-
59093
60921
  // src/lib/project-task-list-ensure.ts
59094
60922
  import { createHash as createHash17 } from "crypto";
59095
60923
  function canonicalJson2(value) {
@@ -59099,11 +60927,11 @@ function canonicalJson2(value) {
59099
60927
  return `[${value.map(canonicalJson2).join(",")}]`;
59100
60928
  return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonicalJson2(item)}`).join(",")}}`;
59101
60929
  }
59102
- function digest2(value) {
60930
+ function digest3(value) {
59103
60931
  return createHash17("sha256").update(canonicalJson2(value)).digest("hex");
59104
60932
  }
59105
60933
  function deriveIdempotencyKey(projectId, slug) {
59106
- return `ptlk_${digest2({ project_id: projectId, slug }).slice(0, 48)}`;
60934
+ return `ptlk_${digest3({ project_id: projectId, slug }).slice(0, 48)}`;
59107
60935
  }
59108
60936
  function normalizeIdempotencyKey(value, projectId, slug) {
59109
60937
  const key2 = value?.trim() || deriveIdempotencyKey(projectId, slug);
@@ -59113,12 +60941,12 @@ function normalizeIdempotencyKey(value, projectId, slug) {
59113
60941
  return key2;
59114
60942
  }
59115
60943
  function receiptId2(projectId, slug, idempotencyKey2) {
59116
- return `ptlr_${digest2({ project_id: projectId, slug, idempotency_key: idempotencyKey2 }).slice(0, 48)}`;
60944
+ return `ptlr_${digest3({ project_id: projectId, slug, idempotency_key: idempotencyKey2 }).slice(0, 48)}`;
59117
60945
  }
59118
60946
  function semanticListDigest(list) {
59119
60947
  const metadata = { ...list.metadata ?? {} };
59120
60948
  delete metadata[RECEIPT_METADATA_KEY];
59121
- return digest2({
60949
+ return digest3({
59122
60950
  project_id: list.project_id,
59123
60951
  slug: list.slug,
59124
60952
  name: list.name,
@@ -59147,7 +60975,7 @@ function receiptFor(store, project, list, idempotencyKey2) {
59147
60975
  }
59148
60976
  return {
59149
60977
  schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
59150
- receipt_id: owned ? marker.receipt_id : `ptlr_existing_${digest2({ project_id: project.id, task_list_id: list.id }).slice(0, 39)}`,
60978
+ receipt_id: owned ? marker.receipt_id : `ptlr_existing_${digest3({ project_id: project.id, task_list_id: list.id }).slice(0, 39)}`,
59151
60979
  idempotency_key: owned ? marker.idempotency_key : idempotencyKey2,
59152
60980
  project_id: project.id,
59153
60981
  task_list_id: list.id,
@@ -59326,7 +61154,7 @@ async function rollbackProjectTaskListEnsure(store, projectId, options) {
59326
61154
  project_id: project.id,
59327
61155
  task_list_id: list.id,
59328
61156
  accepted_receipt_id: options.receipt_id,
59329
- rollback_receipt_id: `ptlr_inverse_${digest2({ accepted_receipt_id: options.receipt_id }).slice(0, 38)}`,
61157
+ rollback_receipt_id: `ptlr_inverse_${digest3({ accepted_receipt_id: options.receipt_id }).slice(0, 38)}`,
59330
61158
  removed_at: new Date().toISOString()
59331
61159
  };
59332
61160
  }
@@ -59421,11 +61249,11 @@ __export(exports_v1, {
59421
61249
  handleV1Request: () => handleV1Request,
59422
61250
  countSnapshotRecords: () => countSnapshotRecords
59423
61251
  });
59424
- function json5(body2, status2 = 200) {
59425
- return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS4 });
61252
+ function json6(body2, status3 = 200) {
61253
+ return new Response(JSON.stringify(body2), { status: status3, headers: JSON_HEADERS5 });
59426
61254
  }
59427
- function error(status2, message, extra) {
59428
- return json5({ error: message, ...extra ?? {} }, status2);
61255
+ function error(status3, message, extra) {
61256
+ return json6({ error: message, ...extra ?? {} }, status3);
59429
61257
  }
59430
61258
  function enumQueryParam(url, name, vocabulary) {
59431
61259
  const raw = url.searchParams.get(name);
@@ -59764,6 +61592,9 @@ function contextFromPrincipal(principal, body2) {
59764
61592
  function redactComment3(comment2) {
59765
61593
  return { ...comment2, content: redactEvidenceText(comment2.content) };
59766
61594
  }
61595
+ function redactPlanComment2(comment2) {
61596
+ return { ...comment2, content: redactEvidenceText(comment2.content) };
61597
+ }
59767
61598
  function normalizeImportSnapshot(raw) {
59768
61599
  const body2 = raw && typeof raw === "object" ? raw : {};
59769
61600
  const arr = (v) => Array.isArray(v) ? v : [];
@@ -59872,6 +61703,9 @@ async function handleV1Request(req, url, dependencies = {}) {
59872
61703
  if (path === "/v1/task-manifest" || path.startsWith("/v1/task-manifest/")) {
59873
61704
  return handleTodosTaskManifestHttpRequest(req, url, (dependencies.getTaskManifestAuthority ?? getCloudTaskManifestAuthority)());
59874
61705
  }
61706
+ if (path === "/v1/task-subtree-transfer" || path.startsWith("/v1/task-subtree-transfer/")) {
61707
+ return handleTodosTaskSubtreeTransferHttpRequest(req, url, (dependencies.getTaskSubtreeTransferAuthority ?? getCloudTaskSubtreeTransferAuthority)());
61708
+ }
59875
61709
  const store = (dependencies.getStorageAdapter ?? getCloudStorageAdapter)();
59876
61710
  const segments = path.split("/").filter(Boolean);
59877
61711
  const resource = segments[1];
@@ -59893,7 +61727,7 @@ async function handleV1Request(req, url, dependencies = {}) {
59893
61727
  const presentSet = new Set(found.map((t) => t.id));
59894
61728
  const present = ids2.filter((i) => presentSet.has(i));
59895
61729
  const missing = ids2.filter((i) => !presentSet.has(i));
59896
- return json5({
61730
+ return json6({
59897
61731
  requested: ids2.length,
59898
61732
  present_count: present.length,
59899
61733
  missing_count: missing.length,
@@ -59943,11 +61777,11 @@ async function handleV1Request(req, url, dependencies = {}) {
59943
61777
  }
59944
61778
  if (!existing) {
59945
61779
  const task3 = await store.tasks.create({ ...fields, title: body2.title }, contextFromPrincipal(principal, body2));
59946
- return json5({ task: task3, created: true }, 201);
61780
+ return json6({ task: task3, created: true }, 201);
59947
61781
  }
59948
61782
  try {
59949
61783
  const task3 = await store.tasks.update(existing.id, { ...fields, version: existing.version }, contextFromPrincipal(principal, body2));
59950
- return json5({ task: task3, created: false });
61784
+ return json6({ task: task3, created: false });
59951
61785
  } catch (e) {
59952
61786
  const msg = e.message || "";
59953
61787
  if (msg.includes("version conflict"))
@@ -59995,7 +61829,7 @@ async function handleV1Request(req, url, dependencies = {}) {
59995
61829
  const tasks = await store.tasks.list(filter);
59996
61830
  const { limit: _l, offset: _o, ...countFilter } = filter;
59997
61831
  const total = await store.tasks.count(countFilter);
59998
- return json5({ tasks, count: tasks.length, total });
61832
+ return json6({ tasks, count: tasks.length, total });
59999
61833
  }
60000
61834
  if (method === "POST") {
60001
61835
  const body2 = await readJson3(req);
@@ -60020,7 +61854,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60020
61854
  if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (body2.parent_id ?? null) || (persisted.plan_id ?? null) !== (body2.plan_id ?? null) || body2.created_by !== undefined && persisted.created_by !== body2.created_by) {
60021
61855
  return error(500, "TASK_CREATE_PERSISTENCE_UNVERIFIED: task create was acknowledged but authoritative readback did not return the same stored task id, parent_id, plan_id, and explicit created_by", { code: "TASK_CREATE_PERSISTENCE_UNVERIFIED" });
60022
61856
  }
60023
- return json5({ task: persisted }, 201);
61857
+ return json6({ task: persisted }, 201);
60024
61858
  }
60025
61859
  return error(405, `method ${method} not allowed on /v1/tasks`);
60026
61860
  }
@@ -60062,7 +61896,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60062
61896
  new_holder: body3.new_holder,
60063
61897
  reason: body3.reason
60064
61898
  }, contextFromPrincipal(principal));
60065
- return json5({ receipt });
61899
+ return json6({ receipt });
60066
61900
  }
60067
61901
  if (action === "comments") {
60068
61902
  if (method === "GET") {
@@ -60076,7 +61910,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60076
61910
  if (legacyPage.length > LEGACY_COMMENT_RESPONSE_LIMIT) {
60077
61911
  return error(426, "task has too many comments for this client; upgrade @hasna/todos to use cursor pagination");
60078
61912
  }
60079
- return json5({
61913
+ return json6({
60080
61914
  comments: legacyPage,
60081
61915
  count: legacyPage.length,
60082
61916
  has_more: false,
@@ -60101,7 +61935,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60101
61935
  const page = (await store.audit.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactComment3);
60102
61936
  const hasMore = page.length > limit;
60103
61937
  const comments = hasMore ? page.slice(1) : page;
60104
- return json5({
61938
+ return json6({
60105
61939
  comments,
60106
61940
  count: comments.length,
60107
61941
  has_more: hasMore,
@@ -60124,7 +61958,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60124
61958
  type: body3.type,
60125
61959
  progress_pct: body3.progress_pct
60126
61960
  }, contextFromPrincipal(principal, body3));
60127
- return json5({ comment: redactComment3(comment2) }, 201);
61961
+ return json6({ comment: redactComment3(comment2) }, 201);
60128
61962
  }
60129
61963
  return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
60130
61964
  }
@@ -60134,7 +61968,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60134
61968
  if (!await store.tasks.get(id))
60135
61969
  return error(404, "task not found");
60136
61970
  const history = await store.audit.getTaskHistory(id);
60137
- return json5({ history, count: history.length });
61971
+ return json6({ history, count: history.length });
60138
61972
  }
60139
61973
  if (action === "lock" || action === "unlock") {
60140
61974
  if (method !== "POST")
@@ -60146,7 +61980,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60146
61980
  if (typeof store.tasks.lock !== "function")
60147
61981
  return error(501, "task locking is not supported by this storage backend");
60148
61982
  const agentId3 = body3.agent_id || principal.agent || "todos-serve";
60149
- return json5({ result: await store.tasks.lock(id, agentId3) });
61983
+ return json6({ result: await store.tasks.lock(id, agentId3) });
60150
61984
  }
60151
61985
  if (typeof store.tasks.unlock !== "function")
60152
61986
  return error(501, "task unlocking is not supported by this storage backend");
@@ -60154,7 +61988,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60154
61988
  if (!principal.scopes.includes("todos:*"))
60155
61989
  return error(403, "force unlock requires todos:* scope");
60156
61990
  const released2 = await store.tasks.unlock(id);
60157
- return json5({ success: released2 });
61991
+ return json6({ success: released2 });
60158
61992
  }
60159
61993
  if (body3.agent_id && body3.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
60160
61994
  return error(403, "unlock agent_id must match the authenticated agent");
@@ -60163,7 +61997,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60163
61997
  if (!agentId2)
60164
61998
  return error(403, "unlock requires an agent-bound key or force=true");
60165
61999
  const released = await store.tasks.unlock(id, agentId2);
60166
- return json5({ success: released });
62000
+ return json6({ success: released });
60167
62001
  }
60168
62002
  if (action === "dependencies") {
60169
62003
  if (!store.dependencies)
@@ -60172,7 +62006,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60172
62006
  if (!await store.tasks.get(id))
60173
62007
  return error(404, "task not found");
60174
62008
  const edges = await store.dependencies.list(id);
60175
- return json5(edges);
62009
+ return json6(edges);
60176
62010
  }
60177
62011
  if (method === "POST") {
60178
62012
  const body3 = await readJson3(req) ?? {};
@@ -60181,7 +62015,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60181
62015
  }
60182
62016
  try {
60183
62017
  const dependency2 = await store.dependencies.add(id, body3.depends_on, contextFromPrincipal(principal));
60184
- return json5({ dependency: dependency2 }, 201);
62018
+ return json6({ dependency: dependency2 }, 201);
60185
62019
  } catch (e) {
60186
62020
  const msg = e.message || "";
60187
62021
  if (msg.includes("not found"))
@@ -60195,7 +62029,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60195
62029
  if (!subId)
60196
62030
  return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
60197
62031
  const removed = await store.dependencies.remove(id, subId);
60198
- return json5({ removed });
62032
+ return json6({ removed });
60199
62033
  }
60200
62034
  return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
60201
62035
  }
@@ -60206,7 +62040,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60206
62040
  if (!await store.tasks.get(id))
60207
62041
  return error(404, "task not found");
60208
62042
  const verifications = await store.verifications.list(id);
60209
- return json5({ verifications, count: verifications.length });
62043
+ return json6({ verifications, count: verifications.length });
60210
62044
  }
60211
62045
  if (method === "POST") {
60212
62046
  const body3 = await readJson3(req) ?? {};
@@ -60222,7 +62056,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60222
62056
  artifact_path: body3.artifact_path,
60223
62057
  agent_id: body3.agent_id
60224
62058
  }, contextFromPrincipal(principal, body3));
60225
- return json5({ verification: verification2 }, 201);
62059
+ return json6({ verification: verification2 }, 201);
60226
62060
  } catch (e) {
60227
62061
  const msg = e.message || "";
60228
62062
  if (msg.includes("not found"))
@@ -60239,7 +62073,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60239
62073
  if (!await store.tasks.get(id))
60240
62074
  return error(404, "task not found");
60241
62075
  const commits = await store.commits.list(id);
60242
- return json5({ commits, count: commits.length });
62076
+ return json6({ commits, count: commits.length });
60243
62077
  }
60244
62078
  if (method === "POST") {
60245
62079
  const body3 = await readJson3(req) ?? {};
@@ -60253,7 +62087,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60253
62087
  author: body3.author,
60254
62088
  files_changed: Array.isArray(body3.files_changed) ? body3.files_changed : undefined
60255
62089
  }, contextFromPrincipal(principal));
60256
- return json5({ commit }, 201);
62090
+ return json6({ commit }, 201);
60257
62091
  } catch (e) {
60258
62092
  const msg = e.message || "";
60259
62093
  if (msg.includes("not found"))
@@ -60270,7 +62104,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60270
62104
  if (!await store.tasks.get(id))
60271
62105
  return error(404, "task not found");
60272
62106
  const refs = await store.gitRefs.list(id);
60273
- return json5({ refs, count: refs.length });
62107
+ return json6({ refs, count: refs.length });
60274
62108
  }
60275
62109
  if (method === "POST") {
60276
62110
  const body3 = await readJson3(req) ?? {};
@@ -60286,7 +62120,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60286
62120
  provider: body3.provider,
60287
62121
  metadata: body3.metadata
60288
62122
  }, contextFromPrincipal(principal));
60289
- return json5({ ref }, 201);
62123
+ return json6({ ref }, 201);
60290
62124
  } catch (e) {
60291
62125
  const msg = e.message || "";
60292
62126
  if (msg.includes("not found"))
@@ -60302,13 +62136,13 @@ async function handleV1Request(req, url, dependencies = {}) {
60302
62136
  const body2 = actionJson.value && typeof actionJson.value === "object" && !Array.isArray(actionJson.value) ? actionJson.value : {};
60303
62137
  const agentId = typeof body2.agent_id === "string" ? body2.agent_id : principal.agent || "todos-serve";
60304
62138
  if (action === "start" && method === "POST") {
60305
- return json5({ task: await store.tasks.start(id, agentId) });
62139
+ return json6({ task: await store.tasks.start(id, agentId) });
60306
62140
  }
60307
62141
  if (action === "complete" && method === "POST") {
60308
62142
  const parsed = validateTaskCompletion(actionJson.value);
60309
62143
  if (!parsed.ok)
60310
62144
  return error(400, parsed.message);
60311
- return json5({
62145
+ return json6({
60312
62146
  task: await store.tasks.complete(id, parsed.agentId || principal.agent || "todos-serve", parsed.options, contextFromPrincipal(principal, body2))
60313
62147
  });
60314
62148
  }
@@ -60316,12 +62150,12 @@ async function handleV1Request(req, url, dependencies = {}) {
60316
62150
  const parsed = validateTaskFailure(actionJson.value);
60317
62151
  if (!parsed.ok)
60318
62152
  return error(400, parsed.message);
60319
- return json5({
62153
+ return json6({
60320
62154
  result: await store.tasks.fail(id, parsed.agentId || principal.agent || "todos-serve", parsed.reason, { retry: parsed.retry }, contextFromPrincipal(principal, body2))
60321
62155
  });
60322
62156
  }
60323
62157
  if (action === "claim" && method === "POST") {
60324
- return json5({ task: await store.tasks.claimNext(agentId, {}) });
62158
+ return json6({ task: await store.tasks.claimNext(agentId, {}) });
60325
62159
  }
60326
62160
  return error(404, `unknown task action: ${action}`);
60327
62161
  }
@@ -60344,7 +62178,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60344
62178
  throw e;
60345
62179
  }
60346
62180
  }
60347
- return task3 ? json5({ task: task3 }) : error(404, "task not found");
62181
+ return task3 ? json6({ task: task3 }) : error(404, "task not found");
60348
62182
  }
60349
62183
  if (method === "PATCH" || method === "PUT") {
60350
62184
  const rawBody = await readJson3(req);
@@ -60363,7 +62197,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60363
62197
  };
60364
62198
  try {
60365
62199
  const task3 = await store.tasks.update(id, patch);
60366
- return task3 ? json5({ task: task3 }) : error(404, "task not found");
62200
+ return task3 ? json6({ task: task3 }) : error(404, "task not found");
60367
62201
  } catch (e) {
60368
62202
  const msg = e.message || "";
60369
62203
  if (msg.includes("version conflict"))
@@ -60373,7 +62207,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60373
62207
  }
60374
62208
  if (method === "DELETE") {
60375
62209
  await store.tasks.delete(id, contextFromPrincipal(principal));
60376
- return json5({ deleted: true, id });
62210
+ return json6({ deleted: true, id });
60377
62211
  }
60378
62212
  return error(405, `method ${method} not allowed on /v1/tasks/:id`);
60379
62213
  }
@@ -60381,7 +62215,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60381
62215
  if (!id) {
60382
62216
  if (method === "GET") {
60383
62217
  const projects = await store.projects.list();
60384
- return json5({ projects, count: projects.length });
62218
+ return json6({ projects, count: projects.length });
60385
62219
  }
60386
62220
  if (method === "POST") {
60387
62221
  const body2 = await readJson3(req);
@@ -60391,13 +62225,13 @@ async function handleV1Request(req, url, dependencies = {}) {
60391
62225
  if (!validated.ok)
60392
62226
  return error(400, validated.message);
60393
62227
  const project = await store.projects.create(validated.input, contextFromPrincipal(principal));
60394
- return json5({ project }, 201);
62228
+ return json6({ project }, 201);
60395
62229
  }
60396
62230
  return error(405, `method ${method} not allowed on /v1/projects`);
60397
62231
  }
60398
62232
  if (action === "task-list" && subId === "ensure") {
60399
62233
  if (method === "GET") {
60400
- return json5(await planProjectTaskListEnsure(store, id));
62234
+ return json6(await planProjectTaskListEnsure(store, id));
60401
62235
  }
60402
62236
  if (method !== "POST") {
60403
62237
  return error(405, `method ${method} not allowed on /v1/projects/:id/task-list/ensure`);
@@ -60418,7 +62252,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60418
62252
  expected_project_revision: body2.expected_project_revision,
60419
62253
  ...typeof body2.idempotency_key === "string" ? { idempotency_key: body2.idempotency_key } : {}
60420
62254
  });
60421
- return json5(result, result.action === "created" ? 201 : 200);
62255
+ return json6(result, result.action === "created" ? 201 : 200);
60422
62256
  }
60423
62257
  if (action === "task-list" && subId === "rollback") {
60424
62258
  if (method !== "POST") {
@@ -60436,7 +62270,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60436
62270
  if (typeof body2.expected_task_list_revision !== "string" || !body2.expected_task_list_revision.trim()) {
60437
62271
  return error(400, "expected_task_list_revision must be a non-empty string from the accepted receipt");
60438
62272
  }
60439
- return json5(await rollbackProjectTaskListEnsure(store, id, {
62273
+ return json6(await rollbackProjectTaskListEnsure(store, id, {
60440
62274
  receipt_id: body2.receipt_id,
60441
62275
  expected_task_list_revision: body2.expected_task_list_revision
60442
62276
  }));
@@ -60454,11 +62288,11 @@ async function handleV1Request(req, url, dependencies = {}) {
60454
62288
  const unknownField = Object.keys(body2).find((key2) => !["new_slug", "name"].includes(key2));
60455
62289
  if (unknownField)
60456
62290
  return error(400, `unknown project rename field: ${unknownField}`);
60457
- return json5(await store.projects.rename(id, body2, contextFromPrincipal(principal)));
62291
+ return json6(await store.projects.rename(id, body2, contextFromPrincipal(principal)));
60458
62292
  }
60459
62293
  if (method === "GET") {
60460
62294
  const project = await store.projects.get(id);
60461
- return project ? json5({ project }) : error(404, "project not found");
62295
+ return project ? json6({ project }) : error(404, "project not found");
60462
62296
  }
60463
62297
  if (method === "PATCH" || method === "PUT") {
60464
62298
  const body2 = await readJson3(req);
@@ -60470,18 +62304,18 @@ async function handleV1Request(req, url, dependencies = {}) {
60470
62304
  if (!await store.projects.get(id))
60471
62305
  return error(404, "project not found");
60472
62306
  const project = await store.projects.update(id, validated.patch);
60473
- return json5({ project });
62307
+ return json6({ project });
60474
62308
  }
60475
62309
  if (method === "DELETE") {
60476
62310
  await store.projects.delete(id, contextFromPrincipal(principal));
60477
- return json5({ deleted: true, id });
62311
+ return json6({ deleted: true, id });
60478
62312
  }
60479
62313
  return error(405, `method ${method} not allowed on /v1/projects/:id`);
60480
62314
  }
60481
62315
  if (resource === "plans") {
60482
62316
  if (!id && method === "GET") {
60483
62317
  const plans = await store.plans.list(url.searchParams.get("project_id") ?? undefined);
60484
- return json5({ plans, count: plans.length });
62318
+ return json6({ plans, count: plans.length });
60485
62319
  }
60486
62320
  if (!id && method === "POST") {
60487
62321
  const body2 = await readJson3(req);
@@ -60499,14 +62333,14 @@ async function handleV1Request(req, url, dependencies = {}) {
60499
62333
  }
60500
62334
  }
60501
62335
  const plan = await store.plans.create(validated.input, contextFromPrincipal(principal, validated.input));
60502
- return json5({ plan }, 201);
62336
+ return json6({ plan }, 201);
60503
62337
  }
60504
62338
  if (id && action === "project-link" && !subId) {
60505
62339
  if (method === "GET") {
60506
62340
  const projectId = url.searchParams.get("project_id");
60507
62341
  if (!projectId?.trim())
60508
62342
  return error(400, "project_id query parameter is required");
60509
- return json5(await planPlanProjectLink(store, id, projectId));
62343
+ return json6(await planPlanProjectLink(store, id, projectId));
60510
62344
  }
60511
62345
  if (method !== "POST")
60512
62346
  return error(405, `method ${method} not allowed on /v1/plans/:id/project-link`);
@@ -60527,7 +62361,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60527
62361
  expected_project_revision: body2.expected_project_revision,
60528
62362
  idempotency_key: body2.idempotency_key
60529
62363
  });
60530
- return json5(result, result.action === "linked" ? 201 : 200);
62364
+ return json6(result, result.action === "linked" ? 201 : 200);
60531
62365
  }
60532
62366
  if (id && action === "project-link" && subId === "rollback") {
60533
62367
  if (method !== "POST")
@@ -60544,14 +62378,69 @@ async function handleV1Request(req, url, dependencies = {}) {
60544
62378
  return error(400, `${field} must be a non-empty string`);
60545
62379
  }
60546
62380
  }
60547
- return json5(await rollbackPlanProjectLink(store, id, body2.project_id, {
62381
+ return json6(await rollbackPlanProjectLink(store, id, body2.project_id, {
60548
62382
  receipt_id: body2.receipt_id,
60549
62383
  expected_plan_revision: body2.expected_plan_revision
60550
62384
  }));
60551
62385
  }
62386
+ if (id && action === "comments") {
62387
+ if (!await store.plans.get(id))
62388
+ return error(404, "plan not found");
62389
+ if (method === "GET") {
62390
+ if (store.plans.getCommentsPage) {
62391
+ const rawLimit = url.searchParams.get("limit");
62392
+ const cursor = url.searchParams.get("cursor");
62393
+ const limit = rawLimit === null ? DEFAULT_COMMENT_PAGE_SIZE : Number(rawLimit);
62394
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_COMMENT_PAGE_SIZE) {
62395
+ return error(400, `limit must be an integer between 1 and ${MAX_COMMENT_PAGE_SIZE}`);
62396
+ }
62397
+ let before;
62398
+ if (cursor) {
62399
+ try {
62400
+ before = decodeCommentCursor(cursor);
62401
+ } catch {
62402
+ return error(400, "invalid comment cursor");
62403
+ }
62404
+ }
62405
+ const page = (await store.plans.getCommentsPage(id, { limit: limit + 1, ...before ? { before } : {} }, contextFromPrincipal(principal))).map(redactPlanComment2);
62406
+ const hasMore = page.length > limit;
62407
+ const comments2 = hasMore ? page.slice(1) : page;
62408
+ return json6({
62409
+ comments: comments2,
62410
+ count: comments2.length,
62411
+ has_more: hasMore,
62412
+ next_cursor: hasMore && comments2[0] ? encodeCommentCursor(comments2[0]) : null
62413
+ });
62414
+ }
62415
+ const comments = (await store.plans.getComments?.(id, contextFromPrincipal(principal)) ?? []).map(redactPlanComment2);
62416
+ return json6({ comments, count: comments.length });
62417
+ }
62418
+ if (method === "POST") {
62419
+ const body2 = await readJson3(req) ?? {};
62420
+ if (typeof body2.content !== "string" || !body2.content.trim()) {
62421
+ return error(400, "content is required");
62422
+ }
62423
+ const target = await store.plans.get(id);
62424
+ if (!target)
62425
+ return error(404, "plan not found");
62426
+ if (!store.plans.addComment) {
62427
+ return error(501, "plan comments are not supported by this storage backend");
62428
+ }
62429
+ const comment2 = await store.plans.addComment({
62430
+ plan_id: id,
62431
+ content: body2.content,
62432
+ agent_id: body2.agent_id ?? principal.agent ?? undefined,
62433
+ session_id: body2.session_id,
62434
+ type: body2.type,
62435
+ progress_pct: body2.progress_pct
62436
+ }, contextFromPrincipal(principal, body2));
62437
+ return json6({ comment: redactPlanComment2(comment2) }, 201);
62438
+ }
62439
+ return error(405, `method ${method} not allowed on /v1/plans/:id/comments`);
62440
+ }
60552
62441
  if (id && method === "GET") {
60553
62442
  const plan = await store.plans.get(id);
60554
- return plan ? json5({ plan }) : error(404, "plan not found");
62443
+ return plan ? json6({ plan }) : error(404, "plan not found");
60555
62444
  }
60556
62445
  if (id && (method === "PATCH" || method === "PUT")) {
60557
62446
  const body2 = await readJson3(req);
@@ -60591,12 +62480,12 @@ async function handleV1Request(req, url, dependencies = {}) {
60591
62480
  }
60592
62481
  }
60593
62482
  const plan = await store.plans.update(id, body2);
60594
- return json5({ plan });
62483
+ return json6({ plan });
60595
62484
  }
60596
62485
  if (id && method === "DELETE") {
60597
62486
  if (!await store.plans.delete(id, contextFromPrincipal(principal)))
60598
62487
  return error(404, "plan not found");
60599
- return json5({ deleted: true, id });
62488
+ return json6({ deleted: true, id });
60600
62489
  }
60601
62490
  if (id)
60602
62491
  return error(405, `method ${method} not allowed on /v1/plans/:id`);
@@ -60605,7 +62494,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60605
62494
  if (!id && method === "GET") {
60606
62495
  const projectId = url.searchParams.get("project_id");
60607
62496
  const templates = (await store.templates.list()).filter((template) => projectId === null || template.project_id === projectId);
60608
- return json5({ templates, count: templates.length });
62497
+ return json6({ templates, count: templates.length });
60609
62498
  }
60610
62499
  if (!id && method === "POST") {
60611
62500
  const body2 = await readJson3(req);
@@ -60613,13 +62502,13 @@ async function handleV1Request(req, url, dependencies = {}) {
60613
62502
  if (!validated.ok)
60614
62503
  return error(400, validated.message);
60615
62504
  const template = await store.templates.create(validated.input, contextFromPrincipal(principal));
60616
- return json5({ template: await store.templates.getWithTasks(template.id) }, 201);
62505
+ return json6({ template: await store.templates.getWithTasks(template.id) }, 201);
60617
62506
  }
60618
62507
  if (!id)
60619
62508
  return error(405, `method ${method} not allowed on /v1/templates`);
60620
62509
  if (method === "GET") {
60621
62510
  const template = await store.templates.getWithTasks(id);
60622
- return template ? json5({ template }) : error(404, "template not found");
62511
+ return template ? json6({ template }) : error(404, "template not found");
60623
62512
  }
60624
62513
  if (method === "PATCH" || method === "PUT") {
60625
62514
  const body2 = await readJson3(req);
@@ -60627,18 +62516,18 @@ async function handleV1Request(req, url, dependencies = {}) {
60627
62516
  if (!validated.ok)
60628
62517
  return error(400, validated.message);
60629
62518
  const template = await store.templates.update(id, validated.patch, contextFromPrincipal(principal));
60630
- return template ? json5({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
62519
+ return template ? json6({ template: await store.templates.getWithTasks(id) }) : error(404, "template not found");
60631
62520
  }
60632
62521
  if (method === "DELETE") {
60633
62522
  const deleted = await store.templates.delete(id, contextFromPrincipal(principal));
60634
- return deleted ? json5({ deleted: true, id }) : error(404, "template not found");
62523
+ return deleted ? json6({ deleted: true, id }) : error(404, "template not found");
60635
62524
  }
60636
62525
  return error(405, `method ${method} not allowed on /v1/templates/:id`);
60637
62526
  }
60638
62527
  if (resource === "agents") {
60639
62528
  if (!id && method === "GET") {
60640
62529
  const agents = await store.agents.list();
60641
- return json5({ agents, count: agents.length });
62530
+ return json6({ agents, count: agents.length });
60642
62531
  }
60643
62532
  if (!id && method === "POST") {
60644
62533
  const body2 = await readJson3(req);
@@ -60648,7 +62537,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60648
62537
  if (result && typeof result === "object" && "conflict" in result) {
60649
62538
  return error(409, result.message ?? "agent name conflict", { conflict: true });
60650
62539
  }
60651
- return json5({ agent: result }, 201);
62540
+ return json6({ agent: result }, 201);
60652
62541
  }
60653
62542
  if (id && action === "heartbeat") {
60654
62543
  if (method !== "POST")
@@ -60657,7 +62546,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60657
62546
  return error(501, "agent heartbeat is not supported by this storage backend");
60658
62547
  }
60659
62548
  const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
60660
- return agent ? json5({ agent }) : error(404, "agent not found");
62549
+ return agent ? json6({ agent }) : error(404, "agent not found");
60661
62550
  }
60662
62551
  if (id && action === "release") {
60663
62552
  if (method !== "POST")
@@ -60672,11 +62561,11 @@ async function handleV1Request(req, url, dependencies = {}) {
60672
62561
  if (!result.released) {
60673
62562
  return error(409, "release denied: session_id does not match agent's current session", { released: false });
60674
62563
  }
60675
- return json5({ agent: result.agent, released: true });
62564
+ return json6({ agent: result.agent, released: true });
60676
62565
  }
60677
62566
  if (id && method === "GET") {
60678
62567
  const agent = await store.agents.get(id);
60679
- return agent ? json5({ agent }) : error(404, "agent not found");
62568
+ return agent ? json6({ agent }) : error(404, "agent not found");
60680
62569
  }
60681
62570
  }
60682
62571
  if (resource === "activity" && !id) {
@@ -60685,13 +62574,13 @@ async function handleV1Request(req, url, dependencies = {}) {
60685
62574
  const limitParam = url.searchParams.get("limit");
60686
62575
  const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
60687
62576
  const activity = await store.audit.getRecentActivity(limit);
60688
- return json5({ activity, count: activity.length });
62577
+ return json6({ activity, count: activity.length });
60689
62578
  }
60690
62579
  if (resource === "task-lists") {
60691
62580
  if (!id && method === "GET") {
60692
62581
  const projectId = url.searchParams.get("project_id") ?? undefined;
60693
62582
  const taskLists = await store.taskLists.list(projectId);
60694
- return json5({ task_lists: taskLists, count: taskLists.length });
62583
+ return json6({ task_lists: taskLists, count: taskLists.length });
60695
62584
  }
60696
62585
  if (!id && method === "POST") {
60697
62586
  const body2 = await readJson3(req);
@@ -60713,17 +62602,17 @@ async function handleV1Request(req, url, dependencies = {}) {
60713
62602
  return error(400, "task-list slug must be non-empty kebab-case");
60714
62603
  }
60715
62604
  const taskList = await store.taskLists.create(body2, contextFromPrincipal(principal));
60716
- return json5({ task_list: taskList }, 201);
62605
+ return json6({ task_list: taskList }, 201);
60717
62606
  }
60718
62607
  if (id && method === "GET") {
60719
62608
  const taskList = await store.taskLists.get(id);
60720
- return taskList ? json5({ task_list: taskList }) : error(404, "task list not found");
62609
+ return taskList ? json6({ task_list: taskList }) : error(404, "task list not found");
60721
62610
  }
60722
62611
  if (id && (method === "PATCH" || method === "PUT")) {
60723
62612
  const body2 = await readJson3(req);
60724
62613
  if (!body2)
60725
62614
  return error(400, "invalid JSON body");
60726
- const unknownField = Object.keys(body2).find((key2) => !["slug", "name", "description", "metadata"].includes(key2));
62615
+ const unknownField = Object.keys(body2).find((key2) => !["slug", "name", "description", "metadata", "project_id"].includes(key2));
60727
62616
  if (unknownField)
60728
62617
  return error(400, `unsupported task-list update field: ${unknownField}`);
60729
62618
  if (Object.keys(body2).length === 0)
@@ -60737,14 +62626,26 @@ async function handleV1Request(req, url, dependencies = {}) {
60737
62626
  if (body2.metadata !== undefined && (!body2.metadata || typeof body2.metadata !== "object" || Array.isArray(body2.metadata))) {
60738
62627
  return error(400, "metadata must be an object");
60739
62628
  }
62629
+ if (body2.project_id !== undefined) {
62630
+ if (body2.project_id !== null && (typeof body2.project_id !== "string" || !body2.project_id.trim())) {
62631
+ return error(400, "project_id must be a non-empty string or null");
62632
+ }
62633
+ if (typeof body2.project_id === "string" && body2.project_id.trim() !== "") {
62634
+ const target = await store.projects.get(body2.project_id.trim());
62635
+ if (!target) {
62636
+ return error(400, `task-list project_id must reference a registered project: ${body2.project_id}`);
62637
+ }
62638
+ body2.project_id = target.id;
62639
+ }
62640
+ }
60740
62641
  if (!await store.taskLists.get(id))
60741
62642
  return error(404, "task list not found");
60742
62643
  const taskList = await store.taskLists.update(id, body2);
60743
- return json5({ task_list: taskList });
62644
+ return json6({ task_list: taskList });
60744
62645
  }
60745
62646
  if (id && method === "DELETE") {
60746
62647
  const deleted = await store.taskLists.delete(id, contextFromPrincipal(principal));
60747
- return deleted ? json5({ deleted: true, id }) : error(404, "task list not found");
62648
+ return deleted ? json6({ deleted: true, id }) : error(404, "task list not found");
60748
62649
  }
60749
62650
  return error(405, `method ${method} not allowed on /v1/task-lists${id ? "/:id" : ""}`);
60750
62651
  }
@@ -60755,7 +62656,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60755
62656
  return error(501, "dependency edge listing is not supported by this storage backend");
60756
62657
  }
60757
62658
  const dependencies2 = await store.dependencies.listAll();
60758
- return json5({ dependencies: dependencies2, count: dependencies2.length });
62659
+ return json6({ dependencies: dependencies2, count: dependencies2.length });
60759
62660
  }
60760
62661
  if (resource === "commits" && id) {
60761
62662
  if (method !== "GET")
@@ -60763,7 +62664,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60763
62664
  if (!store.commits)
60764
62665
  return error(501, "commit links are not supported by this storage backend");
60765
62666
  const commit = await store.commits.find(id);
60766
- return json5({ commit: commit ?? null });
62667
+ return json6({ commit: commit ?? null });
60767
62668
  }
60768
62669
  if (resource === "refs" && id) {
60769
62670
  if (method !== "GET")
@@ -60777,7 +62678,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60777
62678
  return error(400, "ref path segment has invalid percent encoding");
60778
62679
  }
60779
62680
  const refs = await store.gitRefs.find(decodedRef);
60780
- return json5({ refs, count: refs.length });
62681
+ return json6({ refs, count: refs.length });
60781
62682
  }
60782
62683
  if (resource === "next" && !id) {
60783
62684
  if (method !== "GET")
@@ -60789,7 +62690,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60789
62690
  ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
60790
62691
  };
60791
62692
  const task3 = await store.tasks.getNext(agent, filters);
60792
- return json5({ task: task3 ?? null });
62693
+ return json6({ task: task3 ?? null });
60793
62694
  }
60794
62695
  if (resource === "stats" && method === "GET") {
60795
62696
  const [tasks, tasksAll, projects] = await Promise.all([
@@ -60797,7 +62698,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60797
62698
  store.tasks.count({ include_subtasks: true }),
60798
62699
  store.projects.list()
60799
62700
  ]);
60800
- return json5({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
62701
+ return json6({ tasks, tasks_all: tasksAll, subtasks: tasksAll - tasks, projects: projects.length });
60801
62702
  }
60802
62703
  if (resource === "integrity" && !id) {
60803
62704
  if (method !== "GET")
@@ -60806,7 +62707,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60806
62707
  return error(501, "referential-integrity reporting is not supported by this storage backend");
60807
62708
  }
60808
62709
  const integrity = await store.integrity.report();
60809
- return json5({ integrity });
62710
+ return json6({ integrity });
60810
62711
  }
60811
62712
  if (resource === "import") {
60812
62713
  if (method !== "POST")
@@ -60831,7 +62732,7 @@ async function handleV1Request(req, url, dependencies = {}) {
60831
62732
  }
60832
62733
  const operation = completionImports.operations[0];
60833
62734
  const completed = await store.plans.completeAtRevision(operation.id, operation.expected_updated_at, contextFromPrincipal(principal));
60834
- return json5({
62735
+ return json6({
60835
62736
  result: {
60836
62737
  inserted: 0,
60837
62738
  updated: completed.applied ? 1 : 0,
@@ -60870,17 +62771,17 @@ async function handleV1Request(req, url, dependencies = {}) {
60870
62771
  audit_history_id: failure.auditHistoryId
60871
62772
  });
60872
62773
  }
60873
- return json5({ result, received });
62774
+ return json6({ result, received });
60874
62775
  }
60875
62776
  return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
60876
62777
  } catch (e) {
60877
62778
  if (e instanceof PlanProjectLinkError) {
60878
- const status2 = e.code === "PLAN_PROJECT_LINK_PLAN_NOT_FOUND" || e.code === "PLAN_PROJECT_LINK_PROJECT_NOT_FOUND" || e.code === "PLAN_PROJECT_LINK_RECEIPT_NOT_FOUND" ? 404 : e.code === "PLAN_PROJECT_LINK_IDEMPOTENCY_KEY_INVALID" ? 400 : e.code === "PLAN_PROJECT_LINK_UNSUPPORTED" ? 501 : 409;
60879
- return error(status2, e.message, { code: e.code, conflict: status2 === 409, ...e.details });
62779
+ const status3 = e.code === "PLAN_PROJECT_LINK_PLAN_NOT_FOUND" || e.code === "PLAN_PROJECT_LINK_PROJECT_NOT_FOUND" || e.code === "PLAN_PROJECT_LINK_RECEIPT_NOT_FOUND" ? 404 : e.code === "PLAN_PROJECT_LINK_IDEMPOTENCY_KEY_INVALID" ? 400 : e.code === "PLAN_PROJECT_LINK_UNSUPPORTED" ? 501 : 409;
62780
+ return error(status3, e.message, { code: e.code, conflict: status3 === 409, ...e.details });
60880
62781
  }
60881
62782
  if (e instanceof ProjectTaskListEnsureError) {
60882
- const status2 = e.code === "PROJECT_NOT_FOUND" || e.code === "PROJECT_TASK_LIST_RECEIPT_NOT_FOUND" ? 404 : e.code === "PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID" ? 400 : 409;
60883
- return error(status2, e.message, { code: e.code, conflict: status2 === 409, ...e.details });
62783
+ const status3 = e.code === "PROJECT_NOT_FOUND" || e.code === "PROJECT_TASK_LIST_RECEIPT_NOT_FOUND" ? 404 : e.code === "PROJECT_TASK_LIST_IDEMPOTENCY_KEY_INVALID" ? 400 : 409;
62784
+ return error(status3, e.message, { code: e.code, conflict: status3 === 409, ...e.details });
60884
62785
  }
60885
62786
  if (e instanceof TaskReferenceAmbiguousError) {
60886
62787
  return error(409, e.message, {
@@ -60902,10 +62803,10 @@ async function handleV1Request(req, url, dependencies = {}) {
60902
62803
  });
60903
62804
  }
60904
62805
  if (e instanceof StaleLockHandoffError) {
60905
- const status2 = e.code === "STALE_LOCK_HANDOFF_INVALID_TASK_ID" || e.code === "STALE_LOCK_HANDOFF_INVALID_INPUT" ? 400 : e.code === "STALE_LOCK_HANDOFF_ACTOR_MISMATCH" ? 403 : 409;
60906
- return error(status2, e.message, {
62806
+ const status3 = e.code === "STALE_LOCK_HANDOFF_INVALID_TASK_ID" || e.code === "STALE_LOCK_HANDOFF_INVALID_INPUT" ? 400 : e.code === "STALE_LOCK_HANDOFF_ACTOR_MISMATCH" ? 403 : 409;
62807
+ return error(status3, e.message, {
60907
62808
  code: e.code,
60908
- conflict: status2 === 409,
62809
+ conflict: status3 === 409,
60909
62810
  ...e.details
60910
62811
  });
60911
62812
  }
@@ -60933,19 +62834,20 @@ async function handleV1Request(req, url, dependencies = {}) {
60933
62834
  return error(500, e.message || "internal error");
60934
62835
  }
60935
62836
  }
60936
- var JSON_HEADERS4, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500, RFC3339_DATE_TIME;
62837
+ var JSON_HEADERS5, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500, RFC3339_DATE_TIME;
60937
62838
  var init_v1 = __esm(() => {
60938
62839
  init_types();
60939
62840
  init_cloud();
60940
62841
  init_pr_groups();
60941
62842
  init_project_registration();
60942
62843
  init_task_manifest();
62844
+ init_task_subtree_transfer();
60943
62845
  init_redaction();
60944
62846
  init_project_task_list_ensure();
60945
62847
  init_plan_project_link();
60946
62848
  init_stale_lock_handoff();
60947
62849
  init_audit_history_import();
60948
- JSON_HEADERS4 = { "Content-Type": "application/json" };
62850
+ JSON_HEADERS5 = { "Content-Type": "application/json" };
60949
62851
  RFC3339_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
60950
62852
  });
60951
62853
 
@@ -61156,7 +63058,7 @@ class SqlitePrGroupLedgerPersistence {
61156
63058
  }
61157
63059
  }
61158
63060
  var sqliteTransactionTails2;
61159
- var init_sqlite3 = __esm(() => {
63061
+ var init_sqlite4 = __esm(() => {
61160
63062
  sqliteTransactionTails2 = new WeakMap;
61161
63063
  });
61162
63064
 
@@ -61187,10 +63089,10 @@ function createLocalPrGroupLedger(db = getDatabase()) {
61187
63089
  var init_pr_groups2 = __esm(() => {
61188
63090
  init_database();
61189
63091
  init_ledger();
61190
- init_sqlite3();
63092
+ init_sqlite4();
61191
63093
  init_types3();
61192
63094
  init_ledger();
61193
- init_sqlite3();
63095
+ init_sqlite4();
61194
63096
  init_http_client();
61195
63097
  init_postgres();
61196
63098
  });
@@ -61201,7 +63103,7 @@ __export(exports_serve, {
61201
63103
  taskToSummary: () => taskToSummary,
61202
63104
  startServer: () => startServer,
61203
63105
  serveStaticFile: () => serveStaticFile,
61204
- json: () => json3,
63106
+ json: () => json4,
61205
63107
  checkAuth: () => checkAuth,
61206
63108
  SECURITY_HEADERS: () => SECURITY_HEADERS,
61207
63109
  MIME_TYPES: () => MIME_TYPES
@@ -61287,9 +63189,9 @@ function checkRateLimit(ip) {
61287
63189
  }
61288
63190
  return { allowed: true };
61289
63191
  }
61290
- function json3(data, status2 = 200, headers) {
63192
+ function json4(data, status3 = 200, headers) {
61291
63193
  return new Response(JSON.stringify(data), {
61292
- status: status2,
63194
+ status: status3,
61293
63195
  headers: {
61294
63196
  "Content-Type": "application/json",
61295
63197
  ...SECURITY_HEADERS,
@@ -61432,7 +63334,7 @@ Dashboard not found at: ${dashboardDir}`);
61432
63334
  "Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
61433
63335
  Vary: "Origin"
61434
63336
  } : undefined;
61435
- const jsonWithCors = (data, status2 = 200) => json3(data, status2, corsHeaders);
63337
+ const jsonWithCors = (data, status3 = 200) => json4(data, status3, corsHeaders);
61436
63338
  if (method === "OPTIONS") {
61437
63339
  return new Response(null, {
61438
63340
  headers: corsHeaders || {
@@ -61513,13 +63415,13 @@ Dashboard not found at: ${dashboardDir}`);
61513
63415
  return res;
61514
63416
  }
61515
63417
  if (path === "/api/health" && method === "GET") {
61516
- return handleHealth(ctx, json3);
63418
+ return handleHealth(ctx, json4);
61517
63419
  }
61518
63420
  if (path === "/api/headless" && method === "GET") {
61519
- return handleHeadlessBoundary(ctx, json3);
63421
+ return handleHeadlessBoundary(ctx, json4);
61520
63422
  }
61521
63423
  if (path === "/api/stats" && method === "GET") {
61522
- return handleStats(ctx, json3);
63424
+ return handleStats(ctx, json4);
61523
63425
  }
61524
63426
  if (path === "/api/tasks" && method === "GET") {
61525
63427
  return handleListTasks(req, url, ctx, jsonWithCors, taskToSummary);
@@ -61534,16 +63436,16 @@ Dashboard not found at: ${dashboardDir}`);
61534
63436
  return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
61535
63437
  }
61536
63438
  if (path === "/api/tasks/bulk" && method === "POST") {
61537
- return handleTasksBulk(req, ctx, json3);
63439
+ return handleTasksBulk(req, ctx, json4);
61538
63440
  }
61539
63441
  if (path === "/api/tasks/status" && method === "GET") {
61540
- return handleTasksStatus(req, url, ctx, json3);
63442
+ return handleTasksStatus(req, url, ctx, json4);
61541
63443
  }
61542
63444
  if (path === "/api/tasks/next" && method === "GET") {
61543
63445
  return handleTasksNext(req, url, ctx, jsonWithCors, taskToSummary);
61544
63446
  }
61545
63447
  if (path === "/api/tasks/active" && method === "GET") {
61546
- return handleTasksActive(req, url, ctx, json3);
63448
+ return handleTasksActive(req, url, ctx, json4);
61547
63449
  }
61548
63450
  if (path === "/api/tasks/stale" && method === "GET") {
61549
63451
  return handleTasksStale(req, url, ctx, jsonWithCors, taskToSummary);
@@ -61556,11 +63458,11 @@ Dashboard not found at: ${dashboardDir}`);
61556
63458
  }
61557
63459
  const attachmentsMatch = path.match(/^\/api\/tasks\/([^/]+)\/attachments$/);
61558
63460
  if (attachmentsMatch && method === "GET") {
61559
- return handleTaskAttachments(attachmentsMatch[1], ctx, json3);
63461
+ return handleTaskAttachments(attachmentsMatch[1], ctx, json4);
61560
63462
  }
61561
63463
  const progressMatch = path.match(/^\/api\/tasks\/([^/]+)\/progress$/);
61562
63464
  if (progressMatch) {
61563
- const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json3, url);
63465
+ const res = await handleTaskProgress(progressMatch[1], req, method, ctx, json4, url);
61564
63466
  if (res !== null)
61565
63467
  return res;
61566
63468
  }
@@ -61574,7 +63476,7 @@ Dashboard not found at: ${dashboardDir}`);
61574
63476
  return handlePatchTask(id, req, ctx, jsonWithCors, taskToSummary);
61575
63477
  }
61576
63478
  if (method === "DELETE") {
61577
- return handleDeleteTask(id, ctx, json3);
63479
+ return handleDeleteTask(id, ctx, json4);
61578
63480
  }
61579
63481
  }
61580
63482
  const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
@@ -61590,7 +63492,7 @@ Dashboard not found at: ${dashboardDir}`);
61590
63492
  return handleCompleteTask(completeMatch[1], ctx, jsonWithCors, taskToSummary);
61591
63493
  }
61592
63494
  if (path === "/api/projects" && method === "GET") {
61593
- return handleListProjects(url, ctx, json3);
63495
+ return handleListProjects(url, ctx, json4);
61594
63496
  }
61595
63497
  if (path === "/api/agents/me" && method === "GET") {
61596
63498
  return handleAgentMe(req, url, ctx, jsonWithCors, taskToSummary);
@@ -61603,92 +63505,92 @@ Dashboard not found at: ${dashboardDir}`);
61603
63505
  return handleClaimTask(req, ctx, jsonWithCors, taskToSummary);
61604
63506
  }
61605
63507
  if (path === "/api/orgs" && method === "GET") {
61606
- return handleListOrgs(ctx, json3);
63508
+ return handleListOrgs(ctx, json4);
61607
63509
  }
61608
63510
  if (path === "/api/orgs" && method === "POST") {
61609
- return handleCreateOrg(req, ctx, json3);
63511
+ return handleCreateOrg(req, ctx, json4);
61610
63512
  }
61611
63513
  const orgMatch = path.match(/^\/api\/orgs\/([^/]+)$/);
61612
63514
  if (orgMatch && method === "PATCH") {
61613
- return handleUpdateOrg(orgMatch[1], req, ctx, json3);
63515
+ return handleUpdateOrg(orgMatch[1], req, ctx, json4);
61614
63516
  }
61615
63517
  if (orgMatch && method === "DELETE") {
61616
- return handleDeleteOrg(orgMatch[1], ctx, json3);
63518
+ return handleDeleteOrg(orgMatch[1], ctx, json4);
61617
63519
  }
61618
63520
  if (path === "/api/org" && method === "GET") {
61619
- return handleOrgChart(ctx, json3);
63521
+ return handleOrgChart(ctx, json4);
61620
63522
  }
61621
63523
  const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
61622
63524
  if (teamMatch && method === "GET") {
61623
- return handleAgentTeam(teamMatch[1], ctx, json3);
63525
+ return handleAgentTeam(teamMatch[1], ctx, json4);
61624
63526
  }
61625
63527
  if (path === "/api/agents" && method === "GET") {
61626
- return handleListAgents(url, ctx, json3);
63528
+ return handleListAgents(url, ctx, json4);
61627
63529
  }
61628
63530
  if (path === "/api/projects" && method === "POST") {
61629
- return handleCreateProject(req, ctx, json3);
63531
+ return handleCreateProject(req, ctx, json4);
61630
63532
  }
61631
63533
  const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
61632
63534
  if (projectDeleteMatch && method === "DELETE") {
61633
- return handleDeleteProject(projectDeleteMatch[1], ctx, json3);
63535
+ return handleDeleteProject(projectDeleteMatch[1], ctx, json4);
61634
63536
  }
61635
63537
  if (path === "/api/agents" && method === "POST") {
61636
- return handleRegisterAgent(req, ctx, json3);
63538
+ return handleRegisterAgent(req, ctx, json4);
61637
63539
  }
61638
63540
  const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
61639
63541
  if (agentMatch && method === "PATCH") {
61640
- return handleUpdateAgent(agentMatch[1], req, ctx, json3);
63542
+ return handleUpdateAgent(agentMatch[1], req, ctx, json4);
61641
63543
  }
61642
63544
  if (agentMatch && method === "DELETE") {
61643
- return handleDeleteAgent(agentMatch[1], ctx, json3);
63545
+ return handleDeleteAgent(agentMatch[1], ctx, json4);
61644
63546
  }
61645
63547
  if (path === "/api/agents/bulk" && method === "POST") {
61646
- return handleBulkDeleteAgents(req, ctx, json3);
63548
+ return handleBulkDeleteAgents(req, ctx, json4);
61647
63549
  }
61648
63550
  if (path === "/api/projects/bulk" && method === "POST") {
61649
- return handleBulkDeleteProjects(req, ctx, json3);
63551
+ return handleBulkDeleteProjects(req, ctx, json4);
61650
63552
  }
61651
63553
  if (path === "/api/doctor" && method === "GET") {
61652
- return handleDoctor(ctx, json3);
63554
+ return handleDoctor(ctx, json4);
61653
63555
  }
61654
63556
  if (path === "/api/report" && method === "GET") {
61655
- return handleReport(req, url, ctx, json3);
63557
+ return handleReport(req, url, ctx, json4);
61656
63558
  }
61657
63559
  if (path === "/api/activity" && method === "GET") {
61658
- return handleActivity(req, url, ctx, json3);
63560
+ return handleActivity(req, url, ctx, json4);
61659
63561
  }
61660
63562
  const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
61661
63563
  if (historyMatch && method === "GET") {
61662
- return handleTaskHistory(historyMatch[1], ctx, json3, url);
63564
+ return handleTaskHistory(historyMatch[1], ctx, json4, url);
61663
63565
  }
61664
63566
  if (path === "/api/webhooks" && method === "GET") {
61665
- return handleListWebhooks(ctx, json3);
63567
+ return handleListWebhooks(ctx, json4);
61666
63568
  }
61667
63569
  if (path === "/api/webhooks" && method === "POST") {
61668
- return handleCreateWebhook(req, ctx, json3);
63570
+ return handleCreateWebhook(req, ctx, json4);
61669
63571
  }
61670
63572
  const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
61671
63573
  if (webhookMatch && method === "DELETE") {
61672
- return handleDeleteWebhook(webhookMatch[1], ctx, json3);
63574
+ return handleDeleteWebhook(webhookMatch[1], ctx, json4);
61673
63575
  }
61674
63576
  if (path === "/api/templates" && method === "GET") {
61675
- return handleListTemplates(ctx, json3);
63577
+ return handleListTemplates(ctx, json4);
61676
63578
  }
61677
63579
  if (path === "/api/templates" && method === "POST") {
61678
- return handleCreateTemplate(req, ctx, json3);
63580
+ return handleCreateTemplate(req, ctx, json4);
61679
63581
  }
61680
63582
  const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
61681
63583
  if (templateMatch && method === "DELETE") {
61682
- return handleDeleteTemplate(templateMatch[1], ctx, json3);
63584
+ return handleDeleteTemplate(templateMatch[1], ctx, json4);
61683
63585
  }
61684
63586
  if (path === "/api/plans" && method === "GET") {
61685
- return handleListPlans(url, ctx, json3);
63587
+ return handleListPlans(url, ctx, json4);
61686
63588
  }
61687
63589
  if (path === "/api/plans" && method === "POST") {
61688
- return handleCreatePlan(req, ctx, json3);
63590
+ return handleCreatePlan(req, ctx, json4);
61689
63591
  }
61690
63592
  if (path === "/api/plans/bulk" && method === "POST") {
61691
- return handleBulkDeletePlans(req, ctx, json3);
63593
+ return handleBulkDeletePlans(req, ctx, json4);
61692
63594
  }
61693
63595
  const planMatch = path.match(/^\/api\/plans\/([^/]+)$/);
61694
63596
  if (planMatch) {
@@ -61697,16 +63599,16 @@ Dashboard not found at: ${dashboardDir}`);
61697
63599
  return handleGetPlan(id, ctx, jsonWithCors, taskToSummary);
61698
63600
  }
61699
63601
  if (method === "PATCH") {
61700
- return handleUpdatePlan(id, req, ctx, json3);
63602
+ return handleUpdatePlan(id, req, ctx, json4);
61701
63603
  }
61702
63604
  if (method === "DELETE") {
61703
- return handleDeletePlan(id, ctx, json3);
63605
+ return handleDeletePlan(id, ctx, json4);
61704
63606
  }
61705
63607
  }
61706
63608
  const staticRes = handleStaticFiles(path, method, ctx, jsonWithCors, serveStaticFile);
61707
63609
  if (staticRes)
61708
63610
  return staticRes;
61709
- return json3({ error: "Not found" }, 404);
63611
+ return json4({ error: "Not found" }, 404);
61710
63612
  }
61711
63613
  });
61712
63614
  const shutdown = () => {