crewly 1.20.361 → 1.20.363

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 (370) hide show
  1. package/config/hooks/destructive-git-guard/guard.mjs +96 -0
  2. package/config/roles/team-leader/prompt.md +1 -1
  3. package/config/roles/team-leader/tl-addon.md +1 -1
  4. package/config/skills/agent/core/ask-owner/SKILL.md +31 -0
  5. package/config/skills/agent/core/ask-owner/execute.sh +14 -3
  6. package/config/skills/agent/core/ask-owner/execute.test.sh +8 -0
  7. package/config/skills/agent/core/gmail-read/SKILL.md +8 -0
  8. package/config/skills/agent/core/gmail-send/SKILL.md +10 -3
  9. package/config/skills/agent/core/gmail-send/execute.sh +2 -1
  10. package/config/skills/agent/core/gmail-watch-thread/SKILL.md +69 -0
  11. package/config/skills/agent/core/gmail-watch-thread/execute.sh +51 -0
  12. package/config/skills/agent/core/project-tickets/SKILL.md +12 -0
  13. package/config/skills/agent/core/project-tickets/execute.sh +13 -3
  14. package/config/skills/agent/core/project-tickets/execute.test.sh +4 -0
  15. package/config/skills/agent/core/watch-for-event/SKILL.md +22 -0
  16. package/config/skills/orchestrator/harness-login/SKILL.md +1 -1
  17. package/config/skills/team-leader/delegate-task/SKILL.md +21 -6
  18. package/config/skills/team-leader/delegate-task/execute.sh +12 -1
  19. package/dist/backend/backend/src/constants.d.ts +216 -8
  20. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  21. package/dist/backend/backend/src/constants.js +217 -9
  22. package/dist/backend/backend/src/constants.js.map +1 -1
  23. package/dist/backend/backend/src/controllers/google/google.controller.d.ts +44 -0
  24. package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
  25. package/dist/backend/backend/src/controllers/google/google.controller.js +121 -3
  26. package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
  27. package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
  28. package/dist/backend/backend/src/controllers/google/google.routes.js +5 -1
  29. package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
  30. package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts +1 -1
  31. package/dist/backend/backend/src/controllers/harness/owner-login.controller.js +1 -1
  32. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
  33. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  34. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +2 -1
  35. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  36. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  37. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +7 -4
  38. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  39. package/dist/backend/backend/src/index.d.ts.map +1 -1
  40. package/dist/backend/backend/src/index.js +114 -1
  41. package/dist/backend/backend/src/index.js.map +1 -1
  42. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
  43. package/dist/backend/backend/src/services/agent/follow-through.wiring.js +2 -0
  44. package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
  45. package/dist/backend/backend/src/services/agent/handover-interrupted.d.ts +76 -0
  46. package/dist/backend/backend/src/services/agent/handover-interrupted.d.ts.map +1 -0
  47. package/dist/backend/backend/src/services/agent/handover-interrupted.js +139 -0
  48. package/dist/backend/backend/src/services/agent/handover-interrupted.js.map +1 -0
  49. package/dist/backend/backend/src/services/agent/handover-pointers.d.ts +4 -0
  50. package/dist/backend/backend/src/services/agent/handover-pointers.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/agent/handover-pointers.js +11 -4
  52. package/dist/backend/backend/src/services/agent/handover-pointers.js.map +1 -1
  53. package/dist/backend/backend/src/services/agent/idle-work-guard.d.ts +3 -1
  54. package/dist/backend/backend/src/services/agent/idle-work-guard.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/agent/idle-work-guard.js +2 -0
  56. package/dist/backend/backend/src/services/agent/idle-work-guard.js.map +1 -1
  57. package/dist/backend/backend/src/services/agent/resource-mode-protection.d.ts +5 -1
  58. package/dist/backend/backend/src/services/agent/resource-mode-protection.d.ts.map +1 -1
  59. package/dist/backend/backend/src/services/agent/resource-mode-protection.js +6 -1
  60. package/dist/backend/backend/src/services/agent/resource-mode-protection.js.map +1 -1
  61. package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts +7 -0
  62. package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
  63. package/dist/backend/backend/src/services/agent/runtime-session-recovery.js +18 -1
  64. package/dist/backend/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
  65. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +2 -1
  67. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  68. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +8 -0
  69. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -1
  70. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +10 -0
  71. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -1
  72. package/dist/backend/backend/src/services/decisions/decision-card.d.ts +46 -13
  73. package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -1
  74. package/dist/backend/backend/src/services/decisions/decision-card.js +100 -50
  75. package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -1
  76. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts +23 -1
  77. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
  78. package/dist/backend/backend/src/services/decisions/decision-contract.js +76 -4
  79. package/dist/backend/backend/src/services/decisions/decision-contract.js.map +1 -1
  80. package/dist/backend/backend/src/services/decisions/decision-wording.d.ts +167 -0
  81. package/dist/backend/backend/src/services/decisions/decision-wording.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/decisions/decision-wording.js +406 -0
  83. package/dist/backend/backend/src/services/decisions/decision-wording.js.map +1 -0
  84. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +30 -1
  85. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  86. package/dist/backend/backend/src/services/decisions/decision.service.js +44 -8
  87. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  88. package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts.map +1 -1
  89. package/dist/backend/backend/src/services/decisions/decision.wiring.js +17 -0
  90. package/dist/backend/backend/src/services/decisions/decision.wiring.js.map +1 -1
  91. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  92. package/dist/backend/backend/src/services/event-bus/event-bus.service.js +1 -0
  93. package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  94. package/dist/backend/backend/src/services/google/gmail-reply-watch.service.d.ts +142 -0
  95. package/dist/backend/backend/src/services/google/gmail-reply-watch.service.d.ts.map +1 -0
  96. package/dist/backend/backend/src/services/google/gmail-reply-watch.service.js +304 -0
  97. package/dist/backend/backend/src/services/google/gmail-reply-watch.service.js.map +1 -0
  98. package/dist/backend/backend/src/services/google/gmail-send-approval.service.d.ts +130 -0
  99. package/dist/backend/backend/src/services/google/gmail-send-approval.service.d.ts.map +1 -0
  100. package/dist/backend/backend/src/services/google/gmail-send-approval.service.js +216 -0
  101. package/dist/backend/backend/src/services/google/gmail-send-approval.service.js.map +1 -0
  102. package/dist/backend/backend/src/services/google/gmail-send-gate.d.ts +28 -13
  103. package/dist/backend/backend/src/services/google/gmail-send-gate.d.ts.map +1 -1
  104. package/dist/backend/backend/src/services/google/gmail-send-gate.js +82 -29
  105. package/dist/backend/backend/src/services/google/gmail-send-gate.js.map +1 -1
  106. package/dist/backend/backend/src/services/google/gmail.service.d.ts +90 -0
  107. package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -1
  108. package/dist/backend/backend/src/services/google/gmail.service.js +122 -0
  109. package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -1
  110. package/dist/backend/backend/src/services/harness/claude-accounts.d.ts +24 -14
  111. package/dist/backend/backend/src/services/harness/claude-accounts.d.ts.map +1 -1
  112. package/dist/backend/backend/src/services/harness/claude-accounts.js +35 -21
  113. package/dist/backend/backend/src/services/harness/claude-accounts.js.map +1 -1
  114. package/dist/backend/backend/src/services/harness/claude-native-login.d.ts +106 -0
  115. package/dist/backend/backend/src/services/harness/claude-native-login.d.ts.map +1 -0
  116. package/dist/backend/backend/src/services/harness/claude-native-login.js +174 -0
  117. package/dist/backend/backend/src/services/harness/claude-native-login.js.map +1 -0
  118. package/dist/backend/backend/src/services/harness/claude-token-migration.service.d.ts +158 -0
  119. package/dist/backend/backend/src/services/harness/claude-token-migration.service.d.ts.map +1 -0
  120. package/dist/backend/backend/src/services/harness/claude-token-migration.service.js +283 -0
  121. package/dist/backend/backend/src/services/harness/claude-token-migration.service.js.map +1 -0
  122. package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts +44 -24
  123. package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts.map +1 -1
  124. package/dist/backend/backend/src/services/harness/harness-credentials.store.js +96 -39
  125. package/dist/backend/backend/src/services/harness/harness-credentials.store.js.map +1 -1
  126. package/dist/backend/backend/src/services/harness/harness-registry.js +1 -1
  127. package/dist/backend/backend/src/services/harness/harness-registry.js.map +1 -1
  128. package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts +2 -2
  129. package/dist/backend/backend/src/services/harness/harness-relogin.service.js +2 -2
  130. package/dist/backend/backend/src/services/harness/harness.service.d.ts.map +1 -1
  131. package/dist/backend/backend/src/services/harness/harness.service.js +10 -1
  132. package/dist/backend/backend/src/services/harness/harness.service.js.map +1 -1
  133. package/dist/backend/backend/src/services/harness/login-broker.service.d.ts +24 -21
  134. package/dist/backend/backend/src/services/harness/login-broker.service.d.ts.map +1 -1
  135. package/dist/backend/backend/src/services/harness/login-broker.service.js +50 -64
  136. package/dist/backend/backend/src/services/harness/login-broker.service.js.map +1 -1
  137. package/dist/backend/backend/src/services/harness/login-rules.d.ts +17 -11
  138. package/dist/backend/backend/src/services/harness/login-rules.d.ts.map +1 -1
  139. package/dist/backend/backend/src/services/harness/login-rules.js +22 -25
  140. package/dist/backend/backend/src/services/harness/login-rules.js.map +1 -1
  141. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +79 -2
  142. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  143. package/dist/backend/backend/src/services/open-items/open-item-extractor.js +194 -13
  144. package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  145. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +65 -1
  146. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  147. package/dist/backend/backend/src/services/open-items/open-items.service.js +140 -7
  148. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  149. package/dist/backend/backend/src/services/open-items/open-items.wiring.d.ts +8 -0
  150. package/dist/backend/backend/src/services/open-items/open-items.wiring.d.ts.map +1 -1
  151. package/dist/backend/backend/src/services/open-items/open-items.wiring.js +19 -1
  152. package/dist/backend/backend/src/services/open-items/open-items.wiring.js.map +1 -1
  153. package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts +35 -19
  154. package/dist/backend/backend/src/services/orc/owner-completion-report.service.d.ts.map +1 -1
  155. package/dist/backend/backend/src/services/orc/owner-completion-report.service.js +60 -63
  156. package/dist/backend/backend/src/services/orc/owner-completion-report.service.js.map +1 -1
  157. package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts +86 -0
  158. package/dist/backend/backend/src/services/orc/owner-completion-report.text.d.ts.map +1 -0
  159. package/dist/backend/backend/src/services/orc/owner-completion-report.text.js +170 -0
  160. package/dist/backend/backend/src/services/orc/owner-completion-report.text.js.map +1 -0
  161. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.d.ts +24 -0
  162. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.d.ts.map +1 -1
  163. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.js +38 -1
  164. package/dist/backend/backend/src/services/orc/owner-completion-report.wiring.js.map +1 -1
  165. package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts +2 -0
  166. package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
  167. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +8 -3
  168. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
  169. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +76 -0
  170. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  171. package/dist/backend/backend/src/services/orc/work-item-destination.js +83 -3
  172. package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
  173. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts +31 -2
  174. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts.map +1 -1
  175. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js +38 -0
  176. package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js.map +1 -1
  177. package/dist/backend/backend/src/services/project-tickets/project-ticket.service.d.ts +5 -0
  178. package/dist/backend/backend/src/services/project-tickets/project-ticket.service.d.ts.map +1 -1
  179. package/dist/backend/backend/src/services/project-tickets/project-ticket.service.js +30 -6
  180. package/dist/backend/backend/src/services/project-tickets/project-ticket.service.js.map +1 -1
  181. package/dist/backend/backend/src/services/project-tickets/ticket-file.d.ts.map +1 -1
  182. package/dist/backend/backend/src/services/project-tickets/ticket-file.js +6 -1
  183. package/dist/backend/backend/src/services/project-tickets/ticket-file.js.map +1 -1
  184. package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.d.ts +64 -0
  185. package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.d.ts.map +1 -0
  186. package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.js +93 -0
  187. package/dist/backend/backend/src/services/project-tickets/ticket-hold-registry.js.map +1 -0
  188. package/dist/backend/backend/src/services/project-tickets/ticket-hold.d.ts +176 -0
  189. package/dist/backend/backend/src/services/project-tickets/ticket-hold.d.ts.map +1 -0
  190. package/dist/backend/backend/src/services/project-tickets/ticket-hold.js +301 -0
  191. package/dist/backend/backend/src/services/project-tickets/ticket-hold.js.map +1 -0
  192. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +33 -3
  193. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  194. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +50 -4
  195. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  196. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +8 -1
  197. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  198. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +26 -0
  199. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  200. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +6 -1
  201. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  202. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +3 -2
  203. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  204. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.d.ts.map +1 -1
  205. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js +4 -1
  206. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js.map +1 -1
  207. package/dist/backend/backend/src/services/stall/background-run-detector.d.ts +56 -0
  208. package/dist/backend/backend/src/services/stall/background-run-detector.d.ts.map +1 -0
  209. package/dist/backend/backend/src/services/stall/background-run-detector.js +93 -0
  210. package/dist/backend/backend/src/services/stall/background-run-detector.js.map +1 -0
  211. package/dist/backend/backend/src/services/stall/claim-check.d.ts +60 -0
  212. package/dist/backend/backend/src/services/stall/claim-check.d.ts.map +1 -0
  213. package/dist/backend/backend/src/services/stall/claim-check.js +98 -0
  214. package/dist/backend/backend/src/services/stall/claim-check.js.map +1 -0
  215. package/dist/backend/backend/src/services/stall/in-flight-runs.store.d.ts +110 -0
  216. package/dist/backend/backend/src/services/stall/in-flight-runs.store.d.ts.map +1 -0
  217. package/dist/backend/backend/src/services/stall/in-flight-runs.store.js +109 -0
  218. package/dist/backend/backend/src/services/stall/in-flight-runs.store.js.map +1 -0
  219. package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts +490 -0
  220. package/dist/backend/backend/src/services/stall/stall-watcher.service.d.ts.map +1 -0
  221. package/dist/backend/backend/src/services/stall/stall-watcher.service.js +922 -0
  222. package/dist/backend/backend/src/services/stall/stall-watcher.service.js.map +1 -0
  223. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.d.ts +98 -0
  224. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.d.ts.map +1 -0
  225. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.js +217 -0
  226. package/dist/backend/backend/src/services/stall/stall-watcher.wiring.js.map +1 -0
  227. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +53 -0
  228. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  229. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +162 -15
  230. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  231. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js +1 -1
  232. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js.map +1 -1
  233. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts +3 -0
  234. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts.map +1 -1
  235. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js +5 -2
  236. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js.map +1 -1
  237. package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.d.ts +7 -2
  238. package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.d.ts.map +1 -1
  239. package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.js +11 -2
  240. package/dist/backend/backend/src/services/worktree/worktree-unmerged-notice.js.map +1 -1
  241. package/dist/backend/backend/src/types/decision.types.d.ts +53 -1
  242. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
  243. package/dist/backend/backend/src/types/event-bus.types.d.ts +4 -2
  244. package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
  245. package/dist/backend/backend/src/types/event-bus.types.js +8 -0
  246. package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
  247. package/dist/backend/backend/src/types/project-ticket.types.d.ts +7 -0
  248. package/dist/backend/backend/src/types/project-ticket.types.d.ts.map +1 -1
  249. package/dist/backend/backend/src/types/project-ticket.types.js +1 -0
  250. package/dist/backend/backend/src/types/project-ticket.types.js.map +1 -1
  251. package/dist/backend/backend/src/types/v2/open-item.types.d.ts +10 -1
  252. package/dist/backend/backend/src/types/v2/open-item.types.d.ts.map +1 -1
  253. package/dist/backend/build-info.json +2 -2
  254. package/dist/cli/backend/src/constants.d.ts +216 -8
  255. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  256. package/dist/cli/backend/src/constants.js +217 -9
  257. package/dist/cli/backend/src/constants.js.map +1 -1
  258. package/dist/cli/backend/src/services/agent/handover-interrupted.d.ts +76 -0
  259. package/dist/cli/backend/src/services/agent/handover-interrupted.d.ts.map +1 -0
  260. package/dist/cli/backend/src/services/agent/handover-interrupted.js +139 -0
  261. package/dist/cli/backend/src/services/agent/handover-interrupted.js.map +1 -0
  262. package/dist/cli/backend/src/services/agent/handover-pointers.d.ts +4 -0
  263. package/dist/cli/backend/src/services/agent/handover-pointers.d.ts.map +1 -1
  264. package/dist/cli/backend/src/services/agent/handover-pointers.js +11 -4
  265. package/dist/cli/backend/src/services/agent/handover-pointers.js.map +1 -1
  266. package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts +7 -0
  267. package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
  268. package/dist/cli/backend/src/services/agent/runtime-session-recovery.js +18 -1
  269. package/dist/cli/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
  270. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  271. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +2 -1
  272. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  273. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts +8 -0
  274. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -1
  275. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js +10 -0
  276. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -1
  277. package/dist/cli/backend/src/services/decisions/decision-card.d.ts +46 -13
  278. package/dist/cli/backend/src/services/decisions/decision-card.d.ts.map +1 -1
  279. package/dist/cli/backend/src/services/decisions/decision-card.js +100 -50
  280. package/dist/cli/backend/src/services/decisions/decision-card.js.map +1 -1
  281. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts +23 -1
  282. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
  283. package/dist/cli/backend/src/services/decisions/decision-contract.js +76 -4
  284. package/dist/cli/backend/src/services/decisions/decision-contract.js.map +1 -1
  285. package/dist/cli/backend/src/services/decisions/decision-wording.d.ts +167 -0
  286. package/dist/cli/backend/src/services/decisions/decision-wording.d.ts.map +1 -0
  287. package/dist/cli/backend/src/services/decisions/decision-wording.js +406 -0
  288. package/dist/cli/backend/src/services/decisions/decision-wording.js.map +1 -0
  289. package/dist/cli/backend/src/services/decisions/decision.service.d.ts +30 -1
  290. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  291. package/dist/cli/backend/src/services/decisions/decision.service.js +44 -8
  292. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
  293. package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  294. package/dist/cli/backend/src/services/event-bus/event-bus.service.js +1 -0
  295. package/dist/cli/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  296. package/dist/cli/backend/src/services/harness/claude-accounts.d.ts +24 -14
  297. package/dist/cli/backend/src/services/harness/claude-accounts.d.ts.map +1 -1
  298. package/dist/cli/backend/src/services/harness/claude-accounts.js +35 -21
  299. package/dist/cli/backend/src/services/harness/claude-accounts.js.map +1 -1
  300. package/dist/cli/backend/src/services/harness/claude-native-login.d.ts +106 -0
  301. package/dist/cli/backend/src/services/harness/claude-native-login.d.ts.map +1 -0
  302. package/dist/cli/backend/src/services/harness/claude-native-login.js +174 -0
  303. package/dist/cli/backend/src/services/harness/claude-native-login.js.map +1 -0
  304. package/dist/cli/backend/src/services/harness/claude-token-migration.service.d.ts +158 -0
  305. package/dist/cli/backend/src/services/harness/claude-token-migration.service.d.ts.map +1 -0
  306. package/dist/cli/backend/src/services/harness/claude-token-migration.service.js +283 -0
  307. package/dist/cli/backend/src/services/harness/claude-token-migration.service.js.map +1 -0
  308. package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts +44 -24
  309. package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts.map +1 -1
  310. package/dist/cli/backend/src/services/harness/harness-credentials.store.js +96 -39
  311. package/dist/cli/backend/src/services/harness/harness-credentials.store.js.map +1 -1
  312. package/dist/cli/backend/src/services/harness/harness-registry.js +1 -1
  313. package/dist/cli/backend/src/services/harness/harness-registry.js.map +1 -1
  314. package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts +2 -2
  315. package/dist/cli/backend/src/services/harness/harness-relogin.service.js +2 -2
  316. package/dist/cli/backend/src/services/harness/harness.service.d.ts.map +1 -1
  317. package/dist/cli/backend/src/services/harness/harness.service.js +10 -1
  318. package/dist/cli/backend/src/services/harness/harness.service.js.map +1 -1
  319. package/dist/cli/backend/src/services/harness/login-broker.service.d.ts +24 -21
  320. package/dist/cli/backend/src/services/harness/login-broker.service.d.ts.map +1 -1
  321. package/dist/cli/backend/src/services/harness/login-broker.service.js +50 -64
  322. package/dist/cli/backend/src/services/harness/login-broker.service.js.map +1 -1
  323. package/dist/cli/backend/src/services/harness/login-rules.d.ts +17 -11
  324. package/dist/cli/backend/src/services/harness/login-rules.d.ts.map +1 -1
  325. package/dist/cli/backend/src/services/harness/login-rules.js +22 -25
  326. package/dist/cli/backend/src/services/harness/login-rules.js.map +1 -1
  327. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +79 -2
  328. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  329. package/dist/cli/backend/src/services/open-items/open-item-extractor.js +194 -13
  330. package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  331. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +65 -1
  332. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  333. package/dist/cli/backend/src/services/open-items/open-items.service.js +140 -7
  334. package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
  335. package/dist/cli/backend/src/services/orc/owner-completion-report.text.d.ts +86 -0
  336. package/dist/cli/backend/src/services/orc/owner-completion-report.text.d.ts.map +1 -0
  337. package/dist/cli/backend/src/services/orc/owner-completion-report.text.js +170 -0
  338. package/dist/cli/backend/src/services/orc/owner-completion-report.text.js.map +1 -0
  339. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +76 -0
  340. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  341. package/dist/cli/backend/src/services/orc/work-item-destination.js +83 -3
  342. package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
  343. package/dist/cli/backend/src/services/project-tickets/project-ticket.service.d.ts +5 -0
  344. package/dist/cli/backend/src/services/project-tickets/project-ticket.service.d.ts.map +1 -1
  345. package/dist/cli/backend/src/services/project-tickets/project-ticket.service.js +30 -6
  346. package/dist/cli/backend/src/services/project-tickets/project-ticket.service.js.map +1 -1
  347. package/dist/cli/backend/src/services/project-tickets/ticket-file.d.ts.map +1 -1
  348. package/dist/cli/backend/src/services/project-tickets/ticket-file.js +6 -1
  349. package/dist/cli/backend/src/services/project-tickets/ticket-file.js.map +1 -1
  350. package/dist/cli/backend/src/services/project-tickets/ticket-hold.d.ts +176 -0
  351. package/dist/cli/backend/src/services/project-tickets/ticket-hold.d.ts.map +1 -0
  352. package/dist/cli/backend/src/services/project-tickets/ticket-hold.js +301 -0
  353. package/dist/cli/backend/src/services/project-tickets/ticket-hold.js.map +1 -0
  354. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +53 -0
  355. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  356. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +162 -15
  357. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  358. package/dist/cli/backend/src/types/decision.types.d.ts +53 -1
  359. package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
  360. package/dist/cli/backend/src/types/event-bus.types.d.ts +4 -2
  361. package/dist/cli/backend/src/types/event-bus.types.d.ts.map +1 -1
  362. package/dist/cli/backend/src/types/event-bus.types.js +8 -0
  363. package/dist/cli/backend/src/types/event-bus.types.js.map +1 -1
  364. package/dist/cli/backend/src/types/project-ticket.types.d.ts +7 -0
  365. package/dist/cli/backend/src/types/project-ticket.types.d.ts.map +1 -1
  366. package/dist/cli/backend/src/types/project-ticket.types.js +1 -0
  367. package/dist/cli/backend/src/types/project-ticket.types.js.map +1 -1
  368. package/dist/cli/backend/src/types/v2/open-item.types.d.ts +10 -1
  369. package/dist/cli/backend/src/types/v2/open-item.types.d.ts.map +1 -1
  370. package/package.json +1 -1
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Gmail Reply Watch
3
+ *
4
+ * Wakes the agent that owns a Gmail thread when a new reply lands
5
+ * (CREW-257, specs/2026-10-06-gmail-approve-send-and-reply-wake.md §2).
6
+ *
7
+ * One `history.list` per connected account per tick
8
+ * ({@link GOOGLE_WORKSPACE_CONSTANTS.GMAIL_WATCH_POLL_MS}), however many
9
+ * threads are watched, and only while something is watched. New messages in
10
+ * watched threads are published as `gmail:reply_received` — ids only; the
11
+ * message text is outside input and the event never carries it.
12
+ *
13
+ * Cursor (`historyId` per account) and fired message ids are persisted
14
+ * before publishing: a restart never repeats a wake or re-reads old
15
+ * history. A 404 (cursor too old) re-seeds the cursor and resyncs each
16
+ * watched thread once. Every watch expires 14 days after its last reply and
17
+ * goes when its account is no longer connected.
18
+ *
19
+ * @module services/google/gmail-reply-watch
20
+ */
21
+ import { promises as fs } from 'node:fs';
22
+ import path from 'node:path';
23
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
24
+ import { atomicWriteJson } from '../../utils/file-io.utils.js';
25
+ import { LoggerService } from '../core/logger.service.js';
26
+ /** Event-bus session name prefix; per thread so the bus never merges two threads. */
27
+ export const GMAIL_EVENT_SESSION_PREFIX = 'gmail:';
28
+ /** Seen ids kept per thread (oldest dropped). */
29
+ const MAX_SEEN_PER_THREAD = 500;
30
+ /**
31
+ * Whether a message is the owner's own mail or a draft, not a reply.
32
+ *
33
+ * @param m - Thread message
34
+ * @returns True for the owner's own mail
35
+ */
36
+ export function isOwnMessage(m) {
37
+ return m.labelIds.includes('SENT') || m.labelIds.includes('DRAFT');
38
+ }
39
+ /**
40
+ * Watches Gmail threads for replies and publishes an event for each.
41
+ */
42
+ export class GmailReplyWatchService {
43
+ deps;
44
+ logger;
45
+ state = null;
46
+ timer = null;
47
+ polling = false;
48
+ current = Promise.resolve();
49
+ running = false;
50
+ /** Consecutive not-connected ticks per account (memory only) */
51
+ misses = new Map();
52
+ constructor(deps) {
53
+ this.deps = deps;
54
+ this.logger = LoggerService.getInstance().createComponentLogger('GmailReplyWatch');
55
+ }
56
+ now() {
57
+ return this.deps.now ? this.deps.now() : Date.now();
58
+ }
59
+ /** Load persisted watches and arm the timer if there are any. */
60
+ async start() {
61
+ this.running = true;
62
+ await this.load();
63
+ this.arm();
64
+ }
65
+ /** Stop for good (shutdown). */
66
+ stop() {
67
+ this.running = false;
68
+ this.disarm();
69
+ }
70
+ /** The timer runs only while at least one thread is watched. */
71
+ arm() {
72
+ if (!this.running || this.timer || !this.state || this.state.watches.length === 0)
73
+ return;
74
+ this.timer = setInterval(() => void this.poll(), GOOGLE_WORKSPACE_CONSTANTS.GMAIL_WATCH_POLL_MS);
75
+ this.timer.unref?.();
76
+ }
77
+ disarm() {
78
+ if (this.timer)
79
+ clearInterval(this.timer);
80
+ this.timer = null;
81
+ }
82
+ /**
83
+ * Watch a thread for an agent. Existing messages are marked seen; an
84
+ * account's first watch seeds its cursor from the profile.
85
+ *
86
+ * @param threadId - Gmail thread id
87
+ * @param ownerSession - Agent to wake
88
+ * @param account - Google account; the default account when omitted
89
+ * @returns The watch
90
+ */
91
+ async watch(threadId, ownerSession, account) {
92
+ const acct = account?.trim() || (await this.deps.defaultAccount());
93
+ const st = await this.load();
94
+ const existing = st.watches.find((w) => w.threadId === threadId && w.account === acct);
95
+ if (existing) {
96
+ existing.ownerSession = ownerSession;
97
+ existing.lastActivityAt = new Date(this.now()).toISOString();
98
+ await this.save();
99
+ return existing;
100
+ }
101
+ const gmail = this.deps.gmailFor(acct);
102
+ const messages = await gmail.getThread(threadId);
103
+ if (!st.cursors[acct])
104
+ st.cursors[acct] = (await gmail.getProfile()).historyId;
105
+ const iso = new Date(this.now()).toISOString();
106
+ const entry = { threadId, ownerSession, account: acct, seen: messages.map((m) => m.id), createdAt: iso, lastActivityAt: iso };
107
+ st.watches.push(entry);
108
+ await this.save();
109
+ this.arm();
110
+ this.logger.info('Watching Gmail thread', { threadId, ownerSession, account: acct });
111
+ return entry;
112
+ }
113
+ /**
114
+ * Stop watching a thread.
115
+ *
116
+ * @param threadId - Gmail thread id
117
+ * @param ownerSession - Only the owning agent may remove it
118
+ * @returns True when a watch was removed
119
+ */
120
+ async unwatch(threadId, ownerSession) {
121
+ const st = await this.load();
122
+ const before = st.watches.length;
123
+ st.watches = st.watches.filter((w) => !(w.threadId === threadId && w.ownerSession === ownerSession));
124
+ if (st.watches.length === before)
125
+ return false;
126
+ this.prune(st);
127
+ await this.save();
128
+ return true;
129
+ }
130
+ /**
131
+ * The watches, optionally for one agent.
132
+ *
133
+ * @param ownerSession - Filter
134
+ * @returns Watches
135
+ */
136
+ async list(ownerSession) {
137
+ const st = await this.load();
138
+ return ownerSession ? st.watches.filter((w) => w.ownerSession === ownerSession) : [...st.watches];
139
+ }
140
+ /**
141
+ * One tick: expire old watches, then one `history.list` per account that
142
+ * has watches; publish an event per new reply.
143
+ *
144
+ * @returns Number of events published
145
+ */
146
+ poll() {
147
+ const p = this.doPoll();
148
+ this.current = p;
149
+ return p;
150
+ }
151
+ /**
152
+ * Resolves when the tick in progress (if any) has finished.
153
+ *
154
+ * @returns Nothing
155
+ */
156
+ async whenIdle() {
157
+ await this.current;
158
+ }
159
+ async doPoll() {
160
+ if (this.polling)
161
+ return 0;
162
+ this.polling = true;
163
+ let fired = 0;
164
+ try {
165
+ const st = await this.load();
166
+ await this.expire(st);
167
+ for (const account of [...new Set(st.watches.map((w) => w.account))]) {
168
+ if (!(await this.accountStillConnected(account, st)))
169
+ continue;
170
+ try {
171
+ fired += await this.pollAccount(account, st);
172
+ }
173
+ catch (err) {
174
+ this.logger.warn('Gmail reply poll failed', { account, error: err instanceof Error ? err.message : String(err) });
175
+ }
176
+ }
177
+ this.prune(st);
178
+ if (st.watches.length === 0)
179
+ this.disarm();
180
+ }
181
+ finally {
182
+ this.polling = false;
183
+ }
184
+ return fired;
185
+ }
186
+ async pollAccount(account, st) {
187
+ const gmail = this.deps.gmailFor(account);
188
+ const mine = st.watches.filter((w) => w.account === account);
189
+ const cursor = st.cursors[account];
190
+ const fresh = new Map();
191
+ let nextCursor;
192
+ try {
193
+ if (!cursor)
194
+ throw Object.assign(new Error('no cursor'), { status: 404 });
195
+ const { historyId, added } = await gmail.listHistoryAdded(cursor);
196
+ nextCursor = historyId;
197
+ const watched = new Set(mine.map((w) => w.threadId));
198
+ for (const a of added) {
199
+ if (!watched.has(a.threadId) || mine.some((w) => w.threadId === a.threadId && w.seen.includes(a.id)) || fresh.has(a.id))
200
+ continue;
201
+ const meta = await gmail.getMessageMeta(a.id);
202
+ fresh.set(a.id, { ...meta, threadId: a.threadId });
203
+ }
204
+ }
205
+ catch (err) {
206
+ if (err.status !== 404)
207
+ throw err;
208
+ // Cursor missing or too old: re-seed it, then look at each watched thread once.
209
+ this.logger.warn('Gmail history cursor expired — resyncing watched threads', { account });
210
+ nextCursor = (await gmail.getProfile()).historyId;
211
+ for (const w of mine) {
212
+ for (const m of await gmail.getThread(w.threadId)) {
213
+ if (!w.seen.includes(m.id) && !fresh.has(m.id))
214
+ fresh.set(m.id, { ...m, threadId: w.threadId });
215
+ }
216
+ }
217
+ }
218
+ // Persist cursor + fired ids BEFORE publishing (at-most-once).
219
+ const toFire = [];
220
+ for (const m of fresh.values()) {
221
+ const w = mine.find((x) => x.threadId === m.threadId);
222
+ if (!w)
223
+ continue;
224
+ w.seen = [...w.seen, m.id].slice(-MAX_SEEN_PER_THREAD);
225
+ if (!isOwnMessage(m)) {
226
+ w.lastActivityAt = new Date(this.now()).toISOString();
227
+ toFire.push({ w, m });
228
+ }
229
+ }
230
+ st.cursors[account] = nextCursor;
231
+ await this.save();
232
+ for (const { w, m } of toFire)
233
+ this.deps.publish(this.eventFor(w, m));
234
+ return toFire.length;
235
+ }
236
+ /** Drop watches idle for longer than the expiry. */
237
+ async expire(st) {
238
+ const limit = this.now() - GOOGLE_WORKSPACE_CONSTANTS.GMAIL_WATCH_EXPIRY_MS;
239
+ const keep = st.watches.filter((w) => Date.parse(w.lastActivityAt) > limit);
240
+ if (keep.length === st.watches.length)
241
+ return;
242
+ this.logger.info('Gmail thread watches expired', { removed: st.watches.length - keep.length });
243
+ st.watches = keep;
244
+ this.prune(st);
245
+ await this.save();
246
+ }
247
+ /** An account that stays disconnected loses its watches (after a few ticks, not one blip). */
248
+ async accountStillConnected(account, st) {
249
+ if (await this.deps.accountConnected(account)) {
250
+ this.misses.delete(account);
251
+ return true;
252
+ }
253
+ const n = (this.misses.get(account) ?? 0) + 1;
254
+ this.misses.set(account, n);
255
+ if (n >= GOOGLE_WORKSPACE_CONSTANTS.GMAIL_WATCH_DISCONNECT_MISSES) {
256
+ st.watches = st.watches.filter((w) => w.account !== account);
257
+ delete st.cursors[account];
258
+ this.misses.delete(account);
259
+ this.logger.info('Account disconnected — Gmail watches removed', { account });
260
+ await this.save();
261
+ }
262
+ return false;
263
+ }
264
+ /** Forget cursors of accounts with no watches. */
265
+ prune(st) {
266
+ for (const a of Object.keys(st.cursors))
267
+ if (!st.watches.some((w) => w.account === a))
268
+ delete st.cursors[a];
269
+ }
270
+ eventFor(w, m) {
271
+ return {
272
+ id: `gmail:reply:${m.id}`,
273
+ type: 'gmail:reply_received',
274
+ timestamp: new Date(this.now()).toISOString(),
275
+ teamId: '',
276
+ teamName: '',
277
+ memberId: '',
278
+ memberName: '',
279
+ sessionName: `${GMAIL_EVENT_SESSION_PREFIX}${w.threadId}`,
280
+ previousValue: '',
281
+ newValue: m.id,
282
+ changedField: 'gmailReply',
283
+ threadId: w.threadId,
284
+ target: w.ownerSession,
285
+ };
286
+ }
287
+ async load() {
288
+ if (this.state)
289
+ return this.state;
290
+ try {
291
+ const parsed = JSON.parse(await fs.readFile(this.deps.file, 'utf-8'));
292
+ this.state = { cursors: parsed.cursors ?? {}, watches: Array.isArray(parsed.watches) ? parsed.watches : [] };
293
+ }
294
+ catch {
295
+ this.state = { cursors: {}, watches: [] };
296
+ }
297
+ return this.state;
298
+ }
299
+ async save() {
300
+ await fs.mkdir(path.dirname(this.deps.file), { recursive: true });
301
+ await atomicWriteJson(this.deps.file, this.state);
302
+ }
303
+ }
304
+ //# sourceMappingURL=gmail-reply-watch.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-reply-watch.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-reply-watch.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Gmail Send Approval
3
+ *
4
+ * Puts a drafted email in front of the owner as a decision card and sends
5
+ * it only when the owner taps Send (CREW-257,
6
+ * specs/2026-10-06-gmail-approve-send-and-reply-wake.md).
7
+ *
8
+ * The send happens in exactly one place, {@link GmailSendApprovalService.onSettled},
9
+ * which the decision service calls when an owner answer settles a
10
+ * `gmail_send` card (button, reaction, thread reply, dashboard). Nothing an
11
+ * agent can call reaches it, so an agent cannot approve its own mail.
12
+ *
13
+ * Each approval is bound to one hold (`agent:draftId`) and to a fingerprint
14
+ * of the draft as the owner saw it. A hold settles once; a draft that was
15
+ * edited after the card was raised is not sent, a new card is raised for the
16
+ * edited text instead.
17
+ *
18
+ * @module services/google/gmail-send-approval
19
+ */
20
+ import type { OwnerDecision, DecisionOption, GmailSendSubject } from '../../types/decision.types.js';
21
+ import type { DecisionKindHandler } from '../decisions/decision.service.js';
22
+ import type { GmailDraftContent, GmailSendResult } from './gmail.service.js';
23
+ import { type HeldSend } from './gmail-send-gate.js';
24
+ /** Option keys on the card. */
25
+ export declare const GMAIL_SEND_KEYS: {
26
+ readonly SEND: "send";
27
+ readonly NOT_NOW: "not_now";
28
+ };
29
+ /** How long the card waits before the (safe) default, Discard, applies. */
30
+ export declare const GMAIL_SEND_DEADLINE_MS: number;
31
+ /** The Gmail calls the approval needs. */
32
+ export interface GmailDraftApi {
33
+ getDraft(draftId: string): Promise<GmailDraftContent>;
34
+ sendDraft(draftId: string): Promise<GmailSendResult>;
35
+ }
36
+ /** The decision-service surface used here. */
37
+ export interface GmailApprovalDecisions {
38
+ askPrebuilt(ask: {
39
+ kind: 'gmail_send';
40
+ asker: string;
41
+ question: string;
42
+ options: DecisionOption[];
43
+ defaultKey: string;
44
+ yesKey: string;
45
+ deadline: Date;
46
+ sensitive: 'email';
47
+ gmail: GmailSendSubject;
48
+ }): Promise<OwnerDecision>;
49
+ chooseFromDashboard(id: string, optionKey: string): Promise<OwnerDecision>;
50
+ replyInThread?(id: string, text: string): Promise<boolean>;
51
+ cancelWhere?(predicate: (d: OwnerDecision) => boolean, reason: string): Promise<unknown>;
52
+ }
53
+ /** Dependencies, injected so tests need no Slack or Google. */
54
+ export interface GmailSendApprovalDeps {
55
+ /** Gmail access for the account the draft lives in */
56
+ gmailFor: (account?: string) => GmailDraftApi;
57
+ /** The decision service, once cards run */
58
+ decisions: () => GmailApprovalDecisions | null;
59
+ /** Whether a card can be posted at all */
60
+ canAskInSlack: () => boolean;
61
+ /** Tell the drafting agent what happened */
62
+ tellAgent: (session: string, message: string) => Promise<unknown>;
63
+ /** Display name for the card */
64
+ agentNameOf?: (session: string) => Promise<string | undefined>;
65
+ /** Called after a send so replies on the thread wake the agent */
66
+ onSent?: (info: {
67
+ threadId: string;
68
+ agentSession: string;
69
+ account?: string;
70
+ }) => Promise<void> | void;
71
+ }
72
+ /**
73
+ * sha256 of everything that gets sent: sender, every recipient (including
74
+ * Bcc, a recipient the other parties never see), subject, both bodies and
75
+ * the attachments' names and sizes. A change to any of them is a different
76
+ * email from the one the owner approved.
77
+ *
78
+ * @param d - Draft content
79
+ * @returns Hex digest
80
+ */
81
+ export declare function fingerprintDraft(d: GmailDraftContent): string;
82
+ /**
83
+ * The card question: who it goes to (all of them, Bcc included), what it says.
84
+ *
85
+ * @param agentName - Drafting agent
86
+ * @param d - The draft
87
+ * @returns mrkdwn
88
+ */
89
+ export declare function approvalQuestion(agentName: string, d: GmailDraftContent): string;
90
+ /**
91
+ * Raises the send card and applies the owner's answer.
92
+ */
93
+ export declare class GmailSendApprovalService implements DecisionKindHandler {
94
+ private readonly deps;
95
+ private readonly logger;
96
+ /** Holds being discarded by the owner right now (their settle note says the draft is gone) */
97
+ private readonly discarding;
98
+ constructor(deps: GmailSendApprovalDeps);
99
+ /**
100
+ * A draft was held: read it, remember what the owner will be shown and ask.
101
+ *
102
+ * @param hold - The held send
103
+ * @returns True when a card was raised
104
+ */
105
+ onHeld(hold: HeldSend): Promise<boolean>;
106
+ /**
107
+ * The owner answered on the dashboard route: settle the card so it and the
108
+ * send agree.
109
+ *
110
+ * @param holdId - Hold id
111
+ * @param answer - What the owner chose
112
+ * @returns True when a card carried the answer
113
+ */
114
+ answerFromOwner(holdId: string, answer: 'send' | 'discard'): Promise<boolean>;
115
+ /**
116
+ * The owner discarded the draft on the owner-only route (which deletes it).
117
+ * Settles the card as "not now" and tells the agent the draft is gone.
118
+ *
119
+ * @param holdId - Hold id
120
+ */
121
+ discarded(holdId: string): Promise<void>;
122
+ /**
123
+ * A `gmail_send` card settled. The only place a held draft is sent.
124
+ *
125
+ * @param decision - The settled decision
126
+ * @returns Note for the drafting agent, or null
127
+ */
128
+ onSettled(decision: OwnerDecision): Promise<string | null>;
129
+ }
130
+ //# sourceMappingURL=gmail-send-approval.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-send-approval.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail-send-approval.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Gmail Send Approval
3
+ *
4
+ * Puts a drafted email in front of the owner as a decision card and sends
5
+ * it only when the owner taps Send (CREW-257,
6
+ * specs/2026-10-06-gmail-approve-send-and-reply-wake.md).
7
+ *
8
+ * The send happens in exactly one place, {@link GmailSendApprovalService.onSettled},
9
+ * which the decision service calls when an owner answer settles a
10
+ * `gmail_send` card (button, reaction, thread reply, dashboard). Nothing an
11
+ * agent can call reaches it, so an agent cannot approve its own mail.
12
+ *
13
+ * Each approval is bound to one hold (`agent:draftId`) and to a fingerprint
14
+ * of the draft as the owner saw it. A hold settles once; a draft that was
15
+ * edited after the card was raised is not sent, a new card is raised for the
16
+ * edited text instead.
17
+ *
18
+ * @module services/google/gmail-send-approval
19
+ */
20
+ import { createHash } from 'node:crypto';
21
+ import { LoggerService } from '../core/logger.service.js';
22
+ import { clearHeldSend, getHeldSend, holdGmailSend, markSettled, updateHeldSend, wasSettled } from './gmail-send-gate.js';
23
+ /** Option keys on the card. */
24
+ export const GMAIL_SEND_KEYS = { SEND: 'send', NOT_NOW: 'not_now' };
25
+ /** How long the card waits before the (safe) default, Discard, applies. */
26
+ export const GMAIL_SEND_DEADLINE_MS = 24 * 60 * 60 * 1000;
27
+ /** Body characters shown on the card. */
28
+ const PREVIEW_CHARS = 700;
29
+ /**
30
+ * sha256 of everything that gets sent: sender, every recipient (including
31
+ * Bcc, a recipient the other parties never see), subject, both bodies and
32
+ * the attachments' names and sizes. A change to any of them is a different
33
+ * email from the one the owner approved.
34
+ *
35
+ * @param d - Draft content
36
+ * @returns Hex digest
37
+ */
38
+ export function fingerprintDraft(d) {
39
+ return createHash('sha256')
40
+ .update(JSON.stringify([d.from, d.to, d.cc, d.bcc, d.subject, d.body, d.html, d.attachments.map((a) => [a.filename, a.size])]))
41
+ .digest('hex');
42
+ }
43
+ /**
44
+ * The card question: who it goes to (all of them, Bcc included), what it says.
45
+ *
46
+ * @param agentName - Drafting agent
47
+ * @param d - The draft
48
+ * @returns mrkdwn
49
+ */
50
+ export function approvalQuestion(agentName, d) {
51
+ const body = d.body.length > PREVIEW_CHARS ? `${d.body.slice(0, PREVIEW_CHARS)}…` : d.body;
52
+ const lines = [`Send this to ${d.to}?`, `${agentName} drafted an email and wants it sent.`];
53
+ if (d.from)
54
+ lines.push(`*From:* ${d.from}`);
55
+ lines.push(`*To:* ${d.to}`);
56
+ if (d.cc)
57
+ lines.push(`*Cc:* ${d.cc}`);
58
+ if (d.bcc)
59
+ lines.push(`*Bcc:* ${d.bcc} _(the other recipients will not see this)_`);
60
+ lines.push(`*Subject:* ${d.subject}`);
61
+ if (d.attachments.length)
62
+ lines.push(`*Attachments:* ${d.attachments.map((a) => `${a.filename} (${a.size} B)`).join(', ')}`);
63
+ return `${lines.join('\n')}\n\n${body || '(no plain-text body)'}`;
64
+ }
65
+ /**
66
+ * Raises the send card and applies the owner's answer.
67
+ */
68
+ export class GmailSendApprovalService {
69
+ deps;
70
+ logger;
71
+ /** Holds being discarded by the owner right now (their settle note says the draft is gone) */
72
+ discarding = new Set();
73
+ constructor(deps) {
74
+ this.deps = deps;
75
+ this.logger = LoggerService.getInstance().createComponentLogger('GmailSendApproval');
76
+ }
77
+ /**
78
+ * A draft was held: read it, remember what the owner will be shown and ask.
79
+ *
80
+ * @param hold - The held send
81
+ * @returns True when a card was raised
82
+ */
83
+ async onHeld(hold) {
84
+ const decisions = this.deps.decisions();
85
+ if (!decisions || !this.deps.canAskInSlack())
86
+ return false;
87
+ const gmail = this.deps.gmailFor(hold.account);
88
+ const draft = await gmail.getDraft(hold.draftId);
89
+ const fingerprint = fingerprintDraft(draft);
90
+ const agentName = (await this.deps.agentNameOf?.(hold.agentSession).catch(() => undefined)) ?? hold.agentSession;
91
+ const decision = await decisions.askPrebuilt({
92
+ kind: 'gmail_send',
93
+ asker: hold.agentSession,
94
+ question: approvalQuestion(agentName, draft),
95
+ options: [
96
+ { key: GMAIL_SEND_KEYS.SEND, label: 'Yes', detail: 'send exactly this email, once' },
97
+ { key: GMAIL_SEND_KEYS.NOT_NOW, label: 'Not now', detail: 'nothing is sent; the draft stays in Gmail' },
98
+ ],
99
+ defaultKey: GMAIL_SEND_KEYS.NOT_NOW,
100
+ yesKey: GMAIL_SEND_KEYS.SEND,
101
+ deadline: new Date(Date.now() + GMAIL_SEND_DEADLINE_MS),
102
+ sensitive: 'email',
103
+ gmail: { agentSession: hold.agentSession, agentName, holdId: hold.id, draftId: hold.draftId, to: hold.to, subject: hold.subject },
104
+ });
105
+ updateHeldSend(hold.id, { fingerprint, decisionId: decision.id });
106
+ this.logger.info('Asked the owner about a drafted email', { holdId: hold.id, decisionId: decision.id });
107
+ return true;
108
+ }
109
+ /**
110
+ * The owner answered on the dashboard route: settle the card so it and the
111
+ * send agree.
112
+ *
113
+ * @param holdId - Hold id
114
+ * @param answer - What the owner chose
115
+ * @returns True when a card carried the answer
116
+ */
117
+ async answerFromOwner(holdId, answer) {
118
+ const hold = getHeldSend(holdId);
119
+ const decisions = this.deps.decisions();
120
+ if (!hold?.decisionId || !decisions)
121
+ return false;
122
+ await decisions.chooseFromDashboard(hold.decisionId, answer === 'send' ? GMAIL_SEND_KEYS.SEND : GMAIL_SEND_KEYS.NOT_NOW);
123
+ return true;
124
+ }
125
+ /**
126
+ * The owner discarded the draft on the owner-only route (which deletes it).
127
+ * Settles the card as "not now" and tells the agent the draft is gone.
128
+ *
129
+ * @param holdId - Hold id
130
+ */
131
+ async discarded(holdId) {
132
+ this.discarding.add(holdId);
133
+ try {
134
+ await this.answerFromOwner(holdId, 'discard');
135
+ }
136
+ finally {
137
+ this.discarding.delete(holdId);
138
+ }
139
+ }
140
+ /**
141
+ * A `gmail_send` card settled. The only place a held draft is sent.
142
+ *
143
+ * @param decision - The settled decision
144
+ * @returns Note for the drafting agent, or null
145
+ */
146
+ async onSettled(decision) {
147
+ const subject = decision.gmail;
148
+ if (!subject)
149
+ return null;
150
+ const hold = getHeldSend(subject.holdId);
151
+ const sendChosen = decision.status === 'resolved' && decision.chosenKey === GMAIL_SEND_KEYS.SEND;
152
+ if (!hold || hold.decisionId !== decision.id) {
153
+ // Nothing to send. A Not now / expiry with no hold needs no news, but a
154
+ // Yes must never vanish: the card shows Yes, so say it was not sent.
155
+ if (!sendChosen || wasSettled(decision.id))
156
+ return null;
157
+ const why = !hold ? 'the pending send was lost (the draft is no longer held)' : 'this card was replaced by a newer one for the same draft';
158
+ const note = `Not sent: ${why}. The draft is still in Gmail.`;
159
+ this.logger.warn('Send approved but no matching hold — not sent', { holdId: subject.holdId, decisionId: decision.id, held: !!hold });
160
+ await this.deps.decisions()?.replyInThread?.(decision.id, note).catch(() => false);
161
+ return `[GMAIL] The owner tapped Yes on "${subject.subject}" to ${subject.to}, but it was NOT sent: ${why}. The draft is still in Gmail; ask the owner whether to try again.`;
162
+ }
163
+ markSettled(decision.id);
164
+ if (decision.status !== 'resolved' || decision.chosenKey !== GMAIL_SEND_KEYS.SEND) {
165
+ clearHeldSend(hold.id);
166
+ this.logger.info('Drafted email not sent', { holdId: hold.id, status: decision.status });
167
+ // Never deletes the draft: only an owner tap on Discard does (via discarded()).
168
+ if (this.discarding.has(hold.id))
169
+ return `[GMAIL] The owner discarded "${hold.subject}" to ${hold.to}; the draft was deleted. Do not send it another way.`;
170
+ if (decision.status === 'defaulted' || decision.status === 'expired') {
171
+ return `[GMAIL] "${hold.subject}" to ${hold.to} was not approved in 24h; nothing was sent and the draft is left in Gmail.`;
172
+ }
173
+ if (decision.status === 'resolved')
174
+ return `[GMAIL] The owner said not now to "${hold.subject}" to ${hold.to}. Nothing was sent; the draft stays in Gmail. Do not send it another way.`;
175
+ return `[GMAIL] The approval card for "${hold.subject}" to ${hold.to} was withdrawn. Nothing was sent; the draft is left in Gmail.`;
176
+ }
177
+ const gmail = this.deps.gmailFor(hold.account);
178
+ const current = await gmail.getDraft(hold.draftId);
179
+ if (!hold.fingerprint || fingerprintDraft(current) !== hold.fingerprint) {
180
+ // The text changed after the owner looked at it: that approval is for
181
+ // a different email. Ask again, with what is there now.
182
+ clearHeldSend(hold.id);
183
+ const reheld = holdGmailSend({
184
+ agentSession: hold.agentSession,
185
+ draftId: hold.draftId,
186
+ to: hold.to,
187
+ subject: hold.subject,
188
+ ...(hold.claim ? { claim: hold.claim } : {}),
189
+ ...(hold.account ? { account: hold.account } : {}),
190
+ });
191
+ await this.onHeld(reheld);
192
+ this.logger.warn('Drafted email changed after approval — not sent, asked again', { holdId: hold.id });
193
+ return `[GMAIL] "${hold.subject}" was edited after the owner approved it, so it was NOT sent. A new card with the edited text is waiting for the owner.`;
194
+ }
195
+ const sent = await gmail.sendDraft(hold.draftId);
196
+ clearHeldSend(hold.id);
197
+ // Audit line: who, what, to whom, on which owner decision.
198
+ this.logger.info('Owner approved a drafted email — sent', {
199
+ holdId: hold.id,
200
+ decisionId: decision.id,
201
+ to: hold.to,
202
+ subject: hold.subject,
203
+ messageId: sent.id,
204
+ threadId: sent.threadId,
205
+ agentSession: hold.agentSession,
206
+ });
207
+ try {
208
+ await this.deps.onSent?.({ threadId: sent.threadId, agentSession: hold.agentSession, ...(hold.account ? { account: hold.account } : {}) });
209
+ }
210
+ catch (err) {
211
+ this.logger.warn('Could not watch the thread after sending', { threadId: sent.threadId, error: err instanceof Error ? err.message : String(err) });
212
+ }
213
+ return `[GMAIL] The owner approved and "${hold.subject}" was sent to ${hold.to} (thread ${sent.threadId}). Replies on that thread will wake you.`;
214
+ }
215
+ }
216
+ //# sourceMappingURL=gmail-send-approval.service.js.map