@hasna/todos 0.15.4 → 0.15.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"stage-a.d.ts","sourceRoot":"","sources":["../../src/cli/stage-a.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,MAAM,WAAW,CAAC;AAO1C,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9C,MAAM,MAAM,+BAA+B,GACvC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,IAAI,CAAA;CAAE,GACrC;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,CAAC;AAyB/E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;CA8BgB,CAAC;AAoEvD,wBAAgB,kCAAkC,IAAI,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,GAC9C,OAAO,CAKT;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,GAAG,IAAI,CASnH;AAuWD;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,GAAwB,GAC5B,+BAA+B,CAgBjC"}
1
+ {"version":3,"file":"stage-a.d.ts","sourceRoot":"","sources":["../../src/cli/stage-a.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,MAAM,WAAW,CAAC;AAO1C,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9C,MAAM,MAAM,+BAA+B,GACvC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,IAAI,CAAA;CAAE,GACrC;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,CAAC;AAyB/E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;CA8BgB,CAAC;AAyEvD,wBAAgB,kCAAkC,IAAI,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,GAC9C,OAAO,CAKT;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,GAAG,IAAI,CASnH;AAuWD;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,GAAwB,GAC5B,+BAA+B,CAgBjC"}
package/dist/contracts.js CHANGED
@@ -9313,6 +9313,18 @@ function updateTask(id, input, db) {
9313
9313
  sets.push("assigned_to = ?");
9314
9314
  params.push(input.assigned_to);
9315
9315
  }
9316
+ if (input.assigned_by !== undefined) {
9317
+ sets.push("assigned_by = ?");
9318
+ params.push(input.assigned_by);
9319
+ }
9320
+ if (input.delegated_from !== undefined) {
9321
+ sets.push("delegated_from = ?");
9322
+ params.push(input.delegated_from);
9323
+ }
9324
+ if (input.delegation_depth !== undefined) {
9325
+ sets.push("delegation_depth = ?");
9326
+ params.push(input.delegation_depth);
9327
+ }
9316
9328
  if (input.working_dir !== undefined) {
9317
9329
  sets.push("working_dir = ?");
9318
9330
  params.push(input.working_dir);
@@ -12032,7 +12044,7 @@ var init_tasks = __esm(() => {
12032
12044
  // package.json
12033
12045
  var package_default = {
12034
12046
  name: "@hasna/todos",
12035
- version: "0.15.4",
12047
+ version: "0.15.6",
12036
12048
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12037
12049
  type: "module",
12038
12050
  main: "dist/index.js",
@@ -1 +1 @@
1
- {"version":3,"file":"task-crud.d.ts","sourceRoot":"","sources":["../../src/db/task-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,IAAI,EACJ,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAY5F,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAS5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMjF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGlF;AAwCD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAwFtE;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAK9D;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EACV,EAAE,CAAC,EAAE,QAAQ,GACZ,iBAAiB,GAAG,IAAI,CA4D1B;AAQD,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,EAAE,CA0JxE;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,GAAG,IAAI,CAKb;AAcD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,4BAA4B,EACnC,EAAE,CAAC,EAAE,QAAQ,GACZ,6BAA6B,CAmD/B;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,QAAQ,CAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CA0HnG;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,eAAe,EACtB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,CA8PN;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAY7D"}
1
+ {"version":3,"file":"task-crud.d.ts","sourceRoot":"","sources":["../../src/db/task-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,IAAI,EACJ,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAY5F,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAS5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMjF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGlF;AAwCD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAwFtE;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAK9D;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EACV,EAAE,CAAC,EAAE,QAAQ,GACZ,iBAAiB,GAAG,IAAI,CA4D1B;AAQD,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,EAAE,CA0JxE;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,GAAG,IAAI,CAKb;AAcD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,4BAA4B,EACnC,EAAE,CAAC,EAAE,QAAQ,GACZ,6BAA6B,CAmD/B;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,QAAQ,CAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CA0HnG;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,eAAe,EACtB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,CAsRN;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAY7D"}
package/dist/index.js CHANGED
@@ -9313,6 +9313,18 @@ function updateTask(id, input, db) {
9313
9313
  sets.push("assigned_to = ?");
9314
9314
  params.push(input.assigned_to);
9315
9315
  }
9316
+ if (input.assigned_by !== undefined) {
9317
+ sets.push("assigned_by = ?");
9318
+ params.push(input.assigned_by);
9319
+ }
9320
+ if (input.delegated_from !== undefined) {
9321
+ sets.push("delegated_from = ?");
9322
+ params.push(input.delegated_from);
9323
+ }
9324
+ if (input.delegation_depth !== undefined) {
9325
+ sets.push("delegation_depth = ?");
9326
+ params.push(input.delegation_depth);
9327
+ }
9316
9328
  if (input.working_dir !== undefined) {
9317
9329
  sets.push("working_dir = ?");
9318
9330
  params.push(input.working_dir);
@@ -12145,7 +12157,7 @@ var init_dispatches = __esm(() => {
12145
12157
  // package.json
12146
12158
  var package_default = {
12147
12159
  name: "@hasna/todos",
12148
- version: "0.15.4",
12160
+ version: "0.15.6",
12149
12161
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12150
12162
  type: "module",
12151
12163
  main: "dist/index.js",
@@ -0,0 +1,40 @@
1
+ import type { TaskComment } from "../types/index.js";
2
+ /**
3
+ * Keyset cursor for the task-comment read model, and the pure pager that walks
4
+ * it. Both the `/v1` server and the CLI encode and decode the same cursor, so
5
+ * the codec lives here rather than beside either consumer — a second copy of
6
+ * this keyset logic is exactly how the two ends drift into disagreeing about
7
+ * what a cursor means.
8
+ *
9
+ * ORDERING CONTRACT, measured against the live deployment rather than assumed:
10
+ * a page carries the NEWEST `limit` comments in ASCENDING display order, so the
11
+ * newest comment is the LAST element. `next_cursor` encodes the FIRST (oldest)
12
+ * element of the page, and walking it moves toward OLDER history.
13
+ */
14
+ export interface CommentCursor {
15
+ created_at: string;
16
+ id: string;
17
+ }
18
+ export declare const MAX_COMMENT_CURSOR_LENGTH = 1024;
19
+ export declare function encodeCommentCursor(comment: Pick<TaskComment, "created_at" | "id">): string;
20
+ export declare function decodeCommentCursor(value: string): CommentCursor;
21
+ /** True when `comment` sorts strictly before `before` on the `(created_at, id)` keyset. */
22
+ export declare function isStrictlyOlder(comment: Pick<TaskComment, "created_at" | "id">, before: CommentCursor): boolean;
23
+ export interface CommentPageResult<T> {
24
+ comments: T[];
25
+ count: number;
26
+ has_more: boolean;
27
+ next_cursor: string | null;
28
+ limit: number;
29
+ }
30
+ /**
31
+ * Page an already-materialised, ascending comment list. Used by the local
32
+ * (SQLite) read path, where the whole history is in hand; the cloud path gets
33
+ * the identical shape from the server, which applies the same rule against the
34
+ * database.
35
+ */
36
+ export declare function pageComments<T extends Pick<TaskComment, "created_at" | "id">>(all: readonly T[], options: {
37
+ limit: number;
38
+ before?: CommentCursor;
39
+ }): CommentPageResult<T>;
40
+ //# sourceMappingURL=comment-cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-cursor.d.ts","sourceRoot":"","sources":["../../src/lib/comment-cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC,GAAG,MAAM,CAG3F;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAgBhE;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAC7B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC,EAC/C,MAAM,EAAE,aAAa,GACpB,OAAO,CAGT;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,QAAQ,EAAE,CAAC,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC,EAC3E,GAAG,EAAE,SAAS,CAAC,EAAE,EACjB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,GACjD,iBAAiB,CAAC,CAAC,CAAC,CAetB"}
@@ -0,0 +1,31 @@
1
+ export interface DelegationBriefInput {
2
+ /** `--brief <path>`; the literal `-` means stdin. */
3
+ briefPath?: string | undefined;
4
+ /** `--brief-text <text>`. Mutually exclusive with {@link briefPath}. */
5
+ briefText?: string | undefined;
6
+ }
7
+ /** The two reads the gate needs, injected so every branch is testable. */
8
+ export interface DelegationBriefSources {
9
+ /** Reads a file as utf8; THROWS when the path cannot be read. */
10
+ readFile(path: string): string;
11
+ /** Reads stdin to end as utf8. */
12
+ readStdin(): string;
13
+ }
14
+ export type DelegationBriefRefusal = "missing" | "conflict" | "unreadable" | "empty";
15
+ export type DelegationBriefVerdict = {
16
+ ok: true;
17
+ /** The brief exactly as read — never trimmed. */
18
+ text: string;
19
+ /** Where it came from, for the [DISPATCH] record: a path, `(stdin)`, or `(--brief-text)`. */
20
+ source: string;
21
+ /** sha256 of the utf8 bytes of {@link text}. */
22
+ sha256: string;
23
+ /** Byte length of {@link text}. */
24
+ bytes: number;
25
+ } | {
26
+ ok: false;
27
+ reason: DelegationBriefRefusal;
28
+ message: string;
29
+ };
30
+ export declare function resolveDelegationBrief(input: DelegationBriefInput, sources: DelegationBriefSources): DelegationBriefVerdict;
31
+ //# sourceMappingURL=delegation-brief.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation-brief.d.ts","sourceRoot":"","sources":["../../src/lib/delegation-brief.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,kCAAkC;IAClC,SAAS,IAAI,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC;AAErF,MAAM,MAAM,sBAAsB,GAC9B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAInE,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,sBAAsB,GAC9B,sBAAsB,CA6ExB"}
@@ -0,0 +1,32 @@
1
+ export declare function defaultDelegationEmbargoPath(): string;
2
+ /**
3
+ * Normalised names that must not receive a delegation.
4
+ *
5
+ * Accepts either `{ "embargoed": ["name", ...] }` or a bare array, and tolerates
6
+ * entries shaped `{ "slug": "name" }` so the file can carry a reason alongside
7
+ * each name without the reader needing a schema version.
8
+ */
9
+ export declare function loadDelegationEmbargo(path?: string): Set<string>;
10
+ export interface DelegationDepthThreshold {
11
+ /** `null` means no threshold is armed: report the count, never park. */
12
+ value: number | null;
13
+ source: "flag" | "env" | "unset";
14
+ }
15
+ /**
16
+ * Resolve the open-task count above which a delegation parks.
17
+ *
18
+ * THE DEFAULT IS DELIBERATELY UNSET, and this is the clause with a cost
19
+ * attached. Seat queues on this fleet were measured at 190 open rows, so any
20
+ * plausible shipped number would park essentially every delegation — and a gate
21
+ * that fires on every invocation is one operators learn to type past, which
22
+ * converts a real signal into a formality. What the step is actually worth is
23
+ * the PRINTED NUMBER and the RECORDED OVERRIDE, and those are delivered
24
+ * unconditionally.
25
+ *
26
+ * So parking is opt-in until per-seat data justifies a number, and the number
27
+ * lives in configuration rather than in a release when it does.
28
+ */
29
+ export declare function resolveDelegationDepthThreshold(flagValue?: string | number | undefined): DelegationDepthThreshold;
30
+ /** Minutes a dispatched row may sit unclaimed before the steering pass owns it. */
31
+ export declare const DEFAULT_CLAIM_WINDOW_MINUTES = 30;
32
+ //# sourceMappingURL=delegation-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation-policy.d.ts","sourceRoot":"","sources":["../../src/lib/delegation-policy.ts"],"names":[],"mappings":"AA8BA,wBAAgB,4BAA4B,IAAI,MAAM,CAKrD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAuC,GAAG,GAAG,CAAC,MAAM,CAAC,CAiBhG;AAED,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;CAClC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,wBAAwB,CAQjH;AASD,mFAAmF;AACnF,eAAO,MAAM,4BAA4B,KAAK,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The `[DISPATCH]` record, and the one-line channel notice.
3
+ *
4
+ * These are separated from the command so the exact text is testable without
5
+ * spawning a CLI, and so the record's SHAPE is a decision made once rather than
6
+ * a template string buried in an action handler.
7
+ *
8
+ * WHY NOT `lib/dispatch-formatter.ts`, which already exists and already formats
9
+ * a dispatch: that module builds a HUMAN PROMPT to be typed into a tmux pane —
10
+ * it is the message body of the legacy `dispatch` verb. This builds a
11
+ * MACHINE-GREPPABLE AUDIT LINE that lands in a task comment. Bending the
12
+ * existing formatter to do both would couple the record this verb is measured
13
+ * by to the message format of the verb it replaces.
14
+ *
15
+ * The first line is a fixed, anchored marker because the terminal-state
16
+ * counters read it: N1 counts directive-cited rows with NO `[DISPATCH]`
17
+ * comment, and N2 counts `[DISPATCH]` comments older than the claim window
18
+ * whose row still has a null `started_at`. Both are greps, so the marker must
19
+ * be stable and must start the comment.
20
+ */
21
+ export interface DelegationRecordInput {
22
+ taskId: string;
23
+ worker: string;
24
+ dispatcher: string;
25
+ runtime: string | null;
26
+ briefSource: string;
27
+ briefSha256: string;
28
+ briefBytes: number;
29
+ depth: number;
30
+ reportsTo: string;
31
+ seatSlug: string;
32
+ seatOpenTasks: number;
33
+ depthThreshold: number | null;
34
+ override: "despite-depth" | "owner-directive" | null;
35
+ identityOutcome: "created" | "reused" | "skipped";
36
+ dispatchedAt: string;
37
+ claimDeadline: string;
38
+ }
39
+ /** The marker every reader and counter greps for. Anchored at line start. */
40
+ export declare const DISPATCH_COMMENT_MARKER = "[DISPATCH]";
41
+ export declare function formatDispatchComment(input: DelegationRecordInput): string;
42
+ /**
43
+ * The channel notice. ONE line, because it goes to a shared channel that
44
+ * several seats read, and a multi-line dispatch log there is how a channel
45
+ * stops being read at all.
46
+ */
47
+ export declare function formatDispatchNotice(input: DelegationRecordInput): string;
48
+ /** Dispatch time plus the claim window, as an ISO instant. */
49
+ export declare function claimDeadlineFrom(dispatchedAt: Date, windowMinutes: number): string;
50
+ //# sourceMappingURL=delegation-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation-record.d.ts","sourceRoot":"","sources":["../../src/lib/delegation-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CAAC;IACrD,eAAe,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAEpD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAkB1E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAOzE;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAEnF"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Read-back verification for a delegation: did the authority ACTUALLY persist
3
+ * the lineage, or did it accept the patch and drop it?
4
+ *
5
+ * WHY THIS IS NOT PARANOIA. `todos delegate` writes through
6
+ * `PATCH /v1/tasks/:id`, and the authority serving that route is a separately
7
+ * deployed build with its own release cadence. Before this change, NOTHING in
8
+ * the server or storage layer handled `assigned_by`, `delegated_from` or
9
+ * `delegation_depth` — the columns existed, were indexed, and had no write path
10
+ * — so any authority running an earlier build accepts the patch, returns 200
11
+ * with a bumped version, and writes none of the lineage.
12
+ *
13
+ * That produces the one failure shape this verb cannot afford: a successful
14
+ * exit code, a [DISPATCH] comment asserting a lineage, and a row that is
15
+ * indistinguishable from a plain `todos assign`. The whole claim of `delegate`
16
+ * over `assign` is the handover record; reporting one that was silently
17
+ * discarded is worse than not having the verb.
18
+ *
19
+ * So: `secrets exec` returning 0 means the command ran, not that the credential
20
+ * arrived — same shape. A 200 means the request was accepted, not that the
21
+ * field was stored. Verify the PROPERTY, not the CALL.
22
+ *
23
+ * Kept as a pure function over a plain row so both arms are testable without an
24
+ * old server to point at. A check that can only ever be observed passing is not
25
+ * evidence about anything.
26
+ */
27
+ /** The subset of a task row this verification reads. */
28
+ export interface PersistedLineage {
29
+ assigned_to?: string | null;
30
+ assigned_by?: string | null;
31
+ delegated_from?: string | null;
32
+ delegation_depth?: number | null;
33
+ }
34
+ export interface ExpectedLineage {
35
+ assignedTo: string;
36
+ assignedBy: string;
37
+ delegatedFrom: string;
38
+ depth: number;
39
+ }
40
+ /**
41
+ * Field names the authority did not persist as asked. Empty means the handover
42
+ * is fully recorded.
43
+ *
44
+ * Names are compared case-insensitively: agent names are a case-INSENSITIVE
45
+ * identity across this codebase, so a server that canonicalises casing is
46
+ * behaving correctly and must not be reported as having dropped the field.
47
+ * Depth is compared exactly, and `0` is a legitimate value rather than absence
48
+ * — a truthiness test here would call every root dispatch a failure.
49
+ */
50
+ export declare function missingDelegationLineage(persisted: PersistedLineage, expected: ExpectedLineage): string[];
51
+ /**
52
+ * The refusal text for a partial delegation. It names what DID land, so nobody
53
+ * re-runs blindly against a row that is already half-changed, and it names the
54
+ * real remedy rather than sending the reader to debug their connection.
55
+ */
56
+ export declare function partialDelegationMessage(missing: readonly string[], taskId: string): string;
57
+ //# sourceMappingURL=delegation-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation-verify.d.ts","sourceRoot":"","sources":["../../src/lib/delegation-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,eAAe,GACxB,MAAM,EAAE,CAOV;AAMD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAS3F"}
package/dist/mcp/index.js CHANGED
@@ -13960,6 +13960,18 @@ function updateTask(id, input, db) {
13960
13960
  sets.push("assigned_to = ?");
13961
13961
  params.push(input.assigned_to);
13962
13962
  }
13963
+ if (input.assigned_by !== undefined) {
13964
+ sets.push("assigned_by = ?");
13965
+ params.push(input.assigned_by);
13966
+ }
13967
+ if (input.delegated_from !== undefined) {
13968
+ sets.push("delegated_from = ?");
13969
+ params.push(input.delegated_from);
13970
+ }
13971
+ if (input.delegation_depth !== undefined) {
13972
+ sets.push("delegation_depth = ?");
13973
+ params.push(input.delegation_depth);
13974
+ }
13963
13975
  if (input.working_dir !== undefined) {
13964
13976
  sets.push("working_dir = ?");
13965
13977
  params.push(input.working_dir);
@@ -34914,7 +34926,7 @@ var package_default;
34914
34926
  var init_package = __esm(() => {
34915
34927
  package_default = {
34916
34928
  name: "@hasna/todos",
34917
- version: "0.15.4",
34929
+ version: "0.15.6",
34918
34930
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
34919
34931
  type: "module",
34920
34932
  main: "dist/index.js",
@@ -50900,6 +50912,29 @@ var init_pr_groups = __esm(() => {
50900
50912
  JSON_HEADERS = { "Content-Type": "application/json" };
50901
50913
  });
50902
50914
 
50915
+ // src/lib/comment-cursor.ts
50916
+ function encodeCommentCursor(comment) {
50917
+ return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
50918
+ }
50919
+ function decodeCommentCursor(value) {
50920
+ if (value.length > MAX_COMMENT_CURSOR_LENGTH)
50921
+ throw new Error("invalid comment cursor");
50922
+ let parsed;
50923
+ try {
50924
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
50925
+ } catch {
50926
+ throw new Error("invalid comment cursor");
50927
+ }
50928
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
50929
+ throw new Error("invalid comment cursor");
50930
+ const cursor = parsed;
50931
+ if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
50932
+ throw new Error("invalid comment cursor");
50933
+ }
50934
+ return { created_at: cursor["created_at"], id: cursor["id"] };
50935
+ }
50936
+ var MAX_COMMENT_CURSOR_LENGTH = 1024;
50937
+
50903
50938
  // src/server/v1.ts
50904
50939
  var exports_v1 = {};
50905
50940
  __export(exports_v1, {
@@ -51189,26 +51224,6 @@ function contextFromPrincipal(principal, body) {
51189
51224
  function redactComment3(comment) {
51190
51225
  return { ...comment, content: redactEvidenceText(comment.content) };
51191
51226
  }
51192
- function encodeCommentCursor(comment) {
51193
- return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
51194
- }
51195
- function decodeCommentCursor(value) {
51196
- if (value.length > 1024)
51197
- throw new Error("invalid comment cursor");
51198
- let parsed;
51199
- try {
51200
- parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
51201
- } catch {
51202
- throw new Error("invalid comment cursor");
51203
- }
51204
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
51205
- throw new Error("invalid comment cursor");
51206
- const cursor = parsed;
51207
- if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
51208
- throw new Error("invalid comment cursor");
51209
- }
51210
- return { created_at: cursor["created_at"], id: cursor["id"] };
51211
- }
51212
51227
  function normalizeImportSnapshot(raw) {
51213
51228
  const body = raw && typeof raw === "object" ? raw : {};
51214
51229
  const arr = (v) => Array.isArray(v) ? v : [];
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.4",
44
+ version: "0.15.6",
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",
package/dist/registry.js CHANGED
@@ -9313,6 +9313,18 @@ function updateTask(id, input, db) {
9313
9313
  sets.push("assigned_to = ?");
9314
9314
  params.push(input.assigned_to);
9315
9315
  }
9316
+ if (input.assigned_by !== undefined) {
9317
+ sets.push("assigned_by = ?");
9318
+ params.push(input.assigned_by);
9319
+ }
9320
+ if (input.delegated_from !== undefined) {
9321
+ sets.push("delegated_from = ?");
9322
+ params.push(input.delegated_from);
9323
+ }
9324
+ if (input.delegation_depth !== undefined) {
9325
+ sets.push("delegation_depth = ?");
9326
+ params.push(input.delegation_depth);
9327
+ }
9316
9328
  if (input.working_dir !== undefined) {
9317
9329
  sets.push("working_dir = ?");
9318
9330
  params.push(input.working_dir);
@@ -12032,7 +12044,7 @@ var init_tasks = __esm(() => {
12032
12044
  // package.json
12033
12045
  var package_default = {
12034
12046
  name: "@hasna/todos",
12035
- version: "0.15.4",
12047
+ version: "0.15.6",
12036
12048
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12037
12049
  type: "module",
12038
12050
  main: "dist/index.js",
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "packageName": "@hasna/todos",
3
- "packageVersion": "0.15.4",
3
+ "packageVersion": "0.15.6",
4
4
  "repository": "https://github.com/hasna/todos.git",
5
- "gitCommit": "f2cba9a1a2f689b5f657a9e4b0343c06bb5a9053",
6
- "gitTree": "940acc30831db4a32ab7e0ef6d143d50924295ee",
7
- "sourceTreeSha256": "de35dde8cbad7cf3afceb9b03ae2a70dfff78415251ff1c0f62fbdf18f582415",
8
- "generatedAt": "2026-08-04T23:12:15.000Z"
5
+ "gitCommit": "912706df4217032e0fa328b8e63c631cbd2dbaaa",
6
+ "gitTree": "55ffcbf22946c2823608bf05b7db8c77c3d1837f",
7
+ "sourceTreeSha256": "6d97deaa19cf1aca86ae5d2ac810239a96079987884dffad727762b91dbbe660",
8
+ "generatedAt": "2026-08-06T09:35:27.000Z"
9
9
  }
@@ -70,7 +70,7 @@ var package_default;
70
70
  var init_package = __esm(() => {
71
71
  package_default = {
72
72
  name: "@hasna/todos",
73
- version: "0.15.4",
73
+ version: "0.15.6",
74
74
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
75
75
  type: "module",
76
76
  main: "dist/index.js",
@@ -14223,6 +14223,18 @@ function updateTask2(id, input, db) {
14223
14223
  sets.push("assigned_to = ?");
14224
14224
  params.push(input.assigned_to);
14225
14225
  }
14226
+ if (input.assigned_by !== undefined) {
14227
+ sets.push("assigned_by = ?");
14228
+ params.push(input.assigned_by);
14229
+ }
14230
+ if (input.delegated_from !== undefined) {
14231
+ sets.push("delegated_from = ?");
14232
+ params.push(input.delegated_from);
14233
+ }
14234
+ if (input.delegation_depth !== undefined) {
14235
+ sets.push("delegation_depth = ?");
14236
+ params.push(input.delegation_depth);
14237
+ }
14226
14238
  if (input.working_dir !== undefined) {
14227
14239
  sets.push("working_dir = ?");
14228
14240
  params.push(input.working_dir);
@@ -22300,6 +22312,29 @@ var init_pr_groups = __esm(() => {
22300
22312
  JSON_HEADERS = { "Content-Type": "application/json" };
22301
22313
  });
22302
22314
 
22315
+ // src/lib/comment-cursor.ts
22316
+ function encodeCommentCursor(comment) {
22317
+ return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
22318
+ }
22319
+ function decodeCommentCursor(value) {
22320
+ if (value.length > MAX_COMMENT_CURSOR_LENGTH)
22321
+ throw new Error("invalid comment cursor");
22322
+ let parsed;
22323
+ try {
22324
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
22325
+ } catch {
22326
+ throw new Error("invalid comment cursor");
22327
+ }
22328
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
22329
+ throw new Error("invalid comment cursor");
22330
+ const cursor = parsed;
22331
+ if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
22332
+ throw new Error("invalid comment cursor");
22333
+ }
22334
+ return { created_at: cursor["created_at"], id: cursor["id"] };
22335
+ }
22336
+ var MAX_COMMENT_CURSOR_LENGTH = 1024;
22337
+
22303
22338
  // src/server/v1.ts
22304
22339
  var exports_v1 = {};
22305
22340
  __export(exports_v1, {
@@ -22589,26 +22624,6 @@ function contextFromPrincipal(principal, body) {
22589
22624
  function redactComment2(comment) {
22590
22625
  return { ...comment, content: redactEvidenceText(comment.content) };
22591
22626
  }
22592
- function encodeCommentCursor(comment) {
22593
- return Buffer.from(JSON.stringify({ created_at: comment.created_at, id: comment.id }), "utf8").toString("base64url");
22594
- }
22595
- function decodeCommentCursor(value) {
22596
- if (value.length > 1024)
22597
- throw new Error("invalid comment cursor");
22598
- let parsed;
22599
- try {
22600
- parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
22601
- } catch {
22602
- throw new Error("invalid comment cursor");
22603
- }
22604
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
22605
- throw new Error("invalid comment cursor");
22606
- const cursor = parsed;
22607
- if (typeof cursor["created_at"] !== "string" || cursor["created_at"].length > 64 || !Number.isFinite(Date.parse(cursor["created_at"])) || typeof cursor["id"] !== "string" || !cursor["id"] || cursor["id"].length > 256) {
22608
- throw new Error("invalid comment cursor");
22609
- }
22610
- return { created_at: cursor["created_at"], id: cursor["id"] };
22611
- }
22612
22627
  function normalizeImportSnapshot(raw) {
22613
22628
  const body = raw && typeof raw === "object" ? raw : {};
22614
22629
  const arr = (v) => Array.isArray(v) ? v : [];
@@ -1 +1 @@
1
- {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../src/server/v1.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAuB,oBAAoB,EAAmD,MAAM,0BAA0B,CAAC;AAC3I,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAKhH,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,OAAO,gBAAgB,CAAC;IACtC,YAAY,CAAC,EAAE,OAAO,iBAAiB,CAAC;IACxC,iBAAiB,CAAC,EAAE,OAAO,sBAAsB,CAAC;IAClD,gBAAgB,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACjD;AA0TD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,CAiB1E;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAapE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,YAAY,GAAE,qBAA0B,GACvC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAu6B1B"}
1
+ {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../src/server/v1.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAuB,oBAAoB,EAAmD,MAAM,0BAA0B,CAAC;AAC3I,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAMhH,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,OAAO,gBAAgB,CAAC;IACtC,YAAY,CAAC,EAAE,OAAO,iBAAiB,CAAC;IACxC,iBAAiB,CAAC,EAAE,OAAO,sBAAsB,CAAC;IAClD,gBAAgB,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACjD;AAuSD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,CAiB1E;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAapE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,YAAY,GAAE,qBAA0B,GACvC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAu6B1B"}
package/dist/storage.js CHANGED
@@ -9313,6 +9313,18 @@ function updateTask(id, input, db) {
9313
9313
  sets.push("assigned_to = ?");
9314
9314
  params.push(input.assigned_to);
9315
9315
  }
9316
+ if (input.assigned_by !== undefined) {
9317
+ sets.push("assigned_by = ?");
9318
+ params.push(input.assigned_by);
9319
+ }
9320
+ if (input.delegated_from !== undefined) {
9321
+ sets.push("delegated_from = ?");
9322
+ params.push(input.delegated_from);
9323
+ }
9324
+ if (input.delegation_depth !== undefined) {
9325
+ sets.push("delegation_depth = ?");
9326
+ params.push(input.delegation_depth);
9327
+ }
9316
9328
  if (input.working_dir !== undefined) {
9317
9329
  sets.push("working_dir = ?");
9318
9330
  params.push(input.working_dir);
@@ -562,6 +562,24 @@ export interface UpdateTaskInput {
562
562
  requires_approval?: boolean;
563
563
  approved_by?: string;
564
564
  recurrence_rule?: string | null;
565
+ /**
566
+ * Who handed this row over. Stamped at creation from `agent_id` and, until
567
+ * `todos delegate`, WRITE-ONCE by omission rather than by design — there was
568
+ * no update branch, so a task filed by one agent and later dispatched by
569
+ * another kept naming the filer. Distinct from `agent_id` (who owns the row)
570
+ * and `created_by` (who filed it, which stays write-once).
571
+ */
572
+ assigned_by?: string | null;
573
+ /**
574
+ * The parent in the delegation chain. Column present since migration 520 and
575
+ * hardcoded to `null` at every creation site, so nothing could ever set it.
576
+ */
577
+ delegated_from?: string | null;
578
+ /**
579
+ * How deep this row sits in the delegation chain. Column present since
580
+ * migration 521 and hardcoded to `0` at every creation site.
581
+ */
582
+ delegation_depth?: number;
565
583
  version: number;
566
584
  task_type?: string | null;
567
585
  }