@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f

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 (866) hide show
  1. package/ARCHITECTURE.md +94 -91
  2. package/Dockerfile +5 -16
  3. package/README.md +7 -10
  4. package/bun.lock +0 -4
  5. package/docs/architecture/security.md +20 -20
  6. package/docs/stt-provider-onboarding.md +4 -5
  7. package/docs/trusted-contact-access.md +7 -7
  8. package/eslint-rules/cli-no-daemon-internals.js +4 -0
  9. package/eslint.config.mjs +7 -1
  10. package/knip.json +0 -1
  11. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
  13. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
  14. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
  15. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
  17. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
  19. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
  21. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
  22. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
  23. package/openapi.yaml +1073 -225
  24. package/package.json +1 -4
  25. package/scripts/test.sh +35 -13
  26. package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
  27. package/src/__tests__/acp-session.test.ts +4 -1
  28. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
  29. package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
  30. package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
  31. package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
  32. package/src/__tests__/app-source-watcher.test.ts +11 -0
  33. package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
  34. package/src/__tests__/assistant-attachments.test.ts +62 -4
  35. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
  36. package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
  37. package/src/__tests__/attachments-store.test.ts +3 -0
  38. package/src/__tests__/background-shell-bash.test.ts +34 -0
  39. package/src/__tests__/background-shell-host-bash.test.ts +12 -0
  40. package/src/__tests__/background-tool-registry.test.ts +97 -0
  41. package/src/__tests__/background-tool-routes.test.ts +72 -1
  42. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  43. package/src/__tests__/btw-routes.test.ts +57 -3
  44. package/src/__tests__/call-controller.test.ts +474 -84
  45. package/src/__tests__/call-domain.test.ts +48 -1
  46. package/src/__tests__/call-pointer-messages.test.ts +57 -19
  47. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
  48. package/src/__tests__/call-routes-http.test.ts +15 -14
  49. package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
  50. package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
  51. package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
  52. package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
  53. package/src/__tests__/call-setup-flow.test.ts +311 -0
  54. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
  55. package/src/__tests__/channel-guardian.test.ts +4 -7
  56. package/src/__tests__/channel-readiness-service.test.ts +0 -6
  57. package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
  58. package/src/__tests__/channel-retry-sweep.test.ts +76 -64
  59. package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
  60. package/src/__tests__/clear-all-lexical.test.ts +90 -0
  61. package/src/__tests__/compactor-media-strip.test.ts +177 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +5 -5
  63. package/src/__tests__/config-schema.test.ts +17 -0
  64. package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
  65. package/src/__tests__/contacts-relay-reads.test.ts +188 -23
  66. package/src/__tests__/contacts-tools.test.ts +42 -1
  67. package/src/__tests__/contacts-write.test.ts +181 -0
  68. package/src/__tests__/context-search-conversations-source.test.ts +220 -81
  69. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  71. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  72. package/src/__tests__/conversation-agent-loop.test.ts +39 -0
  73. package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
  74. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
  75. package/src/__tests__/conversation-clear-safety.test.ts +0 -1
  76. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
  77. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
  78. package/src/__tests__/conversation-disk-view.test.ts +1 -0
  79. package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
  80. package/src/__tests__/conversation-error.test.ts +0 -18
  81. package/src/__tests__/conversation-fork-crud.test.ts +67 -0
  82. package/src/__tests__/conversation-history-web-search.test.ts +7 -137
  83. package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
  84. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
  85. package/src/__tests__/conversation-process-callsite.test.ts +37 -0
  86. package/src/__tests__/conversation-queue.test.ts +149 -0
  87. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
  88. package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
  89. package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
  90. package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
  91. package/src/__tests__/conversation-skill-tools.test.ts +73 -0
  92. package/src/__tests__/conversation-wipe.test.ts +20 -5
  93. package/src/__tests__/credential-prompt-route.test.ts +69 -0
  94. package/src/__tests__/credential-security-invariants.test.ts +1 -3
  95. package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
  96. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
  97. package/src/__tests__/edit-propagation.test.ts +56 -2
  98. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
  99. package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
  100. package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
  101. package/src/__tests__/guardian-question-mode.test.ts +57 -0
  102. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  103. package/src/__tests__/guardian-wait-controller.test.ts +619 -0
  104. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
  105. package/src/__tests__/heic-fixture.ts +55 -0
  106. package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
  107. package/src/__tests__/http-conversation-lineage.test.ts +6 -1
  108. package/src/__tests__/image-conversion.test.ts +170 -0
  109. package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
  110. package/src/__tests__/introduction-card-resolver.test.ts +354 -0
  111. package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
  112. package/src/__tests__/invite-routes-http.test.ts +246 -420
  113. package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
  114. package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
  115. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
  116. package/src/__tests__/list-messages-attachments.test.ts +159 -0
  117. package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
  118. package/src/__tests__/list-messages-queued.test.ts +25 -0
  119. package/src/__tests__/managed-store.test.ts +129 -1
  120. package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
  121. package/src/__tests__/media-stream-output.test.ts +350 -19
  122. package/src/__tests__/media-stream-server-integration.test.ts +943 -169
  123. package/src/__tests__/media-stream-stt-session.test.ts +498 -1
  124. package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
  125. package/src/__tests__/model-intents.test.ts +1 -1
  126. package/src/__tests__/mtime-cache.test.ts +19 -0
  127. package/src/__tests__/non-member-access-request.test.ts +349 -3
  128. package/src/__tests__/notification-decision-fallback.test.ts +104 -8
  129. package/src/__tests__/notification-decision-strategy.test.ts +57 -8
  130. package/src/__tests__/onboarding-template-contract.test.ts +27 -43
  131. package/src/__tests__/openai-provider.test.ts +33 -0
  132. package/src/__tests__/openai-responses-provider.test.ts +40 -0
  133. package/src/__tests__/persistence-layering-guard.test.ts +29 -20
  134. package/src/__tests__/persona-resolver.test.ts +56 -2
  135. package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
  136. package/src/__tests__/phone.test.ts +1 -15
  137. package/src/__tests__/plugin-bootstrap.test.ts +25 -196
  138. package/src/__tests__/plugin-disabled-state.test.ts +33 -0
  139. package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
  140. package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
  141. package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
  142. package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
  143. package/src/__tests__/plugin-pipeline.test.ts +63 -1
  144. package/src/__tests__/plugin-route-contribution.test.ts +62 -115
  145. package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
  146. package/src/__tests__/plugin-types.test.ts +0 -1
  147. package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
  148. package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
  149. package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
  150. package/src/__tests__/public-ingress-urls.test.ts +0 -48
  151. package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
  152. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
  153. package/src/__tests__/resolve-trust-class.test.ts +28 -10
  154. package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
  155. package/src/__tests__/schedule-retry.test.ts +38 -14
  156. package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
  157. package/src/__tests__/schedule-routes.test.ts +14 -6
  158. package/src/__tests__/schedule-store.test.ts +3 -1
  159. package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
  160. package/src/__tests__/scheduler-recurrence.test.ts +57 -107
  161. package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
  162. package/src/__tests__/scheduler-wake.test.ts +13 -23
  163. package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
  164. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  165. package/src/__tests__/shell-observability.test.ts +1 -0
  166. package/src/__tests__/skill-boundary-guard.test.ts +3 -4
  167. package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
  168. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
  169. package/src/__tests__/skill-script-runner-host.test.ts +53 -0
  170. package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
  171. package/src/__tests__/skill-tool-factory.test.ts +73 -1
  172. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
  173. package/src/__tests__/slack-inbound-verification.test.ts +36 -0
  174. package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
  175. package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
  176. package/src/__tests__/stt-hints.test.ts +2 -256
  177. package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
  178. package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
  179. package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
  180. package/src/__tests__/subagent-persistence.test.ts +122 -0
  181. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  182. package/src/__tests__/subagent-terminal-message.test.ts +99 -0
  183. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
  184. package/src/__tests__/system-prompt.test.ts +17 -118
  185. package/src/__tests__/task-scheduler.test.ts +68 -89
  186. package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
  187. package/src/__tests__/telephony-tts-guard.test.ts +538 -0
  188. package/src/__tests__/title-generate-hook.test.ts +16 -0
  189. package/src/__tests__/tool-approval-handler.test.ts +87 -1
  190. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
  191. package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
  192. package/src/__tests__/trust-context-guards.test.ts +21 -10
  193. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
  194. package/src/__tests__/twilio-provider.test.ts +17 -17
  195. package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
  196. package/src/__tests__/twilio-routes.test.ts +257 -387
  197. package/src/__tests__/twilio-validation.test.ts +1 -1
  198. package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
  199. package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
  200. package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
  201. package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
  202. package/src/__tests__/voice-quality.test.ts +2 -2
  203. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
  204. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
  205. package/src/__tests__/voice-session-bridge.test.ts +1 -10
  206. package/src/__tests__/workspace-git-service.test.ts +1 -1
  207. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
  208. package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
  209. package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
  210. package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
  211. package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
  212. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
  213. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
  214. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
  215. package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
  216. package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
  217. package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
  218. package/src/acp/__tests__/session-manager.test.ts +6 -0
  219. package/src/acp/agent-process.test.ts +116 -0
  220. package/src/acp/agent-process.ts +71 -0
  221. package/src/acp/auto-install.ts +12 -8
  222. package/src/acp/failure-error.test.ts +109 -0
  223. package/src/acp/failure-error.ts +111 -0
  224. package/src/acp/prepare-agent-env.ts +0 -12
  225. package/src/acp/resolve-agent.test.ts +6 -45
  226. package/src/acp/resolve-agent.ts +2 -4
  227. package/src/acp/session-manager.test.ts +312 -0
  228. package/src/acp/session-manager.ts +102 -46
  229. package/src/agent/ax-tree-compaction.test.ts +4 -1
  230. package/src/agent/image-optimize.ts +17 -127
  231. package/src/agent/loop.ts +45 -192
  232. package/src/api/events/background-tool-completed.test.ts +51 -0
  233. package/src/api/events/background-tool-completed.ts +29 -0
  234. package/src/api/events/background-tool-started.test.ts +36 -0
  235. package/src/api/events/background-tool-started.ts +28 -0
  236. package/src/api/events/conversation-error.ts +0 -1
  237. package/src/api/events/open-panel.ts +33 -0
  238. package/src/api/events/subagent-status-changed.ts +4 -1
  239. package/src/api/index.ts +19 -0
  240. package/src/api/responses/conversation-message.ts +34 -0
  241. package/src/approvals/guardian-decision-primitive.ts +74 -24
  242. package/src/approvals/guardian-request-resolvers.ts +450 -122
  243. package/src/apps/app-store.ts +1 -0
  244. package/src/apps/shared-app-links-store.ts +1 -0
  245. package/src/avatar/identity-avatar.ts +3 -7
  246. package/src/background-wake/wake-intent-hooks.test.ts +1 -1
  247. package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
  248. package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
  249. package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
  250. package/src/calls/call-controller.ts +134 -48
  251. package/src/calls/call-domain.ts +18 -42
  252. package/src/calls/call-pointer-messages.ts +51 -67
  253. package/src/calls/call-setup-flow-types.ts +76 -0
  254. package/src/calls/call-setup-flow.ts +1743 -0
  255. package/src/calls/call-speech-output.ts +87 -23
  256. package/src/calls/call-store.ts +4 -0
  257. package/src/calls/call-transport.ts +22 -36
  258. package/src/calls/fish-audio-client.ts +3 -0
  259. package/src/calls/gateway-invite-reader.ts +98 -0
  260. package/src/calls/guardian-wait-controller.ts +451 -0
  261. package/src/calls/media-stream-audio-transcode.ts +82 -2
  262. package/src/calls/media-stream-output.ts +142 -36
  263. package/src/calls/media-stream-server.ts +297 -178
  264. package/src/calls/media-stream-stt-session.ts +393 -50
  265. package/src/calls/media-turn-detector.ts +4 -6
  266. package/src/calls/relay-access-wait.ts +3 -4
  267. package/src/calls/relay-setup-router.ts +22 -43
  268. package/src/calls/relay-verification.ts +17 -24
  269. package/src/calls/resolve-call-tts-provider.ts +96 -29
  270. package/src/calls/stt-hints.ts +24 -87
  271. package/src/calls/telephony-credential-preflight.ts +159 -0
  272. package/src/calls/telephony-tts-capability.ts +183 -0
  273. package/src/calls/tts-call-strategy.ts +18 -20
  274. package/src/calls/twilio-provider.ts +3 -2
  275. package/src/calls/twilio-routes.ts +47 -351
  276. package/src/calls/types.ts +5 -3
  277. package/src/calls/voice-ingress-preflight.ts +9 -0
  278. package/src/calls/voice-quality.ts +13 -18
  279. package/src/calls/voice-session-bridge.ts +25 -35
  280. package/src/channels/__tests__/types.test.ts +28 -0
  281. package/src/channels/channel-verification-sessions.ts +7 -2
  282. package/src/channels/config.ts +0 -42
  283. package/src/channels/slack-thread-store.ts +12 -0
  284. package/src/channels/types.ts +23 -0
  285. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
  286. package/src/cli/commands/__tests__/schedules.test.ts +83 -34
  287. package/src/cli/commands/__tests__/status.test.ts +42 -2
  288. package/src/cli/commands/contacts.ts +2 -2
  289. package/src/cli/commands/credentials.ts +17 -0
  290. package/src/cli/commands/monitoring.ts +191 -0
  291. package/src/cli/commands/schedules-worker.ts +175 -0
  292. package/src/cli/commands/schedules.ts +169 -43
  293. package/src/cli/commands/status.ts +12 -1
  294. package/src/cli/commands/tools.ts +5 -1
  295. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
  296. package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
  297. package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
  298. package/src/cli/lib/search-plugins.ts +6 -0
  299. package/src/cli/lib/toggle-plugin.ts +21 -2
  300. package/src/cli/program.ts +3 -1
  301. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
  302. package/src/config/acp-defaults.test.ts +0 -10
  303. package/src/config/acp-defaults.ts +0 -6
  304. package/src/config/bundled-skills/acp/SKILL.md +13 -45
  305. package/src/config/bundled-skills/acp/TOOLS.json +3 -3
  306. package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
  307. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
  308. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
  309. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
  310. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  311. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
  312. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
  313. package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
  314. package/src/config/call-site-defaults.ts +0 -2
  315. package/src/config/feature-flag-registry.json +16 -8
  316. package/src/config/loader.ts +11 -0
  317. package/src/config/schema.ts +9 -1
  318. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
  319. package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
  320. package/src/config/schemas/call-site-catalog.ts +0 -12
  321. package/src/config/schemas/calls.ts +8 -0
  322. package/src/config/schemas/elevenlabs.ts +1 -1
  323. package/src/config/schemas/llm.ts +0 -2
  324. package/src/config/schemas/memory-v2.ts +1 -1
  325. package/src/config/schemas/memory-v3.ts +82 -0
  326. package/src/config/schemas/monitoring.ts +61 -0
  327. package/src/config/schemas/schedules.ts +35 -0
  328. package/src/config/seed-inference-profiles.ts +2 -2
  329. package/src/config/skills-plugin-filter.test.ts +96 -0
  330. package/src/config/skills.ts +26 -0
  331. package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
  332. package/src/contacts/canonical-guardian-store.ts +5 -0
  333. package/src/contacts/contact-store.ts +81 -35
  334. package/src/contacts/contacts-write.ts +50 -25
  335. package/src/contacts/member-write-relay.ts +115 -31
  336. package/src/contacts/types.ts +0 -8
  337. package/src/context/compactor.ts +29 -15
  338. package/src/context/outbound-sanitize.ts +202 -0
  339. package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
  340. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
  341. package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
  342. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
  343. package/src/daemon/abort-watchdog.ts +7 -0
  344. package/src/daemon/app-source-watcher.ts +9 -0
  345. package/src/daemon/assistant-attachments.ts +42 -0
  346. package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
  347. package/src/daemon/conversation-agent-loop.ts +61 -68
  348. package/src/daemon/conversation-error.ts +1 -15
  349. package/src/daemon/conversation-history.ts +32 -236
  350. package/src/daemon/conversation-initial-prompt.ts +77 -0
  351. package/src/daemon/conversation-lifecycle.ts +25 -1
  352. package/src/daemon/conversation-messaging.ts +2 -2
  353. package/src/daemon/conversation-plugin-scope.ts +38 -0
  354. package/src/daemon/conversation-process.ts +50 -14
  355. package/src/daemon/conversation-runtime-assembly.ts +36 -6
  356. package/src/daemon/conversation-skill-tools.ts +19 -3
  357. package/src/daemon/conversation-store.ts +3 -3
  358. package/src/daemon/conversation-surfaces.ts +171 -0
  359. package/src/daemon/conversation-tool-setup.test.ts +95 -0
  360. package/src/daemon/conversation-tool-setup.ts +116 -1
  361. package/src/daemon/conversation-turn-finalize.ts +211 -0
  362. package/src/daemon/conversation.ts +92 -1
  363. package/src/daemon/disk-pressure-policy.ts +2 -1
  364. package/src/daemon/event-loop-watchdog.ts +22 -6
  365. package/src/daemon/external-plugins-bootstrap.ts +47 -134
  366. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
  367. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
  368. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
  369. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
  370. package/src/daemon/handlers/config-a2a.ts +7 -14
  371. package/src/daemon/handlers/config-channels.ts +2 -10
  372. package/src/daemon/handlers/conversation-history.ts +3 -1
  373. package/src/daemon/handlers/conversations.ts +0 -52
  374. package/src/daemon/handlers/shared.ts +4 -24
  375. package/src/daemon/handlers/skills.ts +4 -1
  376. package/src/daemon/host-cu-proxy.ts +1 -1
  377. package/src/daemon/lifecycle.ts +59 -472
  378. package/src/daemon/message-protocol.ts +3 -3
  379. package/src/daemon/message-provenance.ts +12 -14
  380. package/src/daemon/message-types/background-tools.ts +28 -0
  381. package/src/daemon/message-types/contacts.ts +2 -10
  382. package/src/daemon/message-types/conversations.ts +10 -0
  383. package/src/daemon/message-types/integrations.ts +2 -0
  384. package/src/daemon/message-types/surfaces.ts +1 -0
  385. package/src/daemon/message-types/sync.ts +1 -0
  386. package/src/daemon/pointer-conversation-trust.ts +23 -9
  387. package/src/daemon/pointer-turn-runner.ts +174 -0
  388. package/src/daemon/providers-setup.ts +16 -0
  389. package/src/daemon/shutdown-handlers.ts +139 -47
  390. package/src/daemon/skill-memory-refresh.ts +2 -2
  391. package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
  392. package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
  393. package/src/daemon/tool-setup-types.ts +8 -0
  394. package/src/daemon/trust-context.ts +21 -6
  395. package/src/daemon/turn-latency-tracker.test.ts +93 -0
  396. package/src/daemon/turn-latency-tracker.ts +38 -0
  397. package/src/daemon/wake-conversation-ops.ts +10 -8
  398. package/src/documents/document-comments-store.test.ts +7 -1
  399. package/src/documents/document-comments-store.ts +8 -0
  400. package/src/documents/document-store.ts +15 -0
  401. package/src/export/__tests__/transcript-formatter.test.ts +11 -0
  402. package/src/export/transcript-formatter.ts +49 -67
  403. package/src/home/__tests__/feed-writer.test.ts +247 -1
  404. package/src/home/conversation-starter-checkpoints.ts +1 -0
  405. package/src/home/feed-writer.ts +61 -0
  406. package/src/home/job-handlers/conversation-starters.ts +1 -0
  407. package/src/hooks/hook-loader.ts +12 -0
  408. package/src/hooks/registry.ts +32 -4
  409. package/src/inbound/public-ingress-urls.ts +0 -17
  410. package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
  411. package/src/ipc/assistant-server.ts +28 -7
  412. package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
  413. package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
  414. package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
  415. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
  416. package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
  417. package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
  418. package/src/ipc/routes/db-proxy.ts +5 -2
  419. package/src/ipc/routes/invite-ipc-routes.ts +53 -39
  420. package/src/jobs/worker.ts +9 -2
  421. package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
  422. package/src/live-voice/live-voice-archive.ts +5 -0
  423. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
  424. package/src/messaging/providers/channel-transport.ts +6 -0
  425. package/src/messaging/providers/index.ts +3 -0
  426. package/src/messaging/providers/slack/api.test.ts +49 -1
  427. package/src/messaging/providers/slack/api.ts +110 -0
  428. package/src/messaging/providers/slack/send.test.ts +85 -0
  429. package/src/messaging/providers/slack/send.ts +100 -28
  430. package/src/messaging/providers/slack/transport.ts +7 -13
  431. package/src/messaging/providers/whatsapp/send.ts +29 -9
  432. package/src/monitoring/__tests__/control.test.ts +235 -0
  433. package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
  434. package/src/monitoring/control.ts +105 -0
  435. package/src/monitoring/resource-sampler.ts +258 -0
  436. package/src/monitoring/sample-ring-buffer.ts +108 -0
  437. package/src/monitoring/worker.ts +94 -0
  438. package/src/notifications/README.md +1 -1
  439. package/src/notifications/access-request-copy.ts +94 -23
  440. package/src/notifications/adapters/slack.ts +89 -21
  441. package/src/notifications/approval-card-builder.ts +33 -18
  442. package/src/notifications/approval-card-data.ts +48 -15
  443. package/src/notifications/broadcaster.ts +25 -16
  444. package/src/notifications/decision-engine.ts +131 -32
  445. package/src/notifications/guardian-question-mode.ts +58 -10
  446. package/src/permissions/approval-policy.ts +8 -1
  447. package/src/permissions/checker.ts +2 -0
  448. package/src/permissions/gateway-threshold-reader.ts +7 -6
  449. package/src/permissions/ipc-risk-types.ts +1 -0
  450. package/src/permissions/secret-prompter.ts +11 -3
  451. package/src/permissions/types.ts +2 -2
  452. package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
  453. package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
  454. package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
  455. package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
  456. package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
  457. package/src/persistence/a2a-invite-store.ts +205 -0
  458. package/src/persistence/attachments-store.ts +94 -10
  459. package/src/persistence/checkpoints.ts +45 -0
  460. package/src/persistence/conversation-crud.ts +453 -260
  461. package/src/persistence/conversation-display-order-migration.ts +8 -2
  462. package/src/persistence/conversation-group-migration.test.ts +8 -0
  463. package/src/persistence/conversation-group-migration.ts +9 -0
  464. package/src/persistence/conversation-queries.ts +244 -125
  465. package/src/persistence/conversation-row-batch-delete.ts +9 -5
  466. package/src/persistence/conversation-search-lexical.ts +49 -0
  467. package/src/persistence/db-async-query.ts +42 -0
  468. package/src/persistence/db-connection.ts +13 -0
  469. package/src/persistence/delivery-channels.ts +0 -82
  470. package/src/persistence/delivery-crud.ts +27 -6
  471. package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
  472. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
  473. package/src/persistence/embeddings/embedding-identity.ts +17 -5
  474. package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
  475. package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
  476. package/src/persistence/embeddings/qdrant-client.ts +81 -5
  477. package/src/persistence/group-crud.ts +13 -1
  478. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
  479. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
  480. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
  481. package/src/persistence/job-handlers/cleanup.ts +57 -9
  482. package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
  483. package/src/persistence/job-handlers/message-lexical.ts +251 -0
  484. package/src/persistence/jobs-store.ts +102 -8
  485. package/src/persistence/jobs-worker.ts +68 -28
  486. package/src/persistence/llm-request-log-store.ts +35 -19
  487. package/src/persistence/llm-usage-store.ts +11 -0
  488. package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
  489. package/src/persistence/memory-lifecycle-hooks.ts +55 -1
  490. package/src/persistence/migrations/028-call-session-mode.ts +1 -1
  491. package/src/persistence/migrations/116-messages-fts.ts +8 -1
  492. package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
  493. package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
  494. package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
  495. package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
  496. package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
  497. package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
  498. package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
  499. package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
  500. package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
  501. package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
  502. package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
  503. package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
  504. package/src/persistence/raw-query.ts +50 -11
  505. package/src/persistence/schema/a2a.ts +14 -0
  506. package/src/persistence/schema/contacts.ts +0 -62
  507. package/src/persistence/schema/conversation-groups.ts +12 -0
  508. package/src/persistence/schema/conversations.ts +2 -0
  509. package/src/persistence/schema/documents.ts +65 -0
  510. package/src/persistence/schema/guardian.ts +4 -0
  511. package/src/persistence/schema/index.ts +4 -0
  512. package/src/persistence/schema/memory-injection.ts +62 -0
  513. package/src/persistence/schema/workflows.ts +55 -0
  514. package/src/persistence/slow-query-log.ts +437 -0
  515. package/src/persistence/slow-sync-log.ts +204 -0
  516. package/src/persistence/steps.ts +16 -0
  517. package/src/persistence/subagent-store.ts +125 -0
  518. package/src/persistence/worker-control.ts +45 -238
  519. package/src/plugin-api/event-hub-facade.ts +172 -0
  520. package/src/plugin-api/index.ts +9 -1
  521. package/src/plugin-api/types.ts +40 -9
  522. package/src/plugins/defaults/default-plugin-names.ts +60 -0
  523. package/src/plugins/defaults/index.ts +19 -29
  524. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
  525. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
  526. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
  527. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
  528. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
  529. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
  530. package/src/plugins/defaults/memory/config.ts +11 -0
  531. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
  532. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
  533. package/src/plugins/defaults/memory/embeddings.ts +77 -0
  534. package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
  535. package/src/plugins/defaults/memory/frontmatter.ts +50 -0
  536. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
  537. package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
  538. package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
  539. package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
  540. package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
  541. package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
  542. package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
  543. package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
  544. package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
  545. package/src/plugins/defaults/memory/graph/tools.ts +1 -1
  546. package/src/plugins/defaults/memory/hooks/init.ts +31 -5
  547. package/src/plugins/defaults/memory/indexer.ts +2 -3
  548. package/src/plugins/defaults/memory/injectors.ts +2 -2
  549. package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
  550. package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
  551. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
  552. package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
  553. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
  554. package/src/plugins/defaults/memory/job-handlers.ts +13 -12
  555. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
  556. package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
  557. package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
  558. package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
  559. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
  560. package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
  561. package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
  562. package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
  563. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
  564. package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
  565. package/src/plugins/defaults/memory/search/semantic.ts +2 -2
  566. package/src/plugins/defaults/memory/startup.ts +244 -0
  567. package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
  568. package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
  569. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
  570. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
  571. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
  572. package/src/plugins/defaults/memory/v2/activation.ts +2 -4
  573. package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
  574. package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
  575. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
  576. package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
  577. package/src/plugins/defaults/memory/v2/migration.ts +2 -5
  578. package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
  579. package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
  580. package/src/plugins/defaults/memory/v2/router.ts +5 -5
  581. package/src/plugins/defaults/memory/v2/sim.ts +2 -4
  582. package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
  583. package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
  584. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
  585. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
  586. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
  587. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
  588. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
  589. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
  590. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
  591. package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
  592. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
  593. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
  594. package/src/plugins/defaults/memory/v3/card.ts +1 -4
  595. package/src/plugins/defaults/memory/v3/dense.ts +60 -25
  596. package/src/plugins/defaults/memory/v3/edge.ts +1 -1
  597. package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
  598. package/src/plugins/defaults/memory/v3/gate.ts +158 -0
  599. package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
  600. package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
  601. package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
  602. package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
  603. package/src/plugins/defaults/memory/v3/prune.ts +2 -2
  604. package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
  605. package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
  606. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
  607. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
  608. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
  609. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  610. package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
  611. package/src/plugins/injector-registry.ts +13 -2
  612. package/src/plugins/mtime-cache.ts +36 -47
  613. package/src/plugins/pipeline.ts +15 -1
  614. package/src/plugins/types.ts +14 -23
  615. package/src/prompts/__tests__/system-prompt.test.ts +170 -32
  616. package/src/prompts/persona-resolver.ts +13 -9
  617. package/src/prompts/system-prompt.ts +30 -9
  618. package/src/prompts/templates/BOOTSTRAP.md +16 -119
  619. package/src/prompts/templates/SOUL.md +43 -13
  620. package/src/prompts/templates/system-sections.ts +32 -27
  621. package/src/prompts/templates/users/default.md +19 -0
  622. package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
  623. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
  624. package/src/providers/model-intents.ts +2 -2
  625. package/src/providers/openai/chat-completions-provider.ts +14 -1
  626. package/src/providers/openai/responses-provider.ts +18 -0
  627. package/src/providers/placeholder-sentinels.ts +18 -8
  628. package/src/providers/retry.ts +1 -0
  629. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
  630. package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
  631. package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
  632. package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
  633. package/src/providers/speech-to-text/provider-catalog.ts +0 -89
  634. package/src/providers/speech-to-text/resolve.ts +55 -0
  635. package/src/providers/tool-progress-events.test.ts +50 -0
  636. package/src/providers/tool-progress-events.ts +99 -0
  637. package/src/providers/types.ts +1 -1
  638. package/src/providers/vellum-model-routing.test.ts +51 -0
  639. package/src/providers/vellum-model-routing.ts +84 -0
  640. package/src/runtime/AGENTS.md +5 -4
  641. package/src/runtime/__tests__/agent-wake.test.ts +158 -1
  642. package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
  643. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
  644. package/src/runtime/access-request-helper.ts +154 -3
  645. package/src/runtime/actor-trust-resolver.ts +7 -21
  646. package/src/runtime/agent-wake.ts +41 -1
  647. package/src/runtime/assistant-event.ts +86 -12
  648. package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
  649. package/src/runtime/channel-approval-types.ts +52 -2
  650. package/src/runtime/channel-approvals.ts +23 -8
  651. package/src/runtime/channel-invite-transport.ts +2 -2
  652. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  653. package/src/runtime/channel-readiness-service.ts +3 -5
  654. package/src/runtime/channel-retry-sweep.ts +18 -24
  655. package/src/runtime/channel-verification-service.ts +9 -11
  656. package/src/runtime/finalize-event-delivery.ts +30 -16
  657. package/src/runtime/guardian-action-service.ts +10 -11
  658. package/src/runtime/guardian-reply-router.ts +122 -21
  659. package/src/runtime/http-server.ts +44 -101
  660. package/src/runtime/introduction-policy.test.ts +214 -0
  661. package/src/runtime/introduction-policy.ts +284 -0
  662. package/src/runtime/invite-service.ts +104 -361
  663. package/src/runtime/local-actor-identity.test.ts +38 -0
  664. package/src/runtime/local-actor-identity.ts +14 -0
  665. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
  666. package/src/runtime/middleware/rate-limiter.ts +30 -0
  667. package/src/runtime/middleware/twilio-validation.ts +5 -7
  668. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
  669. package/src/runtime/migrations/origin-mode.ts +6 -6
  670. package/src/runtime/pre-first-message-gate.ts +1 -0
  671. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
  672. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
  673. package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
  674. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
  675. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
  676. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
  677. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
  678. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
  679. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
  680. package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
  681. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
  682. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
  683. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
  684. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  686. package/src/runtime/routes/acp-routes.ts +15 -3
  687. package/src/runtime/routes/app-management-routes.ts +4 -7
  688. package/src/runtime/routes/approval-routes.ts +4 -0
  689. package/src/runtime/routes/attachment-routes.ts +54 -3
  690. package/src/runtime/routes/avatar-routes.ts +0 -4
  691. package/src/runtime/routes/background-tool-routes.ts +34 -5
  692. package/src/runtime/routes/btw-routes.ts +32 -4
  693. package/src/runtime/routes/channel-route-shared.ts +18 -14
  694. package/src/runtime/routes/channel-verification-routes.ts +0 -3
  695. package/src/runtime/routes/contact-routes.ts +298 -117
  696. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  697. package/src/runtime/routes/conversation-management-routes.ts +74 -37
  698. package/src/runtime/routes/conversation-query-routes.ts +5 -2
  699. package/src/runtime/routes/conversation-routes.ts +222 -82
  700. package/src/runtime/routes/conversations-import-routes.ts +8 -1
  701. package/src/runtime/routes/credential-prompt-routes.ts +20 -5
  702. package/src/runtime/routes/debug-routes.ts +1 -0
  703. package/src/runtime/routes/documents-routes.ts +5 -2
  704. package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
  705. package/src/runtime/routes/global-search-routes.ts +2 -1
  706. package/src/runtime/routes/guardian-approval-interception.ts +51 -0
  707. package/src/runtime/routes/home-feed-routes.ts +60 -1
  708. package/src/runtime/routes/identity-routes.ts +4 -87
  709. package/src/runtime/routes/inbound-message-handler.ts +82 -41
  710. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
  711. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
  712. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
  713. package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
  714. package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
  715. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
  716. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
  717. package/src/runtime/routes/index.ts +6 -0
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
  719. package/src/runtime/routes/internal-twilio-routes.ts +0 -15
  720. package/src/runtime/routes/messages-lexical-routes.ts +75 -0
  721. package/src/runtime/routes/migration-routes.ts +69 -6
  722. package/src/runtime/routes/monitoring-routes.ts +237 -0
  723. package/src/runtime/routes/plugins-routes.ts +329 -8
  724. package/src/runtime/routes/schedule-routes.ts +49 -8
  725. package/src/runtime/routes/schedule-worker-routes.ts +210 -0
  726. package/src/runtime/routes/secret-routes.ts +1 -1
  727. package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
  728. package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
  729. package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
  730. package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
  731. package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
  732. package/src/runtime/services/analyze-conversation.ts +14 -4
  733. package/src/runtime/services/conversation-serializer.ts +5 -0
  734. package/src/runtime/slack-no-response.ts +47 -0
  735. package/src/runtime/slack-reply-session.test.ts +633 -0
  736. package/src/runtime/slack-reply-session.ts +382 -0
  737. package/src/runtime/slack-task-progress.ts +95 -0
  738. package/src/runtime/sync/resource-sync-events.ts +25 -0
  739. package/src/runtime/tool-grant-request-helper.ts +17 -1
  740. package/src/runtime/trust-class.test.ts +141 -0
  741. package/src/runtime/trust-class.ts +121 -0
  742. package/src/runtime/trust-verdict-consumer.ts +7 -6
  743. package/src/schedule/__tests__/run-script.test.ts +21 -0
  744. package/src/schedule/__tests__/worker-control.test.ts +241 -0
  745. package/src/schedule/run-script.ts +13 -6
  746. package/src/schedule/schedule-usage-store.ts +1 -0
  747. package/src/schedule/scheduler.ts +178 -127
  748. package/src/schedule/worker-control.ts +118 -0
  749. package/src/schedule/worker.ts +131 -0
  750. package/src/skills/__tests__/categories-cache.test.ts +38 -0
  751. package/src/skills/categories-cache.ts +40 -5
  752. package/src/skills/managed-store.ts +41 -6
  753. package/src/skills/validate-input.ts +47 -0
  754. package/src/stt/types.ts +5 -9
  755. package/src/subagent/manager.ts +267 -39
  756. package/src/subagent/types.ts +6 -2
  757. package/src/subagent/validate-allowlists.ts +65 -0
  758. package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
  759. package/src/telemetry/watchdog-direct-emit.ts +89 -0
  760. package/src/tools/acp/list-agents.test.ts +0 -6
  761. package/src/tools/background-tool-registry.ts +61 -0
  762. package/src/tools/document/document-comment-tool.test.ts +4 -0
  763. package/src/tools/host-terminal/host-shell.test.ts +397 -0
  764. package/src/tools/host-terminal/host-shell.ts +203 -14
  765. package/src/tools/skills/find-similar-skills.test.ts +152 -1
  766. package/src/tools/skills/find-similar-skills.ts +30 -4
  767. package/src/tools/skills/load.ts +36 -0
  768. package/src/tools/skills/scaffold-managed.ts +66 -34
  769. package/src/tools/skills/skill-script-runner.ts +17 -0
  770. package/src/tools/skills/skill-tool-factory.ts +5 -1
  771. package/src/tools/terminal/safe-env.ts +3 -0
  772. package/src/tools/terminal/shell.test.ts +177 -0
  773. package/src/tools/terminal/shell.ts +98 -4
  774. package/src/tools/tool-approval-handler.ts +106 -29
  775. package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
  776. package/src/tools/types.ts +29 -17
  777. package/src/tools/ui-surface/definitions.ts +3 -1
  778. package/src/tts/__tests__/provider-adapters.test.ts +37 -0
  779. package/src/tts/__tests__/provider-catalog.test.ts +21 -0
  780. package/src/tts/provider-catalog.ts +40 -9
  781. package/src/tts/providers/fish-audio-provider.ts +19 -10
  782. package/src/tts/types.ts +3 -4
  783. package/src/util/__tests__/process-tree.test.ts +10 -2
  784. package/src/util/__tests__/text-spacing.test.ts +53 -0
  785. package/src/util/cgroup-memory.ts +142 -0
  786. package/src/util/image-conversion.ts +281 -0
  787. package/src/util/phone.ts +1 -8
  788. package/src/util/platform.ts +33 -0
  789. package/src/util/process-tree.ts +10 -2
  790. package/src/util/text-spacing.ts +53 -0
  791. package/src/util/worker-process.ts +271 -0
  792. package/src/workflows/journal-store.ts +16 -1
  793. package/src/workflows/library.test.ts +35 -20
  794. package/src/workflows/library.ts +105 -66
  795. package/src/workspace/git-service.ts +1 -0
  796. package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
  797. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
  798. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
  799. package/src/workspace/migrations/118-seed-now-md.ts +64 -0
  800. package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
  801. package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
  802. package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
  803. package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
  804. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
  805. package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
  806. package/src/workspace/migrations/registry.ts +12 -0
  807. package/drizzle/0000_dizzy_maggott.sql +0 -301
  808. package/drizzle/meta/0000_snapshot.json +0 -1933
  809. package/drizzle/meta/_journal.json +0 -13
  810. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
  811. package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
  812. package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
  813. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
  814. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
  815. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
  816. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
  817. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
  818. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
  819. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
  820. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
  821. package/src/__tests__/invite-redemption-service.test.ts +0 -1180
  822. package/src/__tests__/invite-service-ipc.test.ts +0 -350
  823. package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
  824. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
  825. package/src/__tests__/relay-server.test.ts +0 -6153
  826. package/src/__tests__/telephony-stt-routing.test.ts +0 -329
  827. package/src/__tests__/voice-invite-redemption.test.ts +0 -702
  828. package/src/calls/relay-server.ts +0 -2463
  829. package/src/calls/telephony-stt-routing.ts +0 -145
  830. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
  831. package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
  832. package/src/daemon/daemon-skill-host.ts +0 -262
  833. package/src/daemon/message-types/meet.ts +0 -143
  834. package/src/daemon/shutdown-registry.ts +0 -40
  835. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  836. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  837. package/src/ipc/skill-ipc-types.ts +0 -54
  838. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  839. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  840. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  841. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  842. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  843. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  844. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  845. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  846. package/src/ipc/skill-routes/config.ts +0 -47
  847. package/src/ipc/skill-routes/events.ts +0 -147
  848. package/src/ipc/skill-routes/identity.ts +0 -21
  849. package/src/ipc/skill-routes/index.ts +0 -37
  850. package/src/ipc/skill-routes/log.ts +0 -40
  851. package/src/ipc/skill-routes/memory.ts +0 -92
  852. package/src/ipc/skill-routes/platform.ts +0 -39
  853. package/src/ipc/skill-routes/providers.ts +0 -162
  854. package/src/ipc/skill-routes/registries.ts +0 -338
  855. package/src/ipc/skill-server.ts +0 -831
  856. package/src/ipc/skill-socket-path.ts +0 -20
  857. package/src/jobs/register-job-handlers.ts +0 -74
  858. package/src/persistence/invite-store.ts +0 -481
  859. package/src/plugins/job-handler-registry.ts +0 -94
  860. package/src/providers/cache-control.ts +0 -26
  861. package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
  862. package/src/runtime/invite-redemption-service.ts +0 -828
  863. package/src/runtime/invite-redemption-templates.ts +0 -46
  864. package/src/runtime/runtime-mode.ts +0 -40
  865. package/src/runtime/slack-dm-text-delivery.ts +0 -177
  866. package/src/util/voice-code.ts +0 -31
