@vellumai/assistant 0.10.0 → 0.10.1-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (824) hide show
  1. package/ARCHITECTURE.md +36 -37
  2. package/bun.lock +3 -0
  3. package/docs/workflows.md +12 -7
  4. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  5. package/node_modules/@slack/types/LICENSE +23 -0
  6. package/node_modules/@slack/types/README.md +32 -0
  7. package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts +953 -0
  8. package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts.map +1 -0
  9. package/node_modules/@slack/types/dist/block-kit/block-elements.js +4 -0
  10. package/node_modules/@slack/types/dist/block-kit/block-elements.js.map +1 -0
  11. package/node_modules/@slack/types/dist/block-kit/blocks.d.ts +474 -0
  12. package/node_modules/@slack/types/dist/block-kit/blocks.d.ts.map +1 -0
  13. package/node_modules/@slack/types/dist/block-kit/blocks.js +3 -0
  14. package/node_modules/@slack/types/dist/block-kit/blocks.js.map +1 -0
  15. package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts +237 -0
  16. package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts.map +1 -0
  17. package/node_modules/@slack/types/dist/block-kit/composition-objects.js +4 -0
  18. package/node_modules/@slack/types/dist/block-kit/composition-objects.js.map +1 -0
  19. package/node_modules/@slack/types/dist/block-kit/extensions.d.ts +88 -0
  20. package/node_modules/@slack/types/dist/block-kit/extensions.d.ts.map +1 -0
  21. package/node_modules/@slack/types/dist/block-kit/extensions.js +3 -0
  22. package/node_modules/@slack/types/dist/block-kit/extensions.js.map +1 -0
  23. package/node_modules/@slack/types/dist/calls.d.ts +26 -0
  24. package/node_modules/@slack/types/dist/calls.d.ts.map +1 -0
  25. package/node_modules/@slack/types/dist/calls.js +6 -0
  26. package/node_modules/@slack/types/dist/calls.js.map +1 -0
  27. package/node_modules/@slack/types/dist/chunk.d.ts +52 -0
  28. package/node_modules/@slack/types/dist/chunk.d.ts.map +1 -0
  29. package/node_modules/@slack/types/dist/chunk.js +3 -0
  30. package/node_modules/@slack/types/dist/chunk.js.map +1 -0
  31. package/node_modules/@slack/types/dist/common/bot-profile.d.ts +12 -0
  32. package/node_modules/@slack/types/dist/common/bot-profile.d.ts.map +1 -0
  33. package/node_modules/@slack/types/dist/common/bot-profile.js +3 -0
  34. package/node_modules/@slack/types/dist/common/bot-profile.js.map +1 -0
  35. package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts +6 -0
  36. package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts.map +1 -0
  37. package/node_modules/@slack/types/dist/common/status-emoji-display-info.js +3 -0
  38. package/node_modules/@slack/types/dist/common/status-emoji-display-info.js.map +1 -0
  39. package/node_modules/@slack/types/dist/dialog.d.ts +36 -0
  40. package/node_modules/@slack/types/dist/dialog.d.ts.map +1 -0
  41. package/node_modules/@slack/types/dist/dialog.js +3 -0
  42. package/node_modules/@slack/types/dist/dialog.js.map +1 -0
  43. package/node_modules/@slack/types/dist/events/app.d.ts +204 -0
  44. package/node_modules/@slack/types/dist/events/app.d.ts.map +1 -0
  45. package/node_modules/@slack/types/dist/events/app.js +3 -0
  46. package/node_modules/@slack/types/dist/events/app.js.map +1 -0
  47. package/node_modules/@slack/types/dist/events/assistant.d.ts +29 -0
  48. package/node_modules/@slack/types/dist/events/assistant.d.ts.map +1 -0
  49. package/node_modules/@slack/types/dist/events/assistant.js +3 -0
  50. package/node_modules/@slack/types/dist/events/assistant.js.map +1 -0
  51. package/node_modules/@slack/types/dist/events/call.d.ts +8 -0
  52. package/node_modules/@slack/types/dist/events/call.d.ts.map +1 -0
  53. package/node_modules/@slack/types/dist/events/call.js +3 -0
  54. package/node_modules/@slack/types/dist/events/call.js.map +1 -0
  55. package/node_modules/@slack/types/dist/events/channel.d.ts +85 -0
  56. package/node_modules/@slack/types/dist/events/channel.d.ts.map +1 -0
  57. package/node_modules/@slack/types/dist/events/channel.js +3 -0
  58. package/node_modules/@slack/types/dist/events/channel.js.map +1 -0
  59. package/node_modules/@slack/types/dist/events/dnd.d.ts +24 -0
  60. package/node_modules/@slack/types/dist/events/dnd.d.ts.map +1 -0
  61. package/node_modules/@slack/types/dist/events/dnd.js +3 -0
  62. package/node_modules/@slack/types/dist/events/dnd.js.map +1 -0
  63. package/node_modules/@slack/types/dist/events/email.d.ts +6 -0
  64. package/node_modules/@slack/types/dist/events/email.d.ts.map +1 -0
  65. package/node_modules/@slack/types/dist/events/email.js +3 -0
  66. package/node_modules/@slack/types/dist/events/email.js.map +1 -0
  67. package/node_modules/@slack/types/dist/events/emoji.d.ts +11 -0
  68. package/node_modules/@slack/types/dist/events/emoji.d.ts.map +1 -0
  69. package/node_modules/@slack/types/dist/events/emoji.js +3 -0
  70. package/node_modules/@slack/types/dist/events/emoji.js.map +1 -0
  71. package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts +21 -0
  72. package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts.map +1 -0
  73. package/node_modules/@slack/types/dist/events/entity-details-requested.js +3 -0
  74. package/node_modules/@slack/types/dist/events/entity-details-requested.js.map +1 -0
  75. package/node_modules/@slack/types/dist/events/file.d.ts +60 -0
  76. package/node_modules/@slack/types/dist/events/file.d.ts.map +1 -0
  77. package/node_modules/@slack/types/dist/events/file.js +4 -0
  78. package/node_modules/@slack/types/dist/events/file.js.map +1 -0
  79. package/node_modules/@slack/types/dist/events/function.d.ts +33 -0
  80. package/node_modules/@slack/types/dist/events/function.d.ts.map +1 -0
  81. package/node_modules/@slack/types/dist/events/function.js +3 -0
  82. package/node_modules/@slack/types/dist/events/function.js.map +1 -0
  83. package/node_modules/@slack/types/dist/events/grid-migration.d.ts +9 -0
  84. package/node_modules/@slack/types/dist/events/grid-migration.d.ts.map +1 -0
  85. package/node_modules/@slack/types/dist/events/grid-migration.js +3 -0
  86. package/node_modules/@slack/types/dist/events/grid-migration.js.map +1 -0
  87. package/node_modules/@slack/types/dist/events/group.d.ts +55 -0
  88. package/node_modules/@slack/types/dist/events/group.d.ts.map +1 -0
  89. package/node_modules/@slack/types/dist/events/group.js +3 -0
  90. package/node_modules/@slack/types/dist/events/group.js.map +1 -0
  91. package/node_modules/@slack/types/dist/events/im.d.ts +26 -0
  92. package/node_modules/@slack/types/dist/events/im.d.ts.map +1 -0
  93. package/node_modules/@slack/types/dist/events/im.js +3 -0
  94. package/node_modules/@slack/types/dist/events/im.js.map +1 -0
  95. package/node_modules/@slack/types/dist/events/index.d.ts +60 -0
  96. package/node_modules/@slack/types/dist/events/index.d.ts.map +1 -0
  97. package/node_modules/@slack/types/dist/events/index.js +43 -0
  98. package/node_modules/@slack/types/dist/events/index.js.map +1 -0
  99. package/node_modules/@slack/types/dist/events/invite.d.ts +20 -0
  100. package/node_modules/@slack/types/dist/events/invite.d.ts.map +1 -0
  101. package/node_modules/@slack/types/dist/events/invite.js +3 -0
  102. package/node_modules/@slack/types/dist/events/invite.js.map +1 -0
  103. package/node_modules/@slack/types/dist/events/link-shared.d.ts +16 -0
  104. package/node_modules/@slack/types/dist/events/link-shared.d.ts.map +1 -0
  105. package/node_modules/@slack/types/dist/events/link-shared.js +3 -0
  106. package/node_modules/@slack/types/dist/events/link-shared.js.map +1 -0
  107. package/node_modules/@slack/types/dist/events/member.d.ts +19 -0
  108. package/node_modules/@slack/types/dist/events/member.d.ts.map +1 -0
  109. package/node_modules/@slack/types/dist/events/member.js +3 -0
  110. package/node_modules/@slack/types/dist/events/member.js.map +1 -0
  111. package/node_modules/@slack/types/dist/events/message-metadata.d.ts +38 -0
  112. package/node_modules/@slack/types/dist/events/message-metadata.d.ts.map +1 -0
  113. package/node_modules/@slack/types/dist/events/message-metadata.js +3 -0
  114. package/node_modules/@slack/types/dist/events/message-metadata.js.map +1 -0
  115. package/node_modules/@slack/types/dist/events/message.d.ts +306 -0
  116. package/node_modules/@slack/types/dist/events/message.d.ts.map +1 -0
  117. package/node_modules/@slack/types/dist/events/message.js +3 -0
  118. package/node_modules/@slack/types/dist/events/message.js.map +1 -0
  119. package/node_modules/@slack/types/dist/events/pin.d.ts +60 -0
  120. package/node_modules/@slack/types/dist/events/pin.d.ts.map +1 -0
  121. package/node_modules/@slack/types/dist/events/pin.js +3 -0
  122. package/node_modules/@slack/types/dist/events/pin.js.map +1 -0
  123. package/node_modules/@slack/types/dist/events/reaction.d.ts +23 -0
  124. package/node_modules/@slack/types/dist/events/reaction.d.ts.map +1 -0
  125. package/node_modules/@slack/types/dist/events/reaction.js +3 -0
  126. package/node_modules/@slack/types/dist/events/reaction.js.map +1 -0
  127. package/node_modules/@slack/types/dist/events/shared-channel.d.ts +134 -0
  128. package/node_modules/@slack/types/dist/events/shared-channel.d.ts.map +1 -0
  129. package/node_modules/@slack/types/dist/events/shared-channel.js +3 -0
  130. package/node_modules/@slack/types/dist/events/shared-channel.js.map +1 -0
  131. package/node_modules/@slack/types/dist/events/star.d.ts +13 -0
  132. package/node_modules/@slack/types/dist/events/star.d.ts.map +1 -0
  133. package/node_modules/@slack/types/dist/events/star.js +3 -0
  134. package/node_modules/@slack/types/dist/events/star.js.map +1 -0
  135. package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts +82 -0
  136. package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts.map +1 -0
  137. package/node_modules/@slack/types/dist/events/steps-from-apps.js +3 -0
  138. package/node_modules/@slack/types/dist/events/steps-from-apps.js.map +1 -0
  139. package/node_modules/@slack/types/dist/events/subteam.d.ts +66 -0
  140. package/node_modules/@slack/types/dist/events/subteam.d.ts.map +1 -0
  141. package/node_modules/@slack/types/dist/events/subteam.js +3 -0
  142. package/node_modules/@slack/types/dist/events/subteam.js.map +1 -0
  143. package/node_modules/@slack/types/dist/events/team.d.ts +99 -0
  144. package/node_modules/@slack/types/dist/events/team.d.ts.map +1 -0
  145. package/node_modules/@slack/types/dist/events/team.js +3 -0
  146. package/node_modules/@slack/types/dist/events/team.js.map +1 -0
  147. package/node_modules/@slack/types/dist/events/token.d.ts +8 -0
  148. package/node_modules/@slack/types/dist/events/token.d.ts.map +1 -0
  149. package/node_modules/@slack/types/dist/events/token.js +3 -0
  150. package/node_modules/@slack/types/dist/events/token.js.map +1 -0
  151. package/node_modules/@slack/types/dist/events/user.d.ts +313 -0
  152. package/node_modules/@slack/types/dist/events/user.d.ts.map +1 -0
  153. package/node_modules/@slack/types/dist/events/user.js +3 -0
  154. package/node_modules/@slack/types/dist/events/user.js.map +1 -0
  155. package/node_modules/@slack/types/dist/index.d.ts +12 -0
  156. package/node_modules/@slack/types/dist/index.d.ts.map +1 -0
  157. package/node_modules/@slack/types/dist/index.js +28 -0
  158. package/node_modules/@slack/types/dist/index.js.map +1 -0
  159. package/node_modules/@slack/types/dist/message-attachments.d.ts +171 -0
  160. package/node_modules/@slack/types/dist/message-attachments.d.ts.map +1 -0
  161. package/node_modules/@slack/types/dist/message-attachments.js +3 -0
  162. package/node_modules/@slack/types/dist/message-attachments.js.map +1 -0
  163. package/node_modules/@slack/types/dist/message-metadata.d.ts +281 -0
  164. package/node_modules/@slack/types/dist/message-metadata.d.ts.map +1 -0
  165. package/node_modules/@slack/types/dist/message-metadata.js +27 -0
  166. package/node_modules/@slack/types/dist/message-metadata.js.map +1 -0
  167. package/node_modules/@slack/types/dist/views.d.ts +71 -0
  168. package/node_modules/@slack/types/dist/views.d.ts.map +1 -0
  169. package/node_modules/@slack/types/dist/views.js +3 -0
  170. package/node_modules/@slack/types/dist/views.js.map +1 -0
  171. package/node_modules/@slack/types/package.json +47 -0
  172. package/node_modules/@vellumai/gateway-client/bun.lock +3 -0
  173. package/node_modules/@vellumai/gateway-client/package.json +1 -0
  174. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +69 -0
  175. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +65 -0
  176. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +162 -0
  177. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -0
  178. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  179. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +4 -2
  180. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +3 -2
  181. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +78 -0
  182. package/openapi.yaml +345 -18
  183. package/package.json +2 -1
  184. package/scripts/memory-inspect.ts +24 -14
  185. package/src/__tests__/access-request-seed-content-blocks.test.ts +83 -103
  186. package/src/__tests__/activation-early-marking.test.ts +1 -1
  187. package/src/__tests__/actor-token-service.test.ts +3 -3
  188. package/src/__tests__/agent-loop-callsite-precedence.test.ts +1 -40
  189. package/src/__tests__/agent-loop-compaction-events.test.ts +0 -1
  190. package/src/__tests__/agent-loop-compaction-strip.test.ts +0 -1
  191. package/src/__tests__/agent-loop-exit-reason.test.ts +0 -1
  192. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  193. package/src/__tests__/agent-loop-regrowth-guard.test.ts +0 -1
  194. package/src/__tests__/agent-loop.test.ts +3 -0
  195. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  196. package/src/__tests__/anthropic-provider.test.ts +143 -9
  197. package/src/__tests__/app-builder-skill-instructions.test.ts +47 -5
  198. package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -1
  199. package/src/__tests__/app-source-watcher.test.ts +30 -10
  200. package/src/__tests__/approval-cascade.test.ts +6 -0
  201. package/src/__tests__/approval-interception-trust-gates.test.ts +151 -0
  202. package/src/__tests__/approval-primitive.test.ts +1 -1
  203. package/src/__tests__/approval-routes-http.test.ts +1 -1
  204. package/src/__tests__/assistant-attachments.test.ts +155 -0
  205. package/src/__tests__/assistant-event-hub-machine-name.test.ts +2 -4
  206. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  207. package/src/__tests__/assistant-events-sse-shed.test.ts +1 -1
  208. package/src/__tests__/attachment-upload-trusted-source.test.ts +13 -8
  209. package/src/__tests__/attachments-store.test.ts +1 -1
  210. package/src/__tests__/audit-log-rotation.test.ts +50 -54
  211. package/src/__tests__/auth-fallback-events-store.test.ts +1 -1
  212. package/src/__tests__/auto-analysis-end-to-end.test.ts +9 -14
  213. package/src/__tests__/background-shell-bash.test.ts +4 -1
  214. package/src/__tests__/background-shell-host-bash.test.ts +17 -3
  215. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
  216. package/src/__tests__/call-controller.test.ts +1 -1
  217. package/src/__tests__/call-conversation-messages.test.ts +1 -1
  218. package/src/__tests__/call-domain.test.ts +1 -1
  219. package/src/__tests__/call-pointer-messages.test.ts +3 -4
  220. package/src/__tests__/call-recovery.test.ts +1 -1
  221. package/src/__tests__/call-routes-http.test.ts +1 -1
  222. package/src/__tests__/call-store.test.ts +1 -1
  223. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  224. package/src/__tests__/canonical-guardian-store.test.ts +24 -1
  225. package/src/__tests__/channel-approval-routes.test.ts +73 -1119
  226. package/src/__tests__/channel-delivery-store.test.ts +1 -1
  227. package/src/__tests__/channel-guardian.test.ts +265 -641
  228. package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -2
  229. package/src/__tests__/channel-retry-sweep.test.ts +1 -1
  230. package/src/__tests__/compaction-events.test.ts +6 -0
  231. package/src/__tests__/compaction-trail-store.test.ts +6 -5
  232. package/src/__tests__/compaction.benchmark.test.ts +0 -1
  233. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  234. package/src/__tests__/config-loader-backfill.test.ts +183 -51
  235. package/src/__tests__/config-schema.test.ts +34 -0
  236. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -2
  237. package/src/__tests__/contact-store-user-file.test.ts +2 -2
  238. package/src/__tests__/contacts-relay-reads.test.ts +409 -0
  239. package/src/__tests__/contacts-tools.test.ts +4 -4
  240. package/src/__tests__/contacts-write.test.ts +1 -2
  241. package/src/__tests__/context-search-conversations-source.test.ts +1 -1
  242. package/src/__tests__/context-window-manager-compact-retry.test.ts +6 -2
  243. package/src/__tests__/context-window-manager-overflow-rung.test.ts +6 -2
  244. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  245. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
  246. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
  247. package/src/__tests__/conversation-agent-loop-overflow.test.ts +3 -0
  248. package/src/__tests__/conversation-agent-loop.test.ts +3 -0
  249. package/src/__tests__/conversation-attachments.test.ts +2 -5
  250. package/src/__tests__/conversation-attention-store.test.ts +1 -1
  251. package/src/__tests__/conversation-attention-telegram.test.ts +1 -2
  252. package/src/__tests__/conversation-clear-safety.test.ts +1 -1
  253. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  254. package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -1
  255. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +12 -19
  256. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -1
  257. package/src/__tests__/conversation-disk-view.test.ts +1 -1
  258. package/src/__tests__/conversation-fork-crud.test.ts +10 -8
  259. package/src/__tests__/conversation-fork-retrospective.test.ts +250 -0
  260. package/src/__tests__/conversation-fork-route.test.ts +1 -1
  261. package/src/__tests__/conversation-inference-profile-list.test.ts +1 -1
  262. package/src/__tests__/conversation-inference-profile-route.test.ts +1 -1
  263. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  264. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  265. package/src/__tests__/conversation-lifecycle.test.ts +117 -0
  266. package/src/__tests__/conversation-list-source.test.ts +3 -3
  267. package/src/__tests__/conversation-process-callsite.test.ts +6 -14
  268. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  269. package/src/__tests__/conversation-queue.test.ts +6 -0
  270. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  271. package/src/__tests__/conversation-runtime-assembly.test.ts +115 -12
  272. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  273. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  274. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  275. package/src/__tests__/conversation-starter-routes.test.ts +5 -5
  276. package/src/__tests__/conversation-store.test.ts +1 -1
  277. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -1
  278. package/src/__tests__/conversation-sync-tags.test.ts +1 -1
  279. package/src/__tests__/conversation-usage.test.ts +1 -1
  280. package/src/__tests__/conversation-wipe.test.ts +9 -8
  281. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  282. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  283. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  284. package/src/__tests__/conversations-import-system-filter.test.ts +1 -1
  285. package/src/__tests__/copy-composer-tc-templates.test.ts +17 -0
  286. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  287. package/src/__tests__/db-acp-history.test.ts +2 -2
  288. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +5 -7
  289. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +6 -7
  290. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +5 -10
  291. package/src/__tests__/db-migration-rollback.test.ts +129 -39
  292. package/src/__tests__/db-proxy-transaction.test.ts +1 -1
  293. package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -11
  294. package/src/__tests__/db-test-helpers.ts +36 -19
  295. package/src/__tests__/delete-propagation.test.ts +1 -1
  296. package/src/__tests__/deterministic-verification-control-plane.test.ts +26 -8
  297. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  298. package/src/__tests__/dm-backfill.test.ts +1 -1
  299. package/src/__tests__/drop-capability-card-state-migration.test.ts +0 -8
  300. package/src/__tests__/edit-propagation.test.ts +1 -1
  301. package/src/__tests__/emit-signal-routing-intent.test.ts +83 -0
  302. package/src/__tests__/empty-response-hook.test.ts +42 -0
  303. package/src/__tests__/events-client-registration.test.ts +1 -1
  304. package/src/__tests__/followup-tools.test.ts +1 -1
  305. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  306. package/src/__tests__/guardian-action-sweep.test.ts +9 -2
  307. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  308. package/src/__tests__/guardian-card-withdrawal.test.ts +1 -1
  309. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +1 -1
  310. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  311. package/src/__tests__/guardian-outbound-http.test.ts +7 -12
  312. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +1 -1
  313. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  314. package/src/__tests__/guardian-routing-state.test.ts +1 -2
  315. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  316. package/src/__tests__/headless-browser-mode.test.ts +2 -2
  317. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  318. package/src/__tests__/heartbeat-service.test.ts +6 -0
  319. package/src/__tests__/helpers/channel-test-adapter.ts +98 -0
  320. package/src/__tests__/http-conversation-lineage.test.ts +1 -1
  321. package/src/__tests__/image-recovery-hook.test.ts +1 -1
  322. package/src/__tests__/inbound-invite-redemption.test.ts +1 -2
  323. package/src/__tests__/inbound-trust-verdict.test.ts +254 -0
  324. package/src/__tests__/inference-profile-reaper.test.ts +1 -1
  325. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  326. package/src/__tests__/inference-profile-session-ipc.test.ts +1 -1
  327. package/src/__tests__/injector-chain.test.ts +1 -1
  328. package/src/__tests__/injector-disk-pressure.test.ts +11 -6
  329. package/src/__tests__/internal-telemetry-routes.test.ts +1 -1
  330. package/src/__tests__/invite-redemption-service.test.ts +244 -43
  331. package/src/__tests__/invite-routes-http.test.ts +35 -186
  332. package/src/__tests__/invite-service-ipc.test.ts +287 -0
  333. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +5 -5
  334. package/src/__tests__/jobs-store-upsert-debounced.test.ts +9 -12
  335. package/src/__tests__/list-messages-attachments.test.ts +42 -1
  336. package/src/__tests__/list-messages-client-message-id.test.ts +1 -1
  337. package/src/__tests__/list-messages-hidden-metadata.test.ts +1 -1
  338. package/src/__tests__/list-messages-page-latest.test.ts +1 -1
  339. package/src/__tests__/list-messages-tool-merge.test.ts +1 -1
  340. package/src/__tests__/llm-context-route-provider.test.ts +69 -4
  341. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +9 -5
  342. package/src/__tests__/llm-request-log-call-site.test.ts +6 -6
  343. package/src/__tests__/llm-request-log-turn-query.test.ts +27 -13
  344. package/src/__tests__/llm-usage-store.test.ts +40 -1
  345. package/src/__tests__/log-export-routes.test.ts +1 -1
  346. package/src/__tests__/log-export-workspace.test.ts +3 -3
  347. package/src/__tests__/memory-jobs-worker-lanes.test.ts +5 -5
  348. package/src/__tests__/memory-recall-log-store.test.ts +1 -1
  349. package/src/__tests__/memory-upsert-concurrency.test.ts +3 -4
  350. package/src/__tests__/messages-after-tiebreaker.test.ts +1 -1
  351. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  352. package/src/__tests__/mtime-cache.test.ts +375 -0
  353. package/src/__tests__/non-member-access-request.test.ts +1 -2
  354. package/src/__tests__/notification-candidate-guardian-context.test.ts +203 -0
  355. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  356. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  357. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  358. package/src/__tests__/oauth-provider-visibility.test.ts +1 -1
  359. package/src/__tests__/oauth-store.test.ts +1 -1
  360. package/src/__tests__/persist-unsendable-image-downscale.test.ts +1 -1
  361. package/src/__tests__/persist-unsendable-image.test.ts +1 -1
  362. package/src/__tests__/persona-resolver.test.ts +39 -1
  363. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  364. package/src/__tests__/playbook-execution.test.ts +1 -1
  365. package/src/__tests__/playbook-tools.test.ts +1 -1
  366. package/src/__tests__/plugin-api-model-profiles.test.ts +74 -21
  367. package/src/__tests__/plugin-bootstrap.test.ts +78 -0
  368. package/src/__tests__/provider-platform-proxy-integration.test.ts +25 -5
  369. package/src/__tests__/provider-usage-tracking.test.ts +1 -1
  370. package/src/__tests__/prune-old-conversations-job.test.ts +1 -1
  371. package/src/__tests__/reaction-persistence.test.ts +1 -1
  372. package/src/__tests__/relay-server.test.ts +357 -56
  373. package/src/__tests__/runtime-attachment-metadata.test.ts +10 -1
  374. package/src/__tests__/runtime-events-sse-bilingual.test.ts +7 -9
  375. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  376. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  377. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  378. package/src/__tests__/schedule-retry.test.ts +1 -1
  379. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -1
  380. package/src/__tests__/schedule-routes.test.ts +1 -1
  381. package/src/__tests__/schedule-store.test.ts +1 -1
  382. package/src/__tests__/schedule-tools.test.ts +1 -1
  383. package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
  384. package/src/__tests__/scheduler-recurrence.test.ts +1 -1
  385. package/src/__tests__/scheduler-reuse-conversation.test.ts +1 -1
  386. package/src/__tests__/scheduler-wake.test.ts +2 -1
  387. package/src/__tests__/scoped-approval-grants.test.ts +1 -1
  388. package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -5
  389. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +0 -8
  390. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
  391. package/src/__tests__/send-endpoint-busy.test.ts +1 -1
  392. package/src/__tests__/sequence-store.test.ts +1 -1
  393. package/src/__tests__/server-history-render.test.ts +40 -1
  394. package/src/__tests__/settings-routes.test.ts +11 -10
  395. package/src/__tests__/skill-load-tool.test.ts +72 -0
  396. package/src/__tests__/slack-inbound-verification.test.ts +1 -3
  397. package/src/__tests__/slack-messaging-token-resolution.test.ts +13 -2
  398. package/src/__tests__/slack-reaction-canonical-approval.test.ts +1 -1
  399. package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -73
  400. package/src/__tests__/subagent-tools.test.ts +1 -31
  401. package/src/__tests__/system-prompt.test.ts +1 -1
  402. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  403. package/src/__tests__/task-compiler.test.ts +1 -1
  404. package/src/__tests__/task-management-tools.test.ts +1 -1
  405. package/src/__tests__/task-memory-cleanup.test.ts +9 -6
  406. package/src/__tests__/task-scheduler.test.ts +1 -1
  407. package/src/__tests__/thread-backfill.test.ts +1 -1
  408. package/src/__tests__/tool-approval-handler.test.ts +1 -1
  409. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +2 -0
  410. package/src/__tests__/tool-executor.test.ts +32 -1
  411. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -2
  412. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +73 -1
  413. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +34 -34
  414. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -2
  415. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  416. package/src/__tests__/turn-boundary-resolution.test.ts +3 -3
  417. package/src/__tests__/turn-events-store.test.ts +1 -1
  418. package/src/__tests__/twilio-routes.test.ts +2 -3
  419. package/src/__tests__/usage-cache-backfill-migration.test.ts +20 -10
  420. package/src/__tests__/usage-routes.test.ts +1 -1
  421. package/src/__tests__/user-plugin-loader.test.ts +34 -29
  422. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  423. package/src/__tests__/voice-invite-redemption.test.ts +134 -36
  424. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  425. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  426. package/src/__tests__/workspace-git-service.test.ts +114 -1
  427. package/src/__tests__/workspace-heartbeat-service.test.ts +45 -0
  428. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -1
  429. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  430. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +88 -18
  431. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +6 -6
  432. package/src/__tests__/workspace-migration-109-swap-quality-profile-to-glm-5p2.test.ts +281 -0
  433. package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +167 -0
  434. package/src/__tests__/workspace-migrations-runner.test.ts +55 -0
  435. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -1
  436. package/src/a2a/__tests__/task-store.test.ts +1 -1
  437. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -1
  438. package/src/acp/__tests__/session-manager-resume.test.ts +22 -11
  439. package/src/acp/__tests__/session-manager-startup.test.ts +1 -1
  440. package/src/acp/__tests__/session-manager.test.ts +72 -1
  441. package/src/acp/index.ts +10 -0
  442. package/src/acp/session-manager.ts +35 -0
  443. package/src/agent/loop.ts +45 -27
  444. package/src/api/index.ts +0 -6
  445. package/src/approvals/AGENTS.md +1 -2
  446. package/src/approvals/guardian-decision-primitive.ts +13 -210
  447. package/src/approvals/guardian-request-resolvers.ts +104 -58
  448. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  449. package/src/calls/__tests__/inbound-trust-reader.test.ts +110 -0
  450. package/src/calls/__tests__/relay-setup-router.test.ts +88 -62
  451. package/src/calls/inbound-trust-reader.ts +40 -0
  452. package/src/calls/relay-server.ts +65 -23
  453. package/src/calls/relay-setup-router.ts +20 -6
  454. package/src/calls/relay-verification.ts +7 -7
  455. package/src/cli/commands/contacts.ts +6 -24
  456. package/src/cli/commands/db/__tests__/repair.test.ts +15 -6
  457. package/src/cli/commands/db/__tests__/status.test.ts +7 -3
  458. package/src/cli/commands/db/status.ts +212 -33
  459. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +6 -1
  460. package/src/cli/commands/memory/index.ts +2 -0
  461. package/src/cli/commands/memory/memory-retrospective.ts +129 -0
  462. package/src/cli/commands/memory/memory-v3.ts +176 -4
  463. package/src/cli/commands/plugins.ts +268 -11
  464. package/src/cli/lib/__tests__/install-from-github.test.ts +40 -0
  465. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  466. package/src/cli/lib/__tests__/toggle-plugin.test.ts +158 -0
  467. package/src/cli/lib/install-from-github.ts +47 -6
  468. package/src/cli/lib/plugin-marketplace.ts +11 -0
  469. package/src/cli/lib/plugin-pin-history.ts +257 -0
  470. package/src/cli/lib/toggle-plugin.ts +146 -0
  471. package/src/config/__tests__/sync-gated-profiles.test.ts +2 -2
  472. package/src/config/bundled-skills/app-builder/SKILL.md +15 -33
  473. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +3 -8
  474. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +64 -37
  475. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +1 -1
  476. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +14 -72
  477. package/src/config/bundled-skills/app-builder/references/examples/README.md +1 -2
  478. package/src/config/bundled-skills/contacts/SKILL.md +7 -12
  479. package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
  480. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  481. package/src/config/bundled-skills/workflows/SKILL.md +4 -3
  482. package/src/config/call-site-defaults.ts +11 -2
  483. package/src/config/feature-flag-registry.json +0 -8
  484. package/src/config/profile-dispatchability.ts +11 -0
  485. package/src/config/schemas/call-site-catalog.ts +7 -0
  486. package/src/config/schemas/llm.ts +2 -0
  487. package/src/config/schemas/memory-lifecycle.ts +5 -3
  488. package/src/config/schemas/timeouts.ts +24 -0
  489. package/src/config/seed-inference-profiles.ts +133 -45
  490. package/src/config/sync-gated-profiles.ts +13 -1
  491. package/src/contacts/contact-store.ts +21 -0
  492. package/src/contacts/member-status.ts +9 -0
  493. package/src/credential-health/credential-health-service.ts +1 -5
  494. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  495. package/src/daemon/app-source-watcher.ts +31 -18
  496. package/src/daemon/assistant-attachments.ts +94 -4
  497. package/src/daemon/conversation-agent-loop-handlers.ts +3 -0
  498. package/src/daemon/conversation-agent-loop.ts +9 -36
  499. package/src/daemon/conversation-runtime-assembly.ts +91 -66
  500. package/src/daemon/conversation-tool-setup.ts +20 -63
  501. package/src/daemon/conversation.ts +144 -52
  502. package/src/daemon/event-loop-watchdog.test.ts +85 -0
  503. package/src/daemon/event-loop-watchdog.ts +133 -0
  504. package/src/daemon/external-plugins-bootstrap.ts +26 -80
  505. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  506. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -1
  507. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -1
  508. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -1
  509. package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -1
  510. package/src/daemon/handlers/config-channels.ts +32 -18
  511. package/src/daemon/handlers/conversations.ts +7 -0
  512. package/src/daemon/handlers/shared.ts +7 -0
  513. package/src/daemon/lifecycle.ts +16 -3
  514. package/src/daemon/message-types/inbox.ts +0 -6
  515. package/src/daemon/message-types/messages.ts +0 -4
  516. package/src/daemon/message-types/surfaces.ts +18 -8
  517. package/src/daemon/server.ts +0 -4
  518. package/src/daemon/tool-setup-types.ts +0 -7
  519. package/src/daemon/trust-context.ts +6 -0
  520. package/src/daemon/wake-conversation-ops.ts +70 -0
  521. package/src/daemon/workspace-tools-watcher.ts +7 -3
  522. package/src/documents/document-comments-store.test.ts +1 -1
  523. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +1 -1
  524. package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -0
  525. package/src/heartbeat/heartbeat-service.ts +3 -4
  526. package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
  527. package/src/ipc/__tests__/browser-ipc.test.ts +73 -2
  528. package/src/ipc/__tests__/watcher-ipc.test.ts +59 -39
  529. package/src/ipc/assistant-server.ts +8 -0
  530. package/src/ipc/gateway-client.ts +2 -1
  531. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +58 -0
  532. package/src/ipc/routes/invite-ipc-routes.ts +66 -0
  533. package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -1
  534. package/src/memory/__tests__/activation-session-store.test.ts +1 -1
  535. package/src/memory/__tests__/auto-analysis-guard.test.ts +1 -1
  536. package/src/memory/__tests__/conversation-group-migration.test.ts +1 -1
  537. package/src/memory/__tests__/conversation-queries.test.ts +1 -1
  538. package/src/memory/__tests__/db-async-query.test.ts +1 -1
  539. package/src/memory/__tests__/db-logs-attach.test.ts +110 -0
  540. package/src/memory/__tests__/db-maintenance.test.ts +28 -36
  541. package/src/memory/__tests__/db-memory-attach.test.ts +113 -0
  542. package/src/memory/__tests__/find-analysis-conversation.test.ts +1 -1
  543. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -1
  544. package/src/memory/__tests__/fork-message-copy.test.ts +232 -0
  545. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +3 -0
  546. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +5 -5
  547. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +8 -6
  548. package/src/memory/__tests__/memory-retrospective-job.test.ts +30 -37
  549. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +69 -66
  550. package/src/memory/__tests__/memory-retrospective-state.test.ts +1 -1
  551. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
  552. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +1 -1
  553. package/src/memory/__tests__/onboarding-events-store.test.ts +1 -1
  554. package/src/memory/__tests__/table-relocation.test.ts +129 -0
  555. package/src/memory/conversation-crud.ts +461 -152
  556. package/src/memory/db-async-query.ts +89 -5
  557. package/src/memory/db-connection.ts +101 -18
  558. package/src/memory/db-init.ts +409 -234
  559. package/src/memory/db-maintenance.ts +43 -38
  560. package/src/memory/db-singleton.ts +45 -19
  561. package/src/memory/fork-message-copy.ts +170 -0
  562. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +92 -0
  563. package/src/memory/graph/bootstrap.test.ts +6 -3
  564. package/src/memory/graph/retriever.test.ts +12 -12
  565. package/src/memory/graph/store.test.ts +15 -25
  566. package/src/memory/graph/store.ts +23 -14
  567. package/src/memory/graph/tool-handlers.ts +34 -5
  568. package/src/memory/graph/tools.ts +5 -2
  569. package/src/memory/indexer.ts +21 -9
  570. package/src/memory/job-handlers/cleanup.ts +10 -3
  571. package/src/memory/job-handlers/embedding.test.ts +4 -4
  572. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +4 -4
  573. package/src/memory/jobs/embed-pkb-file.test.ts +7 -7
  574. package/src/memory/jobs-store.ts +36 -24
  575. package/src/memory/llm-request-log-store.ts +51 -19
  576. package/src/memory/llm-usage-store.ts +31 -1
  577. package/src/memory/memory-retrospective-job.ts +27 -19
  578. package/src/memory/memory-retrospective-startup-cleanup.ts +10 -2
  579. package/src/memory/migrations/{100-core-tables.ts → 000-core-tables.ts} +6 -10
  580. package/src/memory/migrations/104-core-indexes.ts +1 -1
  581. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +189 -196
  582. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +98 -105
  583. package/src/memory/migrations/134-contacts-notes-column.ts +66 -69
  584. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +19 -22
  585. package/src/memory/migrations/136-drop-assistant-id-columns.ts +227 -230
  586. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +204 -209
  587. package/src/memory/migrations/141-rename-verification-table.ts +45 -48
  588. package/src/memory/migrations/142-rename-verification-session-id-column.ts +16 -23
  589. package/src/memory/migrations/143-rename-guardian-verification-values.ts +23 -30
  590. package/src/memory/migrations/144-rename-voice-to-phone.ts +133 -136
  591. package/src/memory/migrations/145-drop-accounts-table.ts +4 -7
  592. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +79 -82
  593. package/src/memory/migrations/148-drop-reminders-table.ts +3 -6
  594. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +71 -78
  595. package/src/memory/migrations/157-invite-contact-id.ts +73 -76
  596. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +44 -58
  597. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +36 -43
  598. package/src/memory/migrations/174-rename-thread-starters-table.ts +30 -37
  599. package/src/memory/migrations/176-drop-capability-card-state.ts +17 -22
  600. package/src/memory/migrations/177-create-trace-events-table.ts +23 -28
  601. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +36 -43
  602. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +14 -21
  603. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +17 -24
  604. package/src/memory/migrations/192-contacts-user-file-column.ts +6 -9
  605. package/src/memory/migrations/193-add-source-type-columns.ts +33 -36
  606. package/src/memory/migrations/194-memory-recall-logs.ts +34 -39
  607. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +59 -66
  608. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +41 -48
  609. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +11 -18
  610. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +76 -83
  611. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +50 -57
  612. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +6 -11
  613. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +4 -9
  614. package/src/memory/migrations/217-conversation-host-access.ts +13 -18
  615. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +86 -93
  616. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +41 -48
  617. package/src/memory/migrations/230-acp-session-history.ts +23 -28
  618. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +58 -62
  619. package/src/memory/migrations/232-activation-state.ts +11 -16
  620. package/src/memory/migrations/233-document-conversations.ts +20 -25
  621. package/src/memory/migrations/234-memory-v2-activation-logs.ts +26 -31
  622. package/src/memory/migrations/235-slack-compaction-watermark.ts +5 -10
  623. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +6 -11
  624. package/src/memory/migrations/237-heartbeat-runs.ts +22 -27
  625. package/src/memory/migrations/239-trace-events-created-at-index.ts +4 -9
  626. package/src/memory/migrations/242-message-bookmarks.ts +17 -22
  627. package/src/memory/migrations/245-memory-retrospective-state.ts +8 -13
  628. package/src/memory/migrations/249-normalize-slack-external-content.ts +37 -41
  629. package/src/memory/migrations/251-a2a-tasks.ts +27 -32
  630. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +12 -17
  631. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +10 -15
  632. package/src/memory/migrations/256-memory-v2-injection-events.ts +70 -74
  633. package/src/memory/migrations/259-conversation-cleaned-at.ts +4 -9
  634. package/src/memory/migrations/260-rename-cleaned-at.ts +11 -16
  635. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +3 -8
  636. package/src/memory/migrations/262-memory-v3-coactivation.ts +21 -26
  637. package/src/memory/migrations/263-memory-v3-auto-edges.ts +14 -19
  638. package/src/memory/migrations/270-schedule-description.ts +7 -12
  639. package/src/memory/migrations/272-acp-session-history-cwd.ts +8 -13
  640. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +8 -13
  641. package/src/memory/migrations/297-move-llm-request-logs-to-logs-db.ts +111 -0
  642. package/src/memory/migrations/298-move-memory-jobs-to-memory-db.ts +128 -0
  643. package/src/memory/migrations/299-canonical-guardian-deliveries-conversation-index.ts +19 -0
  644. package/src/memory/migrations/__tests__/297-move-llm-request-logs.test.ts +180 -0
  645. package/src/memory/migrations/__tests__/run-migrations.test.ts +333 -7
  646. package/src/memory/migrations/helpers/relocation.ts +227 -0
  647. package/src/memory/migrations/registry.ts +63 -0
  648. package/src/memory/migrations/run-migrations.ts +187 -16
  649. package/src/memory/migrations/validate-migration-state.ts +50 -145
  650. package/src/memory/raw-query.ts +47 -2
  651. package/src/memory/skill-loaded-events-store.test.ts +1 -1
  652. package/src/memory/task-memory-cleanup.ts +62 -41
  653. package/src/memory/tool-executed-events-store.test.ts +1 -1
  654. package/src/memory/turn-trace-store.test.ts +1 -1
  655. package/src/memory/v2/__tests__/backfill-jobs.test.ts +16 -15
  656. package/src/memory/v2/__tests__/harness-compare.test.ts +1 -1
  657. package/src/memory/v2/__tests__/harness-oracle.test.ts +1 -1
  658. package/src/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
  659. package/src/memory/v2/__tests__/sweep-job.test.ts +2 -2
  660. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +38 -0
  661. package/src/memory/v3-eval/__tests__/eval-tally.test.ts +139 -0
  662. package/src/memory/v3-eval/eval-packets.ts +197 -12
  663. package/src/memory/v3-eval/eval-tally.ts +234 -0
  664. package/src/messaging/provider.ts +10 -0
  665. package/src/messaging/providers/gmail/adapter.ts +1 -0
  666. package/src/messaging/providers/gmail/client.ts +14 -0
  667. package/src/messaging/providers/index.ts +1 -1
  668. package/src/messaging/providers/slack/send.test.ts +87 -39
  669. package/src/messaging/providers/slack/send.ts +84 -105
  670. package/src/notifications/README.md +9 -5
  671. package/src/notifications/__tests__/deterministic-checks.test.ts +43 -1
  672. package/src/notifications/adapters/slack.ts +12 -10
  673. package/src/notifications/approval-card-builder.ts +81 -20
  674. package/src/notifications/approval-card-data.ts +8 -5
  675. package/src/notifications/canonical-delivery-recorder.ts +7 -5
  676. package/src/notifications/conversation-candidates.ts +24 -59
  677. package/src/notifications/copy-composer.ts +48 -68
  678. package/src/notifications/deterministic-checks.ts +19 -16
  679. package/src/notifications/emit-signal.ts +29 -1
  680. package/src/notifications/trusted-contact-payloads.ts +70 -0
  681. package/src/oauth/byo-connection.test.ts +9 -0
  682. package/src/oauth/connection-resolver.test.ts +146 -6
  683. package/src/oauth/connection-resolver.ts +132 -5
  684. package/src/oauth/oauth-store.ts +16 -3
  685. package/src/oauth/scope-utils.ts +21 -0
  686. package/src/plugin-api/index.ts +9 -4
  687. package/src/plugin-api/model-profiles.test.ts +123 -0
  688. package/src/plugin-api/model-profiles.ts +5 -1
  689. package/src/plugin-api/vision-support.test.ts +149 -0
  690. package/src/plugin-api/vision-support.ts +78 -0
  691. package/src/plugins/defaults/compaction/window-manager.ts +45 -64
  692. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +13 -4
  693. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +302 -0
  694. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +103 -0
  695. package/src/plugins/defaults/image-fallback/package.json +14 -0
  696. package/src/plugins/defaults/image-fallback/src/caption-cache.ts +49 -0
  697. package/src/plugins/defaults/image-fallback/src/image-persist.ts +59 -0
  698. package/src/plugins/defaults/image-fallback/src/vision-caption.ts +120 -0
  699. package/src/plugins/defaults/index.ts +23 -0
  700. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +14 -1
  701. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  702. package/src/plugins/external-plugin-loader.ts +47 -6
  703. package/src/plugins/mtime-cache.ts +772 -0
  704. package/src/plugins/pipeline.ts +7 -2
  705. package/src/plugins/registry.ts +16 -5
  706. package/src/plugins/user-loader.ts +22 -76
  707. package/src/prompts/persona-resolver.ts +29 -11
  708. package/src/prompts/system-prompt.ts +1 -1
  709. package/src/prompts/templates/system-sections.ts +4 -4
  710. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  711. package/src/providers/anthropic/client.ts +254 -185
  712. package/src/providers/call-site-routing.ts +10 -0
  713. package/src/providers/gemini/client.ts +43 -0
  714. package/src/providers/inference/adapter-factory.ts +6 -0
  715. package/src/providers/inference/connections.ts +6 -1
  716. package/src/providers/model-catalog.ts +37 -0
  717. package/src/providers/platform-proxy/constants.ts +5 -0
  718. package/src/providers/ratelimit.ts +9 -0
  719. package/src/providers/retry.ts +10 -0
  720. package/src/providers/together/client.ts +35 -0
  721. package/src/providers/types.ts +16 -0
  722. package/src/providers/usage-tracking.ts +7 -0
  723. package/src/runtime/AGENTS.md +9 -1
  724. package/src/runtime/__tests__/agent-wake.test.ts +259 -4
  725. package/src/runtime/__tests__/slack-block-formatting.test.ts +39 -10
  726. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +417 -0
  727. package/src/runtime/actor-trust-resolver.ts +8 -16
  728. package/src/runtime/agent-wake.ts +183 -60
  729. package/src/runtime/channel-reply-delivery.ts +6 -3
  730. package/src/runtime/guardian-decision-types.ts +3 -22
  731. package/src/runtime/http-server.ts +1 -15
  732. package/src/runtime/invite-redemption-service.ts +155 -6
  733. package/src/runtime/invite-service.ts +113 -62
  734. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +3 -0
  735. package/src/runtime/routes/__tests__/acp-routes.test.ts +1 -1
  736. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -1
  737. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +277 -0
  738. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +140 -0
  739. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +26 -7
  740. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +14 -10
  741. package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +164 -0
  742. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  743. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +1 -1
  744. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +8 -8
  745. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +1 -1
  746. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +1 -3
  747. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +240 -0
  748. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +4 -0
  749. package/src/runtime/routes/__tests__/plugins-routes.test.ts +143 -0
  750. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -1
  751. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +1 -1
  752. package/src/runtime/routes/acp-routes-list.test.ts +4 -0
  753. package/src/runtime/routes/acp-routes.test.ts +5 -6
  754. package/src/runtime/routes/attachment-routes.ts +21 -17
  755. package/src/runtime/routes/browser-routes.ts +19 -1
  756. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -9
  757. package/src/runtime/routes/channel-verification-routes.ts +12 -1
  758. package/src/runtime/routes/contact-routes.ts +275 -164
  759. package/src/runtime/routes/conversation-query-routes.ts +15 -5
  760. package/src/runtime/routes/conversation-routes.ts +24 -3
  761. package/src/runtime/routes/conversation-starter-routes.ts +7 -8
  762. package/src/runtime/routes/guardian-approval-interception.ts +13 -274
  763. package/src/runtime/routes/inbound-message-handler.ts +20 -15
  764. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +285 -0
  765. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +45 -34
  766. package/src/runtime/routes/inbound-stages/admission-policy.ts +20 -5
  767. package/src/runtime/routes/log-export-routes.ts +2 -2
  768. package/src/runtime/routes/memory-eval-routes.ts +92 -0
  769. package/src/runtime/routes/memory-item-routes.test.ts +12 -11
  770. package/src/runtime/routes/migration-routes.ts +51 -40
  771. package/src/runtime/routes/plugins-routes.ts +164 -8
  772. package/src/runtime/routes/schedule-routes.ts +1 -0
  773. package/src/runtime/routes/usage-routes.ts +3 -0
  774. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  775. package/src/runtime/slack-block-formatting.ts +46 -48
  776. package/src/runtime/trust-verdict-consumer.ts +172 -0
  777. package/src/schedule/scheduler.ts +6 -9
  778. package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
  779. package/src/tools/ask-question/ask-question-tool.test.ts +60 -52
  780. package/src/tools/ask-question/ask-question-tool.ts +14 -73
  781. package/src/tools/browser/__tests__/browser-status.test.ts +20 -0
  782. package/src/tools/browser/browser-execution.ts +16 -4
  783. package/src/tools/document/document-comment-tool.test.ts +1 -1
  784. package/src/tools/executor.ts +15 -3
  785. package/src/tools/host-terminal/host-shell.ts +28 -9
  786. package/src/tools/memory/register.test.ts +32 -0
  787. package/src/tools/skills/load.ts +43 -2
  788. package/src/tools/subagent/spawn.ts +4 -10
  789. package/src/tools/terminal/shell.ts +16 -5
  790. package/src/tools/types.ts +1 -0
  791. package/src/util/fs-watcher-error.ts +36 -0
  792. package/src/util/logs-db-path.ts +22 -0
  793. package/src/util/memory-db-path.ts +23 -0
  794. package/src/watcher/providers/gmail.ts +7 -2
  795. package/src/workflows/engine-integration.test.ts +1 -1
  796. package/src/workflows/engine.test.ts +1 -1
  797. package/src/workflows/engine.ts +22 -0
  798. package/src/workflows/fanout-load.test.ts +1 -1
  799. package/src/workflows/journal-store.test.ts +1 -1
  800. package/src/workflows/leaf-runner.test.ts +40 -1
  801. package/src/workflows/leaf-runner.ts +26 -1
  802. package/src/workspace/git-service.ts +144 -29
  803. package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +121 -0
  804. package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +82 -0
  805. package/src/workspace/migrations/registry.ts +4 -0
  806. package/src/workspace/migrations/runner.ts +32 -2
  807. package/src/__tests__/access-request-decision.test.ts +0 -375
  808. package/src/__tests__/guardian-grant-minting.test.ts +0 -607
  809. package/src/__tests__/plugin-source-watcher.test.ts +0 -302
  810. package/src/api/events/turn-profile-auto-routed.ts +0 -28
  811. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +0 -107
  812. package/src/daemon/plugin-source-watcher.ts +0 -278
  813. package/src/daemon/switch-inference-profile-tool.ts +0 -62
  814. package/src/memory/guardian-approvals.ts +0 -361
  815. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +0 -66
  816. package/src/memory/migrations/038-actor-token-records.ts +0 -45
  817. package/src/memory/migrations/039-actor-refresh-token-records.ts +0 -57
  818. package/src/memory/migrations/103-complex-migrations.ts +0 -23
  819. package/src/memory/migrations/113-late-migrations.ts +0 -30
  820. package/src/memory/migrations/index.ts +0 -301
  821. package/src/runtime/routes/access-request-decision.ts +0 -297
  822. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -963
  823. package/src/runtime/routes/channel-guardian-routes.ts +0 -19
  824. package/src/runtime/routes/guardian-expiry-sweep.ts +0 -132
