@hasna/todos 0.11.86 → 0.11.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli/cloud-router.d.ts +35 -2
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/agent-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/task-commands.d.ts +2 -0
  5. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  6. package/dist/cli/index.js +989 -416
  7. package/dist/contracts.js +1 -1
  8. package/dist/db/comments.d.ts.map +1 -1
  9. package/dist/index.js +167 -4
  10. package/dist/mcp/index.js +407 -38
  11. package/dist/registry.js +1 -1
  12. package/dist/release-provenance.json +3 -3
  13. package/dist/sdk/index.d.ts +1 -1
  14. package/dist/sdk/index.d.ts.map +1 -1
  15. package/dist/sdk/index.js +21 -0
  16. package/dist/sdk/v1.generated.d.ts +54 -0
  17. package/dist/sdk/v1.generated.d.ts.map +1 -1
  18. package/dist/server/cloud.d.ts +13 -0
  19. package/dist/server/cloud.d.ts.map +1 -1
  20. package/dist/server/index.js +824 -383
  21. package/dist/server/openapi.d.ts +171 -0
  22. package/dist/server/openapi.d.ts.map +1 -1
  23. package/dist/server/v1.d.ts +7 -1
  24. package/dist/server/v1.d.ts.map +1 -1
  25. package/dist/storage/comment-redaction-backfill.d.ts +32 -0
  26. package/dist/storage/comment-redaction-backfill.d.ts.map +1 -0
  27. package/dist/storage/index.d.ts +4 -2
  28. package/dist/storage/index.d.ts.map +1 -1
  29. package/dist/storage/interfaces.d.ts +16 -0
  30. package/dist/storage/interfaces.d.ts.map +1 -1
  31. package/dist/storage/local-sqlite.d.ts.map +1 -1
  32. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  33. package/dist/storage/postgres-sync.d.ts +6 -0
  34. package/dist/storage/postgres-sync.d.ts.map +1 -1
  35. package/dist/storage.d.ts +3 -3
  36. package/dist/storage.d.ts.map +1 -1
  37. package/dist/storage.js +171 -4
  38. package/package.json +2 -1
  39. package/vendor/hasna-contracts-0.5.1.tgz +0 -0
package/dist/contracts.js CHANGED
@@ -9424,7 +9424,7 @@ function getComment(id, db) {
9424
9424
  }
9425
9425
  function listComments(taskId, db) {
9426
9426
  const d = db || getDatabase();
9427
- return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
9427
+ return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at, rowid").all(taskId);
9428
9428
  }
