@songsid/agend 2.3.2-beta.9 → 2.3.3-beta.1

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 (285) hide show
  1. package/README.md +6 -0
  2. package/README.zh-TW.md +6 -0
  3. package/dist/agent-cli-instructions.md +2 -2
  4. package/dist/agent-cli.js +48 -0
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +6 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.d.ts +21 -0
  9. package/dist/agent-whats-new.js +53 -21
  10. package/dist/agent-whats-new.js.map +1 -1
  11. package/dist/auth-api.js +2 -0
  12. package/dist/auth-api.js.map +1 -1
  13. package/dist/backend/claude-code.d.ts +13 -1
  14. package/dist/backend/claude-code.js +43 -2
  15. package/dist/backend/claude-code.js.map +1 -1
  16. package/dist/backend/codex-session-lookup.d.ts +15 -0
  17. package/dist/backend/codex-session-lookup.js +58 -0
  18. package/dist/backend/codex-session-lookup.js.map +1 -1
  19. package/dist/backend/codex.js +15 -4
  20. package/dist/backend/codex.js.map +1 -1
  21. package/dist/backend/kiro-classic-archive.d.ts +57 -0
  22. package/dist/backend/kiro-classic-archive.js +622 -0
  23. package/dist/backend/kiro-classic-archive.js.map +1 -0
  24. package/dist/backend/kiro-classic-links.d.ts +24 -0
  25. package/dist/backend/kiro-classic-links.js +135 -0
  26. package/dist/backend/kiro-classic-links.js.map +1 -0
  27. package/dist/backend/kiro-identity.d.ts +100 -1
  28. package/dist/backend/kiro-identity.js +262 -31
  29. package/dist/backend/kiro-identity.js.map +1 -1
  30. package/dist/backend/kiro-migration-verify.d.ts +10 -0
  31. package/dist/backend/kiro-migration-verify.js +52 -0
  32. package/dist/backend/kiro-migration-verify.js.map +1 -0
  33. package/dist/backend/kiro-reconcile-store.d.ts +27 -0
  34. package/dist/backend/kiro-reconcile-store.js +351 -0
  35. package/dist/backend/kiro-reconcile-store.js.map +1 -0
  36. package/dist/backend/kiro-v2-store-worker.js +35 -1
  37. package/dist/backend/kiro-v2-store-worker.js.map +1 -1
  38. package/dist/backend/kiro-v2-store.d.ts +31 -1
  39. package/dist/backend/kiro-v2-store.js +22 -12
  40. package/dist/backend/kiro-v2-store.js.map +1 -1
  41. package/dist/backend/kiro.d.ts +124 -6
  42. package/dist/backend/kiro.js +553 -39
  43. package/dist/backend/kiro.js.map +1 -1
  44. package/dist/backend/types.d.ts +109 -4
  45. package/dist/backend/types.js +9 -0
  46. package/dist/backend/types.js.map +1 -1
  47. package/dist/cache-service.d.ts +13 -1
  48. package/dist/cache-service.js +45 -2
  49. package/dist/cache-service.js.map +1 -1
  50. package/dist/channel/ipc-timeouts.js +3 -0
  51. package/dist/channel/ipc-timeouts.js.map +1 -1
  52. package/dist/channel/mcp-tools.js +40 -5
  53. package/dist/channel/mcp-tools.js.map +1 -1
  54. package/dist/channel/tool-router.js +2 -1
  55. package/dist/channel/tool-router.js.map +1 -1
  56. package/dist/classic-channel-manager.d.ts +6 -0
  57. package/dist/classic-channel-manager.js +25 -0
  58. package/dist/classic-channel-manager.js.map +1 -1
  59. package/dist/cli.js +52 -6
  60. package/dist/cli.js.map +1 -1
  61. package/dist/config-validator.d.ts +7 -1
  62. package/dist/config-validator.js +20 -9
  63. package/dist/config-validator.js.map +1 -1
  64. package/dist/config.js +2 -0
  65. package/dist/config.js.map +1 -1
  66. package/dist/cross-instance-envelope.d.ts +10 -5
  67. package/dist/cross-instance-envelope.js +20 -17
  68. package/dist/cross-instance-envelope.js.map +1 -1
  69. package/dist/daemon.d.ts +125 -63
  70. package/dist/daemon.js +694 -330
  71. package/dist/daemon.js.map +1 -1
  72. package/dist/dashboard-permissions.d.ts +15 -4
  73. package/dist/dashboard-permissions.js +49 -23
  74. package/dist/dashboard-permissions.js.map +1 -1
  75. package/dist/delivery-outbox.d.ts +27 -22
  76. package/dist/delivery-outbox.js +109 -161
  77. package/dist/delivery-outbox.js.map +1 -1
  78. package/dist/delivery-outbox.test.js +10 -25
  79. package/dist/delivery-outbox.test.js.map +1 -1
  80. package/dist/doctor.js +2 -1
  81. package/dist/doctor.js.map +1 -1
  82. package/dist/fleet-control-audit.d.ts +1 -1
  83. package/dist/fleet-control-audit.js +3 -0
  84. package/dist/fleet-control-audit.js.map +1 -1
  85. package/dist/fleet-manager.d.ts +94 -15
  86. package/dist/fleet-manager.js +597 -133
  87. package/dist/fleet-manager.js.map +1 -1
  88. package/dist/forged-envelope-scan.d.ts +9 -0
  89. package/dist/forged-envelope-scan.js +30 -5
  90. package/dist/forged-envelope-scan.js.map +1 -1
  91. package/dist/general-instance.d.ts +24 -0
  92. package/dist/general-instance.js +53 -0
  93. package/dist/general-instance.js.map +1 -1
  94. package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
  95. package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
  96. package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -3
  97. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
  98. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +34 -0
  99. package/dist/general-knowledge/skills/session-management/SKILL.md +2 -2
  100. package/dist/general-knowledge/skills/tui-effort/SKILL.md +1 -1
  101. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +32 -0
  102. package/dist/general-knowledge/skills/web-appearance/SKILL.md +32 -0
  103. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +57 -17
  104. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +57 -17
  105. package/dist/instance-config-impact.js +3 -2
  106. package/dist/instance-config-impact.js.map +1 -1
  107. package/dist/instance-lifecycle.d.ts +4 -0
  108. package/dist/instance-lifecycle.js +28 -5
  109. package/dist/instance-lifecycle.js.map +1 -1
  110. package/dist/instructions.js +6 -7
  111. package/dist/instructions.js.map +1 -1
  112. package/dist/kiro-migrate.d.ts +19 -2
  113. package/dist/kiro-migrate.js +133 -35
  114. package/dist/kiro-migrate.js.map +1 -1
  115. package/dist/kiro-reconcile.d.ts +21 -0
  116. package/dist/kiro-reconcile.js +88 -0
  117. package/dist/kiro-reconcile.js.map +1 -0
  118. package/dist/kiro-session-proof.d.ts +41 -0
  119. package/dist/kiro-session-proof.js +89 -0
  120. package/dist/kiro-session-proof.js.map +1 -0
  121. package/dist/kiro-tui-forensic.d.ts +19 -0
  122. package/dist/kiro-tui-forensic.js +124 -0
  123. package/dist/kiro-tui-forensic.js.map +1 -0
  124. package/dist/locale.js +202 -16
  125. package/dist/locale.js.map +1 -1
  126. package/dist/log-service.js +3 -3
  127. package/dist/log-service.js.map +1 -1
  128. package/dist/needs-you.d.ts +3 -1
  129. package/dist/needs-you.js +4 -2
  130. package/dist/needs-you.js.map +1 -1
  131. package/dist/office-levels.d.ts +64 -0
  132. package/dist/office-levels.js +66 -0
  133. package/dist/office-levels.js.map +1 -0
  134. package/dist/office-share-page.d.ts +3 -1
  135. package/dist/office-share-page.js +8 -3
  136. package/dist/office-share-page.js.map +1 -1
  137. package/dist/office-share-service.d.ts +6 -3
  138. package/dist/office-share-service.js +12 -8
  139. package/dist/office-share-service.js.map +1 -1
  140. package/dist/office-share.d.ts +45 -8
  141. package/dist/office-share.js +75 -15
  142. package/dist/office-share.js.map +1 -1
  143. package/dist/outbound-handlers.js +21 -2
  144. package/dist/outbound-handlers.js.map +1 -1
  145. package/dist/outbound-schemas.d.ts +47 -2
  146. package/dist/outbound-schemas.js +42 -3
  147. package/dist/outbound-schemas.js.map +1 -1
  148. package/dist/public-web-gateway.js +1 -1
  149. package/dist/public-web-gateway.js.map +1 -1
  150. package/dist/quickstart-api.d.ts +39 -1
  151. package/dist/quickstart-api.js +115 -17
  152. package/dist/quickstart-api.js.map +1 -1
  153. package/dist/remote-card-answer.d.ts +83 -0
  154. package/dist/remote-card-answer.js +208 -0
  155. package/dist/remote-card-answer.js.map +1 -0
  156. package/dist/remote-card-prompt.d.ts +38 -0
  157. package/dist/remote-card-prompt.js +86 -0
  158. package/dist/remote-card-prompt.js.map +1 -0
  159. package/dist/scheduler/task-board.d.ts +44 -17
  160. package/dist/scheduler/task-board.js +95 -36
  161. package/dist/scheduler/task-board.js.map +1 -1
  162. package/dist/sd-notify.d.ts +13 -14
  163. package/dist/sd-notify.js +62 -28
  164. package/dist/sd-notify.js.map +1 -1
  165. package/dist/service-installer.d.ts +13 -0
  166. package/dist/service-installer.js +15 -0
  167. package/dist/service-installer.js.map +1 -1
  168. package/dist/service-owner.d.ts +25 -0
  169. package/dist/service-owner.js +43 -2
  170. package/dist/service-owner.js.map +1 -1
  171. package/dist/settings-api.d.ts +2 -1
  172. package/dist/settings-api.js +19 -26
  173. package/dist/settings-api.js.map +1 -1
  174. package/dist/settings-change.js +10 -5
  175. package/dist/settings-change.js.map +1 -1
  176. package/dist/settings-effect.js +7 -6
  177. package/dist/settings-effect.js.map +1 -1
  178. package/dist/settings-instance-patch.js +1 -1
  179. package/dist/settings-instance-patch.js.map +1 -1
  180. package/dist/setup-form.d.ts +2 -2
  181. package/dist/setup-form.js +139 -93
  182. package/dist/setup-form.js.map +1 -1
  183. package/dist/setup-i18n.d.ts +25 -0
  184. package/dist/setup-i18n.js +226 -0
  185. package/dist/setup-i18n.js.map +1 -0
  186. package/dist/task-board-waker.d.ts +40 -1
  187. package/dist/task-board-waker.js +115 -9
  188. package/dist/task-board-waker.js.map +1 -1
  189. package/dist/tmux-control.d.ts +1 -1
  190. package/dist/tmux-control.js +3 -2
  191. package/dist/tmux-control.js.map +1 -1
  192. package/dist/tmux-manager.js +18 -16
  193. package/dist/tmux-manager.js.map +1 -1
  194. package/dist/tmux-read.d.ts +2 -0
  195. package/dist/tmux-read.js +7 -1
  196. package/dist/tmux-read.js.map +1 -1
  197. package/dist/tool-permissions.js +18 -0
  198. package/dist/tool-permissions.js.map +1 -1
  199. package/dist/topic-commands.d.ts +2 -0
  200. package/dist/topic-commands.js +2 -1
  201. package/dist/topic-commands.js.map +1 -1
  202. package/dist/transcript-sources.d.ts +1 -0
  203. package/dist/transcript-sources.js +20 -3
  204. package/dist/transcript-sources.js.map +1 -1
  205. package/dist/types.d.ts +11 -5
  206. package/dist/ui/backend-signin.js +4 -3
  207. package/dist/ui/chat-store.js +3 -2
  208. package/dist/ui/chat-strings.js +2 -2
  209. package/dist/ui/fleet-office-card.js +114 -0
  210. package/dist/ui/fleet-office-editor.js +81 -9
  211. package/dist/ui/fleet-office-layout.js +56 -11
  212. package/dist/ui/fleet-office-levels.js +105 -0
  213. package/dist/ui/fleet-office-model.js +9 -1
  214. package/dist/ui/fleet-office-pack.d.ts +15 -3
  215. package/dist/ui/fleet-office-pack.js +177 -23
  216. package/dist/ui/fleet-office-skin.js +39 -3
  217. package/dist/ui/fleet-office-store.js +74 -3
  218. package/dist/ui/fleet-office-view.js +8 -0
  219. package/dist/ui/fleet-office-zones.js +102 -19
  220. package/dist/ui/fleet-office.js +308 -44
  221. package/dist/ui/fleet-org.js +2 -0
  222. package/dist/ui/fleet-task-card.js +2 -2
  223. package/dist/ui/fleet-task-common.js +30 -7
  224. package/dist/ui/fleet-task-work.js +2 -2
  225. package/dist/ui/fleet-tasks.js +10 -11
  226. package/dist/ui/office-share-dialog.js +31 -7
  227. package/dist/ui/office-share-page.js +1 -0
  228. package/dist/ui/panel-chat.js +14 -2
  229. package/dist/ui/panel-log.js +6 -6
  230. package/dist/ui/panel-needs.js +1 -1
  231. package/dist/ui/panel-settings.js +16 -8
  232. package/dist/ui/settings-appearance.js +3 -35
  233. package/dist/ui/settings-discord.js +3 -2
  234. package/dist/ui/settings-guests.js +17 -7
  235. package/dist/ui/settings-model.js +17 -9
  236. package/dist/ui/settings-registry.js +1 -1
  237. package/dist/ui/settings-strings.js +18 -6
  238. package/dist/ui/settings-token.js +3 -2
  239. package/dist/ui/settings-wizard.js +25 -9
  240. package/dist/ui/shared/app-i18n.js +2 -2
  241. package/dist/ui/shared/app-stream.js +1 -1
  242. package/dist/ui/shared/app.css +70 -2
  243. package/dist/ui/shared/setup-host-text.js +107 -0
  244. package/dist/ui/structured-signin.js +89 -38
  245. package/dist/update-check.d.ts +13 -4
  246. package/dist/update-check.js +17 -4
  247. package/dist/update-check.js.map +1 -1
  248. package/dist/usage-rollup.d.ts +34 -3
  249. package/dist/usage-rollup.js +56 -3
  250. package/dist/usage-rollup.js.map +1 -1
  251. package/dist/web-api.d.ts +10 -1
  252. package/dist/web-api.js +207 -17
  253. package/dist/web-api.js.map +1 -1
  254. package/dist/web-auth.js +1 -1
  255. package/dist/web-auth.js.map +1 -1
  256. package/dist/web-experimental.d.ts +6 -2
  257. package/dist/web-experimental.js +8 -4
  258. package/dist/web-experimental.js.map +1 -1
  259. package/dist/web-office-acts.d.ts +5 -2
  260. package/dist/web-office-acts.js +9 -4
  261. package/dist/web-office-acts.js.map +1 -1
  262. package/dist/web-office-pack-read.d.ts +35 -0
  263. package/dist/web-office-pack-read.js +252 -0
  264. package/dist/web-office-pack-read.js.map +1 -0
  265. package/dist/web-office-pack-tools.d.ts +241 -0
  266. package/dist/web-office-pack-tools.js +785 -0
  267. package/dist/web-office-pack-tools.js.map +1 -0
  268. package/dist/web-office-skins.d.ts +68 -4
  269. package/dist/web-office-skins.js +324 -33
  270. package/dist/web-office-skins.js.map +1 -1
  271. package/dist/web-office-store.d.ts +47 -4
  272. package/dist/web-office-store.js +124 -23
  273. package/dist/web-office-store.js.map +1 -1
  274. package/dist/web-settings-patch.d.ts +3 -2
  275. package/dist/web-settings-patch.js +7 -5
  276. package/dist/web-settings-patch.js.map +1 -1
  277. package/dist/workflow-templates/default.md +3 -3
  278. package/dist/working-recovery.d.ts +0 -2
  279. package/dist/working-recovery.js +2 -23
  280. package/dist/working-recovery.js.map +1 -1
  281. package/package.json +2 -2
  282. package/templates/windows-shortcuts.ps1 +18 -17
  283. package/dist/upgrade-notices.d.ts +0 -17
  284. package/dist/upgrade-notices.js +0 -93
  285. package/dist/upgrade-notices.js.map +0 -1
