@chorus-aidlc/chorus 0.16.1 → 0.16.2

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 (386) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +373 -372
  3. package/.next/standalone/.next/app-path-routes-manifest.json +41 -41
  4. package/.next/standalone/.next/build-manifest.json +5 -5
  5. package/.next/standalone/.next/prerender-manifest.json +42 -42
  6. package/.next/standalone/.next/server/app/(dashboard)/project-groups/[uuid]/page.js +1 -1
  7. package/.next/standalone/.next/server/app/(dashboard)/project-groups/[uuid]/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/(dashboard)/project-groups/[uuid]/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/activity/page.js +1 -1
  10. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/activity/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/activity/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/[ideaUuid]/page.js +1 -1
  13. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/[ideaUuid]/page.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/[ideaUuid]/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page.js +1 -1
  16. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/dashboard/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page.js +2 -2
  19. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page.js +1 -1
  22. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/documents/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/graph/page.js +1 -1
  25. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/graph/page.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/graph/page_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page.js +2 -2
  28. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page.js +1 -1
  31. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/new/page_client-reference-manifest.js +1 -1
  33. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page.js +2 -2
  34. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/proposals/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/[taskUuid]/page.js +1 -1
  37. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/[taskUuid]/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/[taskUuid]/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/page.js +1 -1
  40. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/(dashboard)/projects/[uuid]/tasks/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/(dashboard)/projects/page.js +1 -1
  43. package/.next/standalone/.next/server/app/(dashboard)/projects/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/(dashboard)/projects/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/(dashboard)/settings/page.js +2 -2
  46. package/.next/standalone/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  50. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  51. package/.next/standalone/.next/server/app/admin/companies/[uuid]/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/admin/companies/new/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/admin/companies/new.html +2 -2
  54. package/.next/standalone/.next/server/app/admin/companies/new.rsc +2 -2
  55. package/.next/standalone/.next/server/app/admin/companies/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/admin/companies.html +2 -2
  57. package/.next/standalone/.next/server/app/admin/companies.rsc +2 -2
  58. package/.next/standalone/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/admin.html +2 -2
  60. package/.next/standalone/.next/server/app/admin.rsc +2 -2
  61. package/.next/standalone/.next/server/app/api/admin/companies/[uuid]/route_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/api/admin/companies/route_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/api/admin/login/route_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/api/admin/session/route_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/api/agent-connections/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/agent-connections/route_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/api/agents/[uuid]/route_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/api/agents/[uuid]/sessions/route.js +1 -1
  69. package/.next/standalone/.next/server/app/api/agents/[uuid]/sessions/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/agents/[uuid]/sessions/route_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/api/agents/route_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/api/api-keys/[uuid]/route_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/api/api-keys/route_client-reference-manifest.js +1 -1
  74. package/.next/standalone/.next/server/app/api/auth/callback/route_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/app/api/auth/check-default/route_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/api/auth/company-oidc/route_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/api/auth/default-login/route_client-reference-manifest.js +1 -1
  78. package/.next/standalone/.next/server/app/api/auth/identify/route_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -1
  81. package/.next/standalone/.next/server/app/api/auth/sync-token/route_client-reference-manifest.js +1 -1
  82. package/.next/standalone/.next/server/app/api/comments/route.js +1 -1
  83. package/.next/standalone/.next/server/app/api/comments/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/comments/route_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/app/api/daemon/connection-heartbeat/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/api/daemon/connection-heartbeat/route_client-reference-manifest.js +1 -1
  87. package/.next/standalone/.next/server/app/api/daemon/control/route.js +1 -1
  88. package/.next/standalone/.next/server/app/api/daemon/control/route_client-reference-manifest.js +1 -1
  89. package/.next/standalone/.next/server/app/api/daemon/directory-request/report/route.js.nft.json +1 -1
  90. package/.next/standalone/.next/server/app/api/daemon/directory-request/report/route_client-reference-manifest.js +1 -1
  91. package/.next/standalone/.next/server/app/api/daemon/execution-state/route.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/api/daemon/execution-state/route_client-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/app/api/daemon/executions/route.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/api/daemon/executions/route_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/api/daemon/pending-turns/route.js +1 -1
  96. package/.next/standalone/.next/server/app/api/daemon/pending-turns/route.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/api/daemon/pending-turns/route_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/api/daemon/report-interrupt/route.js +1 -1
  99. package/.next/standalone/.next/server/app/api/daemon/report-interrupt/route.js.nft.json +1 -1
  100. package/.next/standalone/.next/server/app/api/daemon/report-interrupt/route_client-reference-manifest.js +1 -1
  101. package/.next/standalone/.next/server/app/api/daemon/resume/route.js +1 -1
  102. package/.next/standalone/.next/server/app/api/daemon/resume/route.js.nft.json +1 -1
  103. package/.next/standalone/.next/server/app/api/daemon/resume/route_client-reference-manifest.js +1 -1
  104. package/.next/standalone/.next/server/app/api/daemon/transcript/route.js +1 -1
  105. package/.next/standalone/.next/server/app/api/daemon/transcript/route.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/api/daemon/transcript/route_client-reference-manifest.js +1 -1
  107. package/.next/standalone/.next/server/app/api/daemon/turn-advance/route.js +1 -1
  108. package/.next/standalone/.next/server/app/api/daemon/turn-advance/route.js.nft.json +1 -1
  109. package/.next/standalone/.next/server/app/api/daemon/turn-advance/route_client-reference-manifest.js +1 -1
  110. package/.next/standalone/.next/server/app/api/daemon-directory-requests/[uuid]/route.js.nft.json +1 -1
  111. package/.next/standalone/.next/server/app/api/daemon-directory-requests/[uuid]/route_client-reference-manifest.js +1 -1
  112. package/.next/standalone/.next/server/app/api/daemon-directory-requests/route.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/api/daemon-directory-requests/route_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/instruction/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/instruction/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/instruction/route_client-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/repoint/route.js +1 -1
  118. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/repoint/route.js.nft.json +1 -1
  119. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/repoint/route_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/route.js +1 -1
  121. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/api/daemon-sessions/[sessionUuid]/route_client-reference-manifest.js +1 -1
  123. package/.next/standalone/.next/server/app/api/daemon-sessions/ad-hoc/route.js +1 -1
  124. package/.next/standalone/.next/server/app/api/daemon-sessions/ad-hoc/route.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/api/daemon-sessions/ad-hoc/route_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/api/daemon-sessions/route.js +1 -1
  127. package/.next/standalone/.next/server/app/api/daemon-sessions/route.js.nft.json +1 -1
  128. package/.next/standalone/.next/server/app/api/daemon-sessions/route_client-reference-manifest.js +1 -1
  129. package/.next/standalone/.next/server/app/api/documents/[uuid]/route_client-reference-manifest.js +1 -1
  130. package/.next/standalone/.next/server/app/api/entities/[type]/[uuid]/root-idea/route.js +1 -1
  131. package/.next/standalone/.next/server/app/api/entities/[type]/[uuid]/root-idea/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/entities/[type]/[uuid]/root-idea/route_client-reference-manifest.js +1 -1
  133. package/.next/standalone/.next/server/app/api/events/notifications/route.js +1 -1
  134. package/.next/standalone/.next/server/app/api/events/notifications/route.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/api/events/notifications/route_client-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/app/api/events/route.js +1 -1
  137. package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
  139. package/.next/standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  140. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route.js +1 -1
  141. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/api/ideas/[uuid]/claim/route_client-reference-manifest.js +1 -1
  143. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route.js +1 -1
  144. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route.js.nft.json +1 -1
  145. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/preview/route_client-reference-manifest.js +1 -1
  146. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route.js +1 -1
  147. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/api/ideas/[uuid]/move/route_client-reference-manifest.js +1 -1
  149. package/.next/standalone/.next/server/app/api/ideas/[uuid]/parent/route.js +1 -1
  150. package/.next/standalone/.next/server/app/api/ideas/[uuid]/parent/route.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/api/ideas/[uuid]/parent/route_client-reference-manifest.js +1 -1
  152. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route.js +1 -1
  153. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route.js.nft.json +1 -1
  154. package/.next/standalone/.next/server/app/api/ideas/[uuid]/release/route_client-reference-manifest.js +1 -1
  155. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route.js +1 -1
  156. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route.js.nft.json +1 -1
  157. package/.next/standalone/.next/server/app/api/ideas/[uuid]/route_client-reference-manifest.js +1 -1
  158. package/.next/standalone/.next/server/app/api/ideas/[uuid]/wake-preview/route.js.nft.json +1 -1
  159. package/.next/standalone/.next/server/app/api/ideas/[uuid]/wake-preview/route_client-reference-manifest.js +1 -1
  160. package/.next/standalone/.next/server/app/api/ideas/conversational/route.js +1 -1
  161. package/.next/standalone/.next/server/app/api/ideas/conversational/route.js.nft.json +1 -1
  162. package/.next/standalone/.next/server/app/api/ideas/conversational/route_client-reference-manifest.js +1 -1
  163. package/.next/standalone/.next/server/app/api/mcp/route.js +2 -2
  164. package/.next/standalone/.next/server/app/api/mcp/route.js.nft.json +1 -1
  165. package/.next/standalone/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  166. package/.next/standalone/.next/server/app/api/me/assignments/route.js +1 -1
  167. package/.next/standalone/.next/server/app/api/me/assignments/route.js.nft.json +1 -1
  168. package/.next/standalone/.next/server/app/api/me/assignments/route_client-reference-manifest.js +1 -1
  169. package/.next/standalone/.next/server/app/api/mentionables/route.js +1 -1
  170. package/.next/standalone/.next/server/app/api/mentionables/route.js.nft.json +1 -1
  171. package/.next/standalone/.next/server/app/api/mentionables/route_client-reference-manifest.js +1 -1
  172. package/.next/standalone/.next/server/app/api/notifications/[uuid]/archive/route.js +1 -1
  173. package/.next/standalone/.next/server/app/api/notifications/[uuid]/archive/route.js.nft.json +1 -1
  174. package/.next/standalone/.next/server/app/api/notifications/[uuid]/archive/route_client-reference-manifest.js +1 -1
  175. package/.next/standalone/.next/server/app/api/notifications/[uuid]/read/route.js +1 -1
  176. package/.next/standalone/.next/server/app/api/notifications/[uuid]/read/route.js.nft.json +1 -1
  177. package/.next/standalone/.next/server/app/api/notifications/[uuid]/read/route_client-reference-manifest.js +1 -1
  178. package/.next/standalone/.next/server/app/api/notifications/preferences/route.js +1 -1
  179. package/.next/standalone/.next/server/app/api/notifications/preferences/route.js.nft.json +1 -1
  180. package/.next/standalone/.next/server/app/api/notifications/preferences/route_client-reference-manifest.js +1 -1
  181. package/.next/standalone/.next/server/app/api/notifications/read-all/route.js +1 -1
  182. package/.next/standalone/.next/server/app/api/notifications/read-all/route.js.nft.json +1 -1
  183. package/.next/standalone/.next/server/app/api/notifications/read-all/route_client-reference-manifest.js +1 -1
  184. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  185. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  186. package/.next/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  187. package/.next/standalone/.next/server/app/api/notifications/unread-count/route.js +1 -1
  188. package/.next/standalone/.next/server/app/api/notifications/unread-count/route.js.nft.json +1 -1
  189. package/.next/standalone/.next/server/app/api/notifications/unread-count/route_client-reference-manifest.js +1 -1
  190. package/.next/standalone/.next/server/app/api/project-groups/[uuid]/dashboard/route_client-reference-manifest.js +1 -1
  191. package/.next/standalone/.next/server/app/api/project-groups/[uuid]/route_client-reference-manifest.js +1 -1
  192. package/.next/standalone/.next/server/app/api/project-groups/route_client-reference-manifest.js +1 -1
  193. package/.next/standalone/.next/server/app/api/project-visits/pin/route_client-reference-manifest.js +1 -1
  194. package/.next/standalone/.next/server/app/api/project-visits/route_client-reference-manifest.js +1 -1
  195. package/.next/standalone/.next/server/app/api/project-visits/visit/route_client-reference-manifest.js +1 -1
  196. package/.next/standalone/.next/server/app/api/projects/[uuid]/activity/route_client-reference-manifest.js +1 -1
  197. package/.next/standalone/.next/server/app/api/projects/[uuid]/agent-cwds/route.js.nft.json +1 -1
  198. package/.next/standalone/.next/server/app/api/projects/[uuid]/agent-cwds/route_client-reference-manifest.js +1 -1
  199. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route.js +1 -1
  200. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route.js.nft.json +1 -1
  201. package/.next/standalone/.next/server/app/api/projects/[uuid]/available/route_client-reference-manifest.js +1 -1
  202. package/.next/standalone/.next/server/app/api/projects/[uuid]/documents/route_client-reference-manifest.js +1 -1
  203. package/.next/standalone/.next/server/app/api/projects/[uuid]/group/route_client-reference-manifest.js +1 -1
  204. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route.js +1 -1
  205. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route.js.nft.json +1 -1
  206. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/route_client-reference-manifest.js +1 -1
  207. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route.js +1 -1
  208. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route.js.nft.json +1 -1
  209. package/.next/standalone/.next/server/app/api/projects/[uuid]/ideas/tracker/route_client-reference-manifest.js +1 -1
  210. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/[proposalUuid]/validate/route_client-reference-manifest.js +1 -1
  211. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/route_client-reference-manifest.js +1 -1
  212. package/.next/standalone/.next/server/app/api/projects/[uuid]/proposals/summary/route_client-reference-manifest.js +1 -1
  213. package/.next/standalone/.next/server/app/api/projects/[uuid]/resource-graph/route.js +1 -1
  214. package/.next/standalone/.next/server/app/api/projects/[uuid]/resource-graph/route.js.nft.json +1 -1
  215. package/.next/standalone/.next/server/app/api/projects/[uuid]/resource-graph/route_client-reference-manifest.js +1 -1
  216. package/.next/standalone/.next/server/app/api/projects/[uuid]/route_client-reference-manifest.js +1 -1
  217. package/.next/standalone/.next/server/app/api/projects/[uuid]/stats/route_client-reference-manifest.js +1 -1
  218. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/dependencies/route.js +1 -1
  219. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/dependencies/route.js.nft.json +1 -1
  220. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/dependencies/route_client-reference-manifest.js +1 -1
  221. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/route.js +1 -1
  222. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/route.js.nft.json +1 -1
  223. package/.next/standalone/.next/server/app/api/projects/[uuid]/tasks/route_client-reference-manifest.js +1 -1
  224. package/.next/standalone/.next/server/app/api/projects/agent-cwd-options/route.js.nft.json +1 -1
  225. package/.next/standalone/.next/server/app/api/projects/agent-cwd-options/route_client-reference-manifest.js +1 -1
  226. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  227. package/.next/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  228. package/.next/standalone/.next/server/app/api/proposals/[uuid]/approve/route_client-reference-manifest.js +1 -1
  229. package/.next/standalone/.next/server/app/api/proposals/[uuid]/close/route_client-reference-manifest.js +1 -1
  230. package/.next/standalone/.next/server/app/api/proposals/[uuid]/reject/route_client-reference-manifest.js +1 -1
  231. package/.next/standalone/.next/server/app/api/proposals/[uuid]/revoke/route_client-reference-manifest.js +1 -1
  232. package/.next/standalone/.next/server/app/api/proposals/[uuid]/route_client-reference-manifest.js +1 -1
  233. package/.next/standalone/.next/server/app/api/references/[uuid]/route_client-reference-manifest.js +1 -1
  234. package/.next/standalone/.next/server/app/api/references/route_client-reference-manifest.js +1 -1
  235. package/.next/standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  236. package/.next/standalone/.next/server/app/api/session/route_client-reference-manifest.js +1 -1
  237. package/.next/standalone/.next/server/app/api/sessions/[uuid]/route.js +1 -1
  238. package/.next/standalone/.next/server/app/api/sessions/[uuid]/route.js.nft.json +1 -1
  239. package/.next/standalone/.next/server/app/api/sessions/[uuid]/route_client-reference-manifest.js +1 -1
  240. package/.next/standalone/.next/server/app/api/tasks/[uuid]/claim/route.js +1 -1
  241. package/.next/standalone/.next/server/app/api/tasks/[uuid]/claim/route.js.nft.json +1 -1
  242. package/.next/standalone/.next/server/app/api/tasks/[uuid]/claim/route_client-reference-manifest.js +1 -1
  243. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/[dependsOnUuid]/route.js +1 -1
  244. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/[dependsOnUuid]/route.js.nft.json +1 -1
  245. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/[dependsOnUuid]/route_client-reference-manifest.js +1 -1
  246. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/route.js +1 -1
  247. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/route.js.nft.json +1 -1
  248. package/.next/standalone/.next/server/app/api/tasks/[uuid]/dependencies/route_client-reference-manifest.js +1 -1
  249. package/.next/standalone/.next/server/app/api/tasks/[uuid]/release/route.js +1 -1
  250. package/.next/standalone/.next/server/app/api/tasks/[uuid]/release/route.js.nft.json +1 -1
  251. package/.next/standalone/.next/server/app/api/tasks/[uuid]/release/route_client-reference-manifest.js +1 -1
  252. package/.next/standalone/.next/server/app/api/tasks/[uuid]/route.js +1 -1
  253. package/.next/standalone/.next/server/app/api/tasks/[uuid]/route.js.nft.json +1 -1
  254. package/.next/standalone/.next/server/app/api/tasks/[uuid]/route_client-reference-manifest.js +1 -1
  255. package/.next/standalone/.next/server/app/api/tasks/[uuid]/sessions/route.js +1 -1
  256. package/.next/standalone/.next/server/app/api/tasks/[uuid]/sessions/route.js.nft.json +1 -1
  257. package/.next/standalone/.next/server/app/api/tasks/[uuid]/sessions/route_client-reference-manifest.js +1 -1
  258. package/.next/standalone/.next/server/app/index.html +2 -2
  259. package/.next/standalone/.next/server/app/index.rsc +2 -2
  260. package/.next/standalone/.next/server/app/login/admin/page_client-reference-manifest.js +1 -1
  261. package/.next/standalone/.next/server/app/login/admin.html +2 -2
  262. package/.next/standalone/.next/server/app/login/admin.rsc +2 -2
  263. package/.next/standalone/.next/server/app/login/callback/page_client-reference-manifest.js +1 -1
  264. package/.next/standalone/.next/server/app/login/callback.html +2 -2
  265. package/.next/standalone/.next/server/app/login/callback.rsc +2 -2
  266. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  267. package/.next/standalone/.next/server/app/login/pick-workspace/page_client-reference-manifest.js +1 -1
  268. package/.next/standalone/.next/server/app/login/pick-workspace.html +2 -2
  269. package/.next/standalone/.next/server/app/login/pick-workspace.rsc +2 -2
  270. package/.next/standalone/.next/server/app/login.html +2 -2
  271. package/.next/standalone/.next/server/app/login.rsc +2 -2
  272. package/.next/standalone/.next/server/app/onboarding/page.js +1 -1
  273. package/.next/standalone/.next/server/app/onboarding/page.js.nft.json +1 -1
  274. package/.next/standalone/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  275. package/.next/standalone/.next/server/app/onboarding.html +2 -2
  276. package/.next/standalone/.next/server/app/onboarding.rsc +3 -3
  277. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  278. package/.next/standalone/.next/server/app/projects.html +2 -2
  279. package/.next/standalone/.next/server/app/projects.rsc +3 -3
  280. package/.next/standalone/.next/server/app/settings.html +2 -2
  281. package/.next/standalone/.next/server/app/settings.rsc +4 -4
  282. package/.next/standalone/.next/server/app-paths-manifest.json +41 -41
  283. package/.next/standalone/.next/server/chunks/2572.js +1 -1
  284. package/.next/standalone/.next/server/chunks/2618.js +1 -1
  285. package/.next/standalone/.next/server/chunks/2961.js +1 -1
  286. package/.next/standalone/.next/server/chunks/3594.js +1 -1
  287. package/.next/standalone/.next/server/chunks/{2547.js → 4011.js} +3 -3
  288. package/.next/standalone/.next/server/chunks/4827.js +1 -1
  289. package/.next/standalone/.next/server/chunks/5205.js +1 -0
  290. package/.next/standalone/.next/server/chunks/5245.js +1 -1
  291. package/.next/standalone/.next/server/chunks/6113.js +1 -1
  292. package/.next/standalone/.next/server/chunks/6506.js +3 -3
  293. package/.next/standalone/.next/server/chunks/6515.js +2 -2
  294. package/.next/standalone/.next/server/chunks/6995.js +1 -1
  295. package/.next/standalone/.next/server/chunks/7364.js +1 -1
  296. package/.next/standalone/.next/server/chunks/7368.js +1 -1
  297. package/.next/standalone/.next/server/chunks/7530.js +1 -1
  298. package/.next/standalone/.next/server/chunks/8454.js +1 -1
  299. package/.next/standalone/.next/server/chunks/920.js +1 -1
  300. package/.next/standalone/.next/server/chunks/9213.js +1 -1
  301. package/.next/standalone/.next/server/chunks/9378.js +1 -1
  302. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  303. package/.next/standalone/.next/server/middleware-manifest.json +1 -1
  304. package/.next/standalone/.next/server/pages/404.html +2 -2
  305. package/.next/standalone/.next/server/pages/500.html +1 -1
  306. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  307. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  308. package/.next/standalone/.next/static/chunks/{11595.1bcd2b65089007ae.js → 11595.b6f8ad33572b32fe.js} +1 -1
  309. package/.next/standalone/.next/static/chunks/19887-9f459ee9e8d0855f.js +1 -0
  310. package/.next/standalone/.next/static/chunks/{29108-e171d8b8c680cbb5.js → 29108-b9b232255811ffa8.js} +1 -1
  311. package/.next/standalone/.next/static/chunks/{31308-bb4a97b3f39c22dd.js → 31308-fda6febfb2882a28.js} +1 -1
  312. package/.next/standalone/.next/static/chunks/{33456.c98d3e21528b61d8.js → 33456.e8550ad410031e29.js} +1 -1
  313. package/.next/standalone/.next/static/chunks/35432-ee83da56c49ef6f8.js +1 -0
  314. package/.next/standalone/.next/static/chunks/{4860.9122a86d33dc6b62.js → 4860.b405fc235b36271b.js} +1 -1
  315. package/.next/standalone/.next/static/chunks/66980-652990fcf3212c94.js +10 -0
  316. package/.next/standalone/.next/static/chunks/{94643-90a9ec7b9a9abaf0.js → 94643-6d31df78aa3074b5.js} +1 -1
  317. package/.next/standalone/.next/static/chunks/96170-75da13decdb6f73f.js +1 -0
  318. package/.next/standalone/.next/static/chunks/{99373.585230dcb315a068.js → 99373.9dce8f30802824fc.js} +1 -1
  319. package/.next/standalone/.next/static/chunks/app/(dashboard)/{layout-3d3b3b9e16c03cda.js → layout-cea0bb3e3a295bea.js} +1 -1
  320. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/dashboard/{page-69d746c98079024d.js → page-007278623926490f.js} +1 -1
  321. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/documents/[documentUuid]/{page-2af705f6c3789131.js → page-b6ecaf52f6b81ec7.js} +1 -1
  322. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/graph/{page-8119df915138a9c3.js → page-f5a351c544ed23d7.js} +1 -1
  323. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page-3cd46bec9383b191.js +1 -0
  324. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/tasks/[taskUuid]/{page-0789c6589363154c.js → page-7f87113e5d8bcf6e.js} +1 -1
  325. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/tasks/{page-a612f6fc5be0239d.js → page-30ab1573c4bd466f.js} +1 -1
  326. package/.next/standalone/.next/static/chunks/app/(dashboard)/settings/{page-5fb9fd3db155d5ca.js → page-b21d4184d9ca0b25.js} +1 -1
  327. package/.next/standalone/.next/static/chunks/app/onboarding/page-3b6c54f887108635.js +1 -0
  328. package/.next/standalone/.next/static/chunks/{webpack-dec1f54ebb9bf963.js → webpack-b54e0c78eded76be.js} +1 -1
  329. package/.next/standalone/.next/static/css/893d99a21153c9b0.css +1 -0
  330. package/.next/standalone/package.json +1 -1
  331. package/.next/standalone/public/chorus-plugin/.claude-plugin/plugin.json +1 -1
  332. package/.next/standalone/public/chorus-plugin/agents/code-reviewer.md +1 -1
  333. package/.next/standalone/public/chorus-plugin/agents/proposal-reviewer.md +1 -0
  334. package/.next/standalone/public/chorus-plugin/agents/task-reviewer.md +2 -0
  335. package/.next/standalone/public/chorus-plugin/skills/brainstorm/SKILL.md +1 -1
  336. package/.next/standalone/public/chorus-plugin/skills/chorus/SKILL.md +3 -3
  337. package/.next/standalone/public/chorus-plugin/skills/develop/SKILL.md +1 -1
  338. package/.next/standalone/public/chorus-plugin/skills/docs/SKILL.md +1 -1
  339. package/.next/standalone/public/chorus-plugin/skills/idea/SKILL.md +1 -1
  340. package/.next/standalone/public/chorus-plugin/skills/openspec-aware/SKILL.md +1 -1
  341. package/.next/standalone/public/chorus-plugin/skills/proposal/SKILL.md +1 -1
  342. package/.next/standalone/public/chorus-plugin/skills/quick-dev/SKILL.md +1 -1
  343. package/.next/standalone/public/chorus-plugin/skills/review/SKILL.md +1 -1
  344. package/.next/standalone/public/chorus-plugin/skills/yolo/SKILL.md +1 -1
  345. package/.next/standalone/public/kiro-plugin/.kiro/agents/chorus-code-reviewer.json +1 -1
  346. package/.next/standalone/public/kiro-plugin/.kiro/agents/chorus-proposal-reviewer.json +1 -1
  347. package/.next/standalone/public/kiro-plugin/.kiro/agents/chorus-task-reviewer.json +1 -1
  348. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-brainstorm/SKILL.md +1 -1
  349. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-develop/SKILL.md +1 -1
  350. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-docs/SKILL.md +1 -1
  351. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-idea/SKILL.md +1 -1
  352. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-openspec-aware/SKILL.md +1 -1
  353. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-proposal/SKILL.md +1 -1
  354. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-quick-dev/SKILL.md +1 -1
  355. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-review/SKILL.md +1 -1
  356. package/.next/standalone/public/kiro-plugin/.kiro/skills/chorus-yolo/SKILL.md +1 -1
  357. package/.next/standalone/public/kiro-plugin/.kiro/steering/chorus.md +2 -2
  358. package/.next/standalone/public/kiro-plugin/bin/chorus-api.sh +1 -1
  359. package/.next/standalone/public/skill/chorus/SKILL.md +2 -2
  360. package/.next/standalone/public/skill/code-reviewer-chorus/SKILL.md +2 -2
  361. package/.next/standalone/public/skill/proposal-reviewer-chorus/SKILL.md +2 -1
  362. package/.next/standalone/public/skill/task-reviewer-chorus/SKILL.md +2 -1
  363. package/README.ja.md +26 -346
  364. package/README.ko.md +28 -347
  365. package/README.md +33 -353
  366. package/README.zh.md +31 -328
  367. package/cli/control-handler.mjs +13 -3
  368. package/cli/daemon-rest-client.mjs +11 -3
  369. package/cli/daemon.mjs +52 -10
  370. package/cli/event-router.mjs +17 -5
  371. package/cli/prompts.mjs +116 -1
  372. package/cli/turn-reporter.mjs +5 -1
  373. package/cli/wake-queue.mjs +57 -35
  374. package/cli/waker.mjs +125 -36
  375. package/package.json +1 -1
  376. package/prisma/migrations/20260814101500_add_typed_assignment_provenance/migration.sql +2 -0
  377. package/prisma/schema.prisma +4 -2
  378. package/.next/standalone/.next/server/chunks/1114.js +0 -1
  379. package/.next/standalone/.next/static/chunks/19887-4d76db57a7ce4437.js +0 -1
  380. package/.next/standalone/.next/static/chunks/4340-a33120fb742af57e.js +0 -1
  381. package/.next/standalone/.next/static/chunks/94426-39b6da238425f9b7.js +0 -10
  382. package/.next/standalone/.next/static/chunks/app/(dashboard)/projects/[uuid]/proposals/[proposalUuid]/page-f619bd65e1eed8c9.js +0 -1
  383. package/.next/standalone/.next/static/chunks/app/onboarding/page-e85315278aafc300.js +0 -1
  384. package/.next/standalone/.next/static/css/67fba0943f494f0e.css +0 -1
  385. /package/.next/standalone/.next/static/{H92ZuhpmDWcL3WOfYRc-- → YTZwWTfE0BsJnjCDYCEHi}/_buildManifest.js +0 -0
  386. /package/.next/standalone/.next/static/{H92ZuhpmDWcL3WOfYRc-- → YTZwWTfE0BsJnjCDYCEHi}/_ssgManifest.js +0 -0