9429
9429
  function deleteComment(id, db) {
9430
9430
  const d = db || getDatabase();
@@ -1 +1 @@
1
- {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/db/comments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMzE,wBAAgB,UAAU,CACxB,KAAK,EAAE,kBAAkB,EACzB,EAAE,CAAC,EAAE,QAAQ,GACZ,WAAW,CA2Bb;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,EAChB,EAAE,CAAC,EAAE,QAAQ,GACZ,WAAW,CAEb;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,IAAI,CAKxE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,WAAW,EAAE,CAOzE;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAC1B,EAAE,CAAC,EAAE,QAAQ,GACZ,WAAW,CAQb;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAIhE"}
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/db/comments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMzE,wBAAgB,UAAU,CACxB,KAAK,EAAE,kBAAkB,EACzB,EAAE,CAAC,EAAE,QAAQ,GACZ,WAAW,CA2Bb;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,EAChB,EAAE,CAAC,EAAE,QAAQ,GACZ,WAAW,CAEb;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,IAAI,CAKxE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,WAAW,EAAE,CAUzE;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAC1B,EAAE,CAAC,EAAE,QAAQ,GACZ,WAAW,CAQb;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAIhE"}
package/dist/index.js CHANGED
@@ -9424,7 +9424,7 @@ function getComment(id, db) {
9424
9424
  }
9425
9425
  function listComments(taskId, db) {
9426
9426
  const d = db || getDatabase();
9427
- return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
9427
+ return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at, rowid").all(taskId);
9428
9428
  }
9429
9429
  function deleteComment(id, db) {
9430
9430
  const d = db || getDatabase();
@@ -23170,6 +23170,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
23170
23170
  list: (filter = {}) => listTasks(filter, database()),
23171
23171
  count: (filter = {}) => countTasks(filter, database()),
23172
23172
  update: (id, input) => updateTask(id, input, database()),
23173
+ unlock: (id, agentId) => {
23174
+ unlockTask(id, agentId, database());
23175
+ return true;
23176
+ },
23173
23177
  delete: (id) => deleteTask(id, database()),
23174
23178
  start: (id, agentId) => startTask(id, agentId, database()),
23175
23179
  complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
@@ -23221,6 +23225,20 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
23221
23225
  logTaskChange: (taskId, action, field2, oldValue, newValue, agentId) => logTaskChange(taskId, action, field2, oldValue, newValue, agentId, database()),
23222
23226
  addComment: (input) => addComment(input, database()),
23223
23227
  getComments: (taskId) => listComments(taskId, database()),
23228
+ getCommentsPage: (taskId, options2) => {
23229
+ if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
23230
+ throw new Error("Comment limit must be an integer between 1 and 1001");
23231
+ }
23232
+ let comments = listComments(taskId, database());
23233
+ comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
23234
+ if (options2?.before) {
23235
+ const before = options2.before;
23236
+ comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
23237
+ }
23238
+ if (options2?.limit !== undefined)
23239
+ comments = comments.slice(-options2.limit);
23240
+ return comments;
23241
+ },
23224
23242
  getTaskHistory: (taskId) => getTaskHistory(taskId, database()),
23225
23243
  getRecentActivity: (limit) => getRecentActivity(limit, database())
23226
23244
  },
@@ -23268,6 +23286,12 @@ function postgresTodosSyncSchemaSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABL
23268
23286
  )`
23269
23287
  ];
23270
23288
  }
23289
+ function postgresTodosCommentCursorIndexSql(tableName = DEFAULT_TODOS_POSTGRES_SYNC_TABLE) {
23290
+ assertSafeIdentifier(tableName);
23291
+ return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${tableName}_comment_task_created_idx
23292
+ ON ${tableName} (service, (payload->>'task_id'), (payload->>'created_at'), object_id)
23293
+ WHERE object_type = 'comments' AND deleted_at IS NULL`;
23294
+ }
23271
23295
 