@@ -9,6 +9,8 @@
9
9
  */
10
10
  import { beforeEach, describe, expect, mock, test } from "bun:test";
11
11
 
12
+ import { sql } from "drizzle-orm";
13
+
12
14
  // ---------------------------------------------------------------------------
13
15
  // Test isolation: in-memory SQLite via temp directory
14
16
  // ---------------------------------------------------------------------------
@@ -94,12 +96,17 @@ function seedGatewayGuardian(
94
96
  }
95
97
 
96
98
  import {
99
+ createCanonicalGuardianRequest,
97
100
  listCanonicalGuardianDeliveries,
98
101
  listCanonicalGuardianRequests,
102
+ resolveCanonicalGuardianRequest,
99
103
  } from "../contacts/canonical-guardian-store.js";
100
104
  import { getDb } from "../persistence/db-connection.js";
101
105
  import { initializeDb } from "../persistence/db-init.js";
102
- import { notifyGuardianOfAccessRequest } from "../runtime/access-request-helper.js";
106
+ import {
107
+ isAccessRequestDenied,
108
+ notifyGuardianOfAccessRequest,
109
+ } from "../runtime/access-request-helper.js";
103
110
  import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
104
111
  import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
105
112
 
@@ -304,6 +311,74 @@ describe("non-member access request notification", () => {
304
311
  expect(pending.length).toBe(1);
305
312
  });
