@hasna/todos 0.15.35 → 0.15.41

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 (85) 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 +3299 -796
  10. package/dist/cli/stage-a.d.ts.map +1 -1
  11. package/dist/contracts.js +62 -54
  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-lifecycle.d.ts.map +1 -1
  17. package/dist/db/task-lists.d.ts.map +1 -1
  18. package/dist/db/task-relations.d.ts.map +1 -1
  19. package/dist/index.d.ts +3 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1787 -309
  22. package/dist/lib/comment-cursor.d.ts +19 -0
  23. package/dist/lib/comment-cursor.d.ts.map +1 -1
  24. package/dist/lib/import-export-bridge.d.ts.map +1 -1
  25. package/dist/mcp/index.js +2273 -471
  26. package/dist/mcp.js +8 -4
  27. package/dist/project-registration.js +207 -63
  28. package/dist/registry.d.ts +1 -1
  29. package/dist/registry.d.ts.map +1 -1
  30. package/dist/registry.js +69 -54
  31. package/dist/release-provenance.json +5 -5
  32. package/dist/sdk/index.d.ts +1 -1
  33. package/dist/sdk/index.d.ts.map +1 -1
  34. package/dist/sdk/index.js +35 -0
  35. package/dist/sdk/v1.generated.d.ts +141 -0
  36. package/dist/sdk/v1.generated.d.ts.map +1 -1
  37. package/dist/server/cloud.d.ts +12 -2
  38. package/dist/server/cloud.d.ts.map +1 -1
  39. package/dist/server/index.js +5171 -8535
  40. package/dist/server/openapi.d.ts +2714 -1937
  41. package/dist/server/openapi.d.ts.map +1 -1
  42. package/dist/server/v1.d.ts +2 -1
  43. package/dist/server/v1.d.ts.map +1 -1
  44. package/dist/storage/config.d.ts +12 -30
  45. package/dist/storage/config.d.ts.map +1 -1
  46. package/dist/storage/index.d.ts +2 -2
  47. package/dist/storage/index.d.ts.map +1 -1
  48. package/dist/storage/interfaces.d.ts +13 -2
  49. package/dist/storage/interfaces.d.ts.map +1 -1
  50. package/dist/storage/local-sqlite.d.ts.map +1 -1
  51. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  52. package/dist/storage/timestamp-backfill.d.ts +62 -0
  53. package/dist/storage/timestamp-backfill.d.ts.map +1 -0
  54. package/dist/storage.d.ts +2 -2
  55. package/dist/storage.d.ts.map +1 -1
  56. package/dist/storage.js +199 -63
  57. package/dist/task-subtree-transfer/authority.d.ts +27 -0
  58. package/dist/task-subtree-transfer/authority.d.ts.map +1 -0
  59. package/dist/task-subtree-transfer/backend.d.ts +9 -0
  60. package/dist/task-subtree-transfer/backend.d.ts.map +1 -0
  61. package/dist/task-subtree-transfer/canonical.d.ts +14 -0
  62. package/dist/task-subtree-transfer/canonical.d.ts.map +1 -0
  63. package/dist/task-subtree-transfer/http.d.ts +16 -0
  64. package/dist/task-subtree-transfer/http.d.ts.map +1 -0
  65. package/dist/task-subtree-transfer/index.d.ts +6 -0
  66. package/dist/task-subtree-transfer/index.d.ts.map +1 -0
  67. package/dist/task-subtree-transfer/planner.d.ts +24 -0
  68. package/dist/task-subtree-transfer/planner.d.ts.map +1 -0
  69. package/dist/task-subtree-transfer/postgres.d.ts +19 -0
  70. package/dist/task-subtree-transfer/postgres.d.ts.map +1 -0
  71. package/dist/task-subtree-transfer/schema-sql.d.ts +5 -0
  72. package/dist/task-subtree-transfer/schema-sql.d.ts.map +1 -0
  73. package/dist/task-subtree-transfer/sqlite.d.ts +16 -0
  74. package/dist/task-subtree-transfer/sqlite.d.ts.map +1 -0
  75. package/dist/task-subtree-transfer/types.d.ts +148 -0
  76. package/dist/task-subtree-transfer/types.d.ts.map +1 -0
  77. package/dist/task-subtree-transfer.d.ts +2 -0
  78. package/dist/task-subtree-transfer.d.ts.map +1 -0
  79. package/dist/task-subtree-transfer.js +1869 -0
  80. package/dist/testing.d.ts +41 -7
  81. package/dist/testing.d.ts.map +1 -1
  82. package/dist/testing.js +44 -1
  83. package/dist/types/index.d.ts +24 -0
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/package.json +8 -4
package/dist/storage.js CHANGED
@@ -42,20 +42,16 @@ var __require = import.meta.require;
42
42
  // src/storage/config.ts
43
43
  var exports_config = {};