23272
23296
  class PostgresTodosSyncStore {
23273
23297
  client;
@@ -23500,7 +23524,9 @@ function createHybridTodosStorageAdapter(options) {
23500
23524
  }
23501
23525
 
23502
23526
  // src/storage/postgres-adapter.ts
23527
+ init_types();
23503
23528
  import { randomUUID as randomUUID3 } from "crypto";
23529
+ init_redaction();
23504
23530
  function createPostgresTodosStorageAdapter(options) {
23505
23531
  const store = new PostgresJsonRecordStore(options);
23506
23532
  const adapter = {
@@ -23596,7 +23622,24 @@ function createPostgresTodosStorageAdapter(options) {
23596
23622
  audit: {
23597
23623
  logTaskChange: (taskId, action, field2, oldValue, newValue, agentId, context) => logTaskChange2(taskId, action, field2, oldValue, newValue, agentId, store, context),
23598
23624
  addComment: (input, context) => addComment2(input, store, context),
23599
- getComments: async (taskId) => (await store.list("comments")).filter((comment) => comment.task_id === taskId).sort((a, b) => a.created_at.localeCompare(b.created_at)),
23625
+ getComments: async (taskId) => {
23626
+ const pages = [];
23627
+ let before;
23628
+ while (true) {
23629
+ const page = await store.listComments(taskId, { limit: 1000, ...before ? { before } : {} });
23630
+ if (page.length === 0)
23631
+ break;
23632
+ pages.unshift(page);
23633
+ if (page.length < 1000)
23634
+ break;
23635
+ const oldest = page[0];
23636
+ before = { created_at: oldest.created_at, id: oldest.id };
23637
+ }
23638
+ return pages.flat().map(redactComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
23639
+ },
23640
+ getCommentsPage: async (taskId, options2) => {
23641
+ return (await store.listComments(taskId, options2)).map(redactComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
23642
+ },
23600
23643
  getTaskHistory: async (taskId) => (await store.list("audit_history")).filter((entry2) => entry2.task_id === taskId).sort((a, b) => a.created_at.localeCompare(b.created_at)),
23601
23644
  getRecentActivity: async (limit = 20) => (await store.list("audit_history")).sort((a, b) => b.created_at.localeCompare(a.created_at)).slice(0, limit)
23602
23645
  },
@@ -23646,6 +23689,27 @@ class PostgresJsonRecordStore {
23646
23689
  async list(type) {
23647
23690
  return (await this.listRecords(type)).map((record) => record.payload);
23648
23691
  }
23692
+ async listComments(taskId, options = {}) {
23693
+ await this.ensureSchema();
23694
+ const limit = options.limit ?? 100;
23695
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
23696
+ throw new Error("Postgres comment limit must be an integer between 1 and 1001");
23697
+ }
23698
+ const params = [this.service, taskId];
23699
+ let cursorPredicate = "";
23700
+ if (options.before) {
23701
+ params.push(options.before.created_at, options.before.id);
23702
+ cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
23703
+ }
23704
+ params.push(limit);
23705
+ const result = await this.options.client.query(`/* todos:list-comments */ SELECT payload FROM ${this.tableName}
23706
+ WHERE service = $1 AND object_type = 'comments' AND deleted_at IS NULL
23707
+ AND payload->>'task_id' = $2
23708
+ ${cursorPredicate}
23709
+ ORDER BY payload->>'created_at' DESC, object_id DESC
23710
+ LIMIT $${params.length}`, params);
23711
+ return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
23712
+ }
23649
23713
  async listRecords(type) {
23650
23714
  await this.ensureSchema();
23651
23715
  const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
@@ -24040,7 +24104,7 @@ async function lockTask2(id, agentId, store) {
24040
24104
  async function unlockTask2(id, agentId, store) {
24041
24105
  const task2 = await requireRecord("tasks", id, store);
24042
24106
  if (agentId && task2.locked_by && task2.locked_by !== agentId) {
24043
- throw new Error(`Task ${id} is locked by ${task2.locked_by}, not ${agentId}`);
24107
+ throw new LockError(id, task2.locked_by);
24044
24108
  }
24045
24109
  await patchTask(task2, { locked_by: null, locked_at: null }, store);
24046
24110
  return true;
@@ -24406,13 +24470,16 @@ async function addComment2(input, store, context) {
24406
24470
  task_id: input.task_id,
24407
24471
  agent_id: input.agent_id ?? context?.agentId ?? null,
24408
24472
  session_id: input.session_id ?? context?.sessionId ?? null,
24409
- content: input.content,
24473
+ content: redactEvidenceText(input.content),
24410
24474
  type: input.type ?? "comment",
24411
24475
  progress_pct: input.progress_pct ?? null,
24412
24476
  created_at: new Date().toISOString()
24413
24477
  };
24414
24478
  return store.upsert("comments", comment, context);
24415
24479
  }
24480
+ function redactComment(comment) {
24481
+ return { ...comment, content: redactEvidenceText(comment.content) };
24482
+ }
24416
24483
  async function exportSnapshot(store) {
24417
24484
  return {
24418
24485
  exportedAt: new Date().toISOString(),
@@ -25344,6 +25411,102 @@ async function closeRuntimeShadowCloud() {
25344
25411
  await cloud.close().catch(() => {});
25345
25412
  }
25346
25413
  }
25414
+ // src/storage/comment-redaction-backfill.ts
25415
+ init_redaction();
25416
+ var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
25417
+ function assertSafeIdentifier2(value) {
25418
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
25419
+ throw new Error(`Unsafe Postgres identifier: ${value}`);
25420
+ }
25421
+ }
25422
+ function payloadObject(value) {
25423
+ if (value && typeof value === "object" && !Array.isArray(value))
25424
+ return value;
25425
+ if (typeof value !== "string")
25426
+ return null;
25427
+ try {
25428
+ const parsed = JSON.parse(value);
25429
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
25430
+ } catch {
25431
+ return null;
25432
+ }
25433
+ }
25434
+ async function backfillPostgresCommentRedaction(client, options = {}) {
25435
+ const apply = options.apply === true;
25436
+ if (apply && options.confirmation !== COMMENT_REDACTION_BACKFILL_CONFIRMATION) {
25437
+ throw new Error(`Applying the comment redaction backfill requires confirmation ${COMMENT_REDACTION_BACKFILL_CONFIRMATION}`);
25438
+ }
25439
+ const tableName = options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE;
25440
+ assertSafeIdentifier2(tableName);
25441
+ const service = options.service ?? "todos";
25442
+ const batchSize = options.batchSize ?? 100;
25443
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 500) {
25444
+ throw new Error("Comment redaction backfill batchSize must be an integer between 1 and 500");
25445
+ }
25446
+ const result = {
25447
+ dry_run: !apply,
25448
+ scanned: 0,
25449
+ candidates: 0,
25450
+ updated: 0,
25451
+ conflicts: 0,
25452
+ batches: 0,
25453
+ remaining_candidates: 0
25454
+ };
25455
+ let afterId = "";
25456
+ while (true) {
25457
+ const page = await client.query(`/* todos:comment-redaction-backfill-scan */
25458
+ SELECT object_id, payload
25459
+ FROM ${tableName}
25460
+ WHERE service = $1 AND object_type = 'comments'
25461
+ AND object_id > $2
25462
+ ORDER BY object_id ASC
25463
+ LIMIT $3`, [service, afterId, batchSize]);
25464
+ if (page.rows.length === 0)
25465
+ break;
25466
+ result.batches += 1;
25467
+ for (const row of page.rows) {
25468
+ afterId = row.object_id;
25469
+ result.scanned += 1;
25470
+ const payload = payloadObject(row.payload);
25471
+ const original = payload?.["content"];
25472
+ if (typeof original !== "string")
25473
+ continue;
25474
+ const redacted = redactEvidenceText(original);
25475
+ if (redacted === original)
25476
+ continue;
25477
+ result.candidates += 1;
25478
+ if (!apply)
25479
+ continue;
25480
+ const nextPayload = { ...payload, content: redacted };
25481
+ const update = await client.query(`/* todos:comment-redaction-backfill-apply */
25482
+ UPDATE ${tableName}
25483
+ SET payload = $3::jsonb
25484
+ WHERE service = $1 AND object_type = 'comments' AND object_id = $2
25485
+ AND payload = $4::jsonb
25486
+ RETURNING object_id`, [service, row.object_id, nextPayload, row.payload]);
25487
+ if (update.rows.length === 1)
25488
+ result.updated += 1;
25489
+ else
25490
+ result.conflicts += 1;
25491
+ }
25492
+ if (page.rows.length < batchSize)
25493
+ break;
25494
+ }
25495
+ if (!apply) {
25496
+ result.remaining_candidates = result.candidates;
25497
+ return result;
25498
+ }
25499
+ const verification = await backfillPostgresCommentRedaction(client, {
25500
+ ...options,
25501
+ apply: false,
25502
+ confirmation: undefined
25503
+ });
25504
+ result.remaining_candidates = verification.candidates;
25505
+ return result;
25506
+ }
25507
+ function isCommentRedactionBackfillComplete(result) {
25508
+ return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
25509
+ }
25347
25510
  // src/storage/s3-artifacts.ts
25348
25511
  import { createHash as createHash9, createHmac as createHmac2 } from "crypto";
25349
25512
  function createTodosS3ArtifactStore(options) {