306
313
 
314
+ // After the guardian denies an access request, subsequent DMs from the same
315
+ // sender do not re-prompt the guardian. Drives the real inbound path for both
316
+ // messages so the deny-dedup matches the exact assistant-scoped
317
+ // conversationId the notify path derives — a hand-crafted fixture could mask
318
+ // a mismatch.
319
+ test("a denied sender's subsequent DM does not re-prompt the guardian", async () => {
320
+ seedGatewayGuardian({
321
+ channelType: "telegram",
322
+ address: "guardian-user-789",
323
+ externalChatId: "guardian-chat-789",
324
+ principalId: anchorPrincipalId,
325
+ });
326
+ createGuardianBinding({
327
+ channel: "telegram",
328
+ guardianExternalUserId: "guardian-user-789",
329
+ guardianDeliveryChatId: "guardian-chat-789",
330
+ guardianPrincipalId: anchorPrincipalId,
331
+ verifiedVia: "test",
332
+ });
333
+
334
+ // 1) First DM from the unknown sender → guardian is prompted once.
335
+ await handleChannelInbound(
336
+ buildInboundRequest(),
337
+ undefined,
338
+ TEST_BEARER_TOKEN,
339
+ );
340
+ expect(emitSignalCalls.length).toBe(1);
341
+
342
+ const created = listCanonicalGuardianRequests({
343
+ status: "pending",
344
+ requesterExternalUserId: "user-unknown-456",
345
+ sourceChannel: "telegram",
346
+ kind: "access_request",
347
+ });
348
+ expect(created.length).toBe(1);
349
+
350
+ // 2) Guardian denies — resolve the *real* request (same CAS transition the
351
+ // deny primitive performs), preserving its conversationId.
352
+ const denied = resolveCanonicalGuardianRequest(created[0].id, "pending", {
353
+ status: "denied",
354
+ decidedByExternalUserId: "guardian-user-789",
355
+ });
356
+ expect(denied?.status).toBe("denied");
357
+
358
+ // 3) Same sender DMs again → still denied, but NO new guardian prompt.
359
+ const resp2 = await handleChannelInbound(
360
+ buildInboundRequest({
361
+ externalMessageId: `msg-after-deny-${Date.now()}`,
362
+ }),
363
+ undefined,
364
+ TEST_BEARER_TOKEN,
365
+ );
366
+ const json2 = (await resp2.json()) as Record<string, unknown>;
367
+ expect(json2.denied).toBe(true);
368
+
369
+ // No additional access-request signal was emitted (still just the first).
370
+ expect(emitSignalCalls.length).toBe(1);
371
+
372
+ // And no fresh pending request was created for the denied sender.
373
+ const pendingAfter = listCanonicalGuardianRequests({
374
+ status: "pending",
375
+ requesterExternalUserId: "user-unknown-456",
376
+ sourceChannel: "telegram",
377
+ kind: "access_request",
378
+ });
379
+ expect(pendingAfter.length).toBe(0);
380
+ });
381
+
307
382
  test("access request is created with self-healed principal even without same-channel guardian binding", async () => {
308
383
  // No guardian binding on any channel — self-heal creates a vellum binding
309
384
  // so the access_request (now decisionable) has a guardianPrincipalId.
@@ -749,7 +824,9 @@ describe("access-request-helper unit tests", () => {
749
824
  });
750
825
 
751
826
  expect(result.notified).toBe(true);
752
- if (!result.notified) return;
827
+ if (!result.notified) {
828
+ return;
829
+ }
753
830
 
754
831
  await flushAsyncAccessRequestBookkeeping();
755
832
 
@@ -810,7 +887,9 @@ describe("access-request-helper unit tests", () => {
810
887
  });
811
888
 
812
889
  expect(result.notified).toBe(true);
813
- if (!result.notified) return;
890
+ if (!result.notified) {
891
+ return;
892
+ }
814
893
 
815
894
  await flushAsyncAccessRequestBookkeeping();
816
895
 
@@ -826,4 +905,271 @@ describe("access-request-helper unit tests", () => {
826
905
  expect(telegram!.destinationChatId).toBe("guardian-chat-456");
827
906
  expect(telegram!.status).toBe("sent");
828
907
  });
908
+
909
+ test("notifyGuardianOfAccessRequest is suppressed after a prior deny for the same sender", async () => {
910
+ // Simulate a previously-denied access request for this sender on this
911
+ // channel/assistant. The conversationId must match the assistant-scoped
912
+ // key the helper derives: access-req-<assistantId>-<channel>-<actor>.
913
+ createCanonicalGuardianRequest({
914
+ id: `denied-${Date.now()}`,
915
+ kind: "access_request",
916
+ sourceType: "channel",
917
+ sourceChannel: "telegram",
918
+ conversationId: "access-req-self-telegram-denied-user",
919
+ requesterExternalUserId: "denied-user",
920
+ guardianPrincipalId: anchorPrincipalId,
921
+ toolName: "ingress_access_request",
922
+ status: "denied",
923
+ });
924
+
925
+ const result = await notifyGuardianOfAccessRequest({
926
+ canonicalAssistantId: "self",
927
+ sourceChannel: "telegram",
928
+ conversationExternalId: "chat-denied",
929
+ actorExternalId: "denied-user",
930
+ actorDisplayName: "Denied User",
931
+ });
932
+
933
+ // Suppressed: no new prompt, no signal, no new pending request.
934
+ expect(result.notified).toBe(false);
935
+ if (!result.notified) {
936
+ expect(result.reason).toBe("already_denied");
937
+ }
938
+ expect(emitSignalCalls.length).toBe(0);
939
+
940
+ const pending = listCanonicalGuardianRequests({
941
+ status: "pending",
942
+ requesterExternalUserId: "denied-user",
943
+ kind: "access_request",
944
+ });
945
+ expect(pending.length).toBe(0);
946
+ });
947
+
948
+ // LUM-2673: inside the post-approval verification window, inbound from the
949
+ // sender must not create a new request or re-notify the guardian — the
950
+ // handshake is waiting on the sender to enter their code.
951
+ test("suppressed while the approval's verification window is open", async () => {
952
+ createCanonicalGuardianRequest({
953
+ id: `approved-${Date.now()}`,
954
+ kind: "access_request",
955
+ sourceType: "channel",
956
+ sourceChannel: "telegram",
957
+ conversationId: "access-req-self-telegram-approved-user",
958
+ requesterExternalUserId: "approved-user",
959
+ guardianPrincipalId: anchorPrincipalId,
960
+ toolName: "ingress_access_request",
961
+ status: "approved",
962
+ });
963
+
964
+ const result = await notifyGuardianOfAccessRequest({
965
+ canonicalAssistantId: "self",
966
+ sourceChannel: "telegram",
967
+ conversationExternalId: "chat-approved",
968
+ actorExternalId: "approved-user",
969
+ actorDisplayName: "Approved User",
970
+ });
971
+
972
+ expect(result.notified).toBe(false);
973
+ if (!result.notified) {
974
+ expect(result.reason).toBe("approval_pending_verification");
975
+ }
976
+ expect(emitSignalCalls.length).toBe(0);
977
+
978
+ const pending = listCanonicalGuardianRequests({
979
+ status: "pending",
980
+ requesterExternalUserId: "approved-user",
981
+ kind: "access_request",
982
+ });
983
+ expect(pending.length).toBe(0);
984
+ });
985
+
986
+ test("re-prompts once the approval's verification window has lapsed", async () => {
987
+ const requestId = `approved-stale-${Date.now()}`;
988
+ createCanonicalGuardianRequest({
989
+ id: requestId,
990
+ kind: "access_request",
991
+ sourceType: "channel",
992
+ sourceChannel: "telegram",
993
+ conversationId: "access-req-self-telegram-stale-user",
994
+ requesterExternalUserId: "stale-user",
995
+ guardianPrincipalId: anchorPrincipalId,
996
+ toolName: "ingress_access_request",
997
+ status: "approved",
998
+ });
999
+ // Age the approval decision past the verification-code TTL: the code can
1000
+ // no longer be redeemed, so the sender's next message legitimately
1001
+ // re-prompts the guardian.
1002
+ const staleUpdatedAt = Date.now() - 11 * 60 * 1000;
1003
+ getDb().run(
1004
+ sql`UPDATE canonical_guardian_requests SET updated_at = ${staleUpdatedAt} WHERE id = ${requestId}`,
1005
+ );
1006
+
1007
+ const result = await notifyGuardianOfAccessRequest({
1008
+ canonicalAssistantId: "self",
1009
+ sourceChannel: "telegram",
1010
+ conversationExternalId: "chat-stale",
1011
+ actorExternalId: "stale-user",
1012
+ actorDisplayName: "Stale User",
1013
+ });
1014
+
1015
+ expect(result.notified).toBe(true);
1016
+ expect(emitSignalCalls.length).toBe(1);
1017
+
1018
+ const pending = listCanonicalGuardianRequests({
1019
+ status: "pending",
1020
+ requesterExternalUserId: "stale-user",
1021
+ kind: "access_request",
1022
+ });
1023
+ expect(pending.length).toBe(1);
1024
+ });
1025
+
1026
+ test("a terminal deny wins over an in-window approval for the same sender", async () => {
1027
+ createCanonicalGuardianRequest({
1028
+ id: `approved-then-denied-a-${Date.now()}`,
1029
+ kind: "access_request",
1030
+ sourceType: "channel",
1031
+ sourceChannel: "telegram",
1032
+ conversationId: "access-req-self-telegram-flip-user",
1033
+ requesterExternalUserId: "flip-user",
1034
+ guardianPrincipalId: anchorPrincipalId,
1035
+ toolName: "ingress_access_request",
1036
+ status: "approved",
1037
+ });
1038
+ createCanonicalGuardianRequest({
1039
+ id: `approved-then-denied-d-${Date.now()}`,
1040
+ kind: "access_request",
1041
+ sourceType: "channel",
1042
+ sourceChannel: "telegram",
1043
+ conversationId: "access-req-self-telegram-flip-user",
1044
+ requesterExternalUserId: "flip-user",
1045
+ guardianPrincipalId: anchorPrincipalId,
1046
+ toolName: "ingress_access_request",
1047
+ status: "denied",
1048
+ });
1049
+
1050
+ const result = await notifyGuardianOfAccessRequest({
1051
+ canonicalAssistantId: "self",
1052
+ sourceChannel: "telegram",
1053
+ conversationExternalId: "chat-flip",
1054
+ actorExternalId: "flip-user",
1055
+ actorDisplayName: "Flip User",
1056
+ });
1057
+
1058
+ expect(result.notified).toBe(false);
1059
+ if (!result.notified) {
1060
+ expect(result.reason).toBe("already_denied");
1061
+ }
1062
+ expect(emitSignalCalls.length).toBe(0);
1063
+ });
1064
+
1065
+ test("a prior deny for one sender does not suppress prompts for a different sender", async () => {
1066
+ createCanonicalGuardianRequest({
1067
+ id: `denied-other-${Date.now()}`,
1068
+ kind: "access_request",
1069
+ sourceType: "channel",
1070
+ sourceChannel: "telegram",
1071
+ conversationId: "access-req-self-telegram-denied-user",
1072
+ requesterExternalUserId: "denied-user",
1073
+ guardianPrincipalId: anchorPrincipalId,
1074
+ toolName: "ingress_access_request",
1075
+ status: "denied",
1076
+ });
1077
+
1078
+ // A different sender still gets a fresh prompt.
1079
+ const result = await notifyGuardianOfAccessRequest({
1080
+ canonicalAssistantId: "self",
1081
+ sourceChannel: "telegram",
1082
+ conversationExternalId: "chat-fresh",
1083
+ actorExternalId: "fresh-user",
1084
+ actorDisplayName: "Fresh User",
1085
+ });
1086
+
1087
+ expect(result.notified).toBe(true);
1088
+ expect(emitSignalCalls.length).toBe(1);
1089
+
1090
+ const pending = listCanonicalGuardianRequests({
1091
+ status: "pending",
1092
+ requesterExternalUserId: "fresh-user",
1093
+ kind: "access_request",
1094
+ });
1095
+ expect(pending.length).toBe(1);
1096
+ });
1097
+
1098
+ test("a denied request on a different channel does not suppress a new channel's prompt", async () => {
1099
+ // Denied on telegram; the same actor id messaging on slack is a distinct
1100
+ // (channel-scoped) context and still surfaces to the guardian.
1101
+ createCanonicalGuardianRequest({
1102
+ id: `denied-tg-${Date.now()}`,
1103
+ kind: "access_request",
1104
+ sourceType: "channel",
1105
+ sourceChannel: "telegram",
1106
+ conversationId: "access-req-self-telegram-cross-user",
1107
+ requesterExternalUserId: "cross-user",
1108
+ guardianPrincipalId: anchorPrincipalId,
1109
+ toolName: "ingress_access_request",
1110
+ status: "denied",
1111
+ });
1112
+
1113
+ const result = await notifyGuardianOfAccessRequest({
1114
+ canonicalAssistantId: "self",
1115
+ sourceChannel: "slack",
1116
+ conversationExternalId: "C-cross",
1117
+ actorExternalId: "cross-user",
1118
+ actorDisplayName: "Cross User",
1119
+ });
1120
+
1121
+ expect(result.notified).toBe(true);
1122
+ const pending = listCanonicalGuardianRequests({
1123
+ status: "pending",
1124
+ requesterExternalUserId: "cross-user",
1125
+ sourceChannel: "slack",
1126
+ kind: "access_request",
1127
+ });
1128
+ expect(pending.length).toBe(1);
1129
+ });
1130
+
1131
+ test("isAccessRequestDenied is true only for the denied (assistant, channel, sender)", () => {
1132
+ const key = {
1133
+ canonicalAssistantId: "self",
1134
+ sourceChannel: "telegram",
1135
+ actorExternalId: "denied-user",
1136
+ };
1137
+ expect(isAccessRequestDenied(key)).toBe(false);
1138
+
1139
+ createCanonicalGuardianRequest({
1140
+ id: `denied-${Date.now()}`,
1141
+ kind: "access_request",
1142
+ sourceType: "channel",
1143
+ sourceChannel: "telegram",
1144
+ conversationId: "access-req-self-telegram-denied-user",
1145
+ requesterExternalUserId: "denied-user",
1146
+ guardianPrincipalId: anchorPrincipalId,
1147
+ toolName: "ingress_access_request",
1148
+ status: "denied",
1149
+ });
1150
+
1151
+ expect(isAccessRequestDenied(key)).toBe(true);
1152
+ // Scoped: a different channel or sender is not treated as denied.
1153
+ expect(isAccessRequestDenied({ ...key, sourceChannel: "slack" })).toBe(
1154
+ false,
1155
+ );
1156
+ expect(isAccessRequestDenied({ ...key, actorExternalId: "other" })).toBe(
1157
+ false,
1158
+ );
1159
+ // A still-pending request is not a terminal deny.
1160
+ createCanonicalGuardianRequest({
1161
+ id: `pending-${Date.now()}`,
1162
+ kind: "access_request",
1163
+ sourceType: "channel",
1164
+ sourceChannel: "telegram",
1165
+ conversationId: "access-req-self-telegram-pending-user",
1166
+ requesterExternalUserId: "pending-user",
1167
+ guardianPrincipalId: anchorPrincipalId,
1168
+ toolName: "ingress_access_request",
1169
+ status: "pending",
1170
+ });
1171
+ expect(
1172
+ isAccessRequestDenied({ ...key, actorExternalId: "pending-user" }),
1173
+ ).toBe(false);
1174
+ });
829
1175
  });