@@ -157,6 +157,10 @@ export interface ProjectStats {
157
157
  /** A card as the dashboard draws it: references by card number. */
158
158
  export interface CardView {
159
159
  id: string;
160
+ /** #2080: in review needing the person's confirmation, or blocked on a question theirs (waitsForPerson): Waiting for you. */
161
+ waitsForPerson: boolean;
162
+ /** #2080: the instance that confirms it in review (its dispatcher, else its assignee); null when the person does. */
163
+ confirmer: string | null;
160
164
  uuid: string;
161
165
  kind: CardKind;
162
166
  projectId: number | null;
@@ -218,13 +222,15 @@ export interface CardEvent {
218
222
  }
219
223
  /**
220
224
  * Why an instance hears about one of its cards (step 2's wakes, task-board-waker.ts): someone else assigned it,
221
- * started it, sent it back, answered its blocked question, commented, or finished the last card it waited for; the
222
- * person confirmed it (its assignee and its dispatcher hear); it is blocked and the instance handed it out (decide);
223
- * it went to the person for confirmation and the instance handed it out, so it tells them (to_confirm);
224
- * a main task went to the person by itself before its owner wrapped it up (wrap_up); or someone else cancelled it — a
225
- * main task's cancel takes its unfinished sub tasks down with it, and their assignees hear (cancelled).
225
+ * started it, sent it back, answered its blocked question, or finished the last card it waited for; it is blocked and
226
+ * the instance handed it out (decide); it went to the person for confirmation and the instance handed it out, so it
227
+ * tells them (to_confirm); a main task went to the person by itself before its owner wrapped it up (wrap_up); or
228
+ * someone else cancelled it — a main task's cancel takes its unfinished sub tasks down with it, and their assignees
229
+ * hear (cancelled). #2801: only a change of status or assignee is news, and only to the one instance that acts on it
230
+ * next — a comment, and a confirmation (the work is finished), wake nobody; but for the answer to its blocked question
231
+ * that waits on the card while its assignee is busy with another (#2303), which it acts on next (answer_kept).
226
232
  */
227
- export type WakeReason = "assigned" | "start" | "sent_back" | "answered" | "comment" | "ready" | "decide" | "to_confirm" | "approved" | "wrap_up" | "cancelled";
233
+ export type WakeReason = "assigned" | "start" | "sent_back" | "answered" | "answer_kept" | "ready" | "decide" | "to_confirm" | "confirm_own" | "wrap_up" | "cancelled";
228
234
  /** One card's news for one instance from one committed write. Never sent to a page (it carries titles and text). */
229
235
  export interface Wake {
230
236
  instance: string;
@@ -248,6 +254,8 @@ export interface BoardChange {
248
254
  projects: boolean;
249
255
  /** Whom to wake about what (the fleet's waker); a page is sent the rest. */
250
256
  wakes: Wake[];
257
+ /** #2801: the cards whose status or assignee this write changed (an older notice about them no longer stands). */
258
+ changed: string[];
251
259
  }
252
260
  export interface CardDetail {
253
261
  card: CardView;
@@ -371,6 +379,11 @@ export interface SearchHit {
371
379
  export declare const personConfirms: (card: Pick<Card, "dispatcher">) => boolean;
372
380
  /** Who that is, in words (an error, the card's text). */
373
381
  export declare function confirmerOf(card: Pick<Card, "dispatcher" | "assignee">): string;
382
+ /**
383
+ * #2080: the instance that confirms a card in review (`confirms`' rule), or null when it is the person's — handed out by
384
+ * them, or by an instance deleted since (`gone`), or nobody's at all.
385
+ */
386
+ export declare function confirmingInstance(card: Pick<Card, "dispatcher" | "assignee" | "createdBy">, gone?: GoneDispatcher): string | null;
374
387
  /** "m3", "M - 3" → "M-3"; null when it is not a card number. */
375
388
  export declare function cardCode(raw: string): string | null;
376
389
  /**
@@ -438,6 +451,8 @@ export declare class TaskBoard {
438
451
  private checkProjectName;
439
452
  /** A new main task's project: the one asked for (not archived), or none. */
440
453
  private projectForMain;
454
+ /** #2801: when the card's status or assignee last changed (ISO), or null — a notice older than that is stale. */
455
+ lastChangeAt(uuid: string): string | null;
441
456
  /** A card by number (M-3), uuid, or a unique uuid prefix of at least 8 hex digits. */
442
457
  findCard(ref: string): Card | undefined;
443
458
  getCard(ref: string): Card;
@@ -462,10 +477,11 @@ export declare class TaskBoard {
462
477
  /**
463
478
  * Each project's numbers (by id; "none" for No project) over all its cards, finished ones included: main tasks,
464
479
  * work units (a main task's sub tasks, or one unit for a main task without them; a done main task counts all its
465
- * units done), cards waiting for the person and blocked, and the last change. One pass over the table.
480
+ * units done), cards waiting for the person and blocked, and the last change. One pass over the table. #2080: waiting
481
+ * counts only the cards in review the person confirms (waitsForPerson, `gone` as for Needs you), never an agent's.
466
482
  */
467
- projectStats(): Record<string, ProjectStats>;
468
- view(card: Card, scope?: ReadScope): CardView;
483
+ projectStats(gone?: GoneDispatcher): Record<string, ProjectStats>;
484
+ view(card: Card, scope?: ReadScope, gone?: GoneDispatcher): CardView;
469
485
  /** ` AND <column> NOT IN (…)` for a scope's left-out authors, with its values (empty when none). */
470
486
  private notBy;
471
487
  /**
@@ -474,8 +490,13 @@ export declare class TaskBoard {
474
490
  * queries per card: a board read stays a handful of statements however many cards it has (it runs on the fleet's
475
491
  * event loop). Same answers as progress(), unmetDeps() and newComments() card by card.
476
492
  */
477
- views(cards: Card[], scope?: ReadScope): CardView[];
478
- detail(ref: string, scope?: ReadScope): CardDetail;
493
+ views(cards: Card[], scope?: ReadScope, gone?: GoneDispatcher): CardView[];
494
+ detail(ref: string, scope?: ReadScope, gone?: GoneDispatcher): CardDetail;
495
+ /**
496
+ * #2757 review: the instance dispatchers on the board that `gone` says are gone, as one string — what a cached read
497
+ * that names waitsForPerson / confirmer must also be keyed by (a dispatcher deleted changes them, the revision not).
498
+ */
499
+ goneDispatchers(gone?: GoneDispatcher): string;
479
500
  /** The latest `limit` entries of a card, oldest first. */
480
501
  entries(uuid: string, kinds: EntryKind[], limit?: number): Entry[];
481
502
  /** Comments and rejections by others that the assignee has not acted on yet. */
@@ -615,6 +636,13 @@ export declare class TaskBoard {
615
636
  result?: string;
616
637
  priority?: CardPriority;
617
638
  }): Card;
639
+ /**
640
+ * #2080: review for an instance's own confirmation is done. An instance moving a card to review that it confirms itself
641
+ * (nobody else handed it out, or it handed it to itself) would wait for nobody but itself — and sat there unnoticed,
642
+ * in the person's Waiting for you too: it finishes instead (the checks for review are the ones for done). A person's
643
+ * move, and a card someone else confirms, stay as asked.
644
+ */
645
+ private ownReview;
618
646
  /**
619
647
  * Cards moved by hand: sub tasks, and main tasks that have no sub tasks. `comment` is the move's own comment (a
620
648
  * dispatcher's answer); `finishing`: a claim on the way to done (the `task` tool's done of an open card).
@@ -688,12 +716,11 @@ export declare class TaskBoard {
688
716
  private wakeReady;
689
717
  /**
690
718
  * What of a wake still stands now (at the end of its write, and again when the fleet sends it): a finished card is
691
- * news only as a confirmation; a card that is no longer the instance's is not; a question only while the card is
692
- * blocked and the instance still handed it out; a wrap-up only while it waits unfinished; and an assignment or a
693
- * comment only once the card can start (its "ready" wakes it then). What asks the instance to act holds only while
694
- * it still can (#2303 review): "ready" while the card is open with nothing left to wait for, "start" and "answered"
695
- * while it is in progress, "sent back" while it is back with the instance; a comment only while its assignee has not
696
- * read it (its own write since showed it, as the `task` tool's answer). null when nothing is left.
719
+ * news only as a cancel; a card that is no longer the instance's is not; a question only while the card is blocked
720
+ * and the instance still handed it out; a wrap-up only while it waits unfinished; and an assignment only once the
721
+ * card can start (its "ready" wakes it then). What asks the instance to act holds only while it still can (#2303
722
+ * review): "ready" while the card is open with nothing left to wait for, "start" and "answered" while it is in
723
+ * progress, "sent back" while it is back with the instance. null when nothing is left.
697
724
  */
698
725
  wakeHolds(wake: Wake): Wake | null;
699
726
  /**
@@ -120,6 +120,17 @@ export function confirmerOf(card) {
120
120
  return card.dispatcher.name;
121
121
  return card.assignee ?? "its assignee";
122
122
  }
123
+ /**
124
+ * #2080: the instance that confirms a card in review (`confirms`' rule), or null when it is the person's — handed out by
125
+ * them, or by an instance deleted since (`gone`), or nobody's at all.
126
+ */
127
+ export function confirmingInstance(card, gone) {
128
+ if (personConfirms(card) || orphaned(card, gone))
129
+ return null;
130
+ if (card.dispatcher?.kind === "instance")
131
+ return card.dispatcher.name;
132
+ return card.assignee ?? (card.createdBy || null);
133
+ }
123
134
  function normalizeAssignee(raw) {
124
135
  const v = raw?.trim() ?? "";
125
136
  if (v === "" || v.toLowerCase() === "none")
@@ -436,6 +447,11 @@ export class TaskBoard {
436
447
  return project.id;
437
448
  }
438
449
  // ── Reading ─────────────────────────────────────────────
450
+ /** #2801: when the card's status or assignee last changed (ISO), or null — a notice older than that is stale. */
451
+ lastChangeAt(uuid) {
452
+ const r = this.db.prepare("SELECT MAX(at) AS at FROM task_events WHERE task_id = ? AND type IN ('created', 'moved', 'assigned')").get(uuid);
453
+ return r?.at ?? null;
454
+ }
439
455
  /** A card by number (M-3), uuid, or a unique uuid prefix of at least 8 hex digits. */
440
456
  findCard(ref) {
441
457
  const text = (ref ?? "").trim();
@@ -518,10 +534,11 @@ export class TaskBoard {
518
534
  /**
519
535
  * Each project's numbers (by id; "none" for No project) over all its cards, finished ones included: main tasks,
520
536
  * work units (a main task's sub tasks, or one unit for a main task without them; a done main task counts all its
521
- * units done), cards waiting for the person and blocked, and the last change. One pass over the table.
537
+ * units done), cards waiting for the person and blocked, and the last change. One pass over the table. #2080: waiting
538
+ * counts only the cards in review the person confirms (waitsForPerson, `gone` as for Needs you), never an agent's.
522
539
  */
523
- projectStats() {
524
- const rows = this.db.prepare("SELECT id, kind, status, project_id, parent_id, updated_at FROM tasks").all();
540
+ projectStats(gone) {
541
+ const rows = this.db.prepare("SELECT id, kind, status, project_id, parent_id, updated_at, needs_review, dispatcher, dispatcher_kind FROM tasks").all();
525
542
  const subs = new Map();
526
543
  for (const r of rows) {
527
544
  if (r.kind !== "sub" || !r.parent_id || r.status === "cancelled")
@@ -540,7 +557,8 @@ export class TaskBoard {
540
557
  const status = r.status;
541
558
  if (status === "cancelled")
542
559
  continue;
543
- if (status === "review")
560
+ if (status === "review" && waitsForPerson({ status, needsReview: Number(r.needs_review) === 1,
561
+ dispatcher: r.dispatcher ? { kind: r.dispatcher_kind === "person" ? "person" : "instance", name: r.dispatcher } : null }, gone))
544
562
  st.waiting++;
545
563
  if (status === "blocked")
546
564
  st.blocked++;
@@ -565,8 +583,8 @@ export class TaskBoard {
565
583
  st.percent = st.units.total ? Math.round((st.units.done / st.units.total) * 100) : 0;
566
584
  return out;
567
585
  }
568
- view(card, scope = {}) {
569
- return this.views([card], scope)[0];
586
+ view(card, scope = {}, gone) {
587
+ return this.views([card], scope, gone)[0];
570
588
  }
571
589
  /** ` AND <column> NOT IN (…)` for a scope's left-out authors, with its values (empty when none). */
572
590
  notBy(scope, column) {
@@ -579,7 +597,7 @@ export class TaskBoard {
579
597
  * queries per card: a board read stays a handful of statements however many cards it has (it runs on the fleet's
580
598
  * event loop). Same answers as progress(), unmetDeps() and newComments() card by card.
581
599
  */
582
- views(cards, scope = {}) {
600
+ views(cards, scope = {}, gone) {
583
601
  if (!cards.length)
584
602
  return [];
585
603
  const all = new Map();
@@ -618,6 +636,8 @@ export class TaskBoard {
618
636
  const c = comments.get(card.uuid);
619
637
  return {
620
638
  id: card.code,
639
+ waitsForPerson: waitsForPerson(card, gone),
640
+ confirmer: confirmingInstance(card, gone),
621
641
  uuid: card.uuid,
622
642
  kind: card.kind,
623
643
  projectId: card.projectId,
@@ -653,18 +673,28 @@ export class TaskBoard {
653
673
  };
654
674
  });
655
675
  }
656
- detail(ref, scope = {}) {
676
+ detail(ref, scope = {}, gone) {
657
677
  const card = this.getCard(ref);
658
678
  const parent = card.parent ? this.byUuid(card.parent) : undefined;
659
679
  return {
660
- card: this.view(card, scope),
680
+ card: this.view(card, scope, gone),
661
681
  parent: parent ? { id: parent.code, title: parent.title, status: parent.status } : null,
662
- children: this.views(this.children(card.uuid), scope),
682
+ children: this.views(this.children(card.uuid), scope, gone),
663
683
  comments: this.entries(card.uuid, ["comment", "reject", "approve"], 200),
664
684
  logs: this.entries(card.uuid, ["log"], 200),
665
685
  history: this.events(card.uuid, 200).filter((e) => e.type !== "reordered"),
666
686
  };
667
687
  }
688
+ /**
689
+ * #2757 review: the instance dispatchers on the board that `gone` says are gone, as one string — what a cached read
690
+ * that names waitsForPerson / confirmer must also be keyed by (a dispatcher deleted changes them, the revision not).
691
+ */
692
+ goneDispatchers(gone) {
693
+ if (!gone)
694
+ return "";
695
+ const rows = this.db.prepare("SELECT DISTINCT dispatcher FROM tasks WHERE dispatcher IS NOT NULL AND (dispatcher_kind IS NULL OR dispatcher_kind != 'person')").all();
696
+ return rows.map((r) => r.dispatcher).filter((n) => gone(n)).sort().join("\u0000");
697
+ }
668
698
  /** The latest `limit` entries of a card, oldest first. */
669
699
  entries(uuid, kinds, limit = 20) {
670
700
  const rows = this.db.prepare(`SELECT * FROM task_entries WHERE task_id = ? AND kind IN (${kinds.map(() => "?").join(", ")}) ORDER BY id DESC LIMIT ?`)
@@ -1236,7 +1266,8 @@ export class TaskBoard {
1236
1266
  if (busy.length) {
1237
1267
  const text = checkText("the comment", comment, CARD_LIMITS.comment);
1238
1268
  this.entry(card, "comment", actor, text);
1239
- this.event(actor, card, "commented", { text });
1269
+ // #2801 review: the one comment that is news — the answer its assignee waits for (`kept`).
1270
+ this.event(actor, card, "commented", { text, kept: true });
1240
1271
  card.version++;
1241
1272
  card.updatedAt = this.nowIso();
1242
1273
  this.save(card);
@@ -1249,7 +1280,7 @@ export class TaskBoard {
1249
1280
  this.checkDerivedMove(card, actor, to, reason, { comment });
1250
1281
  else
1251
1282
  this.checkManualMove(card, actor, to, reason, { comment });
1252
- this.applyMove(card, actor, to, reason, comment);
1283
+ this.applyMove(card, actor, this.ownReview(card, actor, to, reason), reason, comment);
1253
1284
  return { card, from, ...this.spoken() };
1254
1285
  });
1255
1286
  }
@@ -1429,6 +1460,8 @@ export class TaskBoard {
1429
1460
  if (held && to !== card.status)
1430
1461
  this.checkManualMove(card, actor, to, "");
1431
1462
  }
1463
+ // #2757 review: a derived main task too; a card off the board (not held) keeps the tool's old, unchecked finish.
1464
+ to = this.ownReview(card, actor, to, "", held);
1432
1465
  }
1433
1466
  if (to !== card.status) {
1434
1467
  this.applyMove(card, actor, to, "");
@@ -1447,6 +1480,21 @@ export class TaskBoard {
1447
1480
  });
1448
1481
  }
1449
1482
  // ── Rules ───────────────────────────────────────────────
1483
+ /**
1484
+ * #2080: review for an instance's own confirmation is done. An instance moving a card to review that it confirms itself
1485
+ * (nobody else handed it out, or it handed it to itself) would wait for nobody but itself — and sat there unnoticed,
1486
+ * in the person's Waiting for you too: it finishes instead (the checks for review are the ones for done). A person's
1487
+ * move, and a card someone else confirms, stay as asked.
1488
+ */
1489
+ ownReview(card, actor, to, reason = "", check = true) {
1490
+ if (to !== "review" || isPerson(actor) || card.status === "review" || !confirms(card, actor))
1491
+ return to;
1492
+ // #2757 review: finished as done is, whichever way it came (a derived main task's review checks less).
1493
+ if (check)
1494
+ this.checkFinish(card, true, reason);
1495
+ this.say("own_review", [card.code], `${card.code} waits for nobody else's confirmation (you confirm it): done, not review`);
1496
+ return "done";
1497
+ }
1450
1498
  /**
1451
1499
  * Cards moved by hand: sub tasks, and main tasks that have no sub tasks. `comment` is the move's own comment (a
1452
1500
  * dispatcher's answer); `finishing`: a claim on the way to done (the `task` tool's done of an open card).
@@ -1628,6 +1676,13 @@ export class TaskBoard {
1628
1676
  return;
1629
1677
  }
1630
1678
  if (ai) {
1679
+ // #2080: a main task in review by itself (its sub tasks done) is finished by the instance that confirms it.
1680
+ if (from === "review" && to === "done" && confirms(card, actor)) {
1681
+ if (reason)
1682
+ this.entry(card, "approve", actor, reason);
1683
+ this.checkFinish(card, ai, reason);
1684
+ return;
1685
+ }
1631
1686
  if (from === "claimed" && to === "review") {
1632
1687
  if (!own)
1633
1688
  throw notOwner();
@@ -1886,10 +1941,16 @@ export class TaskBoard {
1886
1941
  w.by[reason] = by.name;
1887
1942
  out.set(key, w);
1888
1943
  };
1944
+ // The answer to a blocked question is the comment that came with it (#2801: a comment alone wakes nobody).
1889
1945
  const comments = new Map();
1890
1946
  for (const e of events)
1891
1947
  if (e.type === "commented")
1892
1948
  comments.set(e.uuid, String(e.data.text ?? ""));
1949
+ for (const e of events) {
1950
+ const card = e.type === "commented" && e.data.kept === true ? this.byUuid(e.uuid) : undefined;
1951
+ if (card)
1952
+ add(card, card.assignee, "answer_kept", e.actor, String(e.data.text ?? ""));
1953
+ }
1893
1954
  for (const e of events) {
1894
1955
  const card = this.byUuid(e.uuid);
1895
1956
  if (!card)
@@ -1905,8 +1966,6 @@ export class TaskBoard {
1905
1966
  if (this.isOnBoard(card))
1906
1967
  add(card, d.to ?? null, "assigned", e.actor);
1907
1968
  }
1908
- else if (e.type === "commented")
1909
- add(card, card.assignee, "comment", e.actor, d.text);
1910
1969
  else if (e.type === "moved") {
1911
1970
  if (d.to === "done")
1912
1971
  this.wakeReady(card, e.actor, add);
@@ -1919,10 +1978,16 @@ export class TaskBoard {
1919
1978
  }
1920
1979
  // An agent's card waits for its instance dispatcher to confirm it (#2434: its own to confirm, nobody to ask). A
1921
1980
  // person's card has no instance dispatcher, so no notice: the person has it in Needs you.
1922
- if (d.to === "review" && !isPerson(e.actor) && requiresReview(card))
1923
- add(card, dispatcherOf(), "to_confirm", e.actor);
1981
+ const toConfirm = d.to === "review" && !isPerson(e.actor) && requiresReview(card) ? dispatcherOf() : null;
1982
+ if (toConfirm)
1983
+ add(card, toConfirm, "to_confirm", e.actor);
1984
+ // #2080: a card its own assignee confirms, in review by itself (its sub tasks done) or by someone else's move:
1985
+ // nobody else will confirm it, so its assignee is told it is theirs.
1986
+ if (d.to === "review" && card.assignee && confirmingInstance(card) === card.assignee)
1987
+ add(card, card.assignee, "confirm_own", e.actor);
1924
1988
  if (d.auto) {
1925
- if (d.to === "review" && card.kind === "main" && wrapUpGap(card))
1989
+ // #2801: one change, one instance — the dispatcher's confirmation already says what it lacks.
1990
+ if (d.to === "review" && card.kind === "main" && wrapUpGap(card) && !toConfirm)
1926
1991
  add(card, card.assignee, "wrap_up", e.actor);
1927
1992
  continue;
1928
1993
  }
@@ -1932,10 +1997,6 @@ export class TaskBoard {
1932
1997
  add(card, card.assignee, "answered", e.actor, comments.get(card.uuid));
1933
1998
  else if (d.from === "review" && (d.to === "claimed" || d.to === "open"))
1934
1999
  add(card, card.assignee, "sent_back", e.actor, d.reason);
1935
- else if (d.from === "review" && d.to === "done") {
1936
- add(card, card.assignee, "approved", e.actor, d.reason);
1937
- add(card, dispatcherOf(), "approved", e.actor, d.reason);
1938
- }
1939
2000
  if (d.to === "blocked" && !isPerson(e.actor))
1940
2001
  add(card, dispatcherOf(), "decide", e.actor, d.reason);
1941
2002
  }
@@ -1952,12 +2013,11 @@ export class TaskBoard {
1952
2013
  }
1953
2014
  /**
1954
2015
  * What of a wake still stands now (at the end of its write, and again when the fleet sends it): a finished card is
1955
- * news only as a confirmation; a card that is no longer the instance's is not; a question only while the card is
1956
- * blocked and the instance still handed it out; a wrap-up only while it waits unfinished; and an assignment or a
1957
- * comment only once the card can start (its "ready" wakes it then). What asks the instance to act holds only while
1958
- * it still can (#2303 review): "ready" while the card is open with nothing left to wait for, "start" and "answered"
1959
- * while it is in progress, "sent back" while it is back with the instance; a comment only while its assignee has not
1960
- * read it (its own write since showed it, as the `task` tool's answer). null when nothing is left.
2016
+ * news only as a cancel; a card that is no longer the instance's is not; a question only while the card is blocked
2017
+ * and the instance still handed it out; a wrap-up only while it waits unfinished; and an assignment only once the
2018
+ * card can start (its "ready" wakes it then). What asks the instance to act holds only while it still can (#2303
2019
+ * review): "ready" while the card is open with nothing left to wait for, "start" and "answered" while it is in
2020
+ * progress, "sent back" while it is back with the instance. null when nothing is left.
1961
2021
  */
1962
2022
  wakeHolds(wake) {
1963
2023
  const card = this.byUuid(wake.uuid);
@@ -1967,8 +2027,6 @@ export class TaskBoard {
1967
2027
  const own = card.assignee === wake.instance;
1968
2028
  const waiting = this.unmetDeps(card).length > 0;
1969
2029
  let reasons = wake.reasons.filter((r) => {
1970
- if (r === "approved")
1971
- return card.status === "done" && (own || handedOut);
1972
2030
  if (r === "cancelled")
1973
2031
  return card.status === "cancelled" && own;
1974
2032
  if (isFinished(card))
@@ -1980,25 +2038,25 @@ export class TaskBoard {
1980
2038
  return handedOut && card.status === "review" && requiresReview(card);
1981
2039
  if (!own)
1982
2040
  return false;
2041
+ if (r === "confirm_own")
2042
+ return card.status === "review" && confirmingInstance(card) === wake.instance;
1983
2043
  if (r === "wrap_up")
1984
2044
  return card.status === "review" && wrapUpGap(card) !== null;
1985
2045
  if (r === "assigned")
1986
2046
  return !waiting;
1987
- if (r === "comment")
1988
- return !waiting && this.newComments(card).length > 0;
1989
2047
  if (r === "ready")
1990
2048
  return card.status === "open" && !waiting;
1991
2049
  if (r === "start" || r === "answered")
1992
2050
  return card.status === "claimed";
2051
+ if (r === "answer_kept")
2052
+ return card.status === "blocked";
1993
2053
  if (r === "sent_back")
1994
2054
  return card.status === "claimed" || card.status === "open";
1995
2055
  return true;
1996
2056
  });
1997
- // One piece of news says it all: the answer is the comment, a send-back outweighs the rest.
1998
- if (reasons.includes("answered"))
1999
- reasons = reasons.filter((r) => r !== "comment");
2057
+ // One piece of news says it all: a send-back outweighs the rest.
2000
2058
  if (reasons.includes("sent_back"))
2001
- reasons = reasons.filter((r) => r !== "comment" && r !== "start");
2059
+ reasons = reasons.filter((r) => r !== "start");
2002
2060
  if (reasons.includes("assigned"))
2003
2061
  reasons = reasons.filter((r) => r !== "start" && r !== "ready");
2004
2062
  if (reasons.includes("cancelled"))
@@ -2032,7 +2090,8 @@ export class TaskBoard {
2032
2090
  const touched = this.pending;
2033
2091
  const projects = this.projectsChanged;
2034
2092
  if (touched.length || projects) {
2035
- const change = { rev: this.revision(), ids: [...new Set(touched.map((e) => e.code))], actors: [...new Set(touched.map((e) => e.actor.name))], projects, wakes };
2093
+ const changed = [...new Set(touched.filter((e) => e.type === "moved" || e.type === "assigned").map((e) => e.uuid))];
2094
+ const change = { rev: this.revision(), ids: [...new Set(touched.map((e) => e.code))], actors: [...new Set(touched.map((e) => e.actor.name))], projects, wakes, changed };
2036
2095
  for (const listener of this.listeners) {
2037
2096
  try {
2038
2097
  listener(change);