crewly 1.6.0 → 1.6.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 (448) hide show
  1. package/config/roles/orchestrator/fragments/role-boundary.md +4 -1
  2. package/config/roles/orchestrator/prompt.md +53 -0
  3. package/config/roles/orchestrator/soul.md +47 -10
  4. package/config/skills/_common/lib.sh +28 -0
  5. package/config/skills/agent/core/cancel-followup/execute.sh +0 -19
  6. package/config/skills/agent/core/get-my-active-work/SKILL.md +101 -0
  7. package/config/skills/agent/core/get-my-active-work/execute.sh +122 -0
  8. package/config/skills/agent/core/list-my-followups/execute.sh +0 -19
  9. package/config/skills/agent/core/record-learning/SKILL.md +29 -0
  10. package/config/skills/agent/core/reply-channel/SKILL.md +41 -0
  11. package/config/skills/agent/core/reply-channel/execute.sh +165 -0
  12. package/config/skills/agent/core/reply-channel/execute.test.sh +148 -0
  13. package/config/skills/agent/core/schedule-followup/execute.sh +0 -19
  14. package/config/skills/agent/core/watch-for-event/execute.sh +0 -19
  15. package/config/skills/agent/remote-browser/execute.sh +296 -14
  16. package/config/skills/agent/remote-browser/execute.test.sh +482 -0
  17. package/config/skills/orchestrator/credential-manager/execute.test.sh +88 -0
  18. package/config/skills/orchestrator/send-message/SKILL.md +30 -7
  19. package/config/skills/orchestrator/team-health-scan/SKILL.md +98 -0
  20. package/config/skills/orchestrator/team-health-scan/execute.sh +44 -0
  21. package/config/skills/registry.json +62 -1
  22. package/config/sops/developer/git-workflow.md +38 -3
  23. package/dist/backend/backend/src/config/oauth.config.d.ts +33 -0
  24. package/dist/backend/backend/src/config/oauth.config.d.ts.map +1 -0
  25. package/dist/backend/backend/src/config/oauth.config.js +45 -0
  26. package/dist/backend/backend/src/config/oauth.config.js.map +1 -0
  27. package/dist/backend/backend/src/constants.d.ts +69 -1
  28. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  29. package/dist/backend/backend/src/constants.js +69 -2
  30. package/dist/backend/backend/src/constants.js.map +1 -1
  31. package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts +53 -0
  32. package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts.map +1 -0
  33. package/dist/backend/backend/src/controllers/active-work/active-work.controller.js +92 -0
  34. package/dist/backend/backend/src/controllers/active-work/active-work.controller.js.map +1 -0
  35. package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.d.ts.map +1 -1
  36. package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js +18 -1
  37. package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js.map +1 -1
  38. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +68 -0
  39. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
  40. package/dist/backend/backend/src/controllers/browser/browser.controller.js +233 -5
  41. package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
  42. package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
  43. package/dist/backend/backend/src/controllers/browser/browser.routes.js +10 -1
  44. package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
  45. package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
  46. package/dist/backend/backend/src/controllers/chat/chat.controller.js +8 -3
  47. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  48. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +132 -0
  49. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -0
  50. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +401 -0
  51. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -0
  52. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +29 -0
  53. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -0
  54. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +39 -0
  55. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -0
  56. package/dist/backend/backend/src/controllers/chat-v2/index.d.ts +8 -0
  57. package/dist/backend/backend/src/controllers/chat-v2/index.d.ts.map +1 -0
  58. package/dist/backend/backend/src/controllers/chat-v2/index.js +8 -0
  59. package/dist/backend/backend/src/controllers/chat-v2/index.js.map +1 -0
  60. package/dist/backend/backend/src/controllers/credentials/credentials.controller.d.ts +0 -26
  61. package/dist/backend/backend/src/controllers/credentials/credentials.controller.d.ts.map +1 -1
  62. package/dist/backend/backend/src/controllers/credentials/credentials.controller.js +47 -184
  63. package/dist/backend/backend/src/controllers/credentials/credentials.controller.js.map +1 -1
  64. package/dist/backend/backend/src/controllers/credentials/credentials.routes.d.ts.map +1 -1
  65. package/dist/backend/backend/src/controllers/credentials/credentials.routes.js +2 -1
  66. package/dist/backend/backend/src/controllers/credentials/credentials.routes.js.map +1 -1
  67. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.d.ts +40 -0
  68. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.d.ts.map +1 -0
  69. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.js +162 -0
  70. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.js.map +1 -0
  71. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts +13 -13
  72. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts.map +1 -1
  73. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js +74 -234
  74. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js.map +1 -1
  75. package/dist/backend/backend/src/controllers/request/request.controller.d.ts.map +1 -1
  76. package/dist/backend/backend/src/controllers/request/request.controller.js +4 -6
  77. package/dist/backend/backend/src/controllers/request/request.controller.js.map +1 -1
  78. package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts.map +1 -1
  79. package/dist/backend/backend/src/controllers/skill/skill.controller.js +1 -0
  80. package/dist/backend/backend/src/controllers/skill/skill.controller.js.map +1 -1
  81. package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts +43 -0
  82. package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts.map +1 -1
  83. package/dist/backend/backend/src/controllers/task-management/tasks.controller.js +200 -72
  84. package/dist/backend/backend/src/controllers/task-management/tasks.controller.js.map +1 -1
  85. package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
  86. package/dist/backend/backend/src/controllers/team/team.controller.js +46 -0
  87. package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
  88. package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts +59 -0
  89. package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts.map +1 -0
  90. package/dist/backend/backend/src/controllers/team-health/team-health.controller.js +127 -0
  91. package/dist/backend/backend/src/controllers/team-health/team-health.controller.js.map +1 -0
  92. package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts +13 -0
  93. package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts.map +1 -0
  94. package/dist/backend/backend/src/controllers/team-health/team-health.routes.js +20 -0
  95. package/dist/backend/backend/src/controllers/team-health/team-health.routes.js.map +1 -0
  96. package/dist/backend/backend/src/index.d.ts +9 -0
  97. package/dist/backend/backend/src/index.d.ts.map +1 -1
  98. package/dist/backend/backend/src/index.js +256 -4
  99. package/dist/backend/backend/src/index.js.map +1 -1
  100. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  101. package/dist/backend/backend/src/routes/api.routes.js +43 -6
  102. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  103. package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts +498 -0
  104. package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
  105. package/dist/backend/backend/src/services/agent/active-work-briefing.service.js +759 -0
  106. package/dist/backend/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
  107. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +25 -0
  108. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  109. package/dist/backend/backend/src/services/agent/agent-registration.service.js +193 -57
  110. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  111. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +9 -2
  112. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +1 -1
  113. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +35 -2
  114. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +1 -1
  115. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +8 -2
  116. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
  117. package/dist/backend/backend/src/services/agent/crewly-agent/types.js +1 -0
  118. package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
  119. package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -1
  120. package/dist/backend/backend/src/services/agent/tmux-command.service.js +2 -1
  121. package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -1
  122. package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -1
  123. package/dist/backend/backend/src/services/agent/tmux.service.js +2 -1
  124. package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -1
  125. package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +148 -3
  126. package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
  127. package/dist/backend/backend/src/services/ai/prompt-builder.service.js +241 -2
  128. package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
  129. package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -1
  130. package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js +13 -0
  131. package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -1
  132. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -1
  133. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js +30 -2
  134. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -1
  135. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  136. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +17 -0
  137. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  138. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
  139. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
  140. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
  141. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
  142. package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +161 -0
  143. package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
  144. package/dist/backend/backend/src/services/browser/browser-bridge.service.js +382 -2
  145. package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
  146. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts +105 -0
  147. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -1
  148. package/dist/backend/backend/src/services/browser/browser-proxy.service.js +232 -13
  149. package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
  150. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +178 -0
  151. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -0
  152. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +254 -0
  153. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -0
  154. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts +134 -0
  155. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts.map +1 -0
  156. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js +232 -0
  157. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js.map +1 -0
  158. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts +25 -0
  159. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts.map +1 -0
  160. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js +23 -0
  161. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js.map +1 -0
  162. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +254 -0
  163. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
  164. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +467 -0
  165. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
  166. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
  167. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
  168. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
  169. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
  170. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts +43 -0
  171. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts.map +1 -0
  172. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js +54 -0
  173. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js.map +1 -0
  174. package/dist/backend/backend/src/services/chat-v2/config.d.ts +100 -0
  175. package/dist/backend/backend/src/services/chat-v2/config.d.ts.map +1 -0
  176. package/dist/backend/backend/src/services/chat-v2/config.js +174 -0
  177. package/dist/backend/backend/src/services/chat-v2/config.js.map +1 -0
  178. package/dist/backend/backend/src/services/chat-v2/index.d.ts +11 -0
  179. package/dist/backend/backend/src/services/chat-v2/index.d.ts.map +1 -0
  180. package/dist/backend/backend/src/services/chat-v2/index.js +12 -0
  181. package/dist/backend/backend/src/services/chat-v2/index.js.map +1 -0
  182. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +114 -0
  183. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
  184. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +194 -0
  185. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
  186. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts +100 -0
  187. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
  188. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js +351 -0
  189. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
  190. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +132 -0
  191. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
  192. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +281 -0
  193. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
  194. package/dist/backend/backend/src/services/chat-v2/types.d.ts +295 -0
  195. package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -0
  196. package/dist/backend/backend/src/services/chat-v2/types.js +61 -0
  197. package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -0
  198. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts +113 -0
  199. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts.map +1 -0
  200. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js +179 -0
  201. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js.map +1 -0
  202. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts +131 -0
  203. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts.map +1 -0
  204. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js +227 -0
  205. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js.map +1 -0
  206. package/dist/backend/backend/src/services/core/config.service.js +3 -3
  207. package/dist/backend/backend/src/services/core/config.service.js.map +1 -1
  208. package/dist/backend/backend/src/services/core/storage.service.d.ts +7 -0
  209. package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
  210. package/dist/backend/backend/src/services/core/storage.service.js +15 -0
  211. package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
  212. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts +4 -16
  213. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -1
  214. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +7 -28
  215. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -1
  216. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +69 -1
  217. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  218. package/dist/backend/backend/src/services/event-bus/event-bus.service.js +118 -0
  219. package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  220. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +275 -0
  221. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -0
  222. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +736 -0
  223. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -0
  224. package/dist/backend/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
  225. package/dist/backend/backend/src/services/knowledge/fts5-index.service.js +18 -2
  226. package/dist/backend/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
  227. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
  228. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
  229. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +123 -29
  230. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
  231. package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts +159 -0
  232. package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts.map +1 -0
  233. package/dist/backend/backend/src/services/knowledge/learnings-index.service.js +304 -0
  234. package/dist/backend/backend/src/services/knowledge/learnings-index.service.js.map +1 -0
  235. package/dist/backend/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
  236. package/dist/backend/backend/src/services/knowledge/vector-store.service.js +24 -4
  237. package/dist/backend/backend/src/services/knowledge/vector-store.service.js.map +1 -1
  238. package/dist/backend/backend/src/services/mcp-server.d.ts +46 -2
  239. package/dist/backend/backend/src/services/mcp-server.d.ts.map +1 -1
  240. package/dist/backend/backend/src/services/mcp-server.js +216 -211
  241. package/dist/backend/backend/src/services/mcp-server.js.map +1 -1
  242. package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts +254 -0
  243. package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts.map +1 -0
  244. package/dist/backend/backend/src/services/mcp-tool-definitions.js +285 -0
  245. package/dist/backend/backend/src/services/mcp-tool-definitions.js.map +1 -0
  246. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts +174 -0
  247. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts.map +1 -0
  248. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js +375 -0
  249. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js.map +1 -0
  250. package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts +97 -0
  251. package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts.map +1 -0
  252. package/dist/backend/backend/src/services/memory/learning-format.validator.js +209 -0
  253. package/dist/backend/backend/src/services/memory/learning-format.validator.js.map +1 -0
  254. package/dist/backend/backend/src/services/memory/vector-store.service.d.ts.map +1 -1
  255. package/dist/backend/backend/src/services/memory/vector-store.service.js +19 -4
  256. package/dist/backend/backend/src/services/memory/vector-store.service.js.map +1 -1
  257. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts +16 -5
  258. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts.map +1 -1
  259. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js +32 -5
  260. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js.map +1 -1
  261. package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts +157 -0
  262. package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts.map +1 -0
  263. package/dist/backend/backend/src/services/onboarding/onboarding.service.js +229 -0
  264. package/dist/backend/backend/src/services/onboarding/onboarding.service.js.map +1 -0
  265. package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts +141 -0
  266. package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts.map +1 -0
  267. package/dist/backend/backend/src/services/onboarding/onboarding.types.js +18 -0
  268. package/dist/backend/backend/src/services/onboarding/onboarding.types.js.map +1 -0
  269. package/dist/backend/backend/src/services/pr-review/pr-review.service.d.ts.map +1 -1
  270. package/dist/backend/backend/src/services/pr-review/pr-review.service.js +1 -1
  271. package/dist/backend/backend/src/services/pr-review/pr-review.service.js.map +1 -1
  272. package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -1
  273. package/dist/backend/backend/src/services/project/task.service.js +5 -0
  274. package/dist/backend/backend/src/services/project/task.service.js.map +1 -1
  275. package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts +41 -0
  276. package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  277. package/dist/backend/backend/src/services/skill/skill-executor.service.js +136 -7
  278. package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -1
  279. package/dist/backend/backend/src/services/skill/skill.service.d.ts.map +1 -1
  280. package/dist/backend/backend/src/services/skill/skill.service.js +1 -0
  281. package/dist/backend/backend/src/services/skill/skill.service.js.map +1 -1
  282. package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -1
  283. package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +17 -1
  284. package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -1
  285. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +39 -1
  286. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  287. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +158 -26
  288. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  289. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +248 -6
  290. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  291. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +531 -51
  292. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  293. package/dist/backend/backend/src/services/team-health/index.d.ts +16 -0
  294. package/dist/backend/backend/src/services/team-health/index.d.ts.map +1 -0
  295. package/dist/backend/backend/src/services/team-health/index.js +16 -0
  296. package/dist/backend/backend/src/services/team-health/index.js.map +1 -0
  297. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts +52 -0
  298. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts.map +1 -0
  299. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js +161 -0
  300. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js.map +1 -0
  301. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts +53 -0
  302. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts.map +1 -0
  303. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js +88 -0
  304. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js.map +1 -0
  305. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts +44 -0
  306. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts.map +1 -0
  307. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js +83 -0
  308. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js.map +1 -0
  309. package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts +92 -0
  310. package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts.map +1 -0
  311. package/dist/backend/backend/src/services/team-health/team-health-alert-router.js +328 -0
  312. package/dist/backend/backend/src/services/team-health/team-health-alert-router.js.map +1 -0
  313. package/dist/backend/backend/src/services/team-health/team-health-config.d.ts +41 -0
  314. package/dist/backend/backend/src/services/team-health/team-health-config.d.ts.map +1 -0
  315. package/dist/backend/backend/src/services/team-health/team-health-config.js +213 -0
  316. package/dist/backend/backend/src/services/team-health/team-health-config.js.map +1 -0
  317. package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts +46 -0
  318. package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts.map +1 -0
  319. package/dist/backend/backend/src/services/team-health/team-health-detector.js +347 -0
  320. package/dist/backend/backend/src/services/team-health/team-health-detector.js.map +1 -0
  321. package/dist/backend/backend/src/services/team-health/team-health-types.d.ts +154 -0
  322. package/dist/backend/backend/src/services/team-health/team-health-types.d.ts.map +1 -0
  323. package/dist/backend/backend/src/services/team-health/team-health-types.js +94 -0
  324. package/dist/backend/backend/src/services/team-health/team-health-types.js.map +1 -0
  325. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts +111 -0
  326. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts.map +1 -0
  327. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js +226 -0
  328. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js.map +1 -0
  329. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +148 -0
  330. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -0
  331. package/dist/backend/backend/src/services/v3/mission-reminder.service.js +545 -0
  332. package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -0
  333. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts +499 -0
  334. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
  335. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +1105 -0
  336. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
  337. package/dist/backend/backend/src/services/v3/request.service.d.ts +22 -0
  338. package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
  339. package/dist/backend/backend/src/services/v3/request.service.js +71 -0
  340. package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
  341. package/dist/backend/backend/src/services/v3/v3-data.service.d.ts +1 -0
  342. package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
  343. package/dist/backend/backend/src/services/v3/v3-data.service.js +22 -6
  344. package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
  345. package/dist/backend/backend/src/types/event-bus.types.d.ts +19 -1
  346. package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
  347. package/dist/backend/backend/src/types/event-bus.types.js +43 -0
  348. package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
  349. package/dist/backend/backend/src/types/index.d.ts +22 -1
  350. package/dist/backend/backend/src/types/index.d.ts.map +1 -1
  351. package/dist/backend/backend/src/types/index.js.map +1 -1
  352. package/dist/backend/backend/src/types/review-reason.types.d.ts +63 -0
  353. package/dist/backend/backend/src/types/review-reason.types.d.ts.map +1 -0
  354. package/dist/backend/backend/src/types/review-reason.types.js +50 -0
  355. package/dist/backend/backend/src/types/review-reason.types.js.map +1 -0
  356. package/dist/backend/backend/src/types/skill.types.d.ts +9 -0
  357. package/dist/backend/backend/src/types/skill.types.d.ts.map +1 -1
  358. package/dist/backend/backend/src/types/skill.types.js.map +1 -1
  359. package/dist/backend/backend/src/types/slack.types.d.ts +4 -1
  360. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  361. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  362. package/dist/backend/backend/src/types/v2/mission.types.d.ts +18 -0
  363. package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
  364. package/dist/backend/backend/src/types/v2/mission.types.js +1 -0
  365. package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
  366. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  367. package/dist/backend/backend/src/types/v2/work-item.types.js +25 -1
  368. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  369. package/dist/backend/backend/src/utils/google-userinfo.utils.d.ts +41 -0
  370. package/dist/backend/backend/src/utils/google-userinfo.utils.d.ts.map +1 -0
  371. package/dist/backend/backend/src/utils/google-userinfo.utils.js +44 -0
  372. package/dist/backend/backend/src/utils/google-userinfo.utils.js.map +1 -0
  373. package/dist/backend/backend/src/utils/team.utils.d.ts +38 -0
  374. package/dist/backend/backend/src/utils/team.utils.d.ts.map +1 -0
  375. package/dist/backend/backend/src/utils/team.utils.js +45 -0
  376. package/dist/backend/backend/src/utils/team.utils.js.map +1 -0
  377. package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts +195 -0
  378. package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts.map +1 -0
  379. package/dist/backend/backend/src/websocket/chat-v2.gateway.js +401 -0
  380. package/dist/backend/backend/src/websocket/chat-v2.gateway.js.map +1 -0
  381. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +37 -2
  382. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
  383. package/dist/backend/backend/src/websocket/terminal.gateway.js +106 -5
  384. package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
  385. package/dist/cli/backend/src/config/oauth.config.d.ts +33 -0
  386. package/dist/cli/backend/src/config/oauth.config.d.ts.map +1 -0
  387. package/dist/cli/backend/src/config/oauth.config.js +45 -0
  388. package/dist/cli/backend/src/config/oauth.config.js.map +1 -0
  389. package/dist/cli/backend/src/constants.d.ts +69 -1
  390. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  391. package/dist/cli/backend/src/constants.js +69 -2
  392. package/dist/cli/backend/src/constants.js.map +1 -1
  393. package/dist/cli/backend/src/services/core/config.service.js +3 -3
  394. package/dist/cli/backend/src/services/core/config.service.js.map +1 -1
  395. package/dist/cli/backend/src/services/core/storage.service.d.ts +7 -0
  396. package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
  397. package/dist/cli/backend/src/services/core/storage.service.js +15 -0
  398. package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
  399. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts +4 -16
  400. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -1
  401. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +7 -28
  402. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -1
  403. package/dist/cli/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
  404. package/dist/cli/backend/src/services/knowledge/fts5-index.service.js +18 -2
  405. package/dist/cli/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
  406. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
  407. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
  408. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +123 -29
  409. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
  410. package/dist/cli/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
  411. package/dist/cli/backend/src/services/knowledge/vector-store.service.js +24 -4
  412. package/dist/cli/backend/src/services/knowledge/vector-store.service.js.map +1 -1
  413. package/dist/cli/backend/src/services/mcp-server.d.ts +46 -2
  414. package/dist/cli/backend/src/services/mcp-server.d.ts.map +1 -1
  415. package/dist/cli/backend/src/services/mcp-server.js +216 -211
  416. package/dist/cli/backend/src/services/mcp-server.js.map +1 -1
  417. package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts +254 -0
  418. package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts.map +1 -0
  419. package/dist/cli/backend/src/services/mcp-tool-definitions.js +285 -0
  420. package/dist/cli/backend/src/services/mcp-tool-definitions.js.map +1 -0
  421. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts +18 -0
  422. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  423. package/dist/cli/backend/src/services/skill/skill-executor.service.js +7 -9
  424. package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -1
  425. package/dist/cli/backend/src/types/index.d.ts +22 -1
  426. package/dist/cli/backend/src/types/index.d.ts.map +1 -1
  427. package/dist/cli/backend/src/types/index.js.map +1 -1
  428. package/dist/cli/backend/src/types/skill.types.d.ts +9 -0
  429. package/dist/cli/backend/src/types/skill.types.d.ts.map +1 -1
  430. package/dist/cli/backend/src/types/skill.types.js.map +1 -1
  431. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  432. package/dist/cli/backend/src/types/v2/work-item.types.js +25 -1
  433. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  434. package/dist/cli/backend/src/utils/google-userinfo.utils.d.ts +41 -0
  435. package/dist/cli/backend/src/utils/google-userinfo.utils.d.ts.map +1 -0
  436. package/dist/cli/backend/src/utils/google-userinfo.utils.js +44 -0
  437. package/dist/cli/backend/src/utils/google-userinfo.utils.js.map +1 -0
  438. package/frontend/dist/assets/{index-9e6d97d1.js → index-7a4e7df5.js} +328 -326
  439. package/frontend/dist/assets/index-b7e59b2b.css +33 -0
  440. package/frontend/dist/index.html +2 -2
  441. package/package.json +2 -1
  442. package/config/skills/orchestrator/recall/SKILL.md +0 -47
  443. package/config/skills/orchestrator/recall/execute.sh +0 -13
  444. package/config/skills/orchestrator/record-learning/SKILL.md +0 -47
  445. package/config/skills/orchestrator/record-learning/execute.sh +0 -13
  446. package/config/skills/orchestrator/remember/SKILL.md +0 -55
  447. package/config/skills/orchestrator/remember/execute.sh +0 -15
  448. package/frontend/dist/assets/index-6aaa0630.css +0 -33