@@ -285,6 +285,100 @@ describe("notification decision fallback copy", () => {
285
285
  expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 reject"');
286
286
  expect(decision.renderedCopy.vellum?.body).not.toContain("<your answer>");
287
287
  });
288
+
289
+ test("slack approval copy is stripped of request-code instructions instead of enforced", async () => {
290
+ const signal = makeSignal({
291
+ contextPayload: {
292
+ requestId: "req-grant-slack-1",
293
+ questionText: "Approve tool: bash — ls /tmp (requested by Alice)",
294
+ requestCode: "A1B2C3",
295
+ requestKind: "tool_grant_request",
296
+ toolName: "bash",
297
+ },
298
+ });
299
+
300
+ const decision = await evaluateSignal(signal, [
301
+ "vellum",
302
+ "slack",
303
+ ] as NotificationChannel[]);
304
+
305
+ expect(decision.fallbackUsed).toBe(true);
306
+ // Vellum keeps the code-reply directive.
307
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 approve"');
308
+ // Slack renders Approve/Reject buttons — no code anywhere in its copy.
309
+ expect(decision.renderedCopy.slack?.body).toBe(
310
+ "Approve tool: bash — ls /tmp (requested by Alice)",
311
+ );
312
+ expect(decision.renderedCopy.slack?.body).not.toContain("A1B2C3");
313
+ });
314
+
315
+ test("slack copy strips LLM-authored approval-code phrasing for approval requests", async () => {
316
+ configuredProvider = {
317
+ sendMessage: async () => ({ content: [] }),
318
+ };
319
+ extractedToolUse = {
320
+ name: "record_notification_decision",
321
+ input: {
322
+ shouldNotify: true,
323
+ selectedChannels: ["slack"],
324
+ reasoningSummary: "LLM decision",
325
+ renderedCopy: {
326
+ slack: {
327
+ title: "Tool Grant Request",
328
+ body: "Alice is asking to run ls /tmp.",
329
+ deliveryText:
330
+ 'Alice is asking to run ls /tmp.\nApproval code: A1B2C3\nReference code: A1B2C3. Reply "A1B2C3 approve" or "A1B2C3 reject".',
331
+ },
332
+ },
333
+ dedupeKey: "guardian-question-slack-llm-test",
334
+ confidence: 0.9,
335
+ },
336
+ };
337
+
338
+ const signal = makeSignal({
339
+ contextPayload: {
340
+ requestId: "req-grant-slack-2",
341
+ questionText: "Approve tool: bash — ls /tmp (requested by Alice)",
342
+ requestCode: "A1B2C3",
343
+ requestKind: "tool_grant_request",
344
+ toolName: "bash",
345
+ },
346
+ });
347
+
348
+ const decision = await evaluateSignal(signal, [
349
+ "slack",
350
+ ] as NotificationChannel[]);
351
+
352
+ expect(decision.fallbackUsed).toBe(false);
353
+ expect(decision.renderedCopy.slack?.deliveryText).toBe(
354
+ "Alice is asking to run ls /tmp.",
355
+ );
356
+ expect(decision.renderedCopy.slack?.body).toBe(
357
+ "Alice is asking to run ls /tmp.",
358
+ );
359
+ });
360
+
361
+ test("slack answer-mode questions keep code instructions (no buttons render)", async () => {
362
+ const signal = makeSignal({
363
+ contextPayload: {
364
+ requestId: "req-pending-slack-1",
365
+ questionText: "What is the gate code?",
366
+ requestCode: "A1B2C3",
367
+ requestKind: "pending_question",
368
+ callSessionId: "call-1",
369
+ activeGuardianRequestCount: 1,
370
+ },
371
+ });
372
+
373
+ const decision = await evaluateSignal(signal, [
374
+ "slack",
375
+ ] as NotificationChannel[]);
376
+
377
+ expect(decision.fallbackUsed).toBe(true);
378
+ expect(decision.renderedCopy.slack?.body).toContain(
379
+ '"A1B2C3 <your answer>"',
380
+ );
381
+ });
288
382
  });
