@hasna/todos 0.15.15 → 0.15.16
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.
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +374 -26
- package/dist/contracts.js +1 -1
- package/dist/index.js +90 -14
- package/dist/lib/agent-adapter-docs.d.ts +10 -0
- package/dist/lib/agent-adapter-docs.d.ts.map +1 -1
- package/dist/mcp/index.js +131 -16
- package/dist/mcp.js +1 -1
- package/dist/project-registration.js +1 -1
- package/dist/registry.js +1 -1
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +131 -16
- package/dist/task-manifest/authority.d.ts.map +1 -1
- package/dist/task-manifest/index.d.ts +1 -1
- package/dist/task-manifest/index.d.ts.map +1 -1
- package/dist/task-manifest/postgres.d.ts.map +1 -1
- package/dist/task-manifest/sqlite.d.ts.map +1 -1
- package/dist/task-manifest/types.d.ts +2 -0
- package/dist/task-manifest/types.d.ts.map +1 -1
- package/dist/task-manifest.js +89 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12492,7 +12492,7 @@ var init_dispatches = __esm(() => {
|
|
|
12492
12492
|
// package.json
|
|
12493
12493
|
var package_default = {
|
|
12494
12494
|
name: "@hasna/todos",
|
|
12495
|
-
version: "0.15.
|
|
12495
|
+
version: "0.15.16",
|
|
12496
12496
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12497
12497
|
type: "module",
|
|
12498
12498
|
main: "dist/index.js",
|
|
@@ -39090,6 +39090,9 @@ init_types();
|
|
|
39090
39090
|
// src/task-manifest/types.ts
|
|
39091
39091
|
var TODOS_TASK_MANIFEST_ROUTE = "todos.task-manifest.v1";
|
|
39092
39092
|
var TODOS_TASK_MANIFEST_SCHEMA_VERSION = 1;
|
|
39093
|
+
function supportsIdempotentOutboxDelivery(capability2) {
|
|
39094
|
+
return capability2 !== null && typeof capability2 === "object" && capability2["idempotent_outbox_delivery"] === true;
|
|
39095
|
+
}
|
|
39093
39096
|
|
|
39094
39097
|
class TodosTaskManifestError extends Error {
|
|
39095
39098
|
code;
|
|
@@ -39735,10 +39738,27 @@ class SqliteTodosTaskManifestBackend {
|
|
|
39735
39738
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
39736
39739
|
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
39737
39740
|
AND r.tenant_id = ?
|
|
39741
|
+
AND r.authority = 'todos'
|
|
39742
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
39743
|
+
AND r.schema_version = 1
|
|
39744
|
+
AND r.kind = 'apply'
|
|
39738
39745
|
)`).run(deliveredAt, outboxId, this.tenantId);
|
|
39739
|
-
if (result.changes
|
|
39740
|
-
|
|
39741
|
-
|
|
39746
|
+
if (result.changes === 1)
|
|
39747
|
+
return;
|
|
39748
|
+
const existing = this.db.query(`SELECT o.status
|
|
39749
|
+
FROM todos_task_manifest_outbox o
|
|
39750
|
+
JOIN todos_task_manifest_receipts r
|
|
39751
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
39752
|
+
WHERE r.tenant_id = ?
|
|
39753
|
+
AND r.authority = 'todos'
|
|
39754
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
39755
|
+
AND r.schema_version = 1
|
|
39756
|
+
AND r.kind = 'apply'
|
|
39757
|
+
AND o.id = ?
|
|
39758
|
+
LIMIT 1`).get(this.tenantId, outboxId);
|
|
39759
|
+
if (existing?.status === "delivered")
|
|
39760
|
+
return;
|
|
39761
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
39742
39762
|
});
|
|
39743
39763
|
}
|
|
39744
39764
|
async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
|
|
@@ -40270,6 +40290,22 @@ class PostgresTodosTaskManifestBackend {
|
|
|
40270
40290
|
async markOutboxDelivered(outboxId, deliveredAt) {
|
|
40271
40291
|
await this.ensureSchema();
|
|
40272
40292
|
await this.client.transaction(async (tx) => {
|
|
40293
|
+
const owned = await tx.query(`SELECT r.operation_id
|
|
40294
|
+
FROM todos_task_manifest_outbox o
|
|
40295
|
+
JOIN todos_task_manifest_receipts r
|
|
40296
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
40297
|
+
WHERE r.tenant_id = $1
|
|
40298
|
+
AND r.authority = 'todos'
|
|
40299
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
40300
|
+
AND r.schema_version = 1
|
|
40301
|
+
AND r.kind = 'apply'
|
|
40302
|
+
AND o.id = $2
|
|
40303
|
+
LIMIT 1`, [this.tenantId, outboxId]);
|
|
40304
|
+
const operationId = owned.rows[0]?.operation_id;
|
|
40305
|
+
if (operationId == null) {
|
|
40306
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
40307
|
+
}
|
|
40308
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${String(operationId)}`]);
|
|
40273
40309
|
const result = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
40274
40310
|
SET status = 'delivered', delivered_at = $1, attempts = attempts + 1
|
|
40275
40311
|
WHERE id = $2 AND status = 'pending'
|
|
@@ -40277,10 +40313,28 @@ class PostgresTodosTaskManifestBackend {
|
|
|
40277
40313
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
40278
40314
|
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
40279
40315
|
AND r.tenant_id = $3
|
|
40316
|
+
AND r.authority = 'todos'
|
|
40317
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
40318
|
+
AND r.schema_version = 1
|
|
40319
|
+
AND r.kind = 'apply'
|
|
40280
40320
|
)
|
|
40281
40321
|
RETURNING id`, [deliveredAt, outboxId, this.tenantId]);
|
|
40282
|
-
if (
|
|
40283
|
-
|
|
40322
|
+
if (result.rows[0])
|
|
40323
|
+
return;
|
|
40324
|
+
const existing = await tx.query(`SELECT o.status
|
|
40325
|
+
FROM todos_task_manifest_outbox o
|
|
40326
|
+
JOIN todos_task_manifest_receipts r
|
|
40327
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
40328
|
+
WHERE r.tenant_id = $1
|
|
40329
|
+
AND r.authority = 'todos'
|
|
40330
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
40331
|
+
AND r.schema_version = 1
|
|
40332
|
+
AND r.kind = 'apply'
|
|
40333
|
+
AND o.id = $2
|
|
40334
|
+
LIMIT 1`, [this.tenantId, outboxId]);
|
|
40335
|
+
if (existing.rows[0]?.status === "delivered")
|
|
40336
|
+
return;
|
|
40337
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
40284
40338
|
});
|
|
40285
40339
|
}
|
|
40286
40340
|
async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
|
|
@@ -40312,7 +40366,13 @@ class PostgresTodosTaskManifestBackend {
|
|
|
40312
40366
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
|
|
40313
40367
|
const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
|
|
40314
40368
|
JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
|
|
40315
|
-
WHERE r.tenant_id = $1
|
|
40369
|
+
WHERE r.tenant_id = $1
|
|
40370
|
+
AND r.authority = 'todos'
|
|
40371
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
40372
|
+
AND r.schema_version = 1
|
|
40373
|
+
AND r.kind = 'apply'
|
|
40374
|
+
AND o.apply_receipt_id = $2
|
|
40375
|
+
AND o.status = 'delivered'
|
|
40316
40376
|
LIMIT 1`, [this.tenantId, input.receipt_id]);
|
|
40317
40377
|
if (delivered.rows[0])
|
|
40318
40378
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
|
|
@@ -40325,15 +40385,20 @@ class PostgresTodosTaskManifestBackend {
|
|
|
40325
40385
|
},
|
|
40326
40386
|
...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
|
|
40327
40387
|
];
|
|
40328
|
-
const outboxRows = await tx.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
|
|
40329
|
-
FROM todos_task_manifest_outbox
|
|
40330
|
-
WHERE apply_receipt_id = $1
|
|
40388
|
+
const outboxRows = await tx.query(`SELECT o.id, o.topic, o.payload, o.payload_digest, o.status, o.attempts, o.delivered_at
|
|
40389
|
+
FROM todos_task_manifest_outbox o
|
|
40390
|
+
WHERE o.apply_receipt_id = $1
|
|
40331
40391
|
AND EXISTS (
|
|
40332
40392
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
40333
|
-
WHERE r.receipt_id =
|
|
40393
|
+
WHERE r.receipt_id = o.apply_receipt_id
|
|
40334
40394
|
AND r.tenant_id = $2
|
|
40395
|
+
AND r.authority = 'todos'
|
|
40396
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
40397
|
+
AND r.schema_version = 1
|
|
40398
|
+
AND r.kind = 'apply'
|
|
40335
40399
|
)
|
|
40336
|
-
ORDER BY id
|
|
40400
|
+
ORDER BY o.id
|
|
40401
|
+
FOR UPDATE OF o`, [input.receipt_id, this.tenantId]);
|
|
40337
40402
|
if (outboxRows.rows.length !== expectedEffects.length) {
|
|
40338
40403
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
40339
40404
|
}
|
|
@@ -40433,14 +40498,23 @@ class PostgresTodosTaskManifestBackend {
|
|
|
40433
40498
|
}
|
|
40434
40499
|
if (stored.rows.length !== managedIds.length)
|
|
40435
40500
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph is incomplete");
|
|
40436
|
-
await tx.query(`UPDATE todos_task_manifest_outbox
|
|
40501
|
+
const cancelled = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
40437
40502
|
SET status = 'cancelled'
|
|
40438
40503
|
WHERE apply_receipt_id = $1 AND status = 'pending'
|
|
40439
40504
|
AND EXISTS (
|
|
40440
40505
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
40441
40506
|
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
40442
40507
|
AND r.tenant_id = $2
|
|
40443
|
-
|
|
40508
|
+
AND r.authority = 'todos'
|
|
40509
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
40510
|
+
AND r.schema_version = 1
|
|
40511
|
+
AND r.kind = 'apply'
|
|
40512
|
+
)
|
|
40513
|
+
RETURNING id`, [input.receipt_id, this.tenantId]);
|
|
40514
|
+
const cancelledIds = new Set(cancelled.rows.map((row) => String(row.id)));
|
|
40515
|
+
if (cancelledIds.size !== applyResult.outbox_ids.length || applyResult.outbox_ids.some((id) => !cancelledIds.has(id))) {
|
|
40516
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: failed to cancel every expected outbox row");
|
|
40517
|
+
}
|
|
40444
40518
|
const typedIds = [
|
|
40445
40519
|
["dependencies", applyResult.graph.dependency_ids],
|
|
40446
40520
|
["comments", applyResult.graph.comment_ids],
|
|
@@ -40627,6 +40701,7 @@ class PackageOwnedTodosTaskManifestAuthority {
|
|
|
40627
40701
|
deterministic_ids: true,
|
|
40628
40702
|
immutable_receipts: true,
|
|
40629
40703
|
transactional_outbox: true,
|
|
40704
|
+
idempotent_outbox_delivery: true,
|
|
40630
40705
|
exact_bounded_readback: true,
|
|
40631
40706
|
conditional_compensation: true,
|
|
40632
40707
|
transcript_safe: false,
|
|
@@ -62008,6 +62083,7 @@ export {
|
|
|
62008
62083
|
syncWithAgents,
|
|
62009
62084
|
syncWithAgent,
|
|
62010
62085
|
syncKgEdges,
|
|
62086
|
+
supportsIdempotentOutboxDelivery,
|
|
62011
62087
|
supersedeDecisionRecord,
|
|
62012
62088
|
summarizeTaskRun,
|
|
62013
62089
|
summarizeRoadmap,
|
|
@@ -6,6 +6,16 @@ import { GOAL_COMMAND_RECIPES } from "./goal-workflow.js";
|
|
|
6
6
|
export declare const ADAPTER_DOCS_SCHEMA_VERSION = "todos.agent_adapter_docs.v1";
|
|
7
7
|
export declare const AGENT_ADAPTER_HOSTS: readonly ["codex", "claude-code", "takumi"];
|
|
8
8
|
export type AgentAdapterHost = (typeof AGENT_ADAPTER_HOSTS)[number];
|
|
9
|
+
/**
|
|
10
|
+
* Agents accepted by `todos mcp --register` / `--unregister`. Kept beside the
|
|
11
|
+
* adapter docs so the docs can never advertise a host the CLI rejects — the
|
|
12
|
+
* reader of a recovery field is an agent, and a recovery command that exits 1
|
|
13
|
+
* strands it. `todos mcp --register takumi` shipped in doc metadata while the
|
|
14
|
+
* CLI rejected it; the consistency test in agent-adapter-docs.test.ts pins
|
|
15
|
+
* every advertised `--register <agent>` to this list.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MCP_REGISTRABLE_CLI_AGENTS: readonly ["claude", "codex", "gemini", "cursor", "takumi"];
|
|
18
|
+
export type McpRegistrableCliAgent = (typeof MCP_REGISTRABLE_CLI_AGENTS)[number];
|
|
9
19
|
export interface AdapterWorkflowStep {
|
|
10
20
|
step: number;
|
|
11
21
|
title: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-adapter-docs.d.ts","sourceRoot":"","sources":["../../src/lib/agent-adapter-docs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,eAAO,MAAM,2BAA2B,gCAAgC,CAAC;AAEzE,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,OAAO,2BAA2B,CAAC;IACnD,IAAI,EAAE,gBAAgB,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,GAAG,EAAE;QACH,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,GAAG,EAAE,MAAM,EAAE,CAAC;KACf,CAAC;IACF,aAAa,EAAE,OAAO,oBAAoB,CAAC;IAC3C,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,aAAa,EAAE;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;KACpC,CAAC;IACF,YAAY,EAAE;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAgCD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,gBAAgB,EAAE,eAAe,CA0IxE,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAM3E;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAGvE;AAED,wBAAgB,oBAAoB,IAAI,eAAe,EAAE,CAExD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CA6B9C;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6EpE;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAIrD;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,IAAI,MAAM,CAoBlD"}
|
|
1
|
+
{"version":3,"file":"agent-adapter-docs.d.ts","sourceRoot":"","sources":["../../src/lib/agent-adapter-docs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,eAAO,MAAM,2BAA2B,gCAAgC,CAAC;AAEzE,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,4DAA6D,CAAC;AACrG,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,OAAO,2BAA2B,CAAC;IACnD,IAAI,EAAE,gBAAgB,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,GAAG,EAAE;QACH,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,GAAG,EAAE,MAAM,EAAE,CAAC;KACf,CAAC;IACF,aAAa,EAAE,OAAO,oBAAoB,CAAC;IAC3C,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,aAAa,EAAE;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;KACpC,CAAC;IACF,YAAY,EAAE;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAgCD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,gBAAgB,EAAE,eAAe,CA0IxE,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAM3E;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAGvE;AAED,wBAAgB,oBAAoB,IAAI,eAAe,EAAE,CAExD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CA6B9C;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6EpE;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAIrD;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,IAAI,MAAM,CAoBlD"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -21041,10 +21041,72 @@ async function requireTagsFilterCapability(client) {
|
|
|
21041
21041
|
throw new Error(`REMOTE_TAGS_FILTER_UNSUPPORTED: configured Todos authority ${authority} does not advertise the tags ` + "query param on GET /v1/tasks; deploy the current @hasna/todos /v1 server to filter by tag; " + "no unfiltered task read was issued");
|
|
21042
21042
|
}
|
|
21043
21043
|
}
|
|
21044
|
-
|
|
21044
|
+
function parseCloudTaskTotal(raw) {
|
|
21045
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
21046
|
+
return;
|
|
21047
|
+
const total = raw.total;
|
|
21048
|
+
return typeof total === "number" && Number.isSafeInteger(total) && total >= 0 ? total : undefined;
|
|
21049
|
+
}
|
|
21050
|
+
async function requestRawCloudTaskPage(client, filter) {
|
|
21045
21051
|
const res = await requiredRemoteRoute(client, "/v1/tasks", () => client.list("tasks", { query: toListQuery(filter) }));
|
|
21046
21052
|
const envelope = res.raw;
|
|
21047
|
-
return
|
|
21053
|
+
return {
|
|
21054
|
+
tasks: Array.isArray(envelope?.tasks) ? envelope.tasks : res.items,
|
|
21055
|
+
total: parseCloudTaskTotal(res.raw)
|
|
21056
|
+
};
|
|
21057
|
+
}
|
|
21058
|
+
function cloudTaskListFilterError(code, taskListId, detail) {
|
|
21059
|
+
return new Error(`${code}: hosted authority returned rows outside requested task_list_id ${taskListId}; ${detail}; refusing an incomplete exact-list result`);
|
|
21060
|
+
}
|
|
21061
|
+
async function requestCloudTaskPage(client, filter) {
|
|
21062
|
+
const firstPage = await requestRawCloudTaskPage(client, filter);
|
|
21063
|
+
const tasks = firstPage.tasks;
|
|
21064
|
+
if (filter.task_list_id === undefined)
|
|
21065
|
+
return tasks;
|
|
21066
|
+
const taskListId = filter.task_list_id;
|
|
21067
|
+
if (tasks.every((task) => task.task_list_id === taskListId))
|
|
21068
|
+
return tasks;
|
|
21069
|
+
const scanLimit = filter.limit;
|
|
21070
|
+
const startOffset = filter.offset ?? 0;
|
|
21071
|
+
if (startOffset !== 0 || scanLimit === undefined || !Number.isSafeInteger(scanLimit) || scanLimit <= 0 || firstPage.total === undefined) {
|
|
21072
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, "the response does not provide complete bounded total/offset pagination evidence");
|
|
21073
|
+
}
|
|
21074
|
+
const total = firstPage.total;
|
|
21075
|
+
if (total > scanLimit) {
|
|
21076
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_INCOMPLETE", taskListId, `reported total ${total} exceeds bounded scan limit ${scanLimit}`);
|
|
21077
|
+
}
|
|
21078
|
+
if (tasks.length > total) {
|
|
21079
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `the first page contains ${tasks.length} rows but reports total ${total}`);
|
|
21080
|
+
}
|
|
21081
|
+
const seenTaskIds = new Set;
|
|
21082
|
+
for (const task of tasks) {
|
|
21083
|
+
if (seenTaskIds.has(task.id)) {
|
|
21084
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `the first page repeats task id ${task.id}`);
|
|
21085
|
+
}
|
|
21086
|
+
seenTaskIds.add(task.id);
|
|
21087
|
+
}
|
|
21088
|
+
while (tasks.length < total) {
|
|
21089
|
+
const remaining = total - tasks.length;
|
|
21090
|
+
const page = await requestRawCloudTaskPage(client, {
|
|
21091
|
+
...filter,
|
|
21092
|
+
offset: tasks.length,
|
|
21093
|
+
limit: Math.min(scanLimit - tasks.length, remaining)
|
|
21094
|
+
});
|
|
21095
|
+
if (page.total !== total) {
|
|
21096
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `pagination total changed from ${total} to ${String(page.total)}`);
|
|
21097
|
+
}
|
|
21098
|
+
if (page.tasks.length === 0 || tasks.length + page.tasks.length > total) {
|
|
21099
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, "pagination did not make bounded progress toward the reported total");
|
|
21100
|
+
}
|
|
21101
|
+
for (const task of page.tasks) {
|
|
21102
|
+
if (seenTaskIds.has(task.id)) {
|
|
21103
|
+
throw cloudTaskListFilterError("REMOTE_TASK_LIST_FILTER_UNSUPPORTED", taskListId, `pagination repeats task id ${task.id}`);
|
|
21104
|
+
}
|
|
21105
|
+
seenTaskIds.add(task.id);
|
|
21106
|
+
}
|
|
21107
|
+
tasks.push(...page.tasks);
|
|
21108
|
+
}
|
|
21109
|
+
return tasks.filter((task) => task.task_list_id === taskListId);
|
|
21048
21110
|
}
|
|
21049
21111
|
async function cloudListTasks(client, filter = {}) {
|
|
21050
21112
|
if (filter.tags?.length)
|
|
@@ -21083,12 +21145,10 @@ async function cloudGetTask(client, id) {
|
|
|
21083
21145
|
}
|
|
21084
21146
|
async function cloudCreateTask(client, input) {
|
|
21085
21147
|
const expectedParentId = typeof input["parent_id"] === "string" ? input["parent_id"] : null;
|
|
21086
|
-
const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () =>
|
|
21148
|
+
const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () => client.create("tasks", input, { retry: false }), ["PARENT_TASK_NOT_FOUND"]));
|
|
21087
21149
|
if (!created || typeof created.id !== "string" || !created.id.trim()) {
|
|
21088
21150
|
throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} returned a task create ` + "response without a stored task id; no success row or local SQLite fallback is permitted");
|
|
21089
21151
|
}
|
|
21090
|
-
if (expectedParentId === null)
|
|
21091
|
-
return created;
|
|
21092
21152
|
const persisted = await cloudGetTask(client, created.id);
|
|
21093
21153
|
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId) {
|
|
21094
21154
|
throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + "stored task id and parent_id; no success row or local SQLite fallback is permitted");
|
|
@@ -35415,7 +35475,7 @@ var package_default;
|
|
|
35415
35475
|
var init_package = __esm(() => {
|
|
35416
35476
|
package_default = {
|
|
35417
35477
|
name: "@hasna/todos",
|
|
35418
|
-
version: "0.15.
|
|
35478
|
+
version: "0.15.16",
|
|
35419
35479
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
35420
35480
|
type: "module",
|
|
35421
35481
|
main: "dist/index.js",
|
|
@@ -51606,6 +51666,22 @@ class PostgresTodosTaskManifestBackend {
|
|
|
51606
51666
|
async markOutboxDelivered(outboxId, deliveredAt) {
|
|
51607
51667
|
await this.ensureSchema();
|
|
51608
51668
|
await this.client.transaction(async (tx) => {
|
|
51669
|
+
const owned = await tx.query(`SELECT r.operation_id
|
|
51670
|
+
FROM todos_task_manifest_outbox o
|
|
51671
|
+
JOIN todos_task_manifest_receipts r
|
|
51672
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
51673
|
+
WHERE r.tenant_id = $1
|
|
51674
|
+
AND r.authority = 'todos'
|
|
51675
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51676
|
+
AND r.schema_version = 1
|
|
51677
|
+
AND r.kind = 'apply'
|
|
51678
|
+
AND o.id = $2
|
|
51679
|
+
LIMIT 1`, [this.tenantId, outboxId]);
|
|
51680
|
+
const operationId = owned.rows[0]?.operation_id;
|
|
51681
|
+
if (operationId == null) {
|
|
51682
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
51683
|
+
}
|
|
51684
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${String(operationId)}`]);
|
|
51609
51685
|
const result = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51610
51686
|
SET status = 'delivered', delivered_at = $1, attempts = attempts + 1
|
|
51611
51687
|
WHERE id = $2 AND status = 'pending'
|
|
@@ -51613,10 +51689,28 @@ class PostgresTodosTaskManifestBackend {
|
|
|
51613
51689
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51614
51690
|
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51615
51691
|
AND r.tenant_id = $3
|
|
51692
|
+
AND r.authority = 'todos'
|
|
51693
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51694
|
+
AND r.schema_version = 1
|
|
51695
|
+
AND r.kind = 'apply'
|
|
51616
51696
|
)
|
|
51617
51697
|
RETURNING id`, [deliveredAt, outboxId, this.tenantId]);
|
|
51618
|
-
if (
|
|
51619
|
-
|
|
51698
|
+
if (result.rows[0])
|
|
51699
|
+
return;
|
|
51700
|
+
const existing = await tx.query(`SELECT o.status
|
|
51701
|
+
FROM todos_task_manifest_outbox o
|
|
51702
|
+
JOIN todos_task_manifest_receipts r
|
|
51703
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
51704
|
+
WHERE r.tenant_id = $1
|
|
51705
|
+
AND r.authority = 'todos'
|
|
51706
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51707
|
+
AND r.schema_version = 1
|
|
51708
|
+
AND r.kind = 'apply'
|
|
51709
|
+
AND o.id = $2
|
|
51710
|
+
LIMIT 1`, [this.tenantId, outboxId]);
|
|
51711
|
+
if (existing.rows[0]?.status === "delivered")
|
|
51712
|
+
return;
|
|
51713
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
51620
51714
|
});
|
|
51621
51715
|
}
|
|
51622
51716
|
async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
|
|
@@ -51648,7 +51742,13 @@ class PostgresTodosTaskManifestBackend {
|
|
|
51648
51742
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
|
|
51649
51743
|
const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
|
|
51650
51744
|
JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
|
|
51651
|
-
WHERE r.tenant_id = $1
|
|
51745
|
+
WHERE r.tenant_id = $1
|
|
51746
|
+
AND r.authority = 'todos'
|
|
51747
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51748
|
+
AND r.schema_version = 1
|
|
51749
|
+
AND r.kind = 'apply'
|
|
51750
|
+
AND o.apply_receipt_id = $2
|
|
51751
|
+
AND o.status = 'delivered'
|
|
51652
51752
|
LIMIT 1`, [this.tenantId, input.receipt_id]);
|
|
51653
51753
|
if (delivered.rows[0])
|
|
51654
51754
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
|
|
@@ -51661,15 +51761,20 @@ class PostgresTodosTaskManifestBackend {
|
|
|
51661
51761
|
},
|
|
51662
51762
|
...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
|
|
51663
51763
|
];
|
|
51664
|
-
const outboxRows = await tx.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
|
|
51665
|
-
FROM todos_task_manifest_outbox
|
|
51666
|
-
WHERE apply_receipt_id = $1
|
|
51764
|
+
const outboxRows = await tx.query(`SELECT o.id, o.topic, o.payload, o.payload_digest, o.status, o.attempts, o.delivered_at
|
|
51765
|
+
FROM todos_task_manifest_outbox o
|
|
51766
|
+
WHERE o.apply_receipt_id = $1
|
|
51667
51767
|
AND EXISTS (
|
|
51668
51768
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51669
|
-
WHERE r.receipt_id =
|
|
51769
|
+
WHERE r.receipt_id = o.apply_receipt_id
|
|
51670
51770
|
AND r.tenant_id = $2
|
|
51771
|
+
AND r.authority = 'todos'
|
|
51772
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51773
|
+
AND r.schema_version = 1
|
|
51774
|
+
AND r.kind = 'apply'
|
|
51671
51775
|
)
|
|
51672
|
-
ORDER BY id
|
|
51776
|
+
ORDER BY o.id
|
|
51777
|
+
FOR UPDATE OF o`, [input.receipt_id, this.tenantId]);
|
|
51673
51778
|
if (outboxRows.rows.length !== expectedEffects.length) {
|
|
51674
51779
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
51675
51780
|
}
|
|
@@ -51769,14 +51874,23 @@ class PostgresTodosTaskManifestBackend {
|
|
|
51769
51874
|
}
|
|
51770
51875
|
if (stored.rows.length !== managedIds.length)
|
|
51771
51876
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph is incomplete");
|
|
51772
|
-
await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51877
|
+
const cancelled = await tx.query(`UPDATE todos_task_manifest_outbox
|
|
51773
51878
|
SET status = 'cancelled'
|
|
51774
51879
|
WHERE apply_receipt_id = $1 AND status = 'pending'
|
|
51775
51880
|
AND EXISTS (
|
|
51776
51881
|
SELECT 1 FROM todos_task_manifest_receipts r
|
|
51777
51882
|
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
51778
51883
|
AND r.tenant_id = $2
|
|
51779
|
-
|
|
51884
|
+
AND r.authority = 'todos'
|
|
51885
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
51886
|
+
AND r.schema_version = 1
|
|
51887
|
+
AND r.kind = 'apply'
|
|
51888
|
+
)
|
|
51889
|
+
RETURNING id`, [input.receipt_id, this.tenantId]);
|
|
51890
|
+
const cancelledIds = new Set(cancelled.rows.map((row) => String(row.id)));
|
|
51891
|
+
if (cancelledIds.size !== applyResult.outbox_ids.length || applyResult.outbox_ids.some((id) => !cancelledIds.has(id))) {
|
|
51892
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: failed to cancel every expected outbox row");
|
|
51893
|
+
}
|
|
51780
51894
|
const typedIds = [
|
|
51781
51895
|
["dependencies", applyResult.graph.dependency_ids],
|
|
51782
51896
|
["comments", applyResult.graph.comment_ids],
|
|
@@ -51959,6 +52073,7 @@ class PackageOwnedTodosTaskManifestAuthority {
|
|
|
51959
52073
|
deterministic_ids: true,
|
|
51960
52074
|
immutable_receipts: true,
|
|
51961
52075
|
transactional_outbox: true,
|
|
52076
|
+
idempotent_outbox_delivery: true,
|
|
51962
52077
|
exact_bounded_readback: true,
|
|
51963
52078
|
conditional_compensation: true,
|
|
51964
52079
|
transcript_safe: false,
|
package/dist/mcp.js
CHANGED
|
@@ -41,7 +41,7 @@ var __require = import.meta.require;
|
|
|
41
41
|
// package.json
|
|
42
42
|
var package_default = {
|
|
43
43
|
name: "@hasna/todos",
|
|
44
|
-
version: "0.15.
|
|
44
|
+
version: "0.15.16",
|
|
45
45
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
46
46
|
type: "module",
|
|
47
47
|
main: "dist/index.js",
|
|
@@ -12382,7 +12382,7 @@ import { createHash as createHash5 } from "crypto";
|
|
|
12382
12382
|
// package.json
|
|
12383
12383
|
var package_default = {
|
|
12384
12384
|
name: "@hasna/todos",
|
|
12385
|
-
version: "0.15.
|
|
12385
|
+
version: "0.15.16",
|
|
12386
12386
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12387
12387
|
type: "module",
|
|
12388
12388
|
main: "dist/index.js",
|
package/dist/registry.js
CHANGED
|
@@ -12379,7 +12379,7 @@ var init_tasks = __esm(() => {
|
|
|
12379
12379
|
// package.json
|
|
12380
12380
|
var package_default = {
|
|
12381
12381
|
name: "@hasna/todos",
|
|
12382
|
-
version: "0.15.
|
|
12382
|
+
version: "0.15.16",
|
|
12383
12383
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12384
12384
|
type: "module",
|
|
12385
12385
|
main: "dist/index.js",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageName": "@hasna/todos",
|
|
3
|
-
"packageVersion": "0.15.
|
|
3
|
+
"packageVersion": "0.15.16",
|
|
4
4
|
"repository": "https://github.com/hasna/todos.git",
|
|
5
|
-
"gitCommit": "
|
|
6
|
-
"gitTree": "
|
|
7
|
-
"sourceTreeSha256": "
|
|
8
|
-
"generatedAt": "2026-08-
|
|
5
|
+
"gitCommit": "084685de11895e9c0bbed1ed49111034be5f7abf",
|
|
6
|
+
"gitTree": "abbe0059ad26a5e3d912791a29be4ddf41a5806a",
|
|
7
|
+
"sourceTreeSha256": "b78b8353a1695771896c48af0113c731345a9a15545b984f2778b69cec96eda9",
|
|
8
|
+
"generatedAt": "2026-08-09T02:43:36.000Z"
|
|
9
9
|
}
|