@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.23

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 (256) hide show
  1. package/README.md +2 -2
  2. package/README.zh-TW.md +2 -2
  3. package/dist/agent-cli.js +4 -2
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.d.ts +1 -0
  6. package/dist/agent-endpoint.js +4 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.d.ts +5 -0
  9. package/dist/agent-whats-new.js +16 -1
  10. package/dist/agent-whats-new.js.map +1 -1
  11. package/dist/auth-api.d.ts +7 -0
  12. package/dist/auth-api.js +8 -2
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/backend/codex-package.d.ts +30 -0
  15. package/dist/backend/codex-package.js +133 -0
  16. package/dist/backend/codex-package.js.map +1 -0
  17. package/dist/backend/codex.d.ts +2 -0
  18. package/dist/backend/codex.js +9 -2
  19. package/dist/backend/codex.js.map +1 -1
  20. package/dist/backend/kiro.d.ts +9 -1
  21. package/dist/backend/kiro.js +102 -1
  22. package/dist/backend/kiro.js.map +1 -1
  23. package/dist/backend/opencode-permission.js +1 -1
  24. package/dist/backend/opencode-permission.js.map +1 -1
  25. package/dist/backend/opencode.js +2 -1
  26. package/dist/backend/opencode.js.map +1 -1
  27. package/dist/binary-probe.js +1 -1
  28. package/dist/bug-report-cli.js +1 -1
  29. package/dist/bundled-skills.d.ts +7 -3
  30. package/dist/bundled-skills.js +16 -7
  31. package/dist/bundled-skills.js.map +1 -1
  32. package/dist/channel/adapters/discord.js +3 -3
  33. package/dist/channel/adapters/discord.js.map +1 -1
  34. package/dist/channel/mcp-server.js +12 -4
  35. package/dist/channel/mcp-server.js.map +1 -1
  36. package/dist/channel/mcp-tools.d.ts +3 -1
  37. package/dist/channel/mcp-tools.js +36 -0
  38. package/dist/channel/mcp-tools.js.map +1 -1
  39. package/dist/child-process.d.ts +21 -0
  40. package/dist/child-process.js +54 -0
  41. package/dist/child-process.js.map +1 -0
  42. package/dist/cli.js +167 -182
  43. package/dist/cli.js.map +1 -1
  44. package/dist/command-table.d.ts +1 -1
  45. package/dist/command-table.js +1 -1
  46. package/dist/command-table.js.map +1 -1
  47. package/dist/config-validator.js +20 -1
  48. package/dist/config-validator.js.map +1 -1
  49. package/dist/config.js +3 -1
  50. package/dist/config.js.map +1 -1
  51. package/dist/daemon.d.ts +8 -0
  52. package/dist/daemon.js +31 -8
  53. package/dist/daemon.js.map +1 -1
  54. package/dist/darwin-memory.js +1 -1
  55. package/dist/doctor.d.ts +5 -0
  56. package/dist/doctor.js +42 -0
  57. package/dist/doctor.js.map +1 -1
  58. package/dist/export-import.js +1 -1
  59. package/dist/export-import.js.map +1 -1
  60. package/dist/fleet-level-config.d.ts +1 -1
  61. package/dist/fleet-level-config.js +2 -0
  62. package/dist/fleet-level-config.js.map +1 -1
  63. package/dist/fleet-manager.d.ts +45 -0
  64. package/dist/fleet-manager.js +301 -21
  65. package/dist/fleet-manager.js.map +1 -1
  66. package/dist/full-restart.d.ts +5 -5
  67. package/dist/full-restart.js +5 -5
  68. package/dist/full-restart.js.map +1 -1
  69. package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
  70. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
  71. package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
  72. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
  73. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
  74. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +16 -3
  75. package/dist/general-knowledge/steering/core-rules.md +1 -1
  76. package/dist/instance-config-impact.js +10 -3
  77. package/dist/instance-config-impact.js.map +1 -1
  78. package/dist/instance-lifecycle.js +4 -4
  79. package/dist/locale.js +118 -4
  80. package/dist/locale.js.map +1 -1
  81. package/dist/login-flows.js +1 -1
  82. package/dist/login-shell.js +3 -1
  83. package/dist/login-shell.js.map +1 -1
  84. package/dist/ls-rows.js +1 -1
  85. package/dist/needs-you-hub.d.ts +98 -1
  86. package/dist/needs-you-hub.js +313 -44
  87. package/dist/needs-you-hub.js.map +1 -1
  88. package/dist/needs-you-live.d.ts +5 -2
  89. package/dist/needs-you-live.js +6 -3
  90. package/dist/needs-you-live.js.map +1 -1
  91. package/dist/needs-you.d.ts +58 -3
  92. package/dist/needs-you.js +36 -3
  93. package/dist/needs-you.js.map +1 -1
  94. package/dist/outbound-schemas.d.ts +3 -3
  95. package/dist/public-link-progress.d.ts +12 -1
  96. package/dist/public-link-progress.js +5 -10
  97. package/dist/public-link-progress.js.map +1 -1
  98. package/dist/public-tunnel-config.d.ts +5 -0
  99. package/dist/public-tunnel-config.js +45 -1
  100. package/dist/public-tunnel-config.js.map +1 -1
  101. package/dist/public-web-link.js +6 -5
  102. package/dist/public-web-link.js.map +1 -1
  103. package/dist/quickstart.js +14 -8
  104. package/dist/quickstart.js.map +1 -1
  105. package/dist/resource-report.js +1 -1
  106. package/dist/scheduler/task-board.d.ts +41 -1
  107. package/dist/scheduler/task-board.js +150 -4
  108. package/dist/scheduler/task-board.js.map +1 -1
  109. package/dist/sd-notify.js +2 -1
  110. package/dist/sd-notify.js.map +1 -1
  111. package/dist/service-installer.d.ts +69 -0
  112. package/dist/service-installer.js +181 -0
  113. package/dist/service-installer.js.map +1 -1
  114. package/dist/service-owner.d.ts +44 -0
  115. package/dist/service-owner.js +48 -0
  116. package/dist/service-owner.js.map +1 -0
  117. package/dist/settings-api.d.ts +4 -0
  118. package/dist/settings-api.js +21 -3
  119. package/dist/settings-api.js.map +1 -1
  120. package/dist/settings-change.js +85 -2
  121. package/dist/settings-change.js.map +1 -1
  122. package/dist/settings-effect.js +2 -0
  123. package/dist/settings-effect.js.map +1 -1
  124. package/dist/settings-http-confirmation.d.ts +2 -0
  125. package/dist/settings-http-confirmation.js +3 -2
  126. package/dist/settings-http-confirmation.js.map +1 -1
  127. package/dist/settings-request-capability.d.ts +4 -1
  128. package/dist/settings-request-capability.js +11 -3
  129. package/dist/settings-request-capability.js.map +1 -1
  130. package/dist/setup-host.js +1 -1
  131. package/dist/setup-wizard.js +1 -1
  132. package/dist/setup-wizard.js.map +1 -1
  133. package/dist/task-board-summary.d.ts +101 -0
  134. package/dist/task-board-summary.js +177 -0
  135. package/dist/task-board-summary.js.map +1 -0
  136. package/dist/task-board-waker.d.ts +8 -1
  137. package/dist/task-board-waker.js +15 -6
  138. package/dist/task-board-waker.js.map +1 -1
  139. package/dist/tmux-control.js +1 -1
  140. package/dist/tmux-control.js.map +1 -1
  141. package/dist/tmux-exec-helper.d.ts +5 -1
  142. package/dist/tmux-exec-helper.js +13 -21
  143. package/dist/tmux-exec-helper.js.map +1 -1
  144. package/dist/tmux-manager.js +1 -1
  145. package/dist/tmux-read.js +1 -1
  146. package/dist/tmux-read.js.map +1 -1
  147. package/dist/topic-commands.d.ts +3 -0
  148. package/dist/topic-commands.js +29 -18
  149. package/dist/topic-commands.js.map +1 -1
  150. package/dist/transcript-monitor.js +5 -0
  151. package/dist/transcript-monitor.js.map +1 -1
  152. package/dist/transcript-sources.d.ts +2 -0
  153. package/dist/transcript-sources.js +4 -0
  154. package/dist/transcript-sources.js.map +1 -1
  155. package/dist/tunnel/cloudflared-install.js +3 -1
  156. package/dist/tunnel/cloudflared-install.js.map +1 -1
  157. package/dist/tunnel/cloudflared.d.ts +1 -1
  158. package/dist/tunnel/cloudflared.js +51 -10
  159. package/dist/tunnel/cloudflared.js.map +1 -1
  160. package/dist/tunnel/named-diagnostics.d.ts +13 -0
  161. package/dist/tunnel/named-diagnostics.js +52 -0
  162. package/dist/tunnel/named-diagnostics.js.map +1 -0
  163. package/dist/tunnel/types.d.ts +2 -1
  164. package/dist/tunnel/types.js.map +1 -1
  165. package/dist/types.d.ts +9 -0
  166. package/dist/ui/app-effects.js +10 -4
  167. package/dist/ui/chat-strings.js +3 -0
  168. package/dist/ui/chat-thread.js +14 -7
  169. package/dist/ui/effect-trial-offer.js +2 -31
  170. package/dist/ui/effects-waker.js +2 -70
  171. package/dist/ui/fleet-task-card.js +5 -5
  172. package/dist/ui/fleet-task-common.js +41 -7
  173. package/dist/ui/fleet-task-work.js +107 -0
  174. package/dist/ui/fleet-tasks.js +46 -12
  175. package/dist/ui/image-lightbox.js +39 -1
  176. package/dist/ui/image-zoom.js +242 -0
  177. package/dist/ui/panel-chat.js +24 -13
  178. package/dist/ui/panel-details.js +2 -0
  179. package/dist/ui/panel-fleet.js +6 -2
  180. package/dist/ui/panel-needs.js +21 -6
  181. package/dist/ui/panel-settings.js +35 -13
  182. package/dist/ui/preview-panel.js +2 -1
  183. package/dist/ui/search-dialog.js +2 -1
  184. package/dist/ui/settings-appearance.js +33 -8
  185. package/dist/ui/settings-dialogs.js +33 -17
  186. package/dist/ui/settings-discord.js +6 -2
  187. package/dist/ui/settings-guests.js +2 -1
  188. package/dist/ui/settings-model.js +2 -11
  189. package/dist/ui/settings-mods.js +6 -3
  190. package/dist/ui/settings-strings.js +2 -0
  191. package/dist/ui/settings-token.js +3 -1
  192. package/dist/ui/settings-web-authority.js +6 -3
  193. package/dist/ui/settings-wizard.js +13 -7
  194. package/dist/ui/shared/app-i18n.js +2 -2
  195. package/dist/ui/shared/app-needs.js +2 -2
  196. package/dist/ui/shared/app-store.js +2 -2
  197. package/dist/ui/shared/app.css +80 -9
  198. package/dist/ui/shared/app.js +5 -1
  199. package/dist/ui/shared/effect-app-runtime.js +7 -5
  200. package/dist/ui/shared/effect-chat-runtime.js +2 -7
  201. package/dist/ui/shared/effect-cursor.js +3 -2
  202. package/dist/ui/shared/effect-display-metadata.js +39 -0
  203. package/dist/ui/shared/effect-registry.js +4 -2
  204. package/dist/ui/shared/effect-runtime-loader.js +3 -1
  205. package/dist/ui/shared/effect-stock-trust.js +1 -1
  206. package/dist/ui/shared/feature-flags.js +127 -0
  207. package/dist/ui/shared/identity-color.js +6 -3
  208. package/dist/ui/shared/instance-nav.js +3 -2
  209. package/dist/ui/shared/text-undo.js +210 -0
  210. package/dist/ui/shared/ui-dialog.js +4 -3
  211. package/dist/ui/shared/ui-icons.js +2 -0
  212. package/dist/update-dispatch.js +1 -1
  213. package/dist/update-launch.d.ts +1 -1
  214. package/dist/update-launch.js +1 -2
  215. package/dist/update-launch.js.map +1 -1
  216. package/dist/usage/providers.js +1 -1
  217. package/dist/usage/providers.js.map +1 -1
  218. package/dist/view-api.js +1 -1
  219. package/dist/web-api.d.ts +6 -0
  220. package/dist/web-api.js +152 -14
  221. package/dist/web-api.js.map +1 -1
  222. package/dist/web-appearance-tools.d.ts +1 -0
  223. package/dist/web-appearance-tools.js +7 -3
  224. package/dist/web-appearance-tools.js.map +1 -1
  225. package/dist/web-appearance.d.ts +4 -10
  226. package/dist/web-appearance.js +4 -3
  227. package/dist/web-appearance.js.map +1 -1
  228. package/dist/web-chat-history.d.ts +14 -0
  229. package/dist/web-chat-history.js +26 -1
  230. package/dist/web-chat-history.js.map +1 -1
  231. package/dist/web-experimental-http.d.ts +2 -0
  232. package/dist/web-experimental-http.js +12 -0
  233. package/dist/web-experimental-http.js.map +1 -0
  234. package/dist/web-experimental.d.ts +11 -0
  235. package/dist/web-experimental.js +49 -0
  236. package/dist/web-experimental.js.map +1 -0
  237. package/dist/web-guest-access.d.ts +1 -0
  238. package/dist/web-guest-access.js +4 -2
  239. package/dist/web-guest-access.js.map +1 -1
  240. package/dist/web-mod-store.d.ts +2 -0
  241. package/dist/web-mod-store.js +10 -0
  242. package/dist/web-mod-store.js.map +1 -1
  243. package/dist/web-preview.d.ts +2 -0
  244. package/dist/web-preview.js +2 -0
  245. package/dist/web-preview.js.map +1 -1
  246. package/dist/web-settings-patch.d.ts +2 -1
  247. package/dist/web-settings-patch.js +5 -1
  248. package/dist/web-settings-patch.js.map +1 -1
  249. package/dist/web-settings-tools.d.ts +1 -0
  250. package/dist/web-settings-tools.js +14 -10
  251. package/dist/web-settings-tools.js.map +1 -1
  252. package/dist/web-terminal.js +1 -1
  253. package/dist/web-theme-store.d.ts +2 -2
  254. package/dist/web-theme-store.js +6 -3
  255. package/dist/web-theme-store.js.map +1 -1
  256. package/package.json +1 -1