44
44
  __export(exports_config, {
45
- parseStorageMode: () => parseStorageMode,
46
45
  parseStorageBackend: () => parseStorageBackend,
47
46
  loadTodosStorageConfig: () => loadTodosStorageConfig,
48
47
  loadStorageConfig: () => loadStorageConfig,
49
48
  isTodosShadowEnabled: () => isTodosShadowEnabled,
50
- isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
51
49
  isTodosPostgresBackend: () => isTodosPostgresBackend,
52
50
  getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
53
- getTodosStorageMode: () => getTodosStorageMode,
54
51
  getTodosStorageEnvName: () => getTodosStorageEnvName,
55
52
  getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
56
53
  getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
57
54
  getTodosStorageBackend: () => getTodosStorageBackend,
58
- getStorageMode: () => getStorageMode,
59
55
  getStorageDatabaseUrl: () => getStorageDatabaseUrl,
60
56
  getStorageDatabaseEnv: () => getStorageDatabaseEnv,
61
57
  getCanonicalTodosRdsConfig: () => getCanonicalTodosRdsConfig,
@@ -65,7 +61,6 @@ __export(exports_config, {
65
61
  TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
66
62
  TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
67
63
  STORAGE_TABLES: () => STORAGE_TABLES,
68
- REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
69
64
  CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
70
65
  CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
71
66
  CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
@@ -120,9 +115,6 @@ function loadStorageConfig(env = process.env) {
120
115
  function isTodosPostgresBackend(config) {
121
116
  return config.mode === "postgres";
122
117
  }
123
- function isTodosRemoteStorageEnabled(config) {
124
- return isTodosPostgresBackend(config);
125
- }
126
118
  function assertTodosRemoteStorageConfig(config) {
127
119
  if (!isTodosPostgresBackend(config))
128
120
  return;
@@ -138,32 +130,15 @@ function parseStorageBackend(value) {
138
130
  return "sqlite";
139
131
  if (normalized === "postgres" || normalized === "postgresql")
140
132
  return "postgres";
141
- if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
142
- 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.`);
143
- }
144
133
  throw new Error(`Storage backend must be sqlite or postgres`);
145
134
  }
146
- function parseStorageMode(value) {
147
- return parseStorageBackend(value);
148
- }
149
135
  function getTodosStorageBackend(env = process.env) {
150
- for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
151
- if (Object.hasOwn(env, key) && env[key] !== undefined) {
152
- 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.`);
153
- }
154
- }
155
136
  if (!getTodosStorageDatabaseUrl(env))
156
137
  return "sqlite";
157
138
  if (isTodosShadowEnabled(env))
158
139
  return "sqlite";
159
140
  return "postgres";
160
141
  }
161
- function getTodosStorageMode(env = process.env) {
162
- return getTodosStorageBackend(env);
163
- }
164
- function getStorageMode(env = process.env) {
165
- return getTodosStorageBackend(env);
166
- }
167
142
  function isTodosShadowEnabled(env = process.env) {
168
143
  return parseBoolean(readStorageEnv(env, "shadow").value, false);
169
144
  }
@@ -230,7 +205,7 @@ function parsePositiveInteger(value, fallback) {
230
205
  }
231
206
  return parsed;
232
207
  }
233
- 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";
208
+ 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";
234
209
  var init_config = __esm(() => {
235
210
  TODOS_STORAGE_TABLES = [
236
211
  "todos_sync_records",
@@ -269,12 +244,6 @@ var init_config = __esm(() => {
269
244
  syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
270
245
  syncDryRun: "TODOS_SYNC_DRY_RUN"
271
246
  };
272
- REMOVED_STORAGE_MODE_ENV_KEYS = [
273
- "HASNA_TODOS_STORAGE_MODE",
274
- "HASNA_TODOS_MODE",
275
- "TODOS_STORAGE_MODE",
276
- "TODOS_MODE"
277
- ];
278
247
  });
279
248
 
280
249
  // src/types/index.ts
@@ -3127,6 +3096,20 @@ function ensureSchema(db) {
3127
3096
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
3128
3097
  ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
3129
3098
  ensureColumn("task_comments", "progress_pct", "INTEGER");
3099
+ ensureTable("plan_comments", `
3100
+ CREATE TABLE plan_comments (
3101
+ id TEXT PRIMARY KEY,
3102
+ plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
3103
+ agent_id TEXT,
3104
+ session_id TEXT,
3105
+ content TEXT NOT NULL,
3106
+ type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note')),
3107
+ progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100)),
3108
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
3109
+ machine_id TEXT,
3110
+ synced_at TEXT
3111
+ )`);
3112
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plan_comments_plan ON plan_comments(plan_id)");
3130
3113
  ensureColumn("projects", "machine_id", "TEXT");
3131
3114
  ensureColumn("projects", "synced_at", "TEXT");
3132
3115
  ensureColumn("tasks", "machine_id", "TEXT");
@@ -4108,6 +4091,7 @@ var init_machines = __esm(() => {
4108
4091
  "task_lists",
4109
4092
  "plans",
4110
4093
  "task_comments",
4094
+ "plan_comments",
4111
4095
  "sessions",
4112
4096
  "task_history",
4113
4097
  "webhooks",
@@ -6857,22 +6841,31 @@ function updateTaskList(id, input, db) {
6857
6841
  throw new TaskListNotFoundError(id);
6858
6842
  const sets = ["updated_at = ?"];
6859
6843
  const params = [now()];
6860
- if (input.slug !== undefined) {
6861
- const slug = slugify(input.slug);
6862
- if (!slug)
6863
- throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
6864
- 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);
6844
+ const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? existing.project_id : input.project_id;
6845
+ const scopeChanged = projectId !== existing.project_id;
6846
+ const effectiveSlug = input.slug !== undefined ? slugify(input.slug) : existing.slug;
6847
+ if (input.slug !== undefined && !effectiveSlug) {
6848
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
6849
+ }
6850
+ if (scopeChanged || input.slug !== undefined && effectiveSlug !== existing.slug) {
6851
+ 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);
6865
6852
  if (duplicate) {
6866
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
6853
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
6867
6854
  }
6868
- if (slug !== existing.slug) {
6855
+ if (scopeChanged || effectiveSlug !== existing.slug) {
6869
6856
  releaseCanonicalSlugClaims("task_list", id, d);
6870
- if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(existing.project_id), slug, id, d)) {
6871
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
6857
+ if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(projectId), effectiveSlug, id, d)) {
6858
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
6872
6859
  }
6873
6860
  }
6861
+ }
6862
+ if (input.slug !== undefined) {
6874
6863
  sets.push("slug = ?");
6875
- params.push(slug);
6864
+ params.push(effectiveSlug);
6865
+ }
6866
+ if (projectId !== existing.project_id) {
6867
+ sets.push("project_id = ?");
6868
+ params.push(projectId);
6876
6869
  }
6877
6870
  if (input.name !== undefined) {
6878
6871
  sets.push("name = ?");
@@ -9321,8 +9314,8 @@ function failTask(id, agentId, reason, options, db) {
9321
9314
  const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
9322
9315
  const timestamp2 = now();
9323
9316
  const failTx = d.transaction(() => {
9324
- const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
9325
- WHERE id = ? AND version = ?`, [safeReason, JSON.stringify(safeMeta), timestamp2, id, task.version]);
9317
+ 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 = ?
9318
+ WHERE id = ? AND version = ?`, [safeReason, timestamp2, JSON.stringify(safeMeta), timestamp2, id, task.version]);
9326
9319
  if (res.changes === 0) {
9327
9320
  const current = getTask(id, d);
9328
9321
  throw new VersionConflictError(id, task.version, current?.version ?? -1);
@@ -9334,6 +9327,7 @@ function failTask(id, agentId, reason, options, db) {
9334
9327
  status: "failed",
9335
9328
  locked_by: null,
9336
9329
  locked_at: null,
9330
+ completed_at: task.completed_at ?? timestamp2,
9337
9331
  reason: safeReason,
9338
9332
  metadata: safeMeta,
9339
9333
  version: task.version + 1,
@@ -9982,13 +9976,20 @@ function updateTaskStored(id, input, db) {
9982
9976
  if (isTerminalStatus(input.status)) {
9983
9977
  sets.push("locked_by = NULL");
9984
9978
  sets.push("locked_at = NULL");
9985
- }
9986
- if (input.status === "completed") {
9987
- sets.push("completed_at = ?");
9988
- params.push(completionTimestamp);
9979
+ if (input.status === "completed") {
9980
+ sets.push("completed_at = ?");
9981
+ params.push(completionTimestamp);
9982
+ } else {
9983
+ sets.push("completed_at = COALESCE(completed_at, ?)");
9984
+ params.push(completionTimestamp);
9985
+ }
9989
9986
  } else if (task.status === "completed" && input.completed_at === undefined) {
9990
9987
  sets.push("completed_at = NULL");
9991
9988
  }
9989
+ if (input.status === "in_progress") {
9990
+ sets.push("started_at = COALESCE(started_at, ?)");
9991
+ params.push(timestamp2);
9992
+ }
9992
9993
  }
9993
9994
  if (input.priority !== undefined) {
9994
9995
  sets.push("priority = ?");
@@ -10126,7 +10127,6 @@ function updateTaskStored(id, input, db) {
10126
10127
  if (input.approved_by !== undefined)
10127
10128
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
10128
10129
  const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
10129
- const completedNow = input.status === "completed";
10130
10130
  const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
10131
10131
  const updatedTask = {
10132
10132
  ...task,
@@ -10137,7 +10137,8 @@ function updateTaskStored(id, input, db) {
10137
10137
  updated_at: timestamp2,
10138
10138
  locked_by: terminalNow ? null : task.locked_by,
10139
10139
  locked_at: terminalNow ? null : task.locked_at,
10140
- completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
10140
+ completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : terminalNow ? task.completed_at ?? completionTimestamp : reopened ? null : task.completed_at,
10141
+ started_at: input.status === "in_progress" ? task.started_at ?? timestamp2 : task.started_at,
10141
10142
  sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
10142
10143
  actual_minutes: input.actual_minutes ?? task.actual_minutes,
10143
10144
  confidence: input.confidence !== undefined ? input.confidence : task.confidence,
@@ -10198,6 +10199,9 @@ function deleteTask(id, db) {
10198
10199
  version: row.version
10199
10200
  }, d);
10200
10201
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
10202
+ if (result.changes > 0) {
10203
+ d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
10204
+ }
10201
10205
  return result.changes > 0;
10202
10206
  }
10203
10207
  var init_task_crud = __esm(() => {
@@ -12967,6 +12971,40 @@ init_tasks();
12967
12971
  init_projects();
12968
12972
  init_plans();
12969
12973
 
12974
+ // src/db/plan-comments.ts
12975
+ init_types();
12976
+ init_database();
12977
+ init_plans();
12978
+ init_prewrite_secrets();
12979
+ function addPlanComment(input, db) {
12980
+ const d = db || getDatabase();
12981
+ if (!getPlan(input.plan_id, d)) {
12982
+ throw new PlanNotFoundError(input.plan_id);
12983
+ }
12984
+ const id = uuid();
12985
+ const timestamp2 = now();
12986
+ d.run(`INSERT INTO plan_comments (id, plan_id, agent_id, session_id, content, type, progress_pct, created_at)
12987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
12988
+ id,
12989
+ input.plan_id,
12990
+ input.agent_id || null,
12991
+ input.session_id || null,
12992
+ sanitizePreWriteText(input.content, "comment.content"),
12993
+ input.type || "comment",
12994
+ input.progress_pct ?? null,
12995
+ timestamp2
12996
+ ]);
12997
+ return getPlanComment(id, d);
12998
+ }
12999
+ function getPlanComment(id, db) {
13000
+ const d = db || getDatabase();
13001
+ return d.query("SELECT * FROM plan_comments WHERE id = ?").get(id);
13002
+ }
13003
+ function listPlanComments(planId, db) {
13004
+ const d = db || getDatabase();
13005
+ return d.query("SELECT * FROM plan_comments WHERE plan_id = ? ORDER BY created_at, rowid").all(planId);
13006
+ }
13007
+
12970
13008
  // src/db/agents.ts
12971
13009
  init_types();
12972
13010
  init_database();
@@ -13624,6 +13662,20 @@ init_task_lists();
13624
13662
  init_templates();
13625
13663
  init_audit();
13626
13664
  init_comments();
13665
+
13666
+ // src/lib/comment-cursor.ts
13667
+ function isStrictlyOlder(comment, before) {
13668
+ return comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id;
13669
+ }
13670
+ function compareCommentKeyset(left, right) {
13671
+ if (isStrictlyOlder(left, right))
13672
+ return -1;
13673
+ if (isStrictlyOlder(right, left))
13674
+ return 1;
13675
+ return 0;
13676
+ }
13677
+
13678
+ // src/storage/local-sqlite.ts
13627
13679
  init_database();
13628
13680
 
13629
13681
  // src/db/integrity.ts
@@ -14720,7 +14772,22 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
14720
14772
  list: (projectId) => listPlans(projectId, database()),
14721
14773
  update: (id, input) => updatePlan(id, input, database()),
14722
14774
  completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
14723
- delete: (id) => deletePlan(id, database())
14775
+ delete: (id) => deletePlan(id, database()),
14776
+ addComment: (input) => addPlanComment(input, database()),
14777
+ getComments: (planId) => listPlanComments(planId, database()),
14778
+ getCommentsPage: (planId, options2) => {
14779
+ if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
14780
+ throw new Error("Plan comment limit must be an integer between 1 and 1001");
14781
+ }
14782
+ let comments = listPlanComments(planId, database());
14783
+ comments = comments.sort(compareCommentKeyset);
14784
+ if (options2?.before) {
14785
+ const before = options2.before;
14786
+ comments = comments.filter((comment) => isStrictlyOlder(comment, before));
14787
+ }
14788
+ const limit = options2?.limit ?? 100;
14789
+ return comments.slice(-limit);
14790
+ }
14724
14791
  },
14725
14792
  planProjectLinks: {
14726
14793
  apply: (input) => applyPlanProjectLinkSqlite(input, database()),
@@ -14761,10 +14828,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
14761
14828
  throw new Error("Comment limit must be an integer between 1 and 1001");
14762
14829
  }
14763
14830
  let comments = listComments(taskId, database());
14764
- comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
14831
+ comments = comments.sort(compareCommentKeyset);
14765
14832
  if (options2?.before) {
14766
14833
  const before = options2.before;
14767
- comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
14834
+ comments = comments.filter((comment) => isStrictlyOlder(comment, before));
14768
14835
  }
14769
14836
  if (options2?.limit !== undefined)
14770
14837
  comments = comments.slice(-options2.limit);
@@ -15294,7 +15361,26 @@ function createPostgresTodosStorageAdapter(options) {
15294
15361
  list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
15295
15362
  update: (id, input) => updatePlan2(id, input, store),
15296
15363
  completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
15297
- delete: (id, context) => store.deletePlan(id, context)
15364
+ delete: (id, context) => store.deletePlan(id, context),
15365
+ addComment: (input, context) => addPlanComment2(input, store, context),
15366
+ getComments: async (planId) => {
15367
+ const pages = [];
15368
+ let before;
15369
+ while (true) {
15370
+ const page = await store.listPlanComments(planId, { limit: 1000, ...before ? { before } : {} });
15371
+ if (page.length === 0)
15372
+ break;
15373
+ pages.unshift(page);
15374
+ if (page.length < 1000)
15375
+ break;
15376
+ const oldest = page[0];
15377
+ before = { created_at: oldest.created_at, id: oldest.id };
15378
+ }
15379
+ return pages.flat().map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
15380
+ },
15381
+ getCommentsPage: async (planId, options2) => {
15382
+ return (await store.listPlanComments(planId, options2)).map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
15383
+ }
15298
15384
  },
15299
15385
  planProjectLinks: {
15300
15386
  apply: (input, context) => store.applyPlanProjectLink(input, context),
@@ -15512,6 +15598,27 @@ class PostgresJsonRecordStore {
15512
15598
  LIMIT $${params.length}`, params);
15513
15599
  return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
15514
15600
  }
15601
+ async listPlanComments(planId, options = {}) {
15602
+ await this.ensureSchema();
15603
+ const limit = options.limit ?? 100;
15604
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
15605
+ throw new Error("Postgres plan comment limit must be an integer between 1 and 1001");
15606
+ }
15607
+ const params = [this.service, planId];
15608
+ let cursorPredicate = "";
15609
+ if (options.before) {
15610
+ params.push(options.before.created_at, options.before.id);
15611
+ cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
15612
+ }
15613
+ params.push(limit);
15614
+ const result = await this.options.client.query(`/* todos:list-plan-comments */ SELECT payload FROM ${this.tableName}
15615
+ WHERE service = $1 AND object_type = 'plan_comments' AND deleted_at IS NULL
15616
+ AND payload->>'plan_id' = $2
15617
+ ${cursorPredicate}
15618
+ ORDER BY payload->>'created_at' DESC, object_id DESC
15619
+ LIMIT $${params.length}`, params);
15620
+ return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
15621
+ }
15515
15622
  async listRecords(type) {
15516
15623
  await this.ensureSchema();
15517
15624
  const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
@@ -16673,6 +16780,15 @@ class PostgresJsonRecordStore {
16673
16780
  WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
16674
16781
  AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
16675
16782
  RETURNING 1
16783
+ ), deleted_plan_comments AS (
16784
+ UPDATE ${this.tableName} r SET
16785
+ deleted_at = $3::timestamptz,
16786
+ updated_at = $3::timestamptz,
16787
+ source_machine_id = COALESCE($4, r.source_machine_id),
16788
+ version = COALESCE(r.version, 0) + 1
16789
+ WHERE r.service = $1 AND r.object_type = 'plan_comments' AND r.deleted_at IS NULL
16790
+ AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
16791
+ RETURNING 1
16676
16792
  ), deleted_plan AS (
16677
16793
  UPDATE ${this.tableName} r SET
16678
16794
  deleted_at = $3::timestamptz,
@@ -16848,7 +16964,8 @@ async function updateTask2(id, input, store, context) {
16848
16964
  task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
16849
16965
  parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
16850
16966
  created_by: existing.created_by,
16851
- completed_at: reopened ? null : input.completed_at !== undefined ? input.completed_at : existing.completed_at
16967
+ 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,
16968
+ started_at: input.status === "in_progress" ? existing.started_at ?? new Date().toISOString() : existing.started_at
16852
16969
  };
16853
16970
  const storedTask = await store.upsertTaskWithPlanMembershipGuard(task, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
16854
16971
  operation: "update",
@@ -16893,7 +17010,8 @@ async function failTask2(id, agentId, reason, options, store) {
16893
17010
  status: "failed",
16894
17011
  assigned_to: task.assigned_to ?? agentId ?? null,
16895
17012
  reason: reason ?? task.reason,
16896
- retry_after: options?.retry_after ?? task.retry_after
17013
+ retry_after: options?.retry_after ?? task.retry_after,
17014
+ completed_at: task.completed_at ?? new Date().toISOString()
16897
17015
  }, store);
16898
17016
  if (!options?.retry)
16899
17017
  return { task: failed };
@@ -17341,16 +17459,22 @@ async function createTaskList2(input, store, context) {
17341
17459
  async function updateTaskList2(id, input, store) {
17342
17460
  const list = await requireRecord("task_lists", id, store);
17343
17461
  const patch = definedPatch(input);
17462
+ const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? list.project_id : input.project_id;
17344
17463
  if (input.slug !== undefined) {
17345
17464
  const slug = slugifyRaw(input.slug);
17346
17465
  if (!slug)
17347
17466
  throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
17348
- const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === list.project_id && candidate.slug === slug);
17467
+ patch.slug = slug;
17468
+ }
17469
+ const effectiveSlug = patch.slug ?? list.slug;
17470
+ if (projectId !== list.project_id || effectiveSlug !== list.slug) {
17471
+ const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === projectId && candidate.slug === effectiveSlug);
17349
17472
  if (duplicate) {
17350
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
17473
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
17351
17474
  }
17352
- patch.slug = slug;
17353
17475
  }
17476
+ if (projectId !== list.project_id)
17477
+ patch.project_id = projectId;
17354
17478
  return store.upsert("task_lists", {
17355
17479
  ...list,
17356
17480
  ...patch,
@@ -17443,6 +17567,22 @@ async function addComment2(input, store, context) {
17443
17567
  function redactComment(comment) {
17444
17568
  return { ...comment, content: redactEvidenceText(comment.content) };
17445
17569
  }
17570
+ async function addPlanComment2(input, store, context) {
17571
+ const comment = {
17572
+ id: randomUUID3(),
17573
+ plan_id: input.plan_id,
17574
+ agent_id: input.agent_id ?? context?.agentId ?? null,
17575
+ session_id: input.session_id ?? context?.sessionId ?? null,
17576
+ content: redactEvidenceText(input.content),
17577
+ type: input.type ?? "comment",
17578
+ progress_pct: input.progress_pct ?? null,
17579
+ created_at: new Date().toISOString()
17580
+ };
17581
+ return store.upsert("plan_comments", comment, context);
17582
+ }
17583
+ function redactPlanComment(comment) {
17584
+ return { ...comment, content: redactEvidenceText(comment.content) };
17585
+ }
17446
17586
  async function exportSnapshot(store) {
17447
17587
  return {
17448
17588
  exportedAt: new Date().toISOString(),
@@ -19051,24 +19191,20 @@ export {
19051
19191
  postgresTodosScopedSlugIndexStatusSql,
19052
19192
  postgresTodosCommentCursorIndexSql,
19053
19193
  planRunArtifactsS3Sync,
19054
- parseStorageMode,
19055
19194
  parseStorageBackend,
19056
19195
  maybeInstallShadowCapture2 as maybeInstallShadowCapture,
19057
19196
  loadTodosStorageConfig,
19058
19197
  loadStorageConfig,
19059
19198
  isTodosShadowEnabled,
19060
- isTodosRemoteStorageEnabled,
19061
19199
  isTodosPostgresBackend,
19062
19200
  isCommentRedactionBackfillComplete,
19063
19201
  installShadowOutboxSchema,
19064
19202
  importSqliteTodosStorageSnapshot,
19065
19203
  getTodosStorageShadowEnvName,
19066
- getTodosStorageMode,
19067
19204
  getTodosStorageEnvName,
19068
19205
  getTodosStorageDatabaseUrl,
19069
19206
  getTodosStorageDatabaseEnv,
19070
19207
  getTodosStorageBackend,
19071
- getStorageMode,
19072
19208
  getStorageDatabaseUrl,
19073
19209
  getStorageDatabaseEnv,
19074
19210
  getRuntimeShadowOutbox,
@@ -0,0 +1,27 @@
1
+ import { type PostgresTodosTaskSubtreeTransferAuthorityOptions, type SqliteTodosTaskSubtreeTransferAuthorityOptions, type TodosTaskSubtreeTransferApplyRequest, type TodosTaskSubtreeTransferAuthority, type TodosTaskSubtreeTransferAuthorityOptions, type TodosTaskSubtreeTransferCapability, type TodosTaskSubtreeTransferInspectRequest, type TodosTaskSubtreeTransferRollbackRequest } from "./types.js";
2
+ import type { TodosTaskSubtreeTransferBackend } from "./backend.js";
3
+ export declare function parseTodosTaskSubtreeTransferInspect(input: unknown): TodosTaskSubtreeTransferInspectRequest;
4
+ export declare function parseTodosTaskSubtreeTransferApply(input: unknown): TodosTaskSubtreeTransferApplyRequest;
5
+ export declare function parseTodosTaskSubtreeTransferRollback(input: unknown): TodosTaskSubtreeTransferRollbackRequest;
6
+ export { deriveTodosTaskSubtreeTransferIdempotencyKey, taskSubtreeTransferRequestDigest, taskSubtreeTransferRollbackRequestDigest, } from "./canonical.js";
7
+ export declare function deriveTodosTaskSubtreeTransferApplyPreconditionDigest(input: Pick<TodosTaskSubtreeTransferApplyRequest, "source_project_id" | "destination_project_id" | "destination_task_list_id" | "root_task_id" | "expected_root_parent_id" | "destination_parent_id" | "source_population_digest" | "expected_tasks" | "shared_plan_splits">): string;
8
+ export declare function deriveTodosTaskSubtreeTransferRollbackPreconditionDigest(input: {
9
+ receipt_id: string;
10
+ operation_id: string;
11
+ step_id: string;
12
+ apply_result_digest: string;
13
+ }): string;
14
+ export declare class PackageOwnedTodosTaskSubtreeTransferAuthority implements TodosTaskSubtreeTransferAuthority {
15
+ private readonly backend;
16
+ private readonly tenantId;
17
+ private readonly backendOptions;
18
+ constructor(backend: TodosTaskSubtreeTransferBackend, options?: TodosTaskSubtreeTransferAuthorityOptions);
19
+ capability(): Promise<TodosTaskSubtreeTransferCapability>;
20
+ inspect(input: unknown): Promise<import("./types.js").TodosTaskSubtreeTransferInspection>;
21
+ apply(input: unknown): Promise<import("./types.js").TodosTaskSubtreeTransferResult>;
22
+ readExact(receiptIdValue: string): Promise<import("./types.js").TodosTaskSubtreeTransferResult>;
23
+ rollback(input: unknown): Promise<import("./types.js").TodosTaskSubtreeTransferResult>;
24
+ }
25
+ export declare function createSqliteTodosTaskSubtreeTransferAuthority(options: SqliteTodosTaskSubtreeTransferAuthorityOptions): TodosTaskSubtreeTransferAuthority;
26
+ export declare function createPostgresTodosTaskSubtreeTransferAuthority(client: import("./types.js").TodosTaskSubtreeTransferPostgresClient, options?: PostgresTodosTaskSubtreeTransferAuthorityOptions): TodosTaskSubtreeTransferAuthority;
27
+ //# sourceMappingURL=authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/task-subtree-transfer/authority.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,gDAAgD,EACrD,KAAK,8CAA8C,EACnD,KAAK,oCAAoC,EACzC,KAAK,iCAAiC,EACtC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,sCAAsC,EAC3C,KAAK,uCAAuC,EAC7C,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAsEpE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,sCAAsC,CAgBxC;AAeD,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,OAAO,GACb,oCAAoC,CAkEtC;AAED,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,OAAO,GACb,uCAAuC,CAezC;AAED,OAAO,EACL,4CAA4C,EAC5C,gCAAgC,EAChC,wCAAwC,GACzC,MAAM,gBAAgB,CAAC;AAExB,wBAAgB,qDAAqD,CACnE,KAAK,EAAE,IAAI,CACT,oCAAoC,EACpC,mBAAmB,GACjB,wBAAwB,GACxB,0BAA0B,GAC1B,cAAc,GACd,yBAAyB,GACzB,uBAAuB,GACvB,0BAA0B,GAC1B,gBAAgB,GAChB,oBAAoB,CACvB,GACA,MAAM,CAyBR;AAED,wBAAgB,wDAAwD,CACtE,KAAK,EAAE;IACL,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GACA,MAAM,CASR;AAaD,qBAAa,6CAA8C,YAAW,iCAAiC;IAInG,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2C;gBAEvD,OAAO,EAAE,+BAA+B,EACzD,OAAO,GAAE,wCAA6C;IAMlD,UAAU,IAAI,OAAO,CAAC,kCAAkC,CAAC;IAoBzD,OAAO,CAAC,KAAK,EAAE,OAAO;IAItB,KAAK,CAAC,KAAK,EAAE,OAAO;IA6BpB,SAAS,CAAC,cAAc,EAAE,MAAM;IAIhC,QAAQ,CAAC,KAAK,EAAE,OAAO;CAoB9B;AAED,wBAAgB,6CAA6C,CAC3D,OAAO,EAAE,8CAA8C,GACtD,iCAAiC,CAKnC;AAED,wBAAgB,+CAA+C,CAC7D,MAAM,EAAE,OAAO,YAAY,EAAE,sCAAsC,EACnE,OAAO,GAAE,gDAAqD,GAC7D,iCAAiC,CAKnC"}
@@ -0,0 +1,9 @@
1
+ import type { TodosTaskSubtreeTransferApplyRequest, TodosTaskSubtreeTransferAuthorityOptions, TodosTaskSubtreeTransferInspectRequest, TodosTaskSubtreeTransferInspection, TodosTaskSubtreeTransferResult, TodosTaskSubtreeTransferRollbackRequest } from "./types.js";
2
+ export interface TodosTaskSubtreeTransferBackend {
3
+ readonly kind: "sqlite" | "postgresql";
4
+ inspect(input: TodosTaskSubtreeTransferInspectRequest): Promise<TodosTaskSubtreeTransferInspection>;
5
+ apply(input: TodosTaskSubtreeTransferApplyRequest, options: TodosTaskSubtreeTransferAuthorityOptions): Promise<TodosTaskSubtreeTransferResult>;
6
+ readExact(receiptId: string): Promise<TodosTaskSubtreeTransferResult>;
7
+ rollback(input: TodosTaskSubtreeTransferRollbackRequest, options: TodosTaskSubtreeTransferAuthorityOptions): Promise<TodosTaskSubtreeTransferResult>;
8
+ }
9
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/task-subtree-transfer/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oCAAoC,EACpC,wCAAwC,EACxC,sCAAsC,EACtC,kCAAkC,EAClC,8BAA8B,EAC9B,uCAAuC,EACxC,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;IACvC,OAAO,CAAC,KAAK,EAAE,sCAAsC,GAAG,OAAO,CAAC,kCAAkC,CAAC,CAAC;IACpG,KAAK,CAAC,KAAK,EAAE,oCAAoC,EAAE,OAAO,EAAE,wCAAwC,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC/I,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;IACtE,QAAQ,CAAC,KAAK,EAAE,uCAAuC,EAAE,OAAO,EAAE,wCAAwC,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACtJ"}
@@ -0,0 +1,14 @@
1
+ import { canonicalDigest, canonicalJson, deterministicUuid } from "../task-manifest/canonical.js";
2
+ import { type TodosTaskSubtreeTransferApplyRequest, type TodosTaskSubtreeTransferDirection, type TodosTaskSubtreeTransferRollbackRequest } from "./types.js";
3
+ export { canonicalDigest, canonicalJson, deterministicUuid };
4
+ export declare function taskSubtreeTransferRequestDigest(input: Omit<TodosTaskSubtreeTransferApplyRequest, "idempotency_key">): string;
5
+ export declare function taskSubtreeTransferRollbackRequestDigest(input: Omit<TodosTaskSubtreeTransferRollbackRequest, "idempotency_key">): string;
6
+ export declare function deriveTodosTaskSubtreeTransferIdempotencyKey(input: {
7
+ operation_id: string;
8
+ step_id: string;
9
+ direction: TodosTaskSubtreeTransferDirection;
10
+ target_selector: string;
11
+ request_digest: string;
12
+ precondition_digest: string;
13
+ }): string;
14
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../src/task-subtree-transfer/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,aAAa,EACb,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,KAAK,oCAAoC,EACzC,KAAK,iCAAiC,EACtC,KAAK,uCAAuC,EAC7C,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC;AAE7D,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,IAAI,CAAC,oCAAoC,EAAE,iBAAiB,CAAC,GACnE,MAAM,CAGR;AAED,wBAAgB,wCAAwC,CACtD,KAAK,EAAE,IAAI,CAAC,uCAAuC,EAAE,iBAAiB,CAAC,GACtE,MAAM,CAGR;AAED,wBAAgB,4CAA4C,CAAC,KAAK,EAAE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,iCAAiC,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,MAAM,CAKT"}
@@ -0,0 +1,16 @@
1
+ import { type TodosTaskSubtreeTransferAuthority, type TodosTaskSubtreeTransferHttpClientOptions } from "./types.js";
2
+ export declare function handleTodosTaskSubtreeTransferHttpRequest(request: Request, url: URL, authority: TodosTaskSubtreeTransferAuthority, basePath?: string): Promise<Response | null>;
3
+ export declare class TodosTaskSubtreeTransferHttpClient implements TodosTaskSubtreeTransferAuthority {
4
+ private readonly baseUrl;
5
+ private readonly fetchImpl;
6
+ private readonly headers;
7
+ constructor(options: TodosTaskSubtreeTransferHttpClientOptions);
8
+ private request;
9
+ capability(): Promise<import("./types.js").TodosTaskSubtreeTransferCapability>;
10
+ inspect(input: unknown): Promise<import("./types.js").TodosTaskSubtreeTransferInspection>;
11
+ apply(input: unknown): Promise<import("./types.js").TodosTaskSubtreeTransferResult>;
12
+ readExact(receiptId: string): Promise<import("./types.js").TodosTaskSubtreeTransferResult>;
13
+ rollback(input: unknown): Promise<import("./types.js").TodosTaskSubtreeTransferResult>;
14
+ }
15
+ export declare function createTodosTaskSubtreeTransferHttpClient(options: TodosTaskSubtreeTransferHttpClientOptions): TodosTaskSubtreeTransferAuthority;
16
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/task-subtree-transfer/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iCAAiC,EACtC,KAAK,yCAAyC,EAC/C,MAAM,YAAY,CAAC;AA0EpB,wBAAsB,yCAAyC,CAC7D,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,iCAAiC,EAC5C,QAAQ,SAA8B,GACrC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiC1B;AAED,qBAAa,kCAAmC,YAAW,iCAAiC;IAC1F,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAErC,OAAO,EAAE,yCAAyC;YAUhD,OAAO;IA+Bf,UAAU;IAIV,OAAO,CAAC,KAAK,EAAE,OAAO;IAOtB,KAAK,CAAC,KAAK,EAAE,OAAO;IAOpB,SAAS,CAAC,SAAS,EAAE,MAAM;IAO3B,QAAQ,CAAC,KAAK,EAAE,OAAO;CAM9B;AAED,wBAAgB,wCAAwC,CACtD,OAAO,EAAE,yCAAyC,GACjD,iCAAiC,CAEnC"}
@@ -0,0 +1,6 @@
1
+ export { PackageOwnedTodosTaskSubtreeTransferAuthority, createPostgresTodosTaskSubtreeTransferAuthority, createSqliteTodosTaskSubtreeTransferAuthority, deriveTodosTaskSubtreeTransferApplyPreconditionDigest, deriveTodosTaskSubtreeTransferIdempotencyKey, deriveTodosTaskSubtreeTransferRollbackPreconditionDigest, parseTodosTaskSubtreeTransferApply, parseTodosTaskSubtreeTransferInspect, parseTodosTaskSubtreeTransferRollback, taskSubtreeTransferRequestDigest, taskSubtreeTransferRollbackRequestDigest, } from "./authority.js";
2
+ export { TodosTaskSubtreeTransferHttpClient, createTodosTaskSubtreeTransferHttpClient, handleTodosTaskSubtreeTransferHttpRequest, } from "./http.js";
3
+ export { TODOS_TASK_SUBTREE_TRANSFER_ROUTE, TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION, TodosTaskSubtreeTransferError, } from "./types.js";
4
+ export type * from "./types.js";
5
+ export { postgresTodosTaskSubtreeTransferSchemaSql, sqliteTodosTaskSubtreeTransferSchemaSql, } from "./schema-sql.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/task-subtree-transfer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6CAA6C,EAC7C,+CAA+C,EAC/C,6CAA6C,EAC7C,qDAAqD,EACrD,4CAA4C,EAC5C,wDAAwD,EACxD,kCAAkC,EAClC,oCAAoC,EACpC,qCAAqC,EACrC,gCAAgC,EAChC,wCAAwC,GACzC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kCAAkC,EAClC,wCAAwC,EACxC,yCAAyC,GAC1C,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iCAAiC,EACjC,0CAA0C,EAC1C,6BAA6B,GAC9B,MAAM,YAAY,CAAC;AACpB,mBAAmB,YAAY,CAAC;AAChC,OAAO,EACL,yCAAyC,EACzC,uCAAuC,GACxC,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type TodosTaskSubtreeTransferApplyRequest, type TodosTaskSubtreeTransferInspection, type TodosTaskSubtreeTransferPlanImage, type TodosTaskSubtreeTransferTaskImage } from "./types.js";
2
+ export interface TransferTaskRecord extends TodosTaskSubtreeTransferTaskImage {
3
+ archived_at?: string | null;
4
+ }
5
+ export interface TransferPlanRecord extends TodosTaskSubtreeTransferPlanImage {
6
+ }
7
+ export interface TransferSnapshot {
8
+ source_tasks: TransferTaskRecord[];
9
+ plan_tasks: TransferTaskRecord[];
10
+ plans: TransferPlanRecord[];
11
+ destination_project_found: boolean;
12
+ destination_task_list_found: boolean;
13
+ destination_parent_found: boolean;
14
+ }
15
+ export interface PreparedTransfer {
16
+ inspection: TodosTaskSubtreeTransferInspection;
17
+ prior_tasks: TodosTaskSubtreeTransferTaskImage[];
18
+ prior_plans: TodosTaskSubtreeTransferPlanImage[];
19
+ task_plan_targets: Map<string, string | null>;
20
+ }
21
+ export declare function sourcePopulationDigest(tasks: TransferTaskRecord[]): string;
22
+ export declare function prepareTransfer(snapshot: TransferSnapshot, input: TodosTaskSubtreeTransferApplyRequest | (Omit<TodosTaskSubtreeTransferApplyRequest, "version" | "operation_id" | "step_id" | "idempotency_key" | "precondition_digest" | "expected_root_parent_id" | "source_population_digest" | "expected_tasks" | "shared_plan_splits">)): PreparedTransfer;
23
+ export declare function validateApplySnapshot(prepared: PreparedTransfer, request: TodosTaskSubtreeTransferApplyRequest, plans: TransferPlanRecord[]): void;
24
+ //# sourceMappingURL=planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.d.ts","sourceRoot":"","sources":["../../src/task-subtree-transfer/planner.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,oCAAoC,EACzC,KAAK,kCAAkC,EACvC,KAAK,iCAAiC,EACtC,KAAK,iCAAiC,EACvC,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAmB,SAAQ,iCAAiC;IAC3E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAmB,SAAQ,iCAAiC;CAAG;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,yBAAyB,EAAE,OAAO,CAAC;IACnC,2BAA2B,EAAE,OAAO,CAAC;IACrC,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,kCAAkC,CAAC;IAC/C,WAAW,EAAE,iCAAiC,EAAE,CAAC;IACjD,WAAW,EAAE,iCAAiC,EAAE,CAAC;IACjD,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CAC/C;AAcD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAO1E;AAkDD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,oCAAoC,GAAG,CAAC,IAAI,CAAC,oCAAoC,EACtF,SAAS,GAAG,cAAc,GAAG,SAAS,GAAG,iBAAiB,GAAG,qBAAqB,GAChF,yBAAyB,GAAG,0BAA0B,GAAG,gBAAgB,GAAG,oBAAoB,CAAC,CAAC,GACrG,gBAAgB,CAuElB;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,oCAAoC,EAC7C,KAAK,EAAE,kBAAkB,EAAE,GAC1B,IAAI,CAsEN"}