@@ -11,7 +11,8 @@
11
11
  import { PoolStorage } from './pool-storage.js';
12
12
  import { ClaimService } from './claim.service.js';
13
13
  import { LoggerService } from '../core/logger.service.js';
14
- import { isWorkItem, isValidWorkItemTransition } from '../../types/v2/work-item.types.js';
14
+ import { formatError } from '../../utils/format-error.js';
15
+ import { isWorkItem, isValidWorkItemTransition, isTransitionPermitted } from '../../types/v2/work-item.types.js';
15
16
  import { createTaskClaim, } from '../../types/v2/claim.types.js';
16
17
  // ---------------------------------------------------------------------------
17
18
  // Service
@@ -42,6 +43,15 @@ export class TaskPoolService {
42
43
  storage;
43
44
  claimService;
44
45
  logger;
46
+ /**
47
+ * Optional EventBus reference — wired via {@link setEventBusService} from
48
+ * the boot path. When set, {@link addToPool} publishes a `workitem:queued`
49
+ * event so subscribers (notably {@link RequestSlaSubscriber}) can react to
50
+ * queue mutations. Optional because singleton callers (tests, CLI) bring
51
+ * up the pool before the bus exists; missing bus is treated as a no-op
52
+ * publish at warn level.
53
+ */
54
+ eventBus = null;
45
55
  /**
46
56
  * Serializes claim operations to prevent the race where two concurrent
47
57
  * claimFromPool / claimSpecificItem calls both select the same queued
@@ -55,6 +65,17 @@ export class TaskPoolService {
55
65
  this.claimService = new ClaimService(this.storage);
56
66
  this.logger = LoggerService.getInstance().createComponentLogger('TaskPoolService');
57
67
  }
68
+ /**
69
+ * Wire the EventBus reference used by {@link addToPool} to publish
70
+ * `workitem:queued` events (INBOUND-1.f1). Called from the backend boot
71
+ * path after both services have been constructed. Idempotent and may be
72
+ * called with `null` to disable publishing (testing).
73
+ *
74
+ * @param bus - The EventBus instance, or null to clear
75
+ */
76
+ setEventBusService(bus) {
77
+ this.eventBus = bus;
78
+ }
58
79
  /**
59
80
  * Chains the given critical section after any in-flight claim operation.
60
81
  * Guarantees FIFO ordering even under concurrent invocation.
@@ -118,6 +139,177 @@ export class TaskPoolService {
118
139
  type: workItem.type,
119
140
  title: workItem.title,
120
141
  });
142
+ // INBOUND-1.f1: announce the queue mutation so subscribers (notably the
143
+ // RequestSlaSubscriber) can react. We publish AFTER the storage flush
144
+ // so any subscriber that re-reads via taskPool.findWorkItem sees the
145
+ // committed item. Publish failures are logged-but-isolated — the pool
146
+ // mutation is the source of truth, the event is informational.
147
+ this.publishWorkItemQueued(workItem);
148
+ }
149
+ /**
150
+ * INBOUND-1.f1 helper: publish a `workitem:queued` event with correlation
151
+ * ids the SLA subscriber needs (`requestId`, `missionId`, plus the new
152
+ * `workItemId`). Called by {@link addToPool} after the storage flush.
153
+ *
154
+ * Stays a separate method (vs inlining) so:
155
+ * 1. The dependency on EventBus stays explicit and grep-able.
156
+ * 2. A future caller adding an alternate enqueue path (e.g. a batch
157
+ * addAll) can route through the same publisher for consistent
158
+ * observability.
159
+ * 3. Error handling stays in one place — a thrown publisher must NOT
160
+ * back out the pool mutation (the storage write already committed).
161
+ */
162
+ publishWorkItemQueued(workItem) {
163
+ if (!this.eventBus) {
164
+ this.logger.debug('No EventBus wired — skipping workitem:queued publish', {
165
+ workItemId: workItem.id,
166
+ });
167
+ return;
168
+ }
169
+ try {
170
+ this.eventBus.publish({
171
+ // Deterministic event id keyed on the WI id so a redelivered storage
172
+ // path (theoretical) collapses through the bus's per-(type,session)
173
+ // debounce window without firing the SLA handler twice.
174
+ id: `workitem:queued:${workItem.id}`,
175
+ type: 'workitem:queued',
176
+ timestamp: new Date().toISOString(),
177
+ teamId: '',
178
+ teamName: '',
179
+ memberId: '',
180
+ memberName: '',
181
+ // sessionName is empty — `workitem:queued` is a system-level event
182
+ // not attributable to a specific agent session. The bus's dedup key
183
+ // is `${type}:${sessionName}` so a unique sessionName per WI would
184
+ // defeat the dedup; an empty sessionName scoped per-id is fine since
185
+ // the event id already encodes the WI uniquely.
186
+ sessionName: '',
187
+ previousValue: '',
188
+ newValue: workItem.status,
189
+ changedField: 'taskStatus',
190
+ // INBOUND-1.f1 correlation fields. Mandatory: workItemId. Optional:
191
+ // requestId, missionId — populated when the WI carries them. The SLA
192
+ // subscriber no-ops when requestId is undefined (per spec), so we
193
+ // don't need a fallback chain here.
194
+ workItemId: workItem.id,
195
+ requestId: workItem.requestId,
196
+ missionId: workItem.missionId,
197
+ });
198
+ }
199
+ catch (err) {
200
+ this.logger.warn('workitem:queued publish threw', {
201
+ workItemId: workItem.id,
202
+ error: formatError(err),
203
+ });
204
+ }
205
+ }
206
+ /**
207
+ * F1-BRIDGE-1 helper: publish `task:done_by_worker` after a successful
208
+ * `submitForVerification` transition. The {@link EventToWorkItemBridge}
209
+ * subscribes to this event and creates a verification WorkItem for the
210
+ * resolved team-lead session.
211
+ *
212
+ * Stays a separate method (mirrors {@link publishWorkItemQueued}) so:
213
+ * 1. The dependency on EventBus stays explicit and grep-able — Arch's
214
+ * grep guard checks both the type declaration AND a publish call
215
+ * site for `task:done_by_worker`.
216
+ * 2. Error handling is uniform with the other publishers — a thrown
217
+ * bus must NOT back out the verified state transition; the storage
218
+ * flush has already committed.
219
+ * 3. A future caller adding an alternate verification-submission path
220
+ * (e.g. an admin endpoint or a CLI) routes through the same publisher.
221
+ *
222
+ * The deterministic event id (`task:done_by_worker:${workItemId}`) keys
223
+ * dedup so a redelivered submitForVerification (theoretical) collapses
224
+ * through the bus without firing the bridge handler twice.
225
+ */
226
+ publishTaskDoneByWorker(workItem) {
227
+ if (!this.eventBus) {
228
+ this.logger.debug('No EventBus wired — skipping task:done_by_worker publish', {
229
+ workItemId: workItem.id,
230
+ });
231
+ return;
232
+ }
233
+ try {
234
+ this.eventBus.publish({
235
+ id: `task:done_by_worker:${workItem.id}`,
236
+ type: 'task:done_by_worker',
237
+ timestamp: new Date().toISOString(),
238
+ teamId: '',
239
+ teamName: '',
240
+ memberId: '',
241
+ memberName: '',
242
+ // sessionName is empty — `task:done_by_worker` is published from the
243
+ // pool layer which doesn't carry the worker's PTY session. The bridge
244
+ // uses `workItemId` (and falls back to `taskId`) to resolve the
245
+ // source WI; sessionName is informational. Empty matches the
246
+ // workitem:queued publish convention.
247
+ sessionName: '',
248
+ // The state machine just landed at done_by_worker. Carry both ends
249
+ // so any subscriber filtering on (previousValue, newValue) sees
250
+ // the canonical transition.
251
+ previousValue: 'running',
252
+ newValue: 'done_by_worker',
253
+ changedField: 'taskStatus',
254
+ // BRIDGE-1.1 hybrid extension. `workItemId` is mandatory for the
255
+ // bridge handler; the `taskId` fallback path is unused here because
256
+ // WorkItem itself does not carry a taskId (the bridge resolves the
257
+ // source WI by id alone via taskPool.findWorkItem).
258
+ workItemId: workItem.id,
259
+ missionId: workItem.missionId,
260
+ requestId: workItem.requestId,
261
+ });
262
+ }
263
+ catch (err) {
264
+ this.logger.warn('task:done_by_worker publish threw', {
265
+ workItemId: workItem.id,
266
+ error: formatError(err),
267
+ });
268
+ }
269
+ }
270
+ /**
271
+ * F1-BRIDGE-1 helper: publish `task:rejected` after a successful
272
+ * `verifyItem` transition with verdict='rejected'. The
273
+ * {@link EventToWorkItemBridge} subscribes and either creates a retry
274
+ * WI (`retryCount < maxRetries`) or escalates to a TL review WI with
275
+ * `reviewReason='max_retries_exceeded'` at the cap.
276
+ *
277
+ * Mirrors {@link publishTaskDoneByWorker} for symmetry. Only the
278
+ * verdict='rejected' branch reaches this publisher — `verdict='verified'`
279
+ * does NOT publish (terminal-success path goes through
280
+ * {@link resolveBlockedDependents}, which is not a bridge trigger).
281
+ */
282
+ publishTaskRejected(workItem) {
283
+ if (!this.eventBus) {
284
+ this.logger.debug('No EventBus wired — skipping task:rejected publish', {
285
+ workItemId: workItem.id,
286
+ });
287
+ return;
288
+ }
289
+ try {
290
+ this.eventBus.publish({
291
+ id: `task:rejected:${workItem.id}`,
292
+ type: 'task:rejected',
293
+ timestamp: new Date().toISOString(),
294
+ teamId: '',
295
+ teamName: '',
296
+ memberId: '',
297
+ memberName: '',
298
+ sessionName: '',
299
+ previousValue: 'done_by_worker',
300
+ newValue: 'rejected',
301
+ changedField: 'taskStatus',
302
+ workItemId: workItem.id,
303
+ missionId: workItem.missionId,
304
+ requestId: workItem.requestId,
305
+ });
306
+ }
307
+ catch (err) {
308
+ this.logger.warn('task:rejected publish threw', {
309
+ workItemId: workItem.id,
310
+ error: formatError(err),
311
+ });
312
+ }
121
313
  }
122
314
  /**
123
315
  * Claims the next available WorkItem from the pool for an agent.
@@ -159,13 +351,15 @@ export class TaskPoolService {
159
351
  return null;
160
352
  }
161
353
  const selected = candidates[0];
162
- // Transition item to 'running'
163
- const updated = await this.storage.updateWorkItem(selected.id, (wi) => {
164
- wi.status = 'running';
165
- wi.startedAt = new Date().toISOString();
354
+ // TRANS-2: route the queued → running flip through the guarded
355
+ // transitionStatus helper so internal callers are subject to the
356
+ // same V3 actor-role + state-machine gates as external callers.
357
+ // `startedAt` is set automatically by transitionStatus when
358
+ // newStatus === 'running'; the mutator carries the agent target.
359
+ const claimedItem = await this.transitionStatus(selected.id, 'running', 'system', (wi) => {
166
360
  wi.target = agentId;
167
361
  });
168
- if (!updated) {
362
+ if (!claimedItem) {
169
363
  this.logger.warn('Failed to update WorkItem during claim', { workItemId: selected.id });
170
364
  return null;
171
365
  }
@@ -183,8 +377,6 @@ export class TaskPoolService {
183
377
  claimId: claim.id,
184
378
  title: selected.title,
185
379
  });
186
- // Return the updated item
187
- const claimedItem = await this.storage.findWorkItem(selected.id);
188
380
  return {
189
381
  workItem: claimedItem,
190
382
  claim,
@@ -214,20 +406,19 @@ export class TaskPoolService {
214
406
  const claims = await this.storage.getClaims();
215
407
  if (claims.some((c) => c.workItemId === workItemId && c.status === 'active'))
216
408
  return null;
217
- const updated = await this.storage.updateWorkItem(workItemId, (wi) => {
218
- wi.status = 'running';
219
- wi.startedAt = new Date().toISOString();
409
+ // TRANS-2: route the queued running flip through transitionStatus
410
+ // (mirrors claimFromPool — same V3 + state-machine gates).
411
+ const claimedItem = await this.transitionStatus(workItemId, 'running', 'system', (wi) => {
220
412
  wi.target = agentId;
221
413
  });
222
- if (!updated)
414
+ if (!claimedItem)
223
415
  return null;
224
416
  const claimInput = { workItemId, agentId };
225
417
  const claim = createTaskClaim(claimInput);
226
418
  await this.storage.addClaim(claim);
227
419
  await this.storage.flush();
228
420
  this.logger.info('WorkItem claimed (specific)', { workItemId, agentId, claimId: claim.id });
229
- const claimedItem = await this.storage.findWorkItem(workItemId);
230
- return claimedItem ? { workItem: claimedItem, claim } : null;
421
+ return { workItem: claimedItem, claim };
231
422
  });
232
423
  }
233
424
  /**
@@ -256,11 +447,14 @@ export class TaskPoolService {
256
447
  c.endReason = reason;
257
448
  });
258
449
  }
259
- // Revert item to queued. Preserve target when unblocking so the
260
- // same agent can re-claim via target filter.
450
+ // TRANS-2: route the (running|blocked) queued flip through the
451
+ // guarded transitionStatus helper. The state-machine entry for
452
+ // `running → queued` is a TRANS-2 addition, gated to system/TL/orc;
453
+ // `blocked → queued` was already TL/orc/system-gated by TRANS-1.
454
+ // Side-effect mutations (startedAt clear, target preservation when
455
+ // unblocking, retryCount bump) move into the atomic mutator.
261
456
  const wasBlocked = workItem.status === 'blocked';
262
- await this.storage.updateWorkItem(workItemId, (wi) => {
263
- wi.status = 'queued';
457
+ await this.transitionStatus(workItemId, 'queued', 'system', (wi) => {
264
458
  wi.startedAt = undefined;
265
459
  if (!wasBlocked) {
266
460
  wi.target = undefined;
@@ -275,40 +469,204 @@ export class TaskPoolService {
275
469
  });
276
470
  }
277
471
  /**
278
- * Marks a work item as completed ('done').
472
+ * Decide whether a WorkItem requires TL verification before reaching a
473
+ * terminal-success status.
279
474
  *
280
- * @param workItemId - ID of the work item
281
- * @param result - Optional result data
282
- * @throws Error if work item not found or not in 'running' status
475
+ * Default policy (VERIF-1):
476
+ * - `delegate` items default to *requires verification* — a worker
477
+ * marking their own delegated work as "done" should produce
478
+ * `done_by_worker` and wake the TL for sign-off.
479
+ * - All other types (`cron_run`, `notify`, `reconcile`, `check`,
480
+ * `confirm`, `review`, ...) default to simple completion (`done`).
481
+ *
482
+ * The default is overridable via `wi.metadata.requiresVerification`:
483
+ * - `true` — force verification path even for non-delegate types
484
+ * - `false` — skip verification even for delegate types (this is the
485
+ * F-H affordance REVIEW-1 needs: review WIs are themselves the
486
+ * verification step, so they must NOT loop back to TL self-verify)
487
+ *
488
+ * @param wi - The WorkItem under inspection
489
+ * @returns true when the verification path should fire
283
490
  */
284
- async completeItem(workItemId, result) {
285
- const workItem = await this.storage.findWorkItem(workItemId);
286
- if (!workItem) {
287
- throw new Error(`WorkItem not found: ${workItemId}`);
288
- }
289
- if (workItem.status !== 'running') {
290
- throw new Error(`Cannot complete WorkItem: status must be 'running', got '${workItem.status}'`);
291
- }
292
- // Release the claim
491
+ requiresVerification(wi) {
492
+ const metaFlag = wi.metadata?.requiresVerification;
493
+ if (metaFlag === true)
494
+ return true;
495
+ if (metaFlag === false)
496
+ return false;
497
+ return wi.type === 'delegate';
498
+ }
499
+ /**
500
+ * Internal: release the active claim on a WorkItem (if any). Shared by
501
+ * `completeSimpleItem` and `submitForVerification` because both
502
+ * represent the worker handing the item back to the system.
503
+ *
504
+ * @param workItemId - WorkItem whose claim should be released
505
+ * @param endReason - Reason recorded on the claim (`completed` /
506
+ * `submitted_for_verification`) for auditability
507
+ */
508
+ async releaseClaim(workItemId, endReason) {
293
509
  const claim = await this.storage.findActiveClaimByWorkItem(workItemId);
294
- if (claim) {
295
- await this.storage.updateClaim(claim.id, (c) => {
296
- c.status = 'released';
297
- c.endedAt = new Date().toISOString();
298
- c.endReason = 'completed';
299
- });
510
+ if (!claim)
511
+ return;
512
+ await this.storage.updateClaim(claim.id, (c) => {
513
+ c.status = 'released';
514
+ c.endedAt = new Date().toISOString();
515
+ c.endReason = endReason;
516
+ });
517
+ }
518
+ /**
519
+ * Worker reports a delegated WorkItem as done and submits it for TL
520
+ * verification.
521
+ *
522
+ * Transitions `running → done_by_worker` via {@link transitionStatus},
523
+ * which enforces the V3 actor-role gate (`'agent'` is allowed; any
524
+ * other role throws). The active claim is released as
525
+ * `submitted_for_verification` so the TL queue is the only thing
526
+ * blocking forward progress.
527
+ *
528
+ * Used by the `completeItem` facade for `delegate`-type items and any
529
+ * item whose `metadata.requiresVerification === true`.
530
+ *
531
+ * @param workItemId - WorkItem id
532
+ * @param actorRole - Role of the caller (`'agent'` for normal worker
533
+ * submissions; passed through to `isTransitionPermitted`)
534
+ * @param result - Optional result payload to attach to the WorkItem
535
+ * @returns The updated WorkItem, or `null` if the WI was deleted
536
+ * between the find and the update (race window)
537
+ * @throws When the WorkItem is missing, the transition is invalid, or
538
+ * the actor is not permitted to perform `running → done_by_worker`.
539
+ */
540
+ async submitForVerification(workItemId, actorRole, result) {
541
+ await this.releaseClaim(workItemId, 'submitted_for_verification');
542
+ const updated = await this.transitionStatus(workItemId, 'done_by_worker', actorRole, (wi) => {
543
+ if (result)
544
+ wi.result = result;
545
+ });
546
+ await this.storage.flush();
547
+ this.logger.info('WorkItem submitted for verification', {
548
+ workItemId,
549
+ actorRole,
550
+ // BRIDGE-1 turns this into a real `task:done_by_worker` event below
551
+ // (F1-BRIDGE-1). The log line is preserved as a wake signal for any
552
+ // tail-based TL-watching subscriber that still greps it.
553
+ tlWakeRequested: true,
554
+ });
555
+ // F1-BRIDGE-1: publish AFTER the storage flush so any subscriber that
556
+ // re-reads via taskPool.findWorkItem sees the committed `done_by_worker`
557
+ // status. `updated` is null only on race-window deletion — skip the
558
+ // publish in that case (no source WI for the bridge to resolve).
559
+ if (updated) {
560
+ this.publishTaskDoneByWorker(updated);
300
561
  }
301
- // Mark item done
302
- await this.storage.updateWorkItem(workItemId, (wi) => {
303
- wi.status = 'done';
304
- wi.completedAt = new Date().toISOString();
562
+ return updated;
563
+ }
564
+ /**
565
+ * Worker (or system) marks a non-delegated WorkItem as fully done.
566
+ *
567
+ * Transitions `running → done` via {@link transitionStatus}. Used by
568
+ * the `completeItem` facade for `cron_run`, `notify`, `reconcile`,
569
+ * `check`, `confirm`, `review` types — anything whose lifecycle does
570
+ * NOT include a TL verification step.
571
+ *
572
+ * The Reconciler service is the only system-actor caller and uses
573
+ * `actorRole='system'`, which bypasses the actor check while still
574
+ * respecting the state-machine legality check.
575
+ *
576
+ * @param workItemId - WorkItem id
577
+ * @param actorRole - Role of the caller (`'agent'`, `'system'`, etc.)
578
+ * @param result - Optional result payload to attach
579
+ * @returns The updated WorkItem, or `null` if the WI was deleted
580
+ * between the find and the update (race window)
581
+ * @throws When the WorkItem is missing, the transition is invalid, or
582
+ * the actor is not permitted to perform `running → done`.
583
+ */
584
+ async completeSimpleItem(workItemId, actorRole, result) {
585
+ await this.releaseClaim(workItemId, 'completed');
586
+ const updated = await this.transitionStatus(workItemId, 'done', actorRole, (wi) => {
305
587
  if (result)
306
588
  wi.result = result;
307
589
  });
308
590
  // Promote any blocked dependents whose deps are now all satisfied.
309
591
  await this.resolveBlockedDependents(workItemId);
310
592
  await this.storage.flush();
311
- this.logger.info('WorkItem completed', { workItemId });
593
+ this.logger.info('WorkItem completed', { workItemId, actorRole });
594
+ return updated;
595
+ }
596
+ /**
597
+ * TL records a verdict on a WorkItem in `done_by_worker` status.
598
+ *
599
+ * `verified` advances to terminal success and unblocks dependents;
600
+ * `rejected` parks the item until the TL re-queues it (which TRANS-1
601
+ * gates to TL/orchestrator/system actors). Worker-actor calls throw
602
+ * automatically via {@link transitionStatus}'s permission gate — we
603
+ * do NOT need to add an explicit role check here, the matrix in
604
+ * `TRANSITION_PERMISSIONS` handles it.
605
+ *
606
+ * @param workItemId - WorkItem id (must currently be `done_by_worker`)
607
+ * @param actorRole - Role of the caller (`'team_lead'` or `'orchestrator'`
608
+ * for verify/reject; worker calls throw)
609
+ * @param verdict - `'verified'` or `'rejected'`
610
+ * @param comment - Optional reviewer comment recorded in `wi.error`
611
+ * (the field is reused — the WorkItem schema does not yet have a
612
+ * dedicated `verifierComment` slot; keeping this on `error` lets
613
+ * downstream UIs render TL feedback alongside failure causes)
614
+ * @returns The updated WorkItem, or `null` on race-window deletion
615
+ * @throws When the WorkItem is missing, the verdict is invalid, the
616
+ * transition is illegal, or the actor is not permitted to verify.
617
+ */
618
+ async verifyItem(workItemId, actorRole, verdict, comment) {
619
+ if (verdict !== 'verified' && verdict !== 'rejected') {
620
+ throw new Error(`Invalid verdict: "${verdict}". Must be "verified" or "rejected".`);
621
+ }
622
+ const updated = await this.transitionStatus(workItemId, verdict, actorRole, (wi) => {
623
+ if (comment)
624
+ wi.error = comment;
625
+ });
626
+ if (verdict === 'verified') {
627
+ await this.resolveBlockedDependents(workItemId);
628
+ }
629
+ await this.storage.flush();
630
+ this.logger.info('WorkItem verdict recorded', { workItemId, verdict, actorRole });
631
+ // F1-BRIDGE-1: only the `rejected` branch publishes. The bridge's
632
+ // task:rejected handler creates the retry-or-escalate WI; the `verified`
633
+ // branch is terminal-success and unblocks dependents above without any
634
+ // bridge involvement. Skip the publish on race-window deletion.
635
+ if (verdict === 'rejected' && updated) {
636
+ this.publishTaskRejected(updated);
637
+ }
638
+ return updated;
639
+ }
640
+ /**
641
+ * Legacy facade — picks the verification path for the caller.
642
+ *
643
+ * Existing call sites (REST controller, task-management controllers,
644
+ * V3 data service) invoke `completeItem(id, result)` without an
645
+ * explicit actor role. The facade reads the WorkItem, applies the
646
+ * {@link requiresVerification} policy, and dispatches to either
647
+ * {@link submitForVerification} (delegate items / explicit opt-in)
648
+ * or {@link completeSimpleItem} (everything else).
649
+ *
650
+ * The legacy actor role for these implicit callers is `'agent'`.
651
+ * Migrations to explicit-actor calls can land in follow-up tickets
652
+ * without touching the five call sites in this PR.
653
+ *
654
+ * @param workItemId - WorkItem id
655
+ * @param result - Optional result payload
656
+ * @throws When the WorkItem is missing or the underlying transition
657
+ * is rejected (invalid state, forbidden actor).
658
+ */
659
+ async completeItem(workItemId, result) {
660
+ const workItem = await this.storage.findWorkItem(workItemId);
661
+ if (!workItem) {
662
+ throw new Error(`WorkItem not found: ${workItemId}`);
663
+ }
664
+ if (this.requiresVerification(workItem)) {
665
+ await this.submitForVerification(workItemId, 'agent', result);
666
+ }
667
+ else {
668
+ await this.completeSimpleItem(workItemId, 'agent', result);
669
+ }
312
670
  }
313
671
  /**
314
672
  * Scans blocked WorkItems that list `completedId` in their `dependsOn` and
@@ -332,9 +690,12 @@ export class TaskPoolService {
332
690
  const allSatisfied = (candidate.dependsOn ?? []).every((depId) => terminalSuccess.has(depId));
333
691
  if (!allSatisfied)
334
692
  continue;
335
- await this.storage.updateWorkItem(candidate.id, (wi) => {
336
- wi.status = 'queued';
337
- });
693
+ // TRANS-2: route the blocked → queued promotion through
694
+ // transitionStatus. The 'system' actor matches both the V3
695
+ // permission gate (blocked→queued requires TL/orc/system) and
696
+ // the existing intent — dependency resolution is server-side
697
+ // bookkeeping, not a user-initiated action.
698
+ await this.transitionStatus(candidate.id, 'queued', 'system');
338
699
  this.logger.info('WorkItem unblocked — all deps satisfied', {
339
700
  workItemId: candidate.id,
340
701
  via: completedId,
@@ -366,10 +727,10 @@ export class TaskPoolService {
366
727
  c.endReason = `failed: ${error}`;
367
728
  });
368
729
  }
369
- // Mark item failed
370
- await this.storage.updateWorkItem(workItemId, (wi) => {
371
- wi.status = 'failed';
372
- wi.completedAt = new Date().toISOString();
730
+ // TRANS-2: route the running → failed flip through transitionStatus.
731
+ // `completedAt` is set automatically when newStatus === 'failed'; the
732
+ // mutator only needs to attach the error description.
733
+ await this.transitionStatus(workItemId, 'failed', 'system', (wi) => {
373
734
  wi.error = error;
374
735
  });
375
736
  await this.storage.flush();
@@ -514,6 +875,22 @@ export class TaskPoolService {
514
875
  async getAllItems() {
515
876
  return this.storage.getWorkItems();
516
877
  }
878
+ /**
879
+ * Find a WorkItem by id without mutating it.
880
+ *
881
+ * Public read accessor used by callers that need to inspect a
882
+ * specific item — for example REVIEW-1's reentrancy lock checks the
883
+ * status of a Mission's `pendingReviewWorkItemId` to decide whether
884
+ * to clear the lock. Returns `null` (not `undefined`) so callers can
885
+ * use a uniform null-fallthrough idiom shared with
886
+ * `getWorkItemSnapshot` and `transitionStatus`.
887
+ *
888
+ * @param workItemId - WorkItem id to look up
889
+ * @returns The WorkItem, or `null` if no item has that id
890
+ */
891
+ async findWorkItem(workItemId) {
892
+ return (await this.storage.findWorkItem(workItemId)) ?? null;
893
+ }
517
894
  /**
518
895
  * Removes a WorkItem from the pool entirely.
519
896
  * Used for purging old completed/cancelled items.
@@ -543,11 +920,19 @@ export class TaskPoolService {
543
920
  * Updates a work item's status directly.
544
921
  * Used by the Reconciler for corrections (e.g., stuck → blocked).
545
922
  *
923
+ * Reconciler invocations pass through `actorRole='system'` (default) which
924
+ * bypasses the per-role gate at {@link isTransitionPermitted} but still
925
+ * enforces the state-machine via {@link isValidWorkItemTransition}. Other
926
+ * callers MUST supply the actor's role so TRANS-1 V3 enforcement applies.
927
+ *
546
928
  * @param workItemId - The work item ID
547
929
  * @param newStatus - The target status
548
- * @throws Error if work item not found or transition is invalid
930
+ * @param actorRole - Role of the caller (defaults to `'system'` for Reconciler)
931
+ * @throws Error if work item not found
932
+ * @throws Error if transition is invalid (state machine — see WORK_ITEM_TRANSITIONS)
933
+ * @throws Error if actor is not permitted (role check — see TRANSITION_PERMISSIONS)
549
934
  */
550
- async updateItemStatus(workItemId, newStatus) {
935
+ async updateItemStatus(workItemId, newStatus, actorRole = 'system') {
551
936
  const items = await this.storage.getWorkItems();
552
937
  const item = items.find((wi) => wi.id === workItemId);
553
938
  if (!item) {
@@ -556,6 +941,11 @@ export class TaskPoolService {
556
941
  if (!isValidWorkItemTransition(item.status, newStatus)) {
557
942
  throw new Error(`Invalid status transition for WorkItem ${workItemId}: ${item.status} → ${newStatus}`);
558
943
  }
944
+ // TRANS-1 V3: enforce per-role permissions. system role always passes.
945
+ if (!isTransitionPermitted(item.status, newStatus, actorRole)) {
946
+ throw new Error(`Forbidden transition for WorkItem ${workItemId}: actor='${actorRole}' ` +
947
+ `not permitted to perform ${item.status} → ${newStatus}.`);
948
+ }
559
949
  await this.storage.updateWorkItem(workItemId, (wi) => {
560
950
  wi.status = newStatus;
561
951
  // Use startedAt for running, completedAt for done/failed
@@ -570,7 +960,97 @@ export class TaskPoolService {
570
960
  workItemId,
571
961
  from: item.status,
572
962
  to: newStatus,
963
+ actorRole,
964
+ });
965
+ }
966
+ /**
967
+ * Public guarded transition helper — TRANS-1's canonical entrypoint.
968
+ *
969
+ * Routes any externally-initiated WorkItem status change through the
970
+ * combined state-machine + actor-role gates. Designed as the public API
971
+ * VERIF-1 will call from `submitForVerification` / `verifyItem`, and as
972
+ * the recommended path for any future caller that previously reached
973
+ * for `storage.updateWorkItem` to flip status.
974
+ *
975
+ * Differences vs {@link updateItemStatus}:
976
+ * - Requires `actorRole` explicitly (no default) — forces the caller to
977
+ * decide the trust posture rather than silently inheriting `'system'`.
978
+ * - Accepts an optional `mutator` so callers can carry additional field
979
+ * updates (e.g. `result`, `error`, `completedAt`) atomically with the
980
+ * status flip — preventing races between status update and metadata
981
+ * attachment that direct `storage.updateWorkItem` callers risked.
982
+ *
983
+ * @param workItemId - The work item ID
984
+ * @param newStatus - The target status
985
+ * @param actorRole - Role of the caller (REQUIRED; pass `'system'` for trusted server-internal paths)
986
+ * @param mutator - Optional additional WorkItem field updates applied atomically with the status change
987
+ * @returns The updated WorkItem after the transition
988
+ * @throws Error if work item not found
989
+ * @throws Error if transition is invalid (state machine)
990
+ * @throws Error if actor is not permitted (role check)
991
+ *
992
+ * @example
993
+ * ```typescript
994
+ * // VERIF-1 worker submitting for verification
995
+ * await pool.transitionStatus(wiId, 'done_by_worker', 'agent', (wi) => {
996
+ * wi.result = output;
997
+ * });
998
+ *
999
+ * // VERIF-1 TL verifying
1000
+ * await pool.transitionStatus(wiId, 'verified', 'team_lead');
1001
+ *
1002
+ * // VERIF-1 TL rejecting (allowed for TL only)
1003
+ * await pool.transitionStatus(wiId, 'rejected', 'team_lead', (wi) => {
1004
+ * wi.error = 'Did not meet acceptance criteria';
1005
+ * });
1006
+ * ```
1007
+ */
1008
+ async transitionStatus(workItemId, newStatus, actorRole, mutator) {
1009
+ const item = await this.storage.findWorkItem(workItemId);
1010
+ if (!item) {
1011
+ throw new Error(`WorkItem not found: ${workItemId}`);
1012
+ }
1013
+ if (!isValidWorkItemTransition(item.status, newStatus)) {
1014
+ throw new Error(`Invalid status transition for WorkItem ${workItemId}: ${item.status} → ${newStatus}`);
1015
+ }
1016
+ if (!isTransitionPermitted(item.status, newStatus, actorRole)) {
1017
+ throw new Error(`Forbidden transition for WorkItem ${workItemId}: actor='${actorRole}' ` +
1018
+ `not permitted to perform ${item.status} → ${newStatus}.`);
1019
+ }
1020
+ const ok = await this.storage.updateWorkItem(workItemId, (wi) => {
1021
+ wi.status = newStatus;
1022
+ // Standard timestamp side-effects mirror updateItemStatus so callers
1023
+ // get consistent metadata regardless of which API they used.
1024
+ if (newStatus === 'running') {
1025
+ wi.startedAt = new Date().toISOString();
1026
+ }
1027
+ else if (newStatus === 'done' ||
1028
+ newStatus === 'failed' ||
1029
+ newStatus === 'verified' ||
1030
+ newStatus === 'done_by_worker' ||
1031
+ newStatus === 'rejected') {
1032
+ wi.completedAt = new Date().toISOString();
1033
+ }
1034
+ if (mutator)
1035
+ mutator(wi);
1036
+ });
1037
+ if (!ok) {
1038
+ // Race window: someone else removed the WorkItem between findWorkItem
1039
+ // and updateWorkItem. Surface as null so callers can distinguish from
1040
+ // a thrown invalid-transition / forbidden-transition error.
1041
+ return null;
1042
+ }
1043
+ this.logger.info('WorkItem transitioned', {
1044
+ workItemId,
1045
+ from: item.status,
1046
+ to: newStatus,
1047
+ actorRole,
573
1048
  });
1049
+ // Return the post-update WorkItem snapshot so callers can chain on the
1050
+ // resolved value rather than re-fetching. Coerce `undefined` (item
1051
+ // removed during the race window) to `null` to match the declared
1052
+ // return type.
1053
+ return (await this.storage.findWorkItem(workItemId)) ?? null;
574
1054
  }
575
1055
  /**
576
1056
  * Update token usage and cost on a WorkItem.