@@ -166,12 +166,26 @@ function toCard(r) {
166
166
  seenEntryId: Number(r.seen_entry_id ?? 0),
167
167
  };
168
168
  }
169
+ /**
170
+ * #2352: a lead is a name, or none ("" and null alike). Whether it is one of the fleet's instances is the fleet's to
171
+ * check (the board keeps names, as for an assignee); a lead no longer in fleet.yaml reads as none.
172
+ */
173
+ function checkLeadInstance(raw) {
174
+ const name = (raw ?? "").trim();
175
+ if (!name)
176
+ return null;
177
+ // eslint-disable-next-line no-control-regex
178
+ if (name.length > 256 || /[\s\u0000-\u001f\u007f]/.test(name))
179
+ throw new TaskBoardError("invalid", "a lead is an instance's name", "lead_not_instance");
180
+ return name;
181
+ }
169
182
  function toProject(r) {
170
183
  return {
171
184
  id: Number(r.id),
172
185
  name: r.name,
173
186
  description: r.description,
174
187
  archived: Number(r.archived) === 1,
188
+ leadInstance: typeof r.lead_instance === "string" && r.lead_instance ? r.lead_instance : null,
175
189
  sort: Number(r.sort),
176
190
  createdAt: r.created_at,
177
191
  };
@@ -269,6 +283,17 @@ export class TaskBoard {
269
283
  CREATE INDEX IF NOT EXISTS idx_task_events_task ON task_events(task_id, id);
270
284
  CREATE TABLE IF NOT EXISTS task_counters (prefix TEXT PRIMARY KEY, next INTEGER NOT NULL);
271
285
  `);
286
+ // #2352: a project's lead instance, added in place (null: its waiting cards' buttons stay in General).
287
+ const projectCols = new Set(this.db.prepare("PRAGMA table_info(task_projects)").all().map((c) => c.name));
288
+ if (!projectCols.has("lead_instance")) {
289
+ try {
290
+ this.db.exec("ALTER TABLE task_projects ADD COLUMN lead_instance TEXT");
291
+ }
292
+ catch (err) {
293
+ if (!/duplicate column/i.test(String(err)))
294
+ throw err;
295
+ }
296
+ }
272
297
  // Every open (the CLI opens this file for each command) only reads: the write lock is taken only when a counter
273
298
  // is missing or a row needs a number. (CREATE … IF NOT EXISTS of what exists takes no lock; an INSERT does.)
274
299
  if (this.db.prepare("SELECT COUNT(*) AS n FROM task_counters WHERE prefix IN ('M', 'S', 'R')").get().n < 3) {
@@ -330,8 +355,9 @@ export class TaskBoard {
330
355
  const name = this.checkProjectName(input.name, null);
331
356
  const description = checkText("the description", input.description ?? "", CARD_LIMITS.projectDescription);
332
357
  const { s } = this.db.prepare("SELECT COALESCE(MAX(sort), 0) + 1 AS s FROM task_projects").get();
333
- const row = this.db.prepare("INSERT INTO task_projects (name, description, sort, created_at) VALUES (?, ?, ?, ?) RETURNING *")
334
- .get(name, description, s, this.nowIso());
358
+ const lead = checkLeadInstance(input.leadInstance ?? null);
359
+ const row = this.db.prepare("INSERT INTO task_projects (name, description, sort, created_at, lead_instance) VALUES (?, ?, ?, ?, ?) RETURNING *")
360
+ .get(name, description, s, this.nowIso(), lead);
335
361
  this.projectsChanged = true;
336
362
  return toProject(row);
337
363
  });
@@ -346,8 +372,10 @@ export class TaskBoard {
346
372
  project.description = checkText("the description", patch.description, CARD_LIMITS.projectDescription);
347
373
  if (patch.archived !== undefined)
348
374
  project.archived = patch.archived;
349
- this.db.prepare("UPDATE task_projects SET name = ?, description = ?, archived = ? WHERE id = ?")
350
- .run(project.name, project.description, project.archived ? 1 : 0, project.id);
375
+ if (patch.leadInstance !== undefined)
376
+ project.leadInstance = checkLeadInstance(patch.leadInstance);
377
+ this.db.prepare("UPDATE task_projects SET name = ?, description = ?, archived = ?, lead_instance = ? WHERE id = ?")
378
+ .run(project.name, project.description, project.archived ? 1 : 0, project.leadInstance, project.id);
351
379
  this.projectsChanged = true;
352
380
  return project;
353
381
  });
@@ -733,6 +761,11 @@ export class TaskBoard {
733
761
  * (its own open cards, and the unassigned sub tasks of main tasks it coordinates, dependencies done) and what waits
734
762
  * on dependencies; comments it has not seen and work sent back; the main tasks it coordinates and how far along.
735
763
  */
764
+ /** Who holds a live card (open, in progress, blocked, waiting for the person): the instances, and any other name. */
765
+ liveAssignees() {
766
+ return this.db.prepare("SELECT DISTINCT assignee FROM tasks WHERE assignee IS NOT NULL AND status IN ('open', 'claimed', 'blocked', 'review') ORDER BY assignee").all()
767
+ .map((r) => r.assignee);
768
+ }
736
769
  myWork(name) {
737
770
  const mine = this.db.prepare(`SELECT * FROM tasks WHERE assignee = ? AND status IN ('open', 'claimed', 'blocked', 'review') ORDER BY ${STATUS_ORDER}, ${PRIORITY_ORDER}, sort`)
738
771
  .all(name).map(toCard);
@@ -766,6 +799,113 @@ export class TaskBoard {
766
799
  mains: coordinating.map((card) => ({ card, progress: this.progress(card) })),
767
800
  };
768
801
  }
802
+ /**
803
+ * What waits for the person (step 3: Needs you): cards in review that need their confirmation, and blocked cards —
804
+ * oldest first, each with when it started waiting (its last move to that status).
805
+ */
806
+ waitingForPerson() {
807
+ const rows = this.db.prepare("SELECT * FROM tasks WHERE (status = 'review' AND needs_review = 1) OR status = 'blocked' ORDER BY updated_at").all().map(toCard);
808
+ const moved = this.db.prepare("SELECT at FROM task_events WHERE task_id = ? AND type = 'moved' AND json_extract(data, '$.to') = ? ORDER BY id DESC LIMIT 1");
809
+ return rows.map((card) => {
810
+ const at = moved.get(card.uuid, card.status)?.at;
811
+ return { card, since: at ?? card.submittedAt ?? card.updatedAt };
812
+ }).sort((a, b) => a.since.localeCompare(b.since));
813
+ }
814
+ /** Cards by their numbers, in one read (the ones that exist; #2341 review: the cards a summary line named). */
815
+ cardsByCode(codes) {
816
+ const out = [];
817
+ for (let i = 0; i < codes.length; i += 500) {
818
+ const chunk = codes.slice(i, i + 500);
819
+ out.push(...this.db.prepare(`SELECT * FROM tasks WHERE code IN (${chunk.map(() => "?").join(", ")})`).all(...chunk).map(toCard));
820
+ }
821
+ return out;
822
+ }
823
+ /**
824
+ * myWork() for many names in one read (the dashboard's Instances list, GET /ui/tasks/work): the live cards, every
825
+ * card's light row (sub tasks, dependencies) and the live cards' unseen entries, each read once — three statements
826
+ * however many names and cards (#2316 review: N myWork() calls cost tens of ms on the fleet's event loop). Same
827
+ * answers as myWork() name by name; tests/task-board-work-2316.test.ts pins both.
828
+ */
829
+ workOf(names) {
830
+ const out = new Map();
831
+ if (!names.length)
832
+ return out;
833
+ const live = this.db.prepare(`SELECT * FROM tasks WHERE status IN ('open', 'claimed', 'blocked', 'review') ORDER BY ${STATUS_ORDER}, ${PRIORITY_ORDER}, sort`).all().map(toCard);
834
+ const all = new Map();
835
+ const subs = new Map();
836
+ for (const r of this.db.prepare("SELECT id, code, status, depends_on, parent_id FROM tasks").all()) {
837
+ const light = { code: r.code ?? r.id.slice(0, 8), status: r.status, deps: parseJsonArray(r.depends_on, []), parent: r.parent_id ?? null };
838
+ all.set(r.id, light);
839
+ if (light.parent && light.status !== "cancelled") {
840
+ const p = subs.get(light.parent) ?? { total: 0, open: 0, claimed: 0, blocked: 0, review: 0, done: 0 };
841
+ p.total++;
842
+ p[light.status]++;
843
+ subs.set(light.parent, p);
844
+ }
845
+ }
846
+ // Unseen entries of the live cards: comments and rejections by anyone but the card's assignee, after what it saw.
847
+ const fresh = new Map();
848
+ for (const r of this.db.prepare(`SELECT e.* FROM task_entries e JOIN tasks t ON e.task_id = t.id
849
+ WHERE t.status IN ('open', 'claimed', 'blocked', 'review') AND e.id > t.seen_entry_id AND e.kind IN ('comment', 'reject') AND e.author IS NOT t.assignee ORDER BY e.id`).all()) {
850
+ const list = fresh.get(r.task_id) ?? [];
851
+ list.push({ id: Number(r.id), cardId: all.get(r.task_id)?.code ?? r.task_id.slice(0, 8), kind: r.kind, author: r.author,
852
+ authorKind: r.author_kind, text: r.text, at: r.at });
853
+ fresh.set(r.task_id, list);
854
+ }
855
+ /** unmetDeps(), on the light rows: own dependencies that exist and are not done, then the main task's not yet named. */
856
+ const unmet = (deps, parent) => {
857
+ const own = deps.filter((d) => { const dep = all.get(d); return !!dep && dep.status !== "done"; });
858
+ const main = parent ? all.get(parent) : undefined;
859
+ return main ? [...own, ...unmet(main.deps, main.parent).filter((d) => !own.includes(d))] : own;
860
+ };
861
+ const rank = { urgent: 0, high: 1, normal: 2 };
862
+ const byPriority = (a, b) => (rank[a.priority] ?? 3) - (rank[b.priority] ?? 3) || a.sort - b.sort;
863
+ const openUnassigned = live.filter((c) => c.assignee === null && c.status === "open");
864
+ for (const name of new Set(names)) {
865
+ const mine = live.filter((c) => c.assignee === name);
866
+ const coordinating = mine.filter((c) => c.kind === "main" && (subs.get(c.uuid)?.total ?? 0) > 0);
867
+ const work = mine.filter((c) => !coordinating.includes(c));
868
+ const theirs = new Set(coordinating.map((c) => c.uuid));
869
+ const unassigned = coordinating.length ? openUnassigned.filter((c) => c.parent !== null && theirs.has(c.parent)).sort(byPriority) : [];
870
+ const candidates = [...work.filter((c) => c.status === "open"), ...unassigned].map((card) => ({ card, waitsFor: unmet(card.deps, card.parent).map((d) => all.get(d).code) }));
871
+ const newComments = [];
872
+ const sentBack = [];
873
+ for (const card of mine) {
874
+ const entries = fresh.get(card.uuid) ?? [];
875
+ const reject = [...entries].reverse().find((e) => e.kind === "reject");
876
+ if (reject)
877
+ sentBack.push({ card, entry: reject });
878
+ const comments = entries.filter((e) => e.kind === "comment");
879
+ if (comments.length)
880
+ newComments.push({ card, entries: comments });
881
+ }
882
+ out.set(name, {
883
+ doing: work.filter((c) => c.status === "claimed"),
884
+ blocked: work.filter((c) => c.status === "blocked"),
885
+ review: work.filter((c) => c.status === "review"),
886
+ ready: candidates.filter((x) => !x.waitsFor.length).map((x) => x.card),
887
+ waiting: candidates.filter((x) => x.waitsFor.length),
888
+ newComments,
889
+ sentBack,
890
+ mains: coordinating.map((card) => ({ card, progress: { ...subs.get(card.uuid) } })),
891
+ });
892
+ }
893
+ return out;
894
+ }
895
+ /**
896
+ * Step 3, the daily summary (task-board-summary.ts): what waits for the person now, what got done and what was
897
+ * opened in [since, until) (not cancelled), and every live main task's project and assignee.
898
+ */
899
+ summaryRead(since, until) {
900
+ const between = (sql) => this.db.prepare(sql).all(since, until).map(toCard);
901
+ return {
902
+ waiting: this.waitingForPerson().map((w) => w.card),
903
+ done: between("SELECT * FROM tasks WHERE status = 'done' AND done_at >= ? AND done_at < ? ORDER BY sort"),
904
+ created: between("SELECT * FROM tasks WHERE status != 'cancelled' AND created_at >= ? AND created_at < ? ORDER BY sort"),
905
+ mains: this.db.prepare("SELECT project_id, assignee FROM tasks WHERE kind = 'main' AND status != 'cancelled'").all()
906
+ .map((r) => ({ projectId: r.project_id ?? null, assignee: r.assignee ?? null })),
907
+ };
908
+ }
769
909
  // ── Writing: the board ──────────────────────────────────
770
910
  createCards(actor, input) {
771
911
  if (input.cards.length === 0)
@@ -1683,6 +1823,9 @@ export class TaskBoard {
1683
1823
  add(card, card.assignee, "cancelled", e.actor, why);
1684
1824
  continue;
1685
1825
  }
1826
+ // Its dispatcher tells the person it waits for them (not the board).
1827
+ if (d.to === "review" && !isPerson(e.actor) && requiresReview(card))
1828
+ add(card, dispatcherOf(), "to_confirm", e.actor);
1686
1829
  if (d.auto) {
1687
1830
  if (d.to === "review" && card.kind === "main" && wrapUpGap(card))
1688
1831
  add(card, card.assignee, "wrap_up", e.actor);
@@ -1737,6 +1880,9 @@ export class TaskBoard {
1737
1880
  return false;
1738
1881
  if (r === "decide")
1739
1882
  return handedOut && card.status === "blocked";
1883
+ // #2341 review: and still waiting for the person — its confirmation not switched off during the gather.
1884
+ if (r === "to_confirm")
1885
+ return handedOut && card.status === "review" && requiresReview(card);
1740
1886
  if (!own)
1741
1887
  return false;
1742
1888
  if (r === "wrap_up")