289
383
 
290
384
  // ── Access-request instruction enforcement ──────────────────────────────
@@ -326,9 +420,11 @@ describe("access-request instruction enforcement", () => {
326
420
  ] as NotificationChannel[]);
327
421
 
328
422
  expect(decision.fallbackUsed).toBe(true);
329
- expect(decision.renderedCopy.vellum?.body).toContain("A1B2C3");
330
- expect(decision.renderedCopy.vellum?.body).toContain("approve");
331
- expect(decision.renderedCopy.vellum?.body).toContain("reject");
423
+ // Directive form, not prose mentions: the guardian must be able to reply
424
+ // with these exact code+verb tokens.
425
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 trust"');
426
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 reject"');
427
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 block"');
332
428
  expect(decision.renderedCopy.vellum?.body).toContain("open invite flow");
333
429
  });
334
430
 
@@ -359,9 +455,9 @@ describe("access-request instruction enforcement", () => {
359
455
  ] as NotificationChannel[]);
360
456
 
361
457
  expect(decision.fallbackUsed).toBe(false);
362
- expect(decision.renderedCopy.vellum?.body).toContain("A1B2C3");
363
- expect(decision.renderedCopy.vellum?.body).toContain("approve");
364
- expect(decision.renderedCopy.vellum?.body).toContain("reject");
458
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 trust"');
459
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 reject"');
460
+ expect(decision.renderedCopy.vellum?.body).toContain('"A1B2C3 block"');
365
461
  expect(decision.renderedCopy.vellum?.body).toContain("open invite flow");