@@ -79,22 +79,17 @@ import {
79
79
  import { getDb } from "../memory/db-connection.js";
80
80
  import { initializeDb } from "../memory/db-init.js";
81
81
  import * as deliveryChannels from "../memory/delivery-channels.js";
82
- import {
83
- createApprovalRequest,
84
- getAllPendingApprovalsByGuardianChat,
85
- } from "../memory/guardian-approvals.js";
86
82
  import { resetTestTables } from "../memory/raw-query.js";
87
83
  import { conversations } from "../memory/schema.js";
88
84
  import { initAuthSigningKey } from "../runtime/auth/token-service.js";
89
85
  import * as gatewayClient from "../runtime/gateway-client.js";
90
86
  import * as pendingInteractions from "../runtime/pending-interactions.js";
91
- import { sweepExpiredGuardianApprovals } from "../runtime/routes/channel-guardian-routes.js";
92
87
  import { _setTestPollMaxWait } from "../runtime/routes/channel-route-shared.js";
93
88
  import { resetDbForTesting } from "./db-test-helpers.js";
94
89
  import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
95
90
  import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
96
91
 
97
- initializeDb();
92
+ await initializeDb();
98
93
  initAuthSigningKey(Buffer.from("test-signing-key-at-least-32-bytes-long"));
99
94
 
100
95
  afterAll(() => {
@@ -125,7 +120,6 @@ function resetTables(): void {
125
120
  "scoped_approval_grants",
126
121
  "canonical_guardian_deliveries",
127
122
  "canonical_guardian_requests",
128
- "channel_guardian_approval_requests",
129
123
  "channel_verification_sessions",
130
124
  "conversation_keys",
131
125
  "message_runs",
@@ -1041,290 +1035,14 @@ describe("plain-text channel approval decisions", () => {
1041
1035
  // 21. Guardian decision scoping — callback for older request
1042
1036
  // ═══════════════════════════════════════════════════════════════════════════
1043
1037
 
1044
- describe("guardian decision scoping — multiple pending approvals", () => {
1045
- test("callback for older request resolves to the correct approval request", async () => {
1046
- createGuardianBinding({
1047
- channel: "telegram",
1048
- guardianExternalUserId: "guardian-scope-user",
1049
- guardianDeliveryChatId: "guardian-scope-chat",
1050
- guardianPrincipalId: "guardian-scope-user",
1051
- });
1052
-
1053
- const deliverSpy = spyOn(
1054
- gatewayClient,
1055
- "deliverChannelReply",
1056
- ).mockResolvedValue({ ok: true });
1057
-
1058
- const olderConvId = "conv-scope-older";
1059
- const newerConvId = "conv-scope-newer";
1060
- ensureConversation(olderConvId);
1061
- ensureConversation(newerConvId);
1062
-
1063
- // Register pending interactions and create guardian approval requests
1064
- const olderSession = registerPendingInteraction(
1065
- "req-older",
1066
- olderConvId,
1067
- "shell",
1068
- );
1069
- createApprovalRequest({
1070
- runId: "run-older",
1071
- requestId: "req-older",
1072
- conversationId: olderConvId,
1073
- channel: "telegram",
1074
- requesterExternalUserId: "requester-a",
1075
- requesterChatId: "chat-requester-a",
1076
- guardianExternalUserId: "guardian-scope-user",
1077
- guardianChatId: "guardian-scope-chat",
1078
- toolName: "shell",
1079
- expiresAt: Date.now() + 300_000,
1080
- });
1081
-
1082
- const newerSession = registerPendingInteraction(
1083
- "req-newer",
1084
- newerConvId,
1085
- "browser",
1086
- );
1087
- createApprovalRequest({
1088
- runId: "run-newer",
1089
- requestId: "req-newer",
1090
- conversationId: newerConvId,
1091
- channel: "telegram",
1092
- requesterExternalUserId: "requester-b",
1093
- requesterChatId: "chat-requester-b",
1094
- guardianExternalUserId: "guardian-scope-user",
1095
- guardianChatId: "guardian-scope-chat",
1096
- toolName: "browser",
1097
- expiresAt: Date.now() + 300_000,
1098
- });
1099
-
1100
- // The guardian clicks the approval button for the OLDER request
1101
- const req = makeInboundRequest({
1102
- content: "",
1103
- conversationExternalId: "guardian-scope-chat",
1104
- callbackData: "apr:req-older:approve_once",
1105
- actorExternalId: "guardian-scope-user",
1106
- });
1107
-
1108
- const res = await handleChannelInbound(req, noopProcessMessage);
1109
- const body = (await res.json()) as Record<string, unknown>;
1110
-
1111
- expect(body.accepted).toBe(true);
1112
- expect(body.approval).toBe("guardian_decision_applied");
1113
-
1114
- // The older request's session should have been called
1115
- expect(olderSession).toHaveBeenCalledWith("req-older", "allow", {
1116
- decisionContext: undefined,
1117
- });
1118
-
1119
- // The newer request's session should NOT have been called
1120
- expect(newerSession).not.toHaveBeenCalled();
1121
-
1122
- deliverSpy.mockRestore();
1123
- });
1124
- });
1125
-
1126
1038
  // ═══════════════════════════════════════════════════════════════════════════
1127
1039
  // 22. Ambiguous plain-text decision with multiple pending requests
1128
1040
  // ═══════════════════════════════════════════════════════════════════════════
1129
1041
 
1130
- describe("ambiguous plain-text decision with multiple pending requests", () => {
1131
- test("does not apply plain-text decision to wrong request when multiple pending", async () => {
1132
- createGuardianBinding({
1133
- channel: "telegram",
1134
- guardianExternalUserId: "guardian-ambig-user",
1135
- guardianDeliveryChatId: "guardian-ambig-chat",
1136
- guardianPrincipalId: "guardian-ambig-user",
1137
- });
1138
-
1139
- const deliverSpy = spyOn(
1140
- gatewayClient,
1141
- "deliverChannelReply",
1142
- ).mockResolvedValue({ ok: true });
1143
-
1144
- const convA = "conv-ambig-a";
1145
- const convB = "conv-ambig-b";
1146
- ensureConversation(convA);
1147
- ensureConversation(convB);
1148
-
1149
- const sessionA = registerPendingInteraction("req-ambig-a", convA, "shell");
1150
- createApprovalRequest({
1151
- runId: "run-ambig-a",
1152
- requestId: "req-ambig-a",
1153
- conversationId: convA,
1154
- channel: "telegram",
1155
- requesterExternalUserId: "requester-x",
1156
- requesterChatId: "chat-requester-x",
1157
- guardianExternalUserId: "guardian-ambig-user",
1158
- guardianChatId: "guardian-ambig-chat",
1159
- toolName: "shell",
1160
- expiresAt: Date.now() + 300_000,
1161
- });
1162
-
1163
- const sessionB = registerPendingInteraction(
1164
- "req-ambig-b",
1165
- convB,
1166
- "browser",
1167
- );
1168
- createApprovalRequest({
1169
- runId: "run-ambig-b",
1170
- requestId: "req-ambig-b",
1171
- conversationId: convB,
1172
- channel: "telegram",
1173
- requesterExternalUserId: "requester-y",
1174
- requesterChatId: "chat-requester-y",
1175
- guardianExternalUserId: "guardian-ambig-user",
1176
- guardianChatId: "guardian-ambig-chat",
1177
- toolName: "browser",
1178
- expiresAt: Date.now() + 300_000,
1179
- });
1180
-
1181
- // Conversational engine that returns keep_pending for disambiguation
1182
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
1183
- disposition: "keep_pending" as const,
1184
- replyText: "You have 2 pending requests. Which one?",
1185
- }));
1186
- setTestApprovalConversationGenerator(mockConversationGenerator);
1187
-
1188
- // Guardian sends plain-text "yes" — ambiguous because two approvals are pending
1189
- const req = makeInboundRequest({
1190
- content: "yes",
1191
- conversationExternalId: "guardian-ambig-chat",
1192
- actorExternalId: "guardian-ambig-user",
1193
- });
1194
-
1195
- const res = await handleChannelInbound(
1196
- req,
1197
- noopProcessMessage,
1198
- "self",
1199
- undefined,
1200
- );
1201
- const body = (await res.json()) as Record<string, unknown>;
1202
-
1203
- expect(body.accepted).toBe(true);
1204
- expect(body.approval).toBe("assistant_turn");
1205
-
1206
- // Neither session should have been called — disambiguation was required
1207
- expect(sessionA).not.toHaveBeenCalled();
1208
- expect(sessionB).not.toHaveBeenCalled();
1209
-
1210
- // The conversational engine should have been called with both pending approvals
1211
- expect(mockConversationGenerator).toHaveBeenCalledTimes(1);
1212
- const engineCtx = mockConversationGenerator.mock.calls[0][0] as Record<
1213
- string,
1214
- unknown
1215
- >;
1216
- expect(engineCtx.pendingApprovals as Array<unknown>).toHaveLength(2);
1217
-
1218
- deliverSpy.mockRestore();
1219
- });
1220
- });
1221
-
1222
1042
  // ═══════════════════════════════════════════════════════════════════════════
1223
1043
  // 23. Expired guardian approval auto-denies and transitions to terminal status
1224
1044
  // ═══════════════════════════════════════════════════════════════════════════
1225
1045
 
1226
- describe("expired guardian approval auto-denies via sweep", () => {
1227
- test("sweepExpiredGuardianApprovals auto-denies and notifies both parties", async () => {
1228
- const deliverSpy = spyOn(
1229
- gatewayClient,
1230
- "deliverChannelReply",
1231
- ).mockResolvedValue({ ok: true });
1232
-
1233
- const convId = "conv-expiry-sweep";
1234
- ensureConversation(convId);
1235
-
1236
- // Register a pending interaction so the sweep can resolve the session
1237
- const sessionMock = registerPendingInteraction(
1238
- "req-exp-1",
1239
- convId,
1240
- "shell",
1241
- );
1242
-
1243
- createApprovalRequest({
1244
- runId: "run-exp-1",
1245
- requestId: "req-exp-1",
1246
- conversationId: convId,
1247
- channel: "telegram",
1248
- requesterExternalUserId: "requester-exp",
1249
- requesterChatId: "chat-requester-exp",
1250
- guardianExternalUserId: "guardian-exp-user",
1251
- guardianChatId: "guardian-exp-chat",
1252
- toolName: "shell",
1253
- expiresAt: Date.now() - 1000, // already expired
1254
- });
1255
-
1256
- // Run the sweep
1257
- sweepExpiredGuardianApprovals();
1258
-
1259
- // Wait for async notifications
1260
- await new Promise((resolve) => setTimeout(resolve, 10));
1261
-
1262
- // The session should have been denied
1263
- expect(sessionMock).toHaveBeenCalledWith("req-exp-1", "deny", {
1264
- decisionContext: undefined,
1265
- });
1266
-
1267
- // Both requester and guardian should have been notified
1268
- const requesterNotify = deliverSpy.mock.calls.filter(
1269
- (call) =>
1270
- typeof call[1] === "object" &&
1271
- (call[1] as { chatId?: string }).chatId === "chat-requester-exp" &&
1272
- (call[1] as { text?: string }).text?.includes("expired"),
1273
- );
1274
- expect(requesterNotify.length).toBeGreaterThanOrEqual(1);
1275
-
1276
- const guardianNotify = deliverSpy.mock.calls.filter(
1277
- (call) =>
1278
- typeof call[1] === "object" &&
1279
- (call[1] as { chatId?: string }).chatId === "guardian-exp-chat" &&
1280
- (call[1] as { text?: string }).text?.includes("expired"),
1281
- );
1282
- expect(guardianNotify.length).toBeGreaterThanOrEqual(1);
1283
-
1284
- // Verify the delivery URL is constructed per-channel
1285
- const allDeliverCalls = deliverSpy.mock.calls;
1286
- for (const call of allDeliverCalls) {
1287
- expect(call[0]).toBe("/deliver/telegram");
1288
- }
1289
-
1290
- deliverSpy.mockRestore();
1291
- });
1292
-
1293
- test("non-expired approvals are not affected by the sweep", async () => {
1294
- const deliverSpy = spyOn(
1295
- gatewayClient,
1296
- "deliverChannelReply",
1297
- ).mockResolvedValue({ ok: true });
1298
-
1299
- const convId = "conv-not-expired";
1300
- ensureConversation(convId);
1301
-
1302
- const sessionMock = registerPendingInteraction("req-ne-1", convId, "shell");
1303
-
1304
- createApprovalRequest({
1305
- runId: "run-ne-1",
1306
- requestId: "req-ne-1",
1307
- conversationId: convId,
1308
- channel: "telegram",
1309
- requesterExternalUserId: "requester-ne",
1310
- requesterChatId: "chat-requester-ne",
1311
- guardianExternalUserId: "guardian-ne-user",
1312
- guardianChatId: "guardian-ne-chat",
1313
- toolName: "shell",
1314
- expiresAt: Date.now() + 300_000, // still valid
1315
- });
1316
-
1317
- sweepExpiredGuardianApprovals();
1318
-
1319
- await new Promise((resolve) => setTimeout(resolve, 10));
1320
-
1321
- // The session should NOT have been called
1322
- expect(sessionMock).not.toHaveBeenCalled();
1323
-
1324
- deliverSpy.mockRestore();
1325
- });
1326
- });
1327
-
1328
1046
  // ═══════════════════════════════════════════════════════════════════════════
1329
1047
  // 24. Deliver-once idempotency guard
1330
1048
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1590,53 +1308,49 @@ describe("conversational approval engine — standard path", () => {
1590
1308
  // Guardian conversational approval engine tests
1591
1309
  // ═══════════════════════════════════════════════════════════════════════════
1592
1310
 
1593
- describe("guardian conversational approval via conversation engine", () => {
1594
- test("guardian follow-up clarification: engine returns keep_pending", async () => {
1311
+ // ═══════════════════════════════════════════════════════════════════════════
1312
+ // keep_pending must remain conversational (no deterministic fallback)
1313
+ // ═══════════════════════════════════════════════════════════════════════════
1314
+
1315
+ describe("keep_pending remains conversational — standard path", () => {
1316
+ beforeEach(() => {
1595
1317
  createGuardianBinding({
1596
1318
  channel: "telegram",
1597
- guardianExternalUserId: "guardian-conv-user",
1598
- guardianDeliveryChatId: "guardian-conv-chat",
1599
- guardianPrincipalId: "guardian-conv-user",
1319
+ guardianExternalUserId: "telegram-user-default",
1320
+ guardianDeliveryChatId: "chat-123",
1321
+ guardianPrincipalId: "telegram-user-default",
1600
1322
  });
1323
+ });
1601
1324
 
1325
+ test('explicit "approve" with keep_pending returns assistant_turn and does not auto-decide', async () => {
1602
1326
  const deliverSpy = spyOn(
1603
1327
  gatewayClient,
1604
1328
  "deliverChannelReply",
1605
1329
  ).mockResolvedValue({ ok: true });
1606
1330
 
1607
- const convId = "conv-guardian-clarify";
1608
- ensureConversation(convId);
1331
+ const initReq = makeInboundRequest({ content: "init" });
1332
+ await handleChannelInbound(initReq, noopProcessMessage);
1333
+
1334
+ const db = getDb();
1335
+ const events = db.$client
1336
+ .prepare("SELECT conversation_id FROM channel_inbound_events")
1337
+ .all() as Array<{ conversation_id: string }>;
1338
+ const conversationId = events[0]?.conversation_id;
1339
+ ensureConversation(conversationId!);
1609
1340
 
1610
1341
  const sessionMock = registerPendingInteraction(
1611
- "req-gclarify-1",
1612
- convId,
1342
+ "req-kp-1",
1343
+ conversationId!,
1613
1344
  "shell",
1614
1345
  );
1615
- createApprovalRequest({
1616
- runId: "run-gclarify-1",
1617
- requestId: "req-gclarify-1",
1618
- conversationId: convId,
1619
- channel: "telegram",
1620
- requesterExternalUserId: "requester-clarify",
1621
- requesterChatId: "chat-requester-clarify",
1622
- guardianExternalUserId: "guardian-conv-user",
1623
- guardianChatId: "guardian-conv-chat",
1624
- toolName: "shell",
1625
- expiresAt: Date.now() + 300_000,
1626
- });
1627
1346
 
1628
1347
  const mockConversationGenerator = mock(async (_ctx: unknown) => ({
1629
1348
  disposition: "keep_pending" as const,
1630
- replyText: "Could you clarify which action you want me to approve?",
1349
+ replyText: "Before deciding, can you confirm the intent?",
1631
1350
  }));
1632
1351
  setTestApprovalConversationGenerator(mockConversationGenerator);
1633
1352
 
1634
- const req = makeInboundRequest({
1635
- content: "hmm what does this do?",
1636
- conversationExternalId: "guardian-conv-chat",
1637
- actorExternalId: "guardian-conv-user",
1638
- });
1639
-
1353
+ const req = makeInboundRequest({ content: "approve" });
1640
1354
  const res = await handleChannelInbound(
1641
1355
  req,
1642
1356
  noopProcessMessage,
@@ -1647,76 +1361,67 @@ describe("guardian conversational approval via conversation engine", () => {
1647
1361
 
1648
1362
  expect(body.accepted).toBe(true);
1649
1363
  expect(body.approval).toBe("assistant_turn");
1650
-
1651
- // The engine should have been called with role: 'guardian'
1652
- expect(mockConversationGenerator).toHaveBeenCalledTimes(1);
1653
- const callCtx = mockConversationGenerator.mock.calls[0][0] as Record<
1654
- string,
1655
- unknown
1656
- >;
1657
- expect(callCtx.role).toBe("guardian");
1658
- expect(callCtx.allowedActions).toEqual(["approve_once", "reject"]);
1659
- expect(callCtx.userMessage).toBe("hmm what does this do?");
1660
-
1661
- // The session should NOT have received a decision
1662
1364
  expect(sessionMock).not.toHaveBeenCalled();
1663
1365
 
1664
- // The approval should still be pending
1665
- const pending = getAllPendingApprovalsByGuardianChat(
1666
- "telegram",
1667
- "guardian-conv-chat",
1366
+ const followupReply = deliverSpy.mock.calls.find((call) =>
1367
+ (call[1] as { text?: string }).text?.includes("confirm the intent"),
1668
1368
  );
1669
- expect(pending).toHaveLength(1);
1369
+ expect(followupReply).toBeDefined();
1670
1370
 
1671
1371
  deliverSpy.mockRestore();
1672
1372
  });
1373
+ });
1374
+
1375
+ // ═══════════════════════════════════════════════════════════════════════════
1376
+ // Requester cancel of guardian-gated pending request
1377
+ // ═══════════════════════════════════════════════════════════════════════════
1378
+
1379
+ // ═══════════════════════════════════════════════════════════════════════════
1380
+ // Engine decision race condition — standard path
1381
+ // ═══════════════════════════════════════════════════════════════════════════
1673
1382
 
1674
- test("guardian natural-language approval: engine returns approve_once", async () => {
1383
+ describe("engine decision race condition standard path", () => {
1384
+ beforeEach(() => {
1675
1385
  createGuardianBinding({
1676
1386
  channel: "telegram",
1677
- guardianExternalUserId: "guardian-nlp-user",
1678
- guardianDeliveryChatId: "guardian-nlp-chat",
1679
- guardianPrincipalId: "guardian-nlp-user",
1387
+ guardianExternalUserId: "telegram-user-default",
1388
+ guardianDeliveryChatId: "chat-123",
1389
+ guardianPrincipalId: "telegram-user-default",
1680
1390
  });
1391
+ });
1681
1392
 
1393
+ test("returns stale_ignored when engine approves but interaction was already resolved", async () => {
1682
1394
  const deliverSpy = spyOn(
1683
1395
  gatewayClient,
1684
1396
  "deliverChannelReply",
1685
1397
  ).mockResolvedValue({ ok: true });
1686
1398
 
1687
- const convId = "conv-guardian-nlp";
1688
- ensureConversation(convId);
1399
+ const initReq = makeInboundRequest({ content: "init" });
1400
+ await handleChannelInbound(initReq, noopProcessMessage);
1689
1401
 
1690
- const sessionMock = registerPendingInteraction(
1691
- "req-gnlp-1",
1692
- convId,
1693
- "shell",
1694
- );
1695
- createApprovalRequest({
1696
- runId: "run-gnlp-1",
1697
- requestId: "req-gnlp-1",
1698
- conversationId: convId,
1699
- channel: "telegram",
1700
- requesterExternalUserId: "requester-nlp",
1701
- requesterChatId: "chat-requester-nlp",
1702
- guardianExternalUserId: "guardian-nlp-user",
1703
- guardianChatId: "guardian-nlp-chat",
1704
- toolName: "shell",
1705
- expiresAt: Date.now() + 300_000,
1706
- });
1402
+ const db = getDb();
1403
+ const events = db.$client
1404
+ .prepare("SELECT conversation_id FROM channel_inbound_events")
1405
+ .all() as Array<{ conversation_id: string }>;
1406
+ const conversationId = events[0]?.conversation_id;
1407
+ ensureConversation(conversationId!);
1707
1408
 
1708
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
1709
- disposition: "approve_once" as const,
1710
- replyText: "Approved! The shell command will proceed.",
1711
- }));
1712
- setTestApprovalConversationGenerator(mockConversationGenerator);
1409
+ registerPendingInteraction("req-race-1", conversationId!, "shell");
1713
1410
 
1714
- const req = makeInboundRequest({
1715
- content: "yes go ahead and run it",
1716
- conversationExternalId: "guardian-nlp-chat",
1717
- actorExternalId: "guardian-nlp-user",
1411
+ deliverSpy.mockClear();
1412
+
1413
+ // Engine returns approve_once, but resolves the pending interaction
1414
+ // before handleChannelDecision is called (simulating race condition)
1415
+ const mockConversationGenerator = mock(async (_ctx: unknown) => {
1416
+ pendingInteractions.resolve("req-race-1");
1417
+ return {
1418
+ disposition: "approve_once" as const,
1419
+ replyText: "Approved! Running the command now.",
1420
+ };
1718
1421
  });
1422
+ setTestApprovalConversationGenerator(mockConversationGenerator);
1719
1423
 
1424
+ const req = makeInboundRequest({ content: "go ahead" });
1720
1425
  const res = await handleChannelInbound(
1721
1426
  req,
1722
1427
  noopProcessMessage,
@@ -1726,686 +1431,11 @@ describe("guardian conversational approval via conversation engine", () => {
1726
1431
  const body = (await res.json()) as Record<string, unknown>;
1727
1432
 
1728
1433
  expect(body.accepted).toBe(true);
1729
- expect(body.approval).toBe("guardian_decision_applied");
1730
-
1731
- // The session should have received an 'allow' decision
1732
- expect(sessionMock).toHaveBeenCalledWith("req-gnlp-1", "allow", {
1733
- decisionContext: undefined,
1734
- });
1735
-
1736
- // The approval record should have been updated (no longer pending)
1737
- const pending = getAllPendingApprovalsByGuardianChat(
1738
- "telegram",
1739
- "guardian-nlp-chat",
1740
- );
1741
- expect(pending).toHaveLength(0);
1742
-
1743
- // The engine context only allows approve_once and reject
1744
- const callCtx = mockConversationGenerator.mock.calls[0][0] as Record<
1745
- string,
1746
- unknown
1747
- >;
1748
- expect(callCtx.allowedActions).toEqual(["approve_once", "reject"]);
1749
-
1750
- deliverSpy.mockRestore();
1751
- });
1752
-
1753
- test("guardian callback button approve_always is mapped to approve_once (backward compat)", async () => {
1754
- createGuardianBinding({
1755
- channel: "telegram",
1756
- guardianExternalUserId: "guardian-dg-user",
1757
- guardianDeliveryChatId: "guardian-dg-chat",
1758
- guardianPrincipalId: "guardian-dg-user",
1759
- });
1760
-
1761
- const deliverSpy = spyOn(
1762
- gatewayClient,
1763
- "deliverChannelReply",
1764
- ).mockResolvedValue({ ok: true });
1765
-
1766
- const convId = "conv-guardian-downgrade";
1767
- ensureConversation(convId);
1434
+ expect(body.approval).toBe("stale_ignored");
1768
1435
 
1769
- const sessionMock = registerPendingInteraction(
1770
- "req-gdg-1",
1771
- convId,
1772
- "shell",
1773
- );
1774
- createApprovalRequest({
1775
- runId: "run-gdg-1",
1776
- requestId: "req-gdg-1",
1777
- conversationId: convId,
1778
- channel: "telegram",
1779
- requesterExternalUserId: "requester-dg",
1780
- requesterChatId: "chat-requester-dg",
1781
- guardianExternalUserId: "guardian-dg-user",
1782
- guardianChatId: "guardian-dg-chat",
1783
- toolName: "shell",
1784
- expiresAt: Date.now() + 300_000,
1785
- });
1786
-
1787
- // Guardian sends an approve_always callback — legacy action is mapped to
1788
- // approve_once by LEGACY_CALLBACK_MAP for backward compat with in-flight buttons.
1789
- const req = makeInboundRequest({
1790
- content: "",
1791
- conversationExternalId: "guardian-dg-chat",
1792
- callbackData: "apr:req-gdg-1:approve_always",
1793
- actorExternalId: "guardian-dg-user",
1794
- });
1795
-
1796
- const res = await handleChannelInbound(req, noopProcessMessage, "self");
1797
- const body = (await res.json()) as Record<string, unknown>;
1798
-
1799
- // The legacy action is canonicalized to approve_once — the pending
1800
- // interaction IS resolved (backward compat).
1801
- expect(body.accepted).toBe(true);
1802
- expect(sessionMock).toHaveBeenCalled();
1803
-
1804
- deliverSpy.mockRestore();
1805
- });
1806
-
1807
- test("multi-pending guardian disambiguation: engine requests clarification", async () => {
1808
- createGuardianBinding({
1809
- channel: "telegram",
1810
- guardianExternalUserId: "guardian-multi-user",
1811
- guardianDeliveryChatId: "guardian-multi-chat",
1812
- guardianPrincipalId: "guardian-multi-user",
1813
- });
1814
-
1815
- const deliverSpy = spyOn(
1816
- gatewayClient,
1817
- "deliverChannelReply",
1818
- ).mockResolvedValue({ ok: true });
1819
-
1820
- const convA = "conv-multi-a";
1821
- const convB = "conv-multi-b";
1822
- ensureConversation(convA);
1823
- ensureConversation(convB);
1824
-
1825
- const sessionA = registerPendingInteraction("req-multi-a", convA, "shell");
1826
- createApprovalRequest({
1827
- runId: "run-multi-a",
1828
- requestId: "req-multi-a",
1829
- conversationId: convA,
1830
- channel: "telegram",
1831
- requesterExternalUserId: "requester-multi-a",
1832
- requesterChatId: "chat-requester-multi-a",
1833
- guardianExternalUserId: "guardian-multi-user",
1834
- guardianChatId: "guardian-multi-chat",
1835
- toolName: "shell",
1836
- expiresAt: Date.now() + 300_000,
1837
- });
1838
-
1839
- const sessionB = registerPendingInteraction(
1840
- "req-multi-b",
1841
- convB,
1842
- "file_edit",
1843
- );
1844
- createApprovalRequest({
1845
- runId: "run-multi-b",
1846
- requestId: "req-multi-b",
1847
- conversationId: convB,
1848
- channel: "telegram",
1849
- requesterExternalUserId: "requester-multi-b",
1850
- requesterChatId: "chat-requester-multi-b",
1851
- guardianExternalUserId: "guardian-multi-user",
1852
- guardianChatId: "guardian-multi-chat",
1853
- toolName: "file_edit",
1854
- expiresAt: Date.now() + 300_000,
1855
- });
1856
-
1857
- // Engine returns keep_pending for disambiguation
1858
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
1859
- disposition: "keep_pending" as const,
1860
- replyText: "You have 2 pending requests: shell and file_edit. Which one?",
1861
- }));
1862
- setTestApprovalConversationGenerator(mockConversationGenerator);
1863
-
1864
- const req = makeInboundRequest({
1865
- content: "approve it",
1866
- conversationExternalId: "guardian-multi-chat",
1867
- actorExternalId: "guardian-multi-user",
1868
- });
1869
-
1870
- const res = await handleChannelInbound(
1871
- req,
1872
- noopProcessMessage,
1873
- "self",
1874
- undefined,
1875
- );
1876
- const body = (await res.json()) as Record<string, unknown>;
1877
-
1878
- expect(body.accepted).toBe(true);
1879
- expect(body.approval).toBe("assistant_turn");
1880
-
1881
- // Neither session should have been called
1882
- expect(sessionA).not.toHaveBeenCalled();
1883
- expect(sessionB).not.toHaveBeenCalled();
1884
-
1885
- // The engine should have received both pending approvals
1886
- expect(mockConversationGenerator).toHaveBeenCalledTimes(1);
1887
- const engineCtx = mockConversationGenerator.mock.calls[0][0] as Record<
1888
- string,
1889
- unknown
1890
- >;
1891
- expect(engineCtx.pendingApprovals as Array<unknown>).toHaveLength(2);
1892
- expect(engineCtx.role).toBe("guardian");
1893
-
1894
- // Disambiguation reply delivered to guardian
1895
- const disambigCall = deliverSpy.mock.calls.find((call) =>
1896
- (call[1] as { text?: string }).text?.includes("2 pending requests"),
1897
- );
1898
- expect(disambigCall).toBeTruthy();
1899
-
1900
- deliverSpy.mockRestore();
1901
- });
1902
- });
1903
-
1904
- // ═══════════════════════════════════════════════════════════════════════════
1905
- // keep_pending must remain conversational (no deterministic fallback)
1906
- // ═══════════════════════════════════════════════════════════════════════════
1907
-
1908
- describe("keep_pending remains conversational — standard path", () => {
1909
- beforeEach(() => {
1910
- createGuardianBinding({
1911
- channel: "telegram",
1912
- guardianExternalUserId: "telegram-user-default",
1913
- guardianDeliveryChatId: "chat-123",
1914
- guardianPrincipalId: "telegram-user-default",
1915
- });
1916
- });
1917
-
1918
- test('explicit "approve" with keep_pending returns assistant_turn and does not auto-decide', async () => {
1919
- const deliverSpy = spyOn(
1920
- gatewayClient,
1921
- "deliverChannelReply",
1922
- ).mockResolvedValue({ ok: true });
1923
-
1924
- const initReq = makeInboundRequest({ content: "init" });
1925
- await handleChannelInbound(initReq, noopProcessMessage);
1926
-
1927
- const db = getDb();
1928
- const events = db.$client
1929
- .prepare("SELECT conversation_id FROM channel_inbound_events")
1930
- .all() as Array<{ conversation_id: string }>;
1931
- const conversationId = events[0]?.conversation_id;
1932
- ensureConversation(conversationId!);
1933
-
1934
- const sessionMock = registerPendingInteraction(
1935
- "req-kp-1",
1936
- conversationId!,
1937
- "shell",
1938
- );
1939
-
1940
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
1941
- disposition: "keep_pending" as const,
1942
- replyText: "Before deciding, can you confirm the intent?",
1943
- }));
1944
- setTestApprovalConversationGenerator(mockConversationGenerator);
1945
-
1946
- const req = makeInboundRequest({ content: "approve" });
1947
- const res = await handleChannelInbound(
1948
- req,
1949
- noopProcessMessage,
1950
- "self",
1951
- undefined,
1952
- );
1953
- const body = (await res.json()) as Record<string, unknown>;
1954
-
1955
- expect(body.accepted).toBe(true);
1956
- expect(body.approval).toBe("assistant_turn");
1957
- expect(sessionMock).not.toHaveBeenCalled();
1958
-
1959
- const followupReply = deliverSpy.mock.calls.find((call) =>
1960
- (call[1] as { text?: string }).text?.includes("confirm the intent"),
1961
- );
1962
- expect(followupReply).toBeDefined();
1963
-
1964
- deliverSpy.mockRestore();
1965
- });
1966
- });
1967
-
1968
- describe("keep_pending remains conversational — guardian path", () => {
1969
- test('guardian explicit "yes" with keep_pending returns assistant_turn without applying a decision', async () => {
1970
- createGuardianBinding({
1971
- channel: "telegram",
1972
- guardianExternalUserId: "guardian-user-fb",
1973
- guardianDeliveryChatId: "guardian-chat-fb",
1974
- guardianPrincipalId: "guardian-user-fb",
1975
- });
1976
-
1977
- const deliverSpy = spyOn(
1978
- gatewayClient,
1979
- "deliverChannelReply",
1980
- ).mockResolvedValue({ ok: true });
1981
-
1982
- const convId = "conv-gfb-1";
1983
- ensureConversation(convId);
1984
-
1985
- const sessionMock = registerPendingInteraction(
1986
- "req-gfb-1",
1987
- convId,
1988
- "shell",
1989
- );
1990
- createApprovalRequest({
1991
- runId: "run-gfb-1",
1992
- requestId: "req-gfb-1",
1993
- conversationId: convId,
1994
- channel: "telegram",
1995
- requesterExternalUserId: "requester-user-fb",
1996
- requesterChatId: "requester-chat-fb",
1997
- guardianExternalUserId: "guardian-user-fb",
1998
- guardianChatId: "guardian-chat-fb",
1999
- toolName: "shell",
2000
- expiresAt: Date.now() + 300_000,
2001
- });
2002
-
2003
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
2004
- disposition: "keep_pending" as const,
2005
- replyText: "Which run are you approving?",
2006
- }));
2007
- setTestApprovalConversationGenerator(mockConversationGenerator);
2008
-
2009
- const guardianReq = makeInboundRequest({
2010
- content: "yes",
2011
- conversationExternalId: "guardian-chat-fb",
2012
- actorExternalId: "guardian-user-fb",
2013
- });
2014
- const res = await handleChannelInbound(
2015
- guardianReq,
2016
- noopProcessMessage,
2017
- "self",
2018
- undefined,
2019
- );
2020
- const body = (await res.json()) as Record<string, unknown>;
2021
-
2022
- expect(body.accepted).toBe(true);
2023
- expect(body.approval).toBe("assistant_turn");
2024
- expect(sessionMock).not.toHaveBeenCalled();
2025
-
2026
- const followupReply = deliverSpy.mock.calls.find((call) =>
2027
- (call[1] as { text?: string }).text?.includes(
2028
- "Which run are you approving",
2029
- ),
2030
- );
2031
- expect(followupReply).toBeDefined();
2032
-
2033
- deliverSpy.mockRestore();
2034
- });
2035
- });
2036
-
2037
- // ═══════════════════════════════════════════════════════════════════════════
2038
- // Requester cancel of guardian-gated pending request
2039
- // ═══════════════════════════════════════════════════════════════════════════
2040
-
2041
- describe("requester cancel of guardian-gated pending request", () => {
2042
- beforeEach(() => {
2043
- createGuardianBinding({
2044
- channel: "telegram",
2045
- guardianExternalUserId: "guardian-cancel",
2046
- guardianDeliveryChatId: "guardian-cancel-chat",
2047
- guardianPrincipalId: "guardian-cancel",
2048
- });
2049
- upsertContact({
2050
- displayName: "Requester Cancel User",
2051
- channels: [
2052
- {
2053
- type: "telegram",
2054
- address: "requester-cancel-user",
2055
- status: "active",
2056
- policy: "allow",
2057
- },
2058
- ],
2059
- });
2060
- });
2061
-
2062
- test('requester explicit "deny" can cancel when the conversation engine returns reject', async () => {
2063
- const deliverSpy = spyOn(
2064
- gatewayClient,
2065
- "deliverChannelReply",
2066
- ).mockResolvedValue({ ok: true });
2067
-
2068
- // Create requester conversation
2069
- const initReq = makeInboundRequest({
2070
- content: "init",
2071
- conversationExternalId: "requester-cancel-chat",
2072
- actorExternalId: "requester-cancel-user",
2073
- });
2074
- await handleChannelInbound(initReq, noopProcessMessage);
2075
-
2076
- const db = getDb();
2077
- const events = db.$client
2078
- .prepare("SELECT conversation_id FROM channel_inbound_events")
2079
- .all() as Array<{ conversation_id: string }>;
2080
- const conversationId = events[0]?.conversation_id;
2081
- ensureConversation(conversationId!);
2082
-
2083
- const sessionMock = registerPendingInteraction(
2084
- "req-cancel-1",
2085
- conversationId!,
2086
- "shell",
2087
- );
2088
-
2089
- createApprovalRequest({
2090
- runId: "run-cancel-1",
2091
- requestId: "req-cancel-1",
2092
- conversationId: conversationId!,
2093
- channel: "telegram",
2094
- requesterExternalUserId: "requester-cancel-user",
2095
- requesterChatId: "requester-cancel-chat",
2096
- guardianExternalUserId: "guardian-cancel",
2097
- guardianChatId: "guardian-cancel-chat",
2098
- toolName: "shell",
2099
- expiresAt: Date.now() + 300_000,
2100
- });
2101
-
2102
- deliverSpy.mockClear();
2103
-
2104
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
2105
- disposition: "reject" as const,
2106
- replyText: "Cancelling this request now.",
2107
- }));
2108
- setTestApprovalConversationGenerator(mockConversationGenerator);
2109
-
2110
- const req = makeInboundRequest({
2111
- content: "deny",
2112
- conversationExternalId: "requester-cancel-chat",
2113
- actorExternalId: "requester-cancel-user",
2114
- });
2115
- const res = await handleChannelInbound(
2116
- req,
2117
- noopProcessMessage,
2118
- "self",
2119
- undefined,
2120
- );
2121
- const body = (await res.json()) as Record<string, unknown>;
2122
-
2123
- expect(body.accepted).toBe(true);
2124
- expect(body.approval).toBe("decision_applied");
2125
- expect(sessionMock).toHaveBeenCalledWith("req-cancel-1", "deny", {
2126
- decisionContext: undefined,
2127
- });
2128
-
2129
- // Requester should have been notified
2130
- const requesterReply = deliverSpy.mock.calls.find(
2131
- (call) =>
2132
- (call[1] as { chatId?: string }).chatId === "requester-cancel-chat",
2133
- );
2134
- expect(requesterReply).toBeDefined();
2135
-
2136
- // Guardian should have been notified of the cancellation
2137
- const guardianNotice = deliverSpy.mock.calls.find(
2138
- (call) =>
2139
- (call[1] as { chatId?: string }).chatId === "guardian-cancel-chat",
2140
- );
2141
- expect(guardianNotice).toBeDefined();
2142
-
2143
- deliverSpy.mockRestore();
2144
- });
2145
-
2146
- test('requester "nevermind" via conversational engine cancels guardian-gated request', async () => {
2147
- const deliverSpy = spyOn(
2148
- gatewayClient,
2149
- "deliverChannelReply",
2150
- ).mockResolvedValue({ ok: true });
2151
-
2152
- const initReq = makeInboundRequest({
2153
- content: "init",
2154
- conversationExternalId: "requester-cancel-chat",
2155
- actorExternalId: "requester-cancel-user",
2156
- });
2157
- await handleChannelInbound(initReq, noopProcessMessage);
2158
-
2159
- const db = getDb();
2160
- const events = db.$client
2161
- .prepare("SELECT conversation_id FROM channel_inbound_events")
2162
- .all() as Array<{ conversation_id: string }>;
2163
- const conversationId = events[0]?.conversation_id;
2164
- ensureConversation(conversationId!);
2165
-
2166
- const sessionMock = registerPendingInteraction(
2167
- "req-cancel-2",
2168
- conversationId!,
2169
- "shell",
2170
- );
2171
-
2172
- createApprovalRequest({
2173
- runId: "run-cancel-2",
2174
- requestId: "req-cancel-2",
2175
- conversationId: conversationId!,
2176
- channel: "telegram",
2177
- requesterExternalUserId: "requester-cancel-user",
2178
- requesterChatId: "requester-cancel-chat",
2179
- guardianExternalUserId: "guardian-cancel",
2180
- guardianChatId: "guardian-cancel-chat",
2181
- toolName: "shell",
2182
- expiresAt: Date.now() + 300_000,
2183
- });
2184
-
2185
- deliverSpy.mockClear();
2186
-
2187
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
2188
- disposition: "reject" as const,
2189
- replyText: "OK, I have cancelled the pending request.",
2190
- }));
2191
- setTestApprovalConversationGenerator(mockConversationGenerator);
2192
-
2193
- const req = makeInboundRequest({
2194
- content: "actually never mind, cancel it",
2195
- conversationExternalId: "requester-cancel-chat",
2196
- actorExternalId: "requester-cancel-user",
2197
- });
2198
- const res = await handleChannelInbound(
2199
- req,
2200
- noopProcessMessage,
2201
- "self",
2202
- undefined,
2203
- );
2204
- const body = (await res.json()) as Record<string, unknown>;
2205
-
2206
- expect(body.accepted).toBe(true);
2207
- expect(body.approval).toBe("decision_applied");
2208
- expect(sessionMock).toHaveBeenCalledWith("req-cancel-2", "deny", {
2209
- decisionContext: undefined,
2210
- });
2211
-
2212
- // Engine should have been called with reject-only allowed actions
2213
- expect(mockConversationGenerator).toHaveBeenCalledTimes(1);
2214
- const engineCtx = mockConversationGenerator.mock.calls[0][0] as Record<
2215
- string,
2216
- unknown
2217
- >;
2218
- expect(engineCtx.allowedActions).toEqual(["reject"]);
2219
-
2220
- deliverSpy.mockRestore();
2221
- });
2222
-
2223
- test("requester non-cancel message with keep_pending returns conversational reply", async () => {
2224
- const deliverSpy = spyOn(
2225
- gatewayClient,
2226
- "deliverChannelReply",
2227
- ).mockResolvedValue({ ok: true });
2228
-
2229
- const initReq = makeInboundRequest({
2230
- content: "init",
2231
- conversationExternalId: "requester-cancel-chat",
2232
- actorExternalId: "requester-cancel-user",
2233
- });
2234
- await handleChannelInbound(initReq, noopProcessMessage);
2235
-
2236
- const db = getDb();
2237
- const events = db.$client
2238
- .prepare("SELECT conversation_id FROM channel_inbound_events")
2239
- .all() as Array<{ conversation_id: string }>;
2240
- const conversationId = events[0]?.conversation_id;
2241
- ensureConversation(conversationId!);
2242
-
2243
- const sessionMock = registerPendingInteraction(
2244
- "req-cancel-3",
2245
- conversationId!,
2246
- "shell",
2247
- );
2248
-
2249
- createApprovalRequest({
2250
- runId: "run-cancel-3",
2251
- requestId: "req-cancel-3",
2252
- conversationId: conversationId!,
2253
- channel: "telegram",
2254
- requesterExternalUserId: "requester-cancel-user",
2255
- requesterChatId: "requester-cancel-chat",
2256
- guardianExternalUserId: "guardian-cancel",
2257
- guardianChatId: "guardian-cancel-chat",
2258
- toolName: "shell",
2259
- expiresAt: Date.now() + 300_000,
2260
- });
2261
-
2262
- deliverSpy.mockClear();
2263
-
2264
- const mockConversationGenerator = mock(async (_ctx: unknown) => ({
2265
- disposition: "keep_pending" as const,
2266
- replyText: "Still waiting.",
2267
- }));
2268
- setTestApprovalConversationGenerator(mockConversationGenerator);
2269
-
2270
- const req = makeInboundRequest({
2271
- content: "what is happening?",
2272
- conversationExternalId: "requester-cancel-chat",
2273
- actorExternalId: "requester-cancel-user",
2274
- });
2275
- const res = await handleChannelInbound(
2276
- req,
2277
- noopProcessMessage,
2278
- "self",
2279
- undefined,
2280
- );
2281
- const body = (await res.json()) as Record<string, unknown>;
2282
-
2283
- expect(body.accepted).toBe(true);
2284
- expect(body.approval).toBe("assistant_turn");
2285
- expect(sessionMock).not.toHaveBeenCalled();
2286
-
2287
- const pendingReply = deliverSpy.mock.calls.find((call) =>
2288
- (call[1] as { text?: string }).text?.includes("Still waiting."),
2289
- );
2290
- expect(pendingReply).toBeDefined();
2291
-
2292
- deliverSpy.mockRestore();
2293
- });
2294
-
2295
- test('requester "approve" is blocked — self-approval not allowed even during cancel check', async () => {
2296
- const deliverSpy = spyOn(
2297
- gatewayClient,
2298
- "deliverChannelReply",
2299
- ).mockResolvedValue({ ok: true });
2300
-
2301
- const initReq = makeInboundRequest({
2302
- content: "init",
2303
- conversationExternalId: "requester-cancel-chat",
2304
- actorExternalId: "requester-cancel-user",
2305
- });
2306
- await handleChannelInbound(initReq, noopProcessMessage);
2307
-
2308
- const db = getDb();
2309
- const events = db.$client
2310
- .prepare("SELECT conversation_id FROM channel_inbound_events")
2311
- .all() as Array<{ conversation_id: string }>;
2312
- const conversationId = events[0]?.conversation_id;
2313
- ensureConversation(conversationId!);
2314
-
2315
- registerPendingInteraction("req-cancel-4", conversationId!, "shell");
2316
-
2317
- createApprovalRequest({
2318
- runId: "run-cancel-4",
2319
- requestId: "req-cancel-4",
2320
- conversationId: conversationId!,
2321
- channel: "telegram",
2322
- requesterExternalUserId: "requester-cancel-user",
2323
- requesterChatId: "requester-cancel-chat",
2324
- guardianExternalUserId: "guardian-cancel",
2325
- guardianChatId: "guardian-cancel-chat",
2326
- toolName: "shell",
2327
- expiresAt: Date.now() + 300_000,
2328
- });
2329
-
2330
- deliverSpy.mockClear();
2331
-
2332
- // Requester tries to self-approve while guardian approval is pending.
2333
- const req = makeInboundRequest({
2334
- content: "approve",
2335
- conversationExternalId: "requester-cancel-chat",
2336
- actorExternalId: "requester-cancel-user",
2337
- });
2338
- const res = await handleChannelInbound(req, noopProcessMessage);
2339
- const body = (await res.json()) as Record<string, unknown>;
2340
-
2341
- expect(body.accepted).toBe(true);
2342
- // Should get the guardian-pending notice, NOT decision_applied
2343
- expect(body.approval).toBe("assistant_turn");
2344
-
2345
- deliverSpy.mockRestore();
2346
- });
2347
- });
2348
-
2349
- // ═══════════════════════════════════════════════════════════════════════════
2350
- // Engine decision race condition — standard path
2351
- // ═══════════════════════════════════════════════════════════════════════════
2352
-
2353
- describe("engine decision race condition — standard path", () => {
2354
- beforeEach(() => {
2355
- createGuardianBinding({
2356
- channel: "telegram",
2357
- guardianExternalUserId: "telegram-user-default",
2358
- guardianDeliveryChatId: "chat-123",
2359
- guardianPrincipalId: "telegram-user-default",
2360
- });
2361
- });
2362
-
2363
- test("returns stale_ignored when engine approves but interaction was already resolved", async () => {
2364
- const deliverSpy = spyOn(
2365
- gatewayClient,
2366
- "deliverChannelReply",
2367
- ).mockResolvedValue({ ok: true });
2368
-
2369
- const initReq = makeInboundRequest({ content: "init" });
2370
- await handleChannelInbound(initReq, noopProcessMessage);
2371
-
2372
- const db = getDb();
2373
- const events = db.$client
2374
- .prepare("SELECT conversation_id FROM channel_inbound_events")
2375
- .all() as Array<{ conversation_id: string }>;
2376
- const conversationId = events[0]?.conversation_id;
2377
- ensureConversation(conversationId!);
2378
-
2379
- registerPendingInteraction("req-race-1", conversationId!, "shell");
2380
-
2381
- deliverSpy.mockClear();
2382
-
2383
- // Engine returns approve_once, but resolves the pending interaction
2384
- // before handleChannelDecision is called (simulating race condition)
2385
- const mockConversationGenerator = mock(async (_ctx: unknown) => {
2386
- pendingInteractions.resolve("req-race-1");
2387
- return {
2388
- disposition: "approve_once" as const,
2389
- replyText: "Approved! Running the command now.",
2390
- };
2391
- });
2392
- setTestApprovalConversationGenerator(mockConversationGenerator);
2393
-
2394
- const req = makeInboundRequest({ content: "go ahead" });
2395
- const res = await handleChannelInbound(
2396
- req,
2397
- noopProcessMessage,
2398
- "self",
2399
- undefined,
2400
- );
2401
- const body = (await res.json()) as Record<string, unknown>;
2402
-
2403
- expect(body.accepted).toBe(true);
2404
- expect(body.approval).toBe("stale_ignored");
2405
-
2406
- // The engine's optimistic "Approved!" reply should NOT have been delivered
2407
- const approvedReply = deliverSpy.mock.calls.find((call) =>
2408
- (call[1] as { text?: string }).text?.includes("Approved!"),
1436
+ // The engine's optimistic "Approved!" reply should NOT have been delivered
1437
+ const approvedReply = deliverSpy.mock.calls.find((call) =>
1438
+ (call[1] as { text?: string }).text?.includes("Approved!"),
2409
1439
  );
2410
1440
  expect(approvedReply).toBeUndefined();
2411
1441
 
@@ -2419,82 +1449,6 @@ describe("engine decision race condition — standard path", () => {
2419
1449
  });
2420
1450
  });
2421
1451
 
2422
- describe("engine decision race condition — guardian path", () => {
2423
- test("returns stale_ignored when guardian engine approves but interaction was already resolved", async () => {
2424
- createGuardianBinding({
2425
- channel: "telegram",
2426
- guardianExternalUserId: "guardian-race-user",
2427
- guardianDeliveryChatId: "guardian-race-chat",
2428
- guardianPrincipalId: "guardian-race-user",
2429
- });
2430
-
2431
- const deliverSpy = spyOn(
2432
- gatewayClient,
2433
- "deliverChannelReply",
2434
- ).mockResolvedValue({ ok: true });
2435
-
2436
- const convId = "conv-guardian-race";
2437
- ensureConversation(convId);
2438
-
2439
- registerPendingInteraction("req-grc-1", convId, "shell");
2440
- createApprovalRequest({
2441
- runId: "run-grc-1",
2442
- requestId: "req-grc-1",
2443
- conversationId: convId,
2444
- channel: "telegram",
2445
- requesterExternalUserId: "requester-race-user",
2446
- requesterChatId: "requester-race-chat",
2447
- guardianExternalUserId: "guardian-race-user",
2448
- guardianChatId: "guardian-race-chat",
2449
- toolName: "shell",
2450
- expiresAt: Date.now() + 300_000,
2451
- });
2452
-
2453
- deliverSpy.mockClear();
2454
-
2455
- // Guardian engine returns approve_once, but resolves the pending interaction
2456
- // to simulate a concurrent resolution (expiry sweep or requester cancel)
2457
- const mockConversationGenerator = mock(async (_ctx: unknown) => {
2458
- pendingInteractions.resolve("req-grc-1");
2459
- return {
2460
- disposition: "approve_once" as const,
2461
- replyText: "Approved the request.",
2462
- };
2463
- });
2464
- setTestApprovalConversationGenerator(mockConversationGenerator);
2465
-
2466
- const guardianReq = makeInboundRequest({
2467
- content: "approve it",
2468
- conversationExternalId: "guardian-race-chat",
2469
- actorExternalId: "guardian-race-user",
2470
- });
2471
- const res = await handleChannelInbound(
2472
- guardianReq,
2473
- noopProcessMessage,
2474
- "self",
2475
- undefined,
2476
- );
2477
- const body = (await res.json()) as Record<string, unknown>;
2478
-
2479
- expect(body.accepted).toBe(true);
2480
- expect(body.approval).toBe("stale_ignored");
2481
-
2482
- // The engine's "Approved the request." should NOT be delivered
2483
- const optimisticReply = deliverSpy.mock.calls.find((call) =>
2484
- (call[1] as { text?: string }).text?.includes("Approved the request"),
2485
- );
2486
- expect(optimisticReply).toBeUndefined();
2487
-
2488
- // A stale notice should have been delivered instead
2489
- const staleReply = deliverSpy.mock.calls.find((call) =>
2490
- (call[1] as { text?: string }).text?.includes("already been resolved"),
2491
- );
2492
- expect(staleReply).toBeDefined();
2493
-
2494
- deliverSpy.mockRestore();
2495
- });
2496
- });
2497
-
2498
1452
  // ═══════════════════════════════════════════════════════════════════════════
2499
1453
  // Non-decision status reply for different channels
2500
1454
  // ═══════════════════════════════════════════════════════════════════════════
@@ -3006,10 +1960,10 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
3006
1960
  const conversationId = events[0]?.conversation_id;
3007
1961
  ensureConversation(conversationId!);
3008
1962
 
3009
- // Register a pending interaction — but do NOT create a guardian approval
3010
- // row in channelGuardianApprovalRequests. This simulates the window
3011
- // between the pending confirmation being created (isInteractive=true)
3012
- // and the guardian approval prompt being delivered.
1963
+ // Register a pending interaction — but do NOT create a canonical guardian
1964
+ // request row. This simulates the window between the pending confirmation
1965
+ // being created (isInteractive=true) and the guardian approval prompt being
1966
+ // delivered.
3013
1967
  const sessionMock = registerPendingInteraction(
3014
1968
  "req-tc-selfapproval-1",
3015
1969
  conversationId!,