@@ -37,8 +37,8 @@ export class EventRouter {
37
37
  /**
38
38
  * @param {{
39
39
  * mcpClient: { callTool: (name: string, args?: Record<string, unknown>) => Promise<any> },
40
- * waker: { keyFor: (n: any) => Promise<{ key: string, rootIdeaUuid: string|null, directIdeaUuid: string|null }>, wake: (n: any, key: string, attribution?: any) => Promise<void>, markQueued?: (n: any, key: string, attribution?: any) => void },
41
- * queue: { enqueue: (key: string, task: () => Promise<void>) => void },
40
+ * waker: { keyFor: (n: any) => Promise<{ key: string, rootIdeaUuid: string|null, directIdeaUuid: string|null }>, wakeBatch: (ns: any[], key: string, attribution?: any) => Promise<void>, markQueued?: (n: any, key: string, attribution?: any) => void },
41
+ * queue: { enqueue: (key: string, item: { notification: any, attribution: any }) => void },
42
42
  * wakeActions: Set<string>,
43
43
  * getConnectionUuid?: () => (string|null), This daemon's registered connection uuid
44
44
  * (null until the SSE handshake reports it).
@@ -228,7 +228,8 @@ export class EventRouter {
228
228
  * reason ("user" | "crash") threaded from the control event; it is stamped on the
229
229
  * synthetic notification as `resumedFrom` so the prompt builder can state a crash
230
230
  * explicitly. Absent/unknown → not stamped → the user-resume prompt.
231
- * @param {{ entityType?: string, entityUuid?: string, resumeReason?: string }} target
231
+ * @param {{ entityType?: string, entityUuid?: string, resumeReason?: string,
232
+ * orchestrator?: { type?: string, uuid?: string, name?: string } | null }} target
232
233
  */
233
234
  dispatchResume(target) {
234
235
  const entityType = target?.entityType;
@@ -247,6 +248,11 @@ export class EventRouter {
247
248
  entityUuid,
248
249
  ...(resumedFrom ? { resumedFrom } : {}),
249
250
  ...(typeof target?.runtimeCwd === "string" ? { runtimeCwd: target.runtimeCwd } : {}),
251
+ ...(target?.orchestrator?.type === "agent" &&
252
+ typeof target.orchestrator.uuid === "string" &&
253
+ typeof target.orchestrator.name === "string"
254
+ ? { orchestrator: target.orchestrator }
255
+ : {}),
250
256
  };
251
257
  this.#resolveAndEnqueue(n, `resume:${entityType}:${entityUuid}`).catch((err) => {
252
258
  this.logger.error(`[Chorus] failed to dispatch resume for ${entityType}:${entityUuid}: ${err}`);
@@ -383,7 +389,10 @@ export class EventRouter {
383
389
  } catch (err) {
384
390
  this.logger.warn(`[Chorus] markQueued failed for pending turn ${turnUuid}: ${err}`);
385
391
  }
386
- this.queue.enqueue(key, () => this.waker.wake(n, key, attribution));
392
+ // Enqueue an opaque DATA payload (add-daemon-wake-coalescing §C4), NOT a thunk: the
393
+ // queue coalesces all same-key pending items and hands the whole batch to its runBatch
394
+ // (wired to waker.wakeBatch in daemon.mjs). markQueued was already called per-item above.
395
+ this.queue.enqueue(key, { notification: n, attribution });
387
396
  }
388
397
 
389
398
  /**
@@ -531,6 +540,9 @@ export class EventRouter {
531
540
  } catch (err) {
532
541
  this.logger.warn(`[Chorus] markQueued failed for ${label}: ${err}`);
533
542
  }
534
- this.queue.enqueue(key, () => this.waker.wake(n, key, attribution));
543
+ // Enqueue an opaque DATA payload (add-daemon-wake-coalescing §C4), NOT a thunk: the
544
+ // queue coalesces all same-key pending items and hands the whole batch to its runBatch
545
+ // (wired to waker.wakeBatch in daemon.mjs). markQueued was already called per-item above.
546
+ this.queue.enqueue(key, { notification: n, attribution });
535
547
  }
536
548
  }
package/cli/prompts.mjs CHANGED
@@ -25,6 +25,7 @@
25
25
  * @property {string} actorType
26
26
  * @property {string} actorUuid
27
27
  * @property {string} actorName
28
+ * @property {{type: string, uuid: string, name: string} | null} [orchestrator]
28
29
  * @property {string} [instructionText] Free-text body of a `human_instruction` wake
29
30
  * (子1 — daemon-session-conversation). The server denormalizes the canonical turn
30
31
  * promptText onto the wake notification so the daemon reads it in the
@@ -40,6 +41,29 @@ function mentionGuidance(n, entityType) {
40
41
  );
41
42
  }
42
43
 
44
+ /**
45
+ * Orchestrator-handoff guidance, appended to every wake body by buildPrompt. This is
46
+ * the SINGLE home of the "hand back to the agent that dispatched you" instruction —
47
+ * it is runtime PE (it depends on who the wake resolved as orchestrator), so it lives
48
+ * here in the wake prompt, NOT in the static skill docs. Its OpenClaw twin is
49
+ * `buildOrchestratorGuidance` in packages/openclaw-plugin/src/event-router.ts —
50
+ * KEEP THE TWO WORDINGS IN SYNC.
51
+ * Returns null unless the resource has an AGENT orchestrator; a human-assigned
52
+ * resource hands back to the human via the per-event actor @mention (mentionGuidance).
53
+ * @param {NotificationDetail} n
54
+ */
55
+ function orchestratorGuidance(n) {
56
+ if (n.orchestrator?.type !== "agent") return null;
57
+ return (
58
+ `Your orchestrator for this resource is @${n.orchestrator.name}.\n` +
59
+ `At a human-only gate you cannot cross, or when this child resource is complete, hand control ` +
60
+ `back by commenting on the resource and mentioning ` +
61
+ `@[${n.orchestrator.name}](agent:${n.orchestrator.uuid}) with the decision needed or completion ` +
62
+ `evidence, then leave any human-gated resource pending and end the turn. Do not @mention the ` +
63
+ `orchestrator for ordinary internal progress.`
64
+ );
65
+ }
66
+
43
67
  /**
44
68
  * Shared headless preamble prepended to EVERY wake prompt (add-daemon-headless-
45
69
  * interaction-guard). A daemon-woken session is a headless `claude -p` run with no
@@ -96,7 +120,98 @@ export const HEADLESS_PREAMBLE = [
96
120
  export function buildPrompt(n) {
97
121
  const body = buildPromptBody(n);
98
122
  if (body == null) return null;
99
- return `${HEADLESS_PREAMBLE}\n\n${body}`;
123
+ const handoff = orchestratorGuidance(n);
124
+ return `${HEADLESS_PREAMBLE}\n\n${body}${handoff ? `\n\n${handoff}` : ""}`;
125
+ }
126
+
127
+ /**
128
+ * Build ONE combined wake prompt for a coalesced batch of same-session notifications
129
+ * (add-daemon-wake-coalescing, tech design §C2). The daemon serializes wakes per session
130
+ * key; while a turn runs, later same-key wakes pile up and — when the slot frees — are
131
+ * drained together into a single `claude --resume` turn. This composes their one prompt.
132
+ *
133
+ * Rules:
134
+ * - **Size 1** → delegate to `buildPrompt(n)` verbatim, so the single-event path is
135
+ * BYTE-IDENTICAL to today (and all current prompt tests keep passing). This is keyed on
136
+ * the count of RENDERABLE events (non-null body): a batch that reduces to a single
137
+ * renderable event — e.g. a real wake plus an empty `human_instruction` — also takes
138
+ * the single-event path.
139
+ * - **Size > 1 renderable** → `HEADLESS_PREAMBLE` ONCE (it is a per-turn guard, paid once
140
+ * per turn, not per event), then a short backlog preamble, then one labeled block per
141
+ * event in ARRIVAL order, each block being the reused per-action `buildPromptBody(n)` so
142
+ * every event keeps its own tool hints and @mention guidance.
143
+ * - **Same-entity collapse (Q2)**: events sharing `(action, entityUuid)` collapse into one
144
+ * block at the group's FIRST-SEEN position, noting the occurrence count and showing the
145
+ * NEWEST message. This is safe only because such actions' full content is re-derivable
146
+ * server-side (comments via chorus_get_comments, task lifecycle via chorus_get_*).
147
+ * - **`human_instruction` is NEVER collapsed (Q3, Round-1 BLOCKER-2)**: every queued chat
148
+ * message carries `action=human_instruction` and `entityUuid=directIdeaUuid`, and its
149
+ * text lives ONLY on the turn/notification (not re-fetchable), so collapsing to
150
+ * newest-only would silently drop earlier chat and defeat the feature. Each renders its
151
+ * full body as its own block, in arrival order.
152
+ * - Events whose body is null (empty `human_instruction`, non-wake action) are omitted; a
153
+ * batch with no renderable event returns null (the router then spawns nothing).
154
+ *
155
+ * @param {NotificationDetail[]} notifications Arrival-ordered batch (FIFO drain order).
156
+ * @returns {string | null}
157
+ */
158
+ export function buildBatchPrompt(notifications) {
159
+ if (!Array.isArray(notifications) || notifications.length === 0) return null;
160
+
161
+ // Keep only events with a renderable per-action body, in arrival order.
162
+ const items = [];
163
+ for (const n of notifications) {
164
+ const body = buildPromptBody(n);
165
+ if (body != null) items.push({ n, body });
166
+ }
167
+ if (items.length === 0) return null;
168
+ // One renderable event → byte-identical single-event prompt (delegates to buildPrompt so
169
+ // the exact "PREAMBLE\n\nbody" shape is preserved). Covers a true batch of size 1 and a
170
+ // batch that reduced to one renderable event after null-body events were dropped.
171
+ if (items.length === 1) return buildPrompt(items[0].n);
172
+
173
+ // Group into ordered blocks. Collapsible actions merge by (action, entityUuid) at their
174
+ // first-seen slot; human_instruction is exempt and always gets its own block.
175
+ const blocks = [];
176
+ const collapseIndex = new Map(); // "action::entityUuid" -> index into `blocks`
177
+ for (const { n, body } of items) {
178
+ if (n.action === "human_instruction") {
179
+ blocks.push({ notif: n, body, count: 1 });
180
+ continue;
181
+ }
182
+ const groupKey = `${n.action}::${n.entityUuid ?? ""}`;
183
+ const existing = collapseIndex.get(groupKey);
184
+ if (existing != null) {
185
+ const g = blocks[existing];
186
+ g.count += 1;
187
+ g.notif = n; // newest wins (arrival order → last member is newest)
188
+ g.body = body; // newest body
189
+ } else {
190
+ collapseIndex.set(groupKey, blocks.length);
191
+ blocks.push({ notif: n, body, count: 1 });
192
+ }
193
+ }
194
+
195
+ // Backlog preamble. N = number of renderable events that arrived (not the collapsed block
196
+ // count) — collapse is a rendering optimization; each collapsed block states its own count.
197
+ const backlogPreamble =
198
+ `You have ${items.length} queued Chorus events on this session that arrived while you ` +
199
+ `were busy — handle them together, in order; each is labeled with its type below.`;
200
+
201
+ const rendered = blocks.map((g, idx) => {
202
+ const n = g.notif;
203
+ const entityType = n.entityType || "session";
204
+ const entityUuid = n.entityUuid || "";
205
+ const header = `### Event ${idx + 1} — ${n.action} on ${entityType} ${entityUuid}`.replace(/\s+$/, "");
206
+ const collapseNote =
207
+ g.count > 1
208
+ ? `\n(${g.count} ${n.action} events on this ${entityType} arrived — showing the newest ` +
209
+ `below; earlier ones are re-derivable via the entity's own chorus_get_* tools.)`
210
+ : "";
211
+ return `${header}${collapseNote}\n\n${g.body}`;
212
+ });
213
+
214
+ return [HEADLESS_PREAMBLE, backlogPreamble, ...rendered].join("\n\n");
100
215
  }
101
216
 
102
217
  /**
@@ -52,7 +52,7 @@ export const TURN_INTERRUPT_REASONS = new Set([
52
52
  * logger?: { info(m:string):void, warn(m:string):void, error(m:string):void },
53
53
  * fetchImpl?: typeof fetch, Injectable for tests.
54
54
  * }} opts
55
- * @returns {(params: { sessionId: string, status: "running"|"ended"|"interrupted", entityType?: string|null, entityUuid?: string|null, interruptedReason?: "user"|"crash"|"shutdown"|"invalid_path"|null, transcriptRelayError?: string|null, usage?: import("./upload-hooks.mjs").TokenUsage|null }) => Promise<void>}
55
+ * @returns {(params: { sessionId: string, status: "running"|"ended"|"interrupted", entityType?: string|null, entityUuid?: string|null, interruptedReason?: "user"|"crash"|"shutdown"|"invalid_path"|null, transcriptRelayError?: string|null, usage?: import("./upload-hooks.mjs").TokenUsage|null, coalescedCount?: number }) => Promise<void>}
56
56
  */
57
57
  export function createTurnReporter(opts) {
58
58
  const logger = opts.logger ?? NOOP_LOGGER;
@@ -76,6 +76,7 @@ export function createTurnReporter(opts) {
76
76
  transcriptRelayError,
77
77
  usage,
78
78
  backendSessionId,
79
+ coalescedCount,
79
80
  }) {
80
81
  if (typeof sessionId !== "string" || !sessionId || !TURN_STATUSES.has(status)) {
81
82
  logger.warn(
@@ -111,6 +112,9 @@ export function createTurnReporter(opts) {
111
112
  // only on a terminal edge. No validation here — usage is an opaque nested object.
112
113
  usage,
113
114
  backendSessionId,
115
+ // Coalesced-wake count (add-daemon-wake-coalescing): threaded straight through; the
116
+ // client sends it only on the → running edge and only when > 1 (a single wake omits it).
117
+ coalescedCount,
114
118
  });
115
119
  };
116
120
  }
@@ -1,14 +1,22 @@
1
1
  // cli/wake-queue.mjs
2
- // Per-key FIFO scheduler with a global concurrency cap. This is what makes the
3
- // idea_root session anchor safe (cli-daemon spec "Per-root-idea wake
4
- // serialization", design.md "Concurrency model"):
5
- // within one key (root idea) strictly serial, FIFO. The 2nd wake waits
6
- // for the 1st subprocess to exit, so we never run two
2
+ // Per-key FIFO scheduler with a global concurrency cap, coalescing same-key
3
+ // wakes into batches. This is what makes the idea_root session anchor safe
4
+ // (cli-daemon spec "Per-root-idea wake serialization", design.md "Concurrency
5
+ // model") AND implements daemon-wake-coalescing (design.md §C1):
6
+ // within one key (root idea) → strictly serial, FIFO. The next batch waits
7
+ // for the current runBatch to settle, so we never run two
7
8
  // `claude --resume <sameSessionId>` against one session.
9
+ // • coalescing: when a key's slot frees, the ENTIRE pending array for that key
10
+ // is drained (splice) and delivered to runBatch ONCE as a single batch. So
11
+ // N events that pile up while the previous turn runs become ONE turn.
12
+ // Natural batching only — NO debounce/collect timer, NO batch-size cap.
8
13
  // • across keys → concurrent, bounded by maxConcurrency.
9
14
  // • enqueue() returns immediately — never blocks the SSE loop.
10
- // • a task that throws is logged and the next task for that key proceeds (a
11
- // poisoned wake must not wedge the key's queue forever).
15
+ // • a batch whose runBatch throws is logged and the next batch for that key
16
+ // proceeds (a poisoned wake must not wedge the key's queue forever).
17
+ // The queue carries opaque DATA items (the router passes `{ notification,
18
+ // attribution }`); it never introspects them — the runBatch callback (supplied
19
+ // at construction, wired to waker.wakeBatch in daemon.mjs) does.
12
20
  // Plain ESM, zero deps, in-memory.
13
21
 
14
22
  const NOOP_LOGGER = { info() {}, warn() {}, error() {} };
@@ -18,41 +26,49 @@ export class WakeQueue {
18
26
  * @param {{
19
27
  * maxConcurrency?: number,
20
28
  * logger?: { info(m:string):void, warn(m:string):void, error(m:string):void },
29
+ * runBatch?: (key: string, items: any[]) => Promise<void>,
21
30
  * }} [opts]
22
31
  */
23
32
  constructor(opts = {}) {
24
33
  this.maxConcurrency = opts.maxConcurrency ?? 4;
25
34
  this.logger = opts.logger ?? NOOP_LOGGER;
26
- /** @type {Map<string, Array<() => Promise<void>>>} pending tasks per key. */
35
+ // The batch runner: called ONCE per drained batch with every pending item
36
+ // for the key. Defaults to a no-op so an unwired queue never throws (the
37
+ // daemon always supplies the real waker.wakeBatch runner).
38
+ this.runBatch = opts.runBatch ?? (async () => {});
39
+ /** @type {Map<string, any[]>} pending data items per key. */
27
40
  this.pending = new Map();
28
- /** @type {Set<string>} keys with a task currently running. */
41
+ /** @type {Set<string>} keys with a batch currently running. */
29
42
  this.running = new Set();
30
43
  /** @type {string[]} keys waiting for a global concurrency slot. */
31
44
  this.readyKeys = [];
32
45
  this.activeCount = 0;
33
- // Graceful-shutdown latch: once set, #pump starts NOTHING new — in-flight tasks
34
- // finish (drain observes them) but queued work stays queued and dies with the
35
- // process. Never cleared; a stopping queue is on its way out.
46
+ // Graceful-shutdown latch: once set, #pump starts NOTHING new — in-flight
47
+ // batches finish (drain observes them) but queued work stays queued and dies
48
+ // with the process. Never cleared; a stopping queue is on its way out.
36
49
  this.stopped = false;
37
50
  }
38
51
 
39
- /** Stop starting new tasks (graceful shutdown). In-flight tasks are unaffected. */
52
+ /** Stop starting new batches (graceful shutdown). In-flight batches are unaffected. */
40
53
  stop() {
41
54
  this.stopped = true;
42
55
  }
43
56
 
44
57
  /**
45
- * Enqueue a wake task under a key. Returns immediately. The task is a thunk
46
- * returning a promise (the actual wake). Same key serialized; different
47
- * keys concurrent up to maxConcurrency.
58
+ * Enqueue an opaque data item under a key. Returns immediately. Items on the
59
+ * same key coalesce: while a key's batch runs, later items pile up and are
60
+ * drained together as ONE batch when the slot frees. Different keys run
61
+ * concurrently up to maxConcurrency.
48
62
  * @param {string} key
49
- * @param {() => Promise<void>} task
63
+ * @param {any} item opaque data (e.g. `{ notification, attribution }`)
50
64
  */
51
- enqueue(key, task) {
65
+ enqueue(key, item) {
52
66
  if (!this.pending.has(key)) this.pending.set(key, []);
53
- this.pending.get(key).push(task);
67
+ this.pending.get(key).push(item);
54
68
  // A key becomes "ready" to claim a global slot only when it's not already
55
- // running (serial-per-key) and not already queued for a slot.
69
+ // running (serial-per-key) and not already queued for a slot. While it IS
70
+ // running, later items simply accumulate in `pending` and are picked up by
71
+ // the next batch — this is where coalescing happens.
56
72
  if (!this.running.has(key) && !this.readyKeys.includes(key)) {
57
73
  this.readyKeys.push(key);
58
74
  }
@@ -65,7 +81,7 @@ export class WakeQueue {
65
81
  }
66
82
 
67
83
  /**
68
- * Snapshot of the keys with a task currently running (observability read).
84
+ * Snapshot of the keys with a batch currently running (observability read).
69
85
  * Returns a fresh array so a caller can't mutate the internal Set.
70
86
  * @returns {string[]}
71
87
  */
@@ -74,7 +90,7 @@ export class WakeQueue {
74
90
  }
75
91
 
76
92
  /**
77
- * Snapshot of the keys that have at least one task still waiting to run —
93
+ * Snapshot of the keys that have at least one item still waiting to run —
78
94
  * i.e. enqueued but not yet started (observability read). A key that is
79
95
  * currently running with no further queued work is NOT pending. Returns a
80
96
  * fresh array so a caller can't mutate internal state.
@@ -85,13 +101,13 @@ export class WakeQueue {
85
101
  }
86
102
 
87
103
  /**
88
- * Wait (bounded) for every in-flight task to finish — the graceful-shutdown drain
104
+ * Wait (bounded) for every in-flight batch to finish — the graceful-shutdown drain
89
105
  * (fix-daemon-exit-orphan-running-turn). Resolves `true` when the queue went idle
90
- * (no active task) within `timeoutMs`, `false` on timeout — the caller exits
106
+ * (no active batch) within `timeoutMs`, `false` on timeout — the caller exits
91
107
  * anyway and leaves the rest to the server-side reconcile backstop. Pending
92
- * (not-yet-started) tasks are NOT waited for: a shutting-down daemon stops
108
+ * (not-yet-started) items are NOT waited for: a shutting-down daemon stops
93
109
  * starting new work, so only the in-flight subprocesses (and their exit reports)
94
- * matter. Polling (50ms) keeps this zero-dep and independent of task internals.
110
+ * matter. Polling (50ms) keeps this zero-dep and independent of batch internals.
95
111
  * @param {number} timeoutMs
96
112
  * @returns {Promise<boolean>}
97
113
  */
@@ -112,33 +128,39 @@ export class WakeQueue {
112
128
  if (this.running.has(key)) continue; // already running under another slot
113
129
  const queue = this.pending.get(key);
114
130
  if (!queue || queue.length === 0) continue;
115
- this.#runNext(key);
131
+ this.#startBatch(key);
116
132
  }
117
133
  }
118
134
 
119
- /** Run the next task for a key, then chain to the following one. */
120
- #runNext(key) {
135
+ /**
136
+ * Drain the ENTIRE pending array for a key into one batch and run it, then
137
+ * chain to the following batch. No batch-size cap (design.md §C1, Q7).
138
+ */
139
+ #startBatch(key) {
121
140
  const queue = this.pending.get(key);
122
141
  if (!queue || queue.length === 0) {
123
142
  this.running.delete(key);
124
143
  return;
125
144
  }
126
- const task = queue.shift();
145
+ // Coalesce: take everything pending for this key right now as one batch.
146
+ // Items that arrive after this splice accumulate for the NEXT batch.
147
+ const items = queue.splice(0);
127
148
  this.running.add(key);
128
149
  this.activeCount++;
129
150
 
130
151
  Promise.resolve()
131
- .then(task)
152
+ .then(() => this.runBatch(key, items))
132
153
  .catch((err) => {
133
- // Poisoned wake: log, do NOT let it wedge the key's queue.
134
- this.logger.warn(`[Chorus] wake task for ${key} failed: ${err}`);
154
+ // Poisoned batch: log, do NOT let it wedge the key's queue.
155
+ this.logger.warn(`[Chorus] wake batch for ${key} failed: ${err}`);
135
156
  })
136
157
  .finally(() => {
137
158
  this.activeCount--;
138
159
  const remaining = this.pending.get(key);
139
160
  if (remaining && remaining.length > 0) {
140
- // Same key still has work → it must run serially. Re-mark ready;
141
- // #pump will pick it up (respecting the global cap).
161
+ // Same key accumulated more work while this batch ran → it must run
162
+ // serially as the next batch. Re-mark ready; #pump will pick it up
163
+ // (respecting the global cap).
142
164
  if (!this.readyKeys.includes(key)) this.readyKeys.push(key);
143
165
  this.running.delete(key); // free the key so #pump can re-claim it
144
166
  } else {
package/cli/waker.mjs CHANGED
@@ -12,11 +12,14 @@
12
12
  // the server-resolved root, NEVER re-derived from the (direct-idea) serialization
13
13
  // key.
14
14
  //
15
- // Module contract (design.md): Waker.wake(notification, key, attribution) →
16
- // Promise<void>. A failure is logged and swallowed it must never crash the
17
- // daemon (no-silent-errors: visible log, no throw).
18
-
19
- import { buildPrompt } from "./prompts.mjs";
15
+ // Module contract (design.md): Waker.wakeBatch(notifications, key, attribution) →
16
+ // Promise<void> runs ONE coalesced turn for a batch of same-session notifications
17
+ // (add-daemon-wake-coalescing); Waker.wake(n, key, attribution) is a thin wrapper =
18
+ // wakeBatch([n], ...) so a batch of one is byte-identical to the pre-coalescing single
19
+ // wake. A failure is logged and swallowed — it must never crash the daemon
20
+ // (no-silent-errors: visible log, no throw).
21
+
22
+ import { buildBatchPrompt } from "./prompts.mjs";
20
23
  import { writeMcpConfig } from "./mcp-config.mjs";
21
24
  import { isNewSession } from "./claude-spawner.mjs";
22
25
  import { killProcessTree, DEFAULT_SIGINT_TIMEOUT_MS } from "./process-killer.mjs";
@@ -320,49 +323,122 @@ export class Waker {
320
323
  }
321
324
 
322
325
  /**
323
- * Run one wake. Never throws.
326
+ * Run one wake. Never throws. Thin wrapper over {@link wakeBatch} with a batch of one,
327
+ * so a single wake is byte-identical to the pre-coalescing behavior (prompt, execution
328
+ * row, turn accounting, coalescedCount = 1).
324
329
  * @param {import("./prompts.mjs").NotificationDetail} notification
325
330
  * @param {string} key The serialization key (from keyFor) — anchored on the direct idea.
326
331
  * @param {{ rootIdeaUuid?: string|null, directIdeaUuid?: string|null }} [attribution]
327
- * Server-resolved ids from keyFor. `rootIdeaUuid` → execution snapshot;
328
- * `directIdeaUuid` → the deterministic Claude session id (when the entity has
329
- * an idea ancestor). When there is no direct idea, the session is anchored on
330
- * the entity's own uuid instead (see below).
331
332
  */
332
333
  async wake(notification, key, attribution) {
334
+ return this.wakeBatch([notification], key, attribution);
335
+ }
336
+
337
+ /**
338
+ * Run ONE coalesced turn for a batch of same-session notifications
339
+ * (add-daemon-wake-coalescing, Tech Design §C3). The WakeQueue drains all same-key
340
+ * pending wakes and hands them here as one batch; this composes their single combined
341
+ * prompt (buildBatchPrompt), spawns ONE subprocess (one `claude --resume <anchor>`), and
342
+ * accounts for the whole thing as ONE running turn. Never throws.
343
+ *
344
+ * A batch of ONE is byte-identical to the pre-coalescing single wake: `buildBatchPrompt`
345
+ * delegates to `buildPrompt`, the execution row + turn-advance carry the item's OWN
346
+ * entity, and `coalescedCount = 1` (so the wire is unchanged).
347
+ *
348
+ * Execution snapshot (Q6): a coalesced batch (> 1) is anchored on the SESSION, so it emits
349
+ * ONE running row synthesized from `attribution` — `idea:<directIdeaUuid>` (NOT necessarily
350
+ * one of the merged items — a batch of mentions on child tasks A+B under idea D has no
351
+ * `idea:D` item, yet the anchor IS `idea:D`), else the ad-hoc entity. Every drained/merged
352
+ * resource is removed from the executions map so the server reconcile ends its queued row.
353
+ *
354
+ * @param {import("./prompts.mjs").NotificationDetail[]} notifications Arrival-ordered batch.
355
+ * @param {string} key The serialization key (from keyFor) — shared by every item.
356
+ * @param {{ rootIdeaUuid?: string|null, directIdeaUuid?: string|null }} [attribution]
357
+ * Server-resolved ids (shared across the batch — the key IS `idea:<directIdeaUuid>`, so
358
+ * all items carry the same ids). `rootIdeaUuid` → snapshot; `directIdeaUuid` → session anchor.
359
+ */
360
+ async wakeBatch(notifications, key, attribution) {
333
361
  let cfg;
334
- const entity = this.#entityOf(notification);
335
- const execKey = entity ? this.#execKey(entity.entityType, entity.entityUuid) : null;
362
+ const list = Array.isArray(notifications) ? notifications : [];
363
+ // Physical batch size how many wakes were coalesced into this one turn. Drives the
364
+ // session-anchor synthesis (a coalesced batch shows ONE synthesized idea row) and the
365
+ // arrival log label. Independent of the wire count below.
366
+ const batchSize = list.length;
367
+ // The number of coalesced wakes reported to the server on the running-transition so it can
368
+ // settle the next (coalescedCount − 1) same-session pending turns. Counted as ONLY the
369
+ // TURN-BACKED items: each notification-backed wake created a server-side pending
370
+ // DaemonSessionTurn at the chokepoint, so those are the turns the server can settle.
371
+ //
372
+ // A synthetic `resource_resumed` (子3 control-channel resume) is EXCLUDED: it is NEVER a
373
+ // persisted notification and creates NO server pending turn (src/services/notification-turn.ts
374
+ // deliberately omits it from the trigger map), so counting it would inflate N. An inflated N
375
+ // lets the server's `take: coalescedCount-1` seq-window settlement over-reach and mark a
376
+ // genuinely-separate post-drain pending turn as `merged` — which getPendingTurnsForConnection
377
+ // then never re-dispatches, silently dropping a piled-up chat message (defeats Q3).
378
+ // `resource_resumed` is the ONLY synthetic, non-notification wake action; every other
379
+ // WAKE_ACTIONS entry (incl. human_instruction) is notification-backed and turn-creating. A
380
+ // batch that reduces to 0 or 1 turn-backed items reports ≤ 1, which the client omits from the
381
+ // wire (see #advanceTurn) so no settlement runs.
382
+ const coalescedCount = list.filter((n) => n?.action !== "resource_resumed").length;
383
+ const first = list[0];
336
384
  // Both ids come from the resolved `attribution` (supplied by keyFor via the
337
385
  // router) and are threaded SEPARATELY — NEVER sliced from `key`. The ROOT idea
338
386
  // is reported in the snapshot; the DIRECT idea is the preferred session anchor.
339
387
  const rootIdeaUuid = attribution?.rootIdeaUuid ?? null;
340
388
  const directIdeaUuid = attribution?.directIdeaUuid ?? null;
389
+ // The resources markQueued tracked for THIS batch — every item's own entity. They must
390
+ // all leave the executions map when the batch settles so the server reconcile ends the
391
+ // queued rows the batch coalesced away (a coalesced batch shows only ONE running row).
392
+ const drainedExecKeys = [];
393
+ for (const n of list) {
394
+ const e = this.#entityOf(n);
395
+ if (e) drainedExecKeys.push(this.#execKey(e.entityType, e.entityUuid));
396
+ }
397
+ // The session-anchor resource for the ONE running row:
398
+ // - a coalesced batch (> 1) is anchored on the SESSION, synthesized from attribution:
399
+ // idea:<directIdeaUuid> when idea-anchored (NOT necessarily one of the merged items),
400
+ // else the ad-hoc entity (all ad-hoc items share one entity → the first item's entity).
401
+ // - a single wake (batch of one) keeps the item's OWN entity — byte-identical to before.
402
+ const entity =
403
+ batchSize > 1 && directIdeaUuid
404
+ ? { entityType: "idea", entityUuid: directIdeaUuid }
405
+ : this.#entityOf(first);
406
+ const execKey = entity ? this.#execKey(entity.entityType, entity.entityUuid) : null;
341
407
  // Per-wake lifecycle logging: stamp the start so completion can report a
342
408
  // duration. `Date.now()` is fine here (runtime metric, not a resume seed).
343
409
  const startMs = Date.now();
344
410
  const target = entity ? `${entity.entityType}:${entity.entityUuid}` : key;
345
- const sessionId = directIdeaUuid ?? notification.entityUuid ?? null;
411
+ const sessionId = directIdeaUuid ?? first?.entityUuid ?? null;
346
412
  const requestedRuntimeCwd =
347
- typeof notification?.runtimeCwd === "string" && notification.runtimeCwd
348
- ? notification.runtimeCwd
413
+ typeof first?.runtimeCwd === "string" && first.runtimeCwd
414
+ ? first.runtimeCwd
349
415
  : null;
416
+ // Arrival label: the single action for a batch of one, else a batch count (the physical
417
+ // number of merged events — includes any resource_resumed, which is a real wake even
418
+ // though it is not turn-backed for settlement).
419
+ const arrivalAction =
420
+ batchSize > 1 ? `coalesced batch of ${batchSize}` : first?.action;
350
421
  try {
351
- const prompt = buildPrompt(notification);
422
+ const prompt = buildBatchPrompt(list);
352
423
  if (!prompt) {
353
- this.logger.info(`[Chorus] no wake prompt for action "${notification.action}" — skipping`);
424
+ this.logger.info(`[Chorus] no wake prompt for ${arrivalAction} on ${key} — skipping`);
354
425
  return;
355
426
  }
356
427
 
357
- // Lifecycle line 1 — arrival: which action targets which idea/task/entity.
358
- this.logger.info(`[Chorus] ▶ wake: ${notification.action} → ${target}`);
428
+ // Lifecycle line 1 — arrival: which action(s) target which idea/task/entity.
429
+ this.logger.info(`[Chorus] ▶ wake: ${arrivalAction} → ${target}`);
359
430
 
360
- // Transition this resource to RUNNING with a start timestamp and emit a
361
- // fresh snapshot, so the server/UI sees it leave the queue and begin
362
- // executing. Report the server-resolved ROOT idea (not the direct-idea key).
363
- // `child` starts null and is filled in by the spawner's onChild the moment the
364
- // subprocess spawns (子3) — so the control handler can target it for interrupt.
431
+ // Transition to RUNNING and emit a fresh snapshot. First REMOVE every merged/drained
432
+ // resource (so the server reconcile ends their queued rows), then set the ONE
433
+ // synthesized anchor running row with a start timestamp — so the server/UI sees the
434
+ // batch as a single running entry with no leftover queued entries. Report the
435
+ // server-resolved ROOT idea (not the direct-idea key). `child` starts null and is
436
+ // filled in by the spawner's onChild the moment the subprocess spawns (子3) — so the
437
+ // control handler can target it for interrupt.
365
438
  if (entity && execKey) {
439
+ for (const dk of drainedExecKeys) {
440
+ if (dk !== execKey) this.executions.delete(dk);
441
+ }
366
442
  this.executions.set(execKey, {
367
443
  entityType: entity.entityType,
368
444
  entityUuid: entity.entityUuid,
@@ -416,7 +492,7 @@ export class Waker {
416
492
  this.logger.info(`[Chorus] dispatching session ${sessionId ?? "(none)"}`);
417
493
  }
418
494
  if (this.verbose) {
419
- this.logger.info(`[Chorus] cwd=${cwd} action=${notification.action} root=${rootIdeaUuid ?? "(none)"}`);
495
+ this.logger.info(`[Chorus] cwd=${cwd} action=${arrivalAction} root=${rootIdeaUuid ?? "(none)"}`);
420
496
  }
421
497
 
422
498
  cfg = this.writeMcpConfigFn(this.creds);
@@ -456,7 +532,10 @@ export class Waker {
456
532
  turnAdvancedToRunning = true;
457
533
  // Fire-and-forget; #advanceTurn swallows + logs its own failures so a
458
534
  // turn-report error never crashes the spawn callback (no-silent-errors).
459
- this.#advanceTurn(sessionId, "running", entity).catch(() => {});
535
+ // The coalescedCount rides this running edge so the server settles the
536
+ // (count − 1) same-session pending turns coalesced into this one (a single
537
+ // wake reports 1, which the client omits from the wire).
538
+ this.#advanceTurn(sessionId, "running", entity, null, null, null, null, coalescedCount).catch(() => {});
460
539
  }
461
540
  },
462
541
  onMessage: (message) => {
@@ -577,7 +656,7 @@ export class Waker {
577
656
  );
578
657
  if (this.verbose) {
579
658
  this.logger.info(
580
- `[Chorus] action=${notification.action} session=${result?.sessionId} key=${key}`
659
+ `[Chorus] action=${arrivalAction} session=${result?.sessionId} key=${key}`
581
660
  );
582
661
  }
583
662
  } catch (err) {
@@ -593,17 +672,22 @@ export class Waker {
593
672
  );
594
673
  }
595
674
  } finally {
596
- // Wake finished (cleanly or not): the resource leaves the active set. Drop
597
- // it and emit a fresh snapshot so the server ends its running/queued row.
598
- // The server reconcile is snapshot-authoritative, so absence == ended. Also
599
- // clear the per-entity interrupting flag so it can never leak into a later
600
- // wake of the same entity.
675
+ // Wake finished (cleanly or not): EVERY resource this batch touched leaves the active
676
+ // set the running anchor AND any drained/merged resources that were never promoted to
677
+ // the running row (e.g. a null-prompt batch that returned before the running transition,
678
+ // so their queued rows still linger). Drop them all and emit ONE fresh snapshot so the
679
+ // server ends their running/queued rows. The server reconcile is snapshot-authoritative,
680
+ // so absence == ended. Also clear the anchor's interrupting flag so it can never leak
681
+ // into a later wake of the same entity.
682
+ let changed = false;
683
+ for (const dk of drainedExecKeys) {
684
+ if (this.executions.delete(dk)) changed = true;
685
+ }
601
686
  if (execKey) {
602
687
  this.interrupting.delete(execKey);
603
- if (this.executions.delete(execKey)) {
604
- this.#emitExecutionChange();
605
- }
688
+ if (this.executions.delete(execKey)) changed = true;
606
689
  }
690
+ if (changed) this.#emitExecutionChange();
607
691
  try {
608
692
  cfg?.cleanup?.();
609
693
  } catch {
@@ -647,8 +731,12 @@ export class Waker {
647
731
  * @param {string|null} [transcriptRelayError]
648
732
  * @param {import("./upload-hooks.mjs").TokenUsage|null} [usage] Per-turn token usage
649
733
  * (daemon-token-usage); forwarded only on a terminal edge, mirroring transcriptRelayError.
734
+ * @param {string|null} [backendSessionId]
735
+ * @param {number} [coalescedCount] Number of wakes coalesced into this turn
736
+ * (add-daemon-wake-coalescing); forwarded only on the → running edge and only when > 1,
737
+ * so a single wake (default 1) leaves the field absent — byte-identical to before.
650
738
  */
651
- async #advanceTurn(sessionId, status, entity, interruptedReason = null, transcriptRelayError = null, usage = null, backendSessionId = null) {
739
+ async #advanceTurn(sessionId, status, entity, interruptedReason = null, transcriptRelayError = null, usage = null, backendSessionId = null, coalescedCount = 1) {
652
740
  try {
653
741
  await this.advanceTurn({
654
742
  sessionId,
@@ -659,6 +747,7 @@ export class Waker {
659
747
  ...(transcriptRelayError ? { transcriptRelayError } : {}),
660
748
  ...(usage ? { usage } : {}),
661
749
  ...(backendSessionId ? { backendSessionId } : {}),
750
+ ...(status === "running" && coalescedCount > 1 ? { coalescedCount } : {}),
662
751
  });
663
752
  } catch (err) {
664
753
  this.logger.warn(