366
462
  });
367
463
 
@@ -397,7 +493,7 @@ describe("access-request instruction enforcement", () => {
397
493
 
398
494
  test("enforcement does not duplicate when LLM copy already has all required elements", async () => {
399
495
  const fullBody =
400
- 'Alice wants access.\nReply "A1B2C3 approve" to grant access or "A1B2C3 reject" to deny.\nReply "open invite flow" to start Trusted Contacts invite flow.';
496
+ 'Alice wants access.\nReply "A1B2C3 verify" to send them a verification code, "A1B2C3 trust" to trust them without one, "A1B2C3 reject" to leave them unverified, or "A1B2C3 block" to block them.\nReply "open invite flow" to start Trusted Contacts invite flow.';
401
497
  configuredProvider = {
402
498
  sendMessage: async () => ({ content: [] }),
403
499
  };
@@ -495,7 +591,7 @@ describe("access-request instruction enforcement", () => {
495
591
  ] as NotificationChannel[]);
496
592
 
497
593
  // Must contain the proper contract instructions despite conflicting LLM copy
498
- expect(decision.renderedCopy.vellum?.body).toContain("A1B2C3 approve");
594
+ expect(decision.renderedCopy.vellum?.body).toContain("A1B2C3 verify");
499
595
  expect(decision.renderedCopy.vellum?.body).toContain("A1B2C3 reject");
500
596
  expect(decision.renderedCopy.vellum?.body).toContain("open invite flow");
501
597
  });
@@ -278,8 +278,8 @@ describe("notification decision strategy", () => {
278
278
  const copy = composeFallbackCopy(signal, channels);
279
279
  expect(copy.vellum).toBeDefined();
280
280
  expect(copy.vellum!.body).toContain("D4E5F6");
281
- expect(copy.vellum!.body).toContain("approve");
282
- expect(copy.vellum!.body).toContain("reject");
281
+ expect(copy.vellum!.body).toContain("trust");
282
+ expect(copy.vellum!.body).toContain("block");
283
283
  });
284
284
 
285
285
  test("ingress.access_request template includes invite flow instruction", () => {
@@ -709,10 +709,21 @@ describe("notification decision strategy", () => {
709
709
  describe("access-request instruction detection", () => {
710
710
  test("detects complete access-request instructions with full directive patterns", () => {
711
711
  const text =
712
- 'Alice wants access.\nReply "A1B2C3 approve" to grant access or "A1B2C3 reject" to deny.\nReply "open invite flow" to start.';
712
+ 'Alice wants access.\nReply "A1B2C3 verify" to send them a verification code, "A1B2C3 trust" to trust them without one, "A1B2C3 reject" to leave them unverified, or "A1B2C3 block" to block them.\nReply "open invite flow" to start.';
713
713
  expect(hasAccessRequestInstructions(text, "A1B2C3")).toBe(true);
714
714
  });
715
715
 
716
+ test("does not require the verify directive when the handshake is not offered", () => {
717
+ const text =
718
+ 'Alice wants access.\nReply "A1B2C3 trust" to trust them, "A1B2C3 reject" to leave them unverified, or "A1B2C3 block" to block them.\nReply "open invite flow" to start.';
719
+ expect(hasAccessRequestInstructions(text, "A1B2C3")).toBe(false);
720
+ expect(
721
+ hasAccessRequestInstructions(text, "A1B2C3", {
722
+ handshakeOffered: false,
723
+ }),
724
+ ).toBe(true);
725
+ });
726
+
716
727
  test("fails when request code is missing", () => {
717
728
  const text = 'Alice wants access.\nReply "open invite flow" to start.';
718
729
  expect(hasAccessRequestInstructions(text, "A1B2C3")).toBe(false);
@@ -732,7 +743,7 @@ describe("notification decision strategy", () => {
732
743
 
733
744
  test("is case-insensitive for request code matching", () => {
734
745
  const text =
735
- 'Reply "a1b2c3 approve" to grant access or "a1b2c3 reject" to deny.\nReply "open invite flow" to start.';
746
+ 'Reply "a1b2c3 verify" to send a code, "a1b2c3 trust" to trust them, "a1b2c3 reject" to leave them unverified, or "a1b2c3 block" to block them.\nReply "open invite flow" to start.';
736
747
  expect(hasAccessRequestInstructions(text, "A1B2C3")).toBe(true);
737
748
  });
738
749
 
@@ -788,7 +799,7 @@ describe("notification decision strategy", () => {
788
799
 
789
800
  test("accepts directives at the start of text (no preceding newline needed)", () => {
790
801
  const text =
791
- 'Reply "A1B2C3 approve" to grant or "A1B2C3 reject" to deny. Reply "open invite flow" to start.';
802
+ 'Reply "A1B2C3 verify" to send a code, "A1B2C3 trust" to trust, "A1B2C3 reject" to leave unverified, or "A1B2C3 block" to block. Reply "open invite flow" to start.';
792
803
  expect(hasAccessRequestInstructions(text, "A1B2C3")).toBe(true);
793
804
  });
794
805
 
@@ -857,8 +868,11 @@ describe("notification decision strategy", () => {
857
868
  previousMemberStatus: "revoked",
858
869
  });
859
870
  expect(text).toContain("Alice");
860
- expect(text).toContain("D4E5F6 approve");
871
+ // Telegram carries no workspace identity — the handshake leads.
872
+ expect(text).toContain("D4E5F6 verify");
873
+ expect(text).toContain("D4E5F6 trust");
861
874
  expect(text).toContain("D4E5F6 reject");
875
+ expect(text).toContain("D4E5F6 block");
862
876
  expect(text).toContain("open invite flow");
863
877
  expect(text).toContain("previously revoked");
864
878
  });
@@ -869,10 +883,45 @@ describe("notification decision strategy", () => {
869
883
  requestCode: "A1B2C3",
870
884
  });
871
885
  expect(text).not.toContain("revoked");
872
- expect(text).toContain("A1B2C3 approve");
886
+ expect(text).toContain("A1B2C3 trust");
873
887
  expect(text).toContain("open invite flow");
874
888
  });
875
889
 
890
+ test("workspace-member contract omits the verify directive", () => {
891
+ const text = buildAccessRequestContractText({
892
+ senderIdentifier: "Alice",
893
+ requestCode: "A1B2C3",
894
+ sourceChannel: "slack",
895
+ isStranger: false,
896
+ isRestricted: false,
897
+ });
898
+ expect(text).not.toContain("A1B2C3 verify");
899
+ expect(text).toContain("A1B2C3 trust");
900
+ expect(text).toContain("A1B2C3 block");
901
+ });
902
+
903
+ test("slack contract with unknown signals keeps the verify directive (fail-safe)", () => {
904
+ const text = buildAccessRequestContractText({
905
+ senderIdentifier: "Alice",
906
+ requestCode: "A1B2C3",
907
+ sourceChannel: "slack",
908
+ });
909
+ expect(text).toContain("A1B2C3 verify");
910
+ });
911
+
912
+ test("bot contract never offers the code option", () => {
913
+ const text = buildAccessRequestContractText({
914
+ senderIdentifier: "Shard",
915
+ requestCode: "A1B2C3",
916
+ sourceChannel: "slack",
917
+ isBot: true,
918
+ isStranger: true,
919
+ });
920
+ expect(text).not.toContain("A1B2C3 verify");
921
+ expect(text).toContain("A1B2C3 trust");
922
+ expect(text).toContain("code verification isn't possible");
923
+ });
924
+
876
925
  test("builds contract without decision directive when no request code", () => {
877
926
  const text = buildAccessRequestContractText({
878
927
  senderIdentifier: "Charlie",
@@ -891,7 +940,7 @@ describe("notification decision strategy", () => {
891
940
  });
892
941
  expect(text).not.toContain("\n\n\n"); // no triple newlines from injected newlines
893
942
  expect(text).not.toContain("\x00");
894
- expect(text).toContain("A1B2C3 approve");
943
+ expect(text).toContain("A1B2C3 verify");
895
944
  expect(text).toContain("open invite flow");
896
945
  });
897
946
  });