@vellumai/assistant 0.4.56 → 0.5.0

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 (457) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +3 -0
  3. package/README.md +11 -11
  4. package/docs/architecture/integrations.md +2 -2
  5. package/docs/architecture/memory.md +3 -4
  6. package/docs/credential-execution-service.md +13 -20
  7. package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +7 -7
  10. package/src/__tests__/anthropic-provider.test.ts +172 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
  12. package/src/__tests__/approval-cascade.test.ts +2 -2
  13. package/src/__tests__/approval-routes-http.test.ts +3 -4
  14. package/src/__tests__/asset-materialize-tool.test.ts +5 -5
  15. package/src/__tests__/asset-search-tool.test.ts +1 -1
  16. package/src/__tests__/assistant-attachments.test.ts +5 -5
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
  19. package/src/__tests__/attachments-store.test.ts +2 -2
  20. package/src/__tests__/avatar-e2e.test.ts +5 -3
  21. package/src/__tests__/browser-skill-endstate.test.ts +0 -1
  22. package/src/__tests__/call-routes-http.test.ts +2 -2
  23. package/src/__tests__/callback-handoff-copy.test.ts +1 -1
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
  25. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  26. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  27. package/src/__tests__/checker.test.ts +31 -32
  28. package/src/__tests__/chrome-cdp.test.ts +47 -18
  29. package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
  30. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  31. package/src/__tests__/config-schema.test.ts +9 -18
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
  34. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
  35. package/src/__tests__/conversation-agent-loop.test.ts +11 -4
  36. package/src/__tests__/conversation-attachments.test.ts +1 -1
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
  38. package/src/__tests__/conversation-error.test.ts +33 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
  40. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  41. package/src/__tests__/conversation-pairing.test.ts +1 -1
  42. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
  44. package/src/__tests__/conversation-queue.test.ts +23 -14
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +204 -185
  47. package/src/__tests__/conversation-seed-composer.test.ts +1 -1
  48. package/src/__tests__/conversation-slash-queue.test.ts +4 -4
  49. package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
  50. package/src/__tests__/conversation-starter-routes.test.ts +291 -0
  51. package/src/__tests__/conversation-wipe.test.ts +438 -0
  52. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
  53. package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
  54. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
  55. package/src/__tests__/credential-security-e2e.test.ts +20 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault-unit.test.ts +227 -0
  58. package/src/__tests__/credentials-cli.test.ts +3 -0
  59. package/src/__tests__/date-context.test.ts +59 -377
  60. package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
  61. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
  62. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  63. package/src/__tests__/encrypted-store.test.ts +249 -15
  64. package/src/__tests__/ephemeral-permissions.test.ts +4 -5
  65. package/src/__tests__/event-bus.test.ts +3 -3
  66. package/src/__tests__/file-read-tool.test.ts +40 -0
  67. package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
  68. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  69. package/src/__tests__/gemini-image-service.test.ts +4 -4
  70. package/src/__tests__/gemini-provider.test.ts +6 -9
  71. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  72. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  73. package/src/__tests__/host-file-read-tool.test.ts +87 -0
  74. package/src/__tests__/host-shell-tool.test.ts +6 -6
  75. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  76. package/src/__tests__/identity-intro-cache.test.ts +209 -0
  77. package/src/__tests__/intent-routing.test.ts +51 -99
  78. package/src/__tests__/invite-routes-http.test.ts +5 -0
  79. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  80. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  81. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  82. package/src/__tests__/media-generate-image.test.ts +32 -15
  83. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  84. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  86. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  87. package/src/__tests__/memory-regressions.test.ts +86 -90
  88. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  89. package/src/__tests__/migration-export-http.test.ts +26 -27
  90. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  91. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  92. package/src/__tests__/migration-validate-http.test.ts +16 -16
  93. package/src/__tests__/model-intents.test.ts +2 -2
  94. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  95. package/src/__tests__/non-member-access-request.test.ts +3 -3
  96. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  97. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  98. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  99. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  100. package/src/__tests__/notification-deep-link.test.ts +1 -1
  101. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  102. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  103. package/src/__tests__/oauth-store.test.ts +1 -3
  104. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  105. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  106. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  107. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  108. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  109. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  110. package/src/__tests__/qdrant-manager.test.ts +7 -7
  111. package/src/__tests__/ratelimit.test.ts +0 -74
  112. package/src/__tests__/recording-handler.test.ts +0 -1
  113. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  115. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  116. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  117. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  118. package/src/__tests__/schema-transforms.test.ts +114 -54
  119. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  120. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  121. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  122. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  123. package/src/__tests__/send-notification-tool.test.ts +2 -2
  124. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  125. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  126. package/src/__tests__/skill-memory.test.ts +549 -0
  127. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  128. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  129. package/src/__tests__/slack-channel-config.test.ts +109 -94
  130. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  131. package/src/__tests__/swarm-recursion.test.ts +2 -2
  132. package/src/__tests__/swarm-tool.test.ts +2 -2
  133. package/src/__tests__/system-prompt.test.ts +19 -66
  134. package/src/__tests__/telegram-config.test.ts +121 -0
  135. package/src/__tests__/terminal-tools.test.ts +1 -1
  136. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  138. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  139. package/src/__tests__/tool-executor.test.ts +1 -1
  140. package/src/__tests__/trace-emitter.test.ts +8 -1
  141. package/src/__tests__/trust-store.test.ts +7 -8
  142. package/src/__tests__/twilio-routes.test.ts +1 -18
  143. package/src/__tests__/user-reference.test.ts +82 -2
  144. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  145. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  146. package/src/approvals/guardian-request-resolvers.ts +3 -3
  147. package/src/avatar/ascii-renderer.ts +2 -2
  148. package/src/avatar/png-renderer.ts +2 -2
  149. package/src/avatar/resvg-lazy.ts +21 -0
  150. package/src/calls/guardian-dispatch.ts +1 -1
  151. package/src/calls/relay-access-wait.ts +2 -2
  152. package/src/calls/twilio-rest.ts +0 -248
  153. package/src/cli/AGENTS.md +5 -8
  154. package/src/cli/__tests__/notifications.test.ts +5 -5
  155. package/src/cli/commands/avatar.ts +64 -2
  156. package/src/cli/commands/conversations.ts +131 -1
  157. package/src/cli/commands/credentials.ts +2 -0
  158. package/src/cli/commands/notifications.ts +3 -3
  159. package/src/cli.ts +10 -0
  160. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  161. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  162. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  163. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  164. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  165. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  166. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  167. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  168. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  169. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  170. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  171. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  172. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  173. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  174. package/src/config/bundled-skills/document/SKILL.md +4 -4
  175. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  176. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  177. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  178. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  179. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  180. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  181. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  182. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  183. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  184. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  185. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  186. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  187. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  188. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  189. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  190. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  192. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  193. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  195. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  196. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  197. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  198. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  199. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  200. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  201. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  202. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  203. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  204. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  205. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  206. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  207. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  208. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  209. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  210. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  211. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  212. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  213. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  214. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  215. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  216. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  217. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  218. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  219. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  220. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  221. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  222. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  223. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  224. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  225. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  226. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  227. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  228. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  229. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  230. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  231. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  232. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  233. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  234. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  235. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  236. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  237. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  238. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  240. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  241. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  242. package/src/config/feature-flag-registry.json +33 -17
  243. package/src/config/schemas/sandbox.ts +1 -1
  244. package/src/config/schemas/services.ts +13 -3
  245. package/src/config/schemas/timeouts.ts +0 -10
  246. package/src/contacts/contact-store.ts +63 -0
  247. package/src/contacts/contacts-write.ts +1 -1
  248. package/src/daemon/assistant-attachments.ts +2 -2
  249. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  250. package/src/daemon/conversation-agent-loop.ts +7 -30
  251. package/src/daemon/conversation-error.ts +24 -0
  252. package/src/daemon/conversation-memory.ts +8 -7
  253. package/src/daemon/conversation-runtime-assembly.ts +141 -275
  254. package/src/daemon/conversation-slash.ts +7 -26
  255. package/src/daemon/conversation-surfaces.ts +14 -0
  256. package/src/daemon/conversation-tool-setup.ts +9 -8
  257. package/src/daemon/conversation.ts +2 -0
  258. package/src/daemon/daemon-control.ts +1 -1
  259. package/src/daemon/date-context.ts +10 -83
  260. package/src/daemon/handlers/config-channels.ts +12 -2
  261. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  262. package/src/daemon/handlers/config-telegram.ts +6 -1
  263. package/src/daemon/handlers/conversations.ts +2 -2
  264. package/src/daemon/handlers/skills.ts +4 -0
  265. package/src/daemon/lifecycle.ts +28 -4
  266. package/src/daemon/providers-setup.ts +1 -1
  267. package/src/daemon/server.ts +1 -5
  268. package/src/daemon/shutdown-handlers.ts +9 -3
  269. package/src/daemon/tool-side-effects.ts +40 -0
  270. package/src/daemon/trace-emitter.ts +26 -2
  271. package/src/events/domain-events.ts +1 -1
  272. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  273. package/src/inbound/platform-callback-registration.ts +0 -18
  274. package/src/media/app-icon-generator.ts +15 -8
  275. package/src/media/avatar-router.ts +15 -8
  276. package/src/media/gemini-image-service.ts +125 -21
  277. package/src/memory/attachments-store.ts +3 -3
  278. package/src/memory/channel-verification-sessions.ts +6 -6
  279. package/src/memory/conversation-crud.ts +196 -1
  280. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  281. package/src/memory/conversation-title-service.ts +2 -3
  282. package/src/memory/db-init.ts +25 -1
  283. package/src/memory/invite-store.ts +4 -4
  284. package/src/memory/items-extractor.ts +4 -4
  285. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  286. package/src/memory/jobs-store.ts +3 -2
  287. package/src/memory/jobs-worker.ts +7 -5
  288. package/src/memory/lifecycle-events-store.ts +63 -0
  289. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  290. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  291. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  292. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  293. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  294. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  295. package/src/memory/migrations/index.ts +6 -0
  296. package/src/memory/migrations/registry.ts +13 -0
  297. package/src/memory/retriever.test.ts +223 -96
  298. package/src/memory/retriever.ts +115 -138
  299. package/src/memory/schema/calls.ts +1 -1
  300. package/src/memory/schema/contacts.ts +1 -1
  301. package/src/memory/schema/infrastructure.ts +29 -0
  302. package/src/memory/schema/memory-core.ts +7 -17
  303. package/src/memory/schema/notifications.ts +1 -1
  304. package/src/memory/search/formatting.ts +23 -6
  305. package/src/memory/search/lexical.ts +2 -0
  306. package/src/memory/search/semantic.ts +2 -0
  307. package/src/memory/search/staleness.ts +5 -1
  308. package/src/memory/search/types.ts +4 -0
  309. package/src/memory/task-memory-cleanup.ts +96 -6
  310. package/src/memory/trace-event-store.ts +148 -0
  311. package/src/notifications/README.md +1 -1
  312. package/src/notifications/decision-engine.ts +45 -4
  313. package/src/notifications/emit-signal.ts +5 -4
  314. package/src/notifications/events-store.ts +4 -4
  315. package/src/notifications/signal.ts +1 -1
  316. package/src/oauth/manual-token-connection.ts +49 -25
  317. package/src/permissions/checker.ts +6 -5
  318. package/src/permissions/defaults.ts +4 -4
  319. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  320. package/src/prompts/cache-boundary.ts +8 -0
  321. package/src/prompts/system-prompt.ts +105 -634
  322. package/src/prompts/templates/BOOTSTRAP.md +172 -33
  323. package/src/prompts/templates/IDENTITY.md +8 -24
  324. package/src/prompts/templates/SOUL.md +20 -41
  325. package/src/prompts/templates/USER.md +3 -19
  326. package/src/prompts/user-reference.ts +14 -16
  327. package/src/providers/anthropic/client.ts +51 -19
  328. package/src/providers/gemini/client.ts +6 -9
  329. package/src/providers/managed-proxy/constants.ts +1 -7
  330. package/src/providers/managed-proxy/context.ts +0 -1
  331. package/src/providers/model-intents.ts +5 -5
  332. package/src/providers/openai/client.ts +10 -1
  333. package/src/providers/openrouter/client.ts +1 -0
  334. package/src/providers/ratelimit.ts +0 -35
  335. package/src/providers/registry.ts +3 -5
  336. package/src/providers/retry.ts +18 -1
  337. package/src/runtime/access-request-helper.ts +16 -2
  338. package/src/runtime/auth/route-policy.ts +7 -0
  339. package/src/runtime/channel-verification-service.ts +1 -1
  340. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  341. package/src/runtime/guardian-vellum-migration.ts +61 -1
  342. package/src/runtime/http-server.ts +8 -4
  343. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  344. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  345. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  346. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  347. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  348. package/src/runtime/routes/attachment-routes.ts +2 -2
  349. package/src/runtime/routes/btw-routes.ts +93 -0
  350. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  351. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  352. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-routes.ts +49 -5
  354. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  355. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  356. package/src/runtime/routes/identity-intro-cache.ts +105 -0
  357. package/src/runtime/routes/identity-routes.ts +51 -0
  358. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  359. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  360. package/src/runtime/routes/migration-routes.ts +25 -13
  361. package/src/runtime/routes/secret-routes.ts +18 -0
  362. package/src/runtime/routes/settings-routes.ts +9 -9
  363. package/src/runtime/routes/telemetry-routes.ts +53 -0
  364. package/src/runtime/routes/trace-event-routes.ts +62 -0
  365. package/src/runtime/tool-grant-request-helper.ts +1 -1
  366. package/src/runtime/verification-outbound-actions.ts +47 -31
  367. package/src/security/encrypted-store.ts +262 -78
  368. package/src/skills/catalog-install.ts +10 -0
  369. package/src/skills/managed-store.ts +2 -0
  370. package/src/skills/skill-memory.ts +222 -0
  371. package/src/subagent/manager.ts +1 -4
  372. package/src/telemetry/types.ts +10 -1
  373. package/src/telemetry/usage-telemetry-reporter.test.ts +7 -2
  374. package/src/telemetry/usage-telemetry-reporter.ts +53 -4
  375. package/src/tools/AGENTS.md +11 -11
  376. package/src/tools/acp/spawn.ts +1 -1
  377. package/src/tools/apps/executors.ts +8 -8
  378. package/src/tools/apps/registry.ts +1 -1
  379. package/src/tools/assets/materialize.ts +6 -6
  380. package/src/tools/assets/search.ts +10 -10
  381. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  382. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  383. package/src/tools/browser/auth-detector.ts +6 -6
  384. package/src/tools/browser/browser-execution.ts +13 -13
  385. package/src/tools/browser/browser-manager.ts +3 -3
  386. package/src/tools/browser/chrome-cdp.ts +5 -5
  387. package/src/tools/browser/jit-auth.ts +2 -2
  388. package/src/tools/browser/network-recorder.test.ts +2 -2
  389. package/src/tools/browser/network-recorder.ts +3 -3
  390. package/src/tools/browser/runtime-check.ts +3 -3
  391. package/src/tools/claude-code/claude-code.ts +2 -2
  392. package/src/tools/computer-use/definitions.ts +18 -18
  393. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  394. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  395. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  396. package/src/tools/credentials/broker-types.ts +5 -5
  397. package/src/tools/credentials/broker.ts +15 -15
  398. package/src/tools/credentials/metadata-store.ts +2 -2
  399. package/src/tools/credentials/resolve.ts +1 -1
  400. package/src/tools/credentials/selection.ts +1 -1
  401. package/src/tools/credentials/tool-policy.ts +1 -1
  402. package/src/tools/credentials/vault.ts +115 -25
  403. package/src/tools/execution-target.ts +2 -2
  404. package/src/tools/executor.ts +7 -7
  405. package/src/tools/filesystem/edit.ts +2 -2
  406. package/src/tools/filesystem/read.ts +15 -4
  407. package/src/tools/filesystem/write.ts +1 -1
  408. package/src/tools/host-filesystem/edit.ts +2 -1
  409. package/src/tools/host-filesystem/read.ts +18 -1
  410. package/src/tools/host-filesystem/write.ts +1 -1
  411. package/src/tools/host-terminal/host-shell.ts +9 -8
  412. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  413. package/src/tools/memory/definitions.ts +6 -5
  414. package/src/tools/memory/handlers.test.ts +1 -1
  415. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  416. package/src/tools/network/domain-normalize.ts +2 -2
  417. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  418. package/src/tools/network/web-fetch.ts +1 -1
  419. package/src/tools/network/web-search.ts +3 -3
  420. package/src/tools/permission-checker.ts +8 -8
  421. package/src/tools/registry.ts +7 -7
  422. package/src/tools/schedule/list.ts +2 -2
  423. package/src/tools/schema-transforms.ts +31 -21
  424. package/src/tools/secret-detection-handler.ts +1 -1
  425. package/src/tools/sensitive-output-placeholders.ts +1 -1
  426. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  427. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  428. package/src/tools/shared/filesystem/image-read.ts +25 -5
  429. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  430. package/src/tools/shared/shell-output.ts +1 -1
  431. package/src/tools/side-effects.ts +1 -1
  432. package/src/tools/skills/execute.ts +1 -1
  433. package/src/tools/skills/load.ts +3 -3
  434. package/src/tools/skills/sandbox-runner.ts +3 -3
  435. package/src/tools/subagent/read.ts +1 -1
  436. package/src/tools/subagent/spawn.ts +2 -2
  437. package/src/tools/swarm/delegate.ts +3 -3
  438. package/src/tools/system/request-permission.ts +5 -4
  439. package/src/tools/terminal/backends/native.ts +4 -4
  440. package/src/tools/terminal/parser.ts +6 -6
  441. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  442. package/src/tools/terminal/shell.ts +16 -16
  443. package/src/tools/tool-approval-handler.ts +21 -12
  444. package/src/tools/tool-manifest.ts +4 -4
  445. package/src/tools/types.ts +3 -3
  446. package/src/tools/ui-surface/definitions.ts +9 -37
  447. package/src/tools/watcher/list.ts +1 -1
  448. package/src/util/logger.ts +7 -2
  449. package/src/util/pricing.ts +4 -0
  450. package/src/util/retry.ts +29 -1
  451. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  452. package/src/workspace/migrations/registry.ts +2 -0
  453. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  454. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  455. package/src/cli/reference.ts +0 -38
  456. package/src/memory/job-handlers/capability-cards.ts +0 -420
  457. package/src/runtime/routes/thread-starter-routes.ts +0 -294
@@ -69,11 +69,10 @@ mock.module("../config/loader.js", () => ({
69
69
  nonInteractiveLatestTurnCompression: "truncate",
70
70
  },
71
71
  },
72
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
72
+ rateLimit: { maxRequestsPerMinute: 0 },
73
73
  timeouts: { permissionTimeoutSec: 1 },
74
74
  skills: { entries: {}, allowBundled: true },
75
75
  permissions: { mode: "workspace" },
76
- sandbox: { enabled: false },
77
76
  daemon: {
78
77
  startupSocketWaitMs: 5000,
79
78
  stopTimeoutMs: 5000,
@@ -90,9 +89,9 @@ mock.module("../config/loader.js", () => ({
90
89
  "image-generation": {
91
90
  mode: "your-own",
92
91
  provider: "gemini",
93
- model: "gemini-2.5-flash-image",
92
+ model: "gemini-3.1-flash-image-preview",
94
93
  },
95
- "web-search": { mode: "your-own", provider: "anthropic-native" },
94
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
96
95
  },
97
96
  }),
98
97
  loadRawConfig: () => ({}),
@@ -178,7 +177,7 @@ mock.module("../memory/retriever.js", () => ({
178
177
  injectedTokens: 0,
179
178
  latencyMs: 0,
180
179
  }),
181
- stripMemoryRecallMessages: (msgs: Message[]) => msgs,
180
+ injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
182
181
  }));
183
182
 
184
183
  mock.module("../context/window-manager.js", () => ({
@@ -1735,12 +1734,23 @@ describe("Regression: cancel semantics and error channel split", () => {
1735
1734
  });
1736
1735
 
1737
1736
  test("commitTurnChanges never resolving within budget -> turn still completes and drains queue", async () => {
1738
- const conversation = makeConversation();
1739
- await conversation.loadFromDb();
1740
-
1741
- turnCommitHangForever = true;
1737
+ // Replace setTimeout with a zero-delay version so the 4000ms
1738
+ // raceWithTimeout fires instantly instead of waiting real time.
1739
+ const origSetTimeout = globalThis.setTimeout;
1740
+ globalThis.setTimeout = ((
1741
+ fn: TimerHandler,
1742
+ _ms?: number,
1743
+ ...args: unknown[]
1744
+ ) => {
1745
+ return origSetTimeout(fn, 0, ...args);
1746
+ }) as typeof setTimeout;
1742
1747
 
1743
1748
  try {
1749
+ const conversation = makeConversation();
1750
+ await conversation.loadFromDb();
1751
+
1752
+ turnCommitHangForever = true;
1753
+
1744
1754
  const events1: ServerMessage[] = [];
1745
1755
  const events2: ServerMessage[] = [];
1746
1756
 
@@ -1761,10 +1771,8 @@ describe("Regression: cancel semantics and error channel split", () => {
1761
1771
 
1762
1772
  // The turn should still complete (timeout fires) and drain the queue
1763
1773
  // even though commitTurnChanges never resolves.
1764
- // The default turnCommitMaxWaitMs is 4000ms in the config mock,
1765
- // but the mock config doesn't set it, so it defaults to 4000ms.
1766
- // We wait for the second run to be registered, which proves the
1767
- // turn completed and the queue drained despite the hanging commit.
1774
+ // With the zero-delay setTimeout wrapper the 4000ms budget fires
1775
+ // instantly, so we only need a short wait for the second run.
1768
1776
  await waitForPendingRun(2, 10_000);
1769
1777
 
1770
1778
  // First message should have completed
@@ -1781,9 +1789,10 @@ describe("Regression: cancel semantics and error channel split", () => {
1781
1789
  // Complete the second run so the test can clean up
1782
1790
  turnCommitHangForever = false;
1783
1791
  resolveRun(1);
1784
- await new Promise((r) => setTimeout(r, 10));
1792
+ await new Promise((r) => origSetTimeout(r, 10));
1785
1793
  } finally {
1786
1794
  turnCommitHangForever = false;
1795
+ globalThis.setTimeout = origSetTimeout;
1787
1796
  }
1788
1797
  }, 15_000);
1789
1798
  });
@@ -29,7 +29,7 @@ mock.module("../config/loader.js", () => ({
29
29
  model: "claude-opus-4-6",
30
30
  provider: "anthropic",
31
31
  memory: { enabled: false },
32
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
32
+ rateLimit: { maxRequestsPerMinute: 0 },
33
33
  secretDetection: { enabled: false },
34
34
  contextWindow: { maxInputTokens: 200000 },
35
35
  services: {
@@ -41,9 +41,9 @@ mock.module("../config/loader.js", () => ({
41
41
  "image-generation": {
42
42
  mode: "your-own",
43
43
  provider: "gemini",
44
- model: "gemini-2.5-flash-image",
44
+ model: "gemini-3.1-flash-image-preview",
45
45
  },
46
- "web-search": { mode: "your-own", provider: "anthropic-native" },
46
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
47
47
  },
48
48
  }),
49
49
  }));
@@ -7,20 +7,19 @@ import type {
7
7
  } from "../daemon/conversation-runtime-assembly.js";
8
8
  import {
9
9
  applyRuntimeInjections,
10
- buildChannelTurnContextBlock,
10
+ buildTurnContextBlock,
11
11
  injectChannelCapabilityContext,
12
- injectChannelTurnContext,
12
+ injectChannelCommandContext,
13
13
  injectInboundActorContext,
14
14
  injectTemporalContext,
15
+ injectTurnContext,
15
16
  isGroupChatType,
16
17
  resolveChannelCapabilities,
17
- sanitizePttActivationKey,
18
18
  stripChannelCapabilityContext,
19
19
  stripChannelTurnContext,
20
20
  stripInboundActorContext,
21
21
  stripTemporalContext,
22
22
  } from "../daemon/conversation-runtime-assembly.js";
23
- import { buildChannelAwarenessSection } from "../prompts/system-prompt.js";
24
23
  import type { Message } from "../providers/types.js";
25
24
 
26
25
  // ---------------------------------------------------------------------------
@@ -135,7 +134,7 @@ describe("resolveChannelCapabilities", () => {
135
134
  });
136
135
 
137
136
  test("propagates chatType when provided", () => {
138
- const caps = resolveChannelCapabilities("telegram", null, null, "group");
137
+ const caps = resolveChannelCapabilities("telegram", null, "group");
139
138
  expect(caps.chatType).toBe("group");
140
139
  });
141
140
 
@@ -155,7 +154,7 @@ describe("injectChannelCapabilityContext", () => {
155
154
  content: [{ type: "text", text: "Hello" }],
156
155
  };
157
156
 
158
- test("injects channel capabilities block for dashboard channel", () => {
157
+ test("skips injection entirely for desktop happy path (all capabilities true)", () => {
159
158
  const caps: ChannelCapabilities = {
160
159
  channel: "vellum",
161
160
  dashboardCapable: true,
@@ -165,26 +164,9 @@ describe("injectChannelCapabilityContext", () => {
165
164
 
166
165
  const result = injectChannelCapabilityContext(baseUserMessage, caps);
167
166
 
168
- // Should prepend a text block with channel_capabilities
169
- expect(result.content.length).toBe(2);
170
- const injected = result.content[0];
171
- expect(injected.type).toBe("text");
172
- expect((injected as { type: "text"; text: string }).text).toContain(
173
- "<channel_capabilities>",
174
- );
175
- expect((injected as { type: "text"; text: string }).text).toContain(
176
- "dashboard_capable: true",
177
- );
178
- expect((injected as { type: "text"; text: string }).text).toContain(
179
- "supports_dynamic_ui: true",
180
- );
181
- expect((injected as { type: "text"; text: string }).text).toContain(
182
- "</channel_capabilities>",
183
- );
184
- // Should NOT contain constraint rules for dashboard
185
- expect((injected as { type: "text"; text: string }).text).not.toContain(
186
- "CHANNEL CONSTRAINTS",
187
- );
167
+ // Message returned unchanged no injection at all
168
+ expect(result).toBe(baseUserMessage);
169
+ expect(result.content.length).toBe(1);
188
170
  });
189
171
 
190
172
  test("injects constraint rules for non-dashboard channel", () => {
@@ -293,6 +275,50 @@ describe("injectChannelCapabilityContext", () => {
293
275
  expect(text).toContain("GROUP CHAT ETIQUETTE");
294
276
  expect(text).toContain("emoji reactions");
295
277
  });
278
+
279
+ test("still injects for group chats even when all capabilities are true", () => {
280
+ const caps: ChannelCapabilities = {
281
+ channel: "slack",
282
+ dashboardCapable: true,
283
+ supportsDynamicUi: true,
284
+ supportsVoiceInput: true,
285
+ chatType: "channel",
286
+ };
287
+
288
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
289
+ // Not the happy path because chatType is a group type
290
+ expect(result).not.toBe(baseUserMessage);
291
+ const text = (result.content[0] as { type: "text"; text: string }).text;
292
+ expect(text).toContain("GROUP CHAT ETIQUETTE");
293
+ });
294
+
295
+ test("injects WhatsApp formatting constraint for whatsapp channel", () => {
296
+ const caps: ChannelCapabilities = {
297
+ channel: "whatsapp",
298
+ dashboardCapable: false,
299
+ supportsDynamicUi: false,
300
+ supportsVoiceInput: false,
301
+ };
302
+
303
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
304
+ const text = (result.content[0] as { type: "text"; text: string }).text;
305
+ expect(text).toContain("Do NOT use markdown tables");
306
+ expect(text).toContain("bullet lists");
307
+ expect(text).toContain("CAPS for emphasis");
308
+ });
309
+
310
+ test("does NOT inject WhatsApp formatting for non-whatsapp channels", () => {
311
+ const caps: ChannelCapabilities = {
312
+ channel: "telegram",
313
+ dashboardCapable: false,
314
+ supportsDynamicUi: false,
315
+ supportsVoiceInput: false,
316
+ };
317
+
318
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
319
+ const text = (result.content[0] as { type: "text"; text: string }).text;
320
+ expect(text).not.toContain("Do NOT use markdown tables");
321
+ });
296
322
  });
297
323
 
298
324
  // ---------------------------------------------------------------------------
@@ -449,60 +475,12 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
449
475
  });
450
476
  });
451
477
 
452
- // ---------------------------------------------------------------------------
453
- // buildChannelAwarenessSection
454
- // ---------------------------------------------------------------------------
455
-
456
- describe("buildChannelAwarenessSection", () => {
457
- test("includes channel awareness heading", () => {
458
- const section = buildChannelAwarenessSection();
459
- expect(section).toContain("## Channel Awareness & Trust Gating");
460
- });
461
-
462
- test("includes channel-specific rules", () => {
463
- const section = buildChannelAwarenessSection();
464
- expect(section).toContain("dashboard_capable");
465
- expect(section).toContain("supports_dynamic_ui");
466
- expect(section).toContain("supports_voice_input");
467
- });
468
-
469
- test("includes trust gating rules for permission asks", () => {
470
- const section = buildChannelAwarenessSection();
471
- expect(section).toContain("firstConversationComplete");
472
- expect(section).toContain("Permission ask trust gating");
473
- expect(section).toContain(
474
- "Do NOT proactively ask for elevated permissions",
475
- );
476
- });
477
-
478
- test("gates microphone permissions on voice capability", () => {
479
- const section = buildChannelAwarenessSection();
480
- expect(section).toContain("Do not ask for microphone permissions");
481
- });
482
-
483
- test("gates computer-control on dashboard channel", () => {
484
- const section = buildChannelAwarenessSection();
485
- expect(section).toContain("computer-control permissions on non-dashboard");
486
- });
487
-
488
- test("does NOT include group chat etiquette (gated per-turn instead)", () => {
489
- const section = buildChannelAwarenessSection();
490
- expect(section).not.toContain("Group chat etiquette");
491
- expect(section).not.toContain("Stay silent when");
492
- });
493
-
494
- test("does NOT include Discord references (not a supported channel)", () => {
495
- const section = buildChannelAwarenessSection();
496
- expect(section).not.toContain("Discord");
497
- });
498
- });
499
-
500
478
  // ---------------------------------------------------------------------------
501
479
  // Trust-gating behavior: channel constraints for permission asks
502
480
  // ---------------------------------------------------------------------------
503
481
 
504
482
  describe("trust-gating via channel capabilities", () => {
505
- test("vellum channel with macos interface does not add constraint rules", () => {
483
+ test("vellum channel with macos interface skips injection (happy path)", () => {
506
484
  const caps = resolveChannelCapabilities("vellum", "macos");
507
485
  const message: Message = {
508
486
  role: "user",
@@ -510,10 +488,9 @@ describe("trust-gating via channel capabilities", () => {
510
488
  };
511
489
 
512
490
  const result = injectChannelCapabilityContext(message, caps);
513
- const injected = (result.content[0] as { type: "text"; text: string }).text;
514
491
 
515
- expect(injected).not.toContain("CHANNEL CONSTRAINTS");
516
- expect(injected).toContain("dashboard_capable: true");
492
+ // Happy path: message returned unchanged
493
+ expect(result).toBe(message);
517
494
  });
518
495
 
519
496
  test("non-dashboard channel adds constraint rules preventing UI references", () => {
@@ -554,6 +531,50 @@ describe("trust-gating via channel capabilities", () => {
554
531
  });
555
532
  });
556
533
 
534
+ // ---------------------------------------------------------------------------
535
+ // injectChannelCommandContext
536
+ // ---------------------------------------------------------------------------
537
+
538
+ describe("injectChannelCommandContext", () => {
539
+ const baseUserMessage: Message = {
540
+ role: "user",
541
+ content: [{ type: "text", text: "Hello" }],
542
+ };
543
+
544
+ test("injects start command instructions when type is start", () => {
545
+ const result = injectChannelCommandContext(baseUserMessage, {
546
+ type: "start",
547
+ });
548
+ const text = (result.content[0] as { type: "text"; text: string }).text;
549
+ expect(text).toContain("command_type: start");
550
+ expect(text).toContain("warm, brief greeting");
551
+ expect(text).toContain("Treat /start as a hello");
552
+ expect(text).toContain("Do NOT reset conversation");
553
+ });
554
+
555
+ test("includes language code and payload when provided", () => {
556
+ const result = injectChannelCommandContext(baseUserMessage, {
557
+ type: "start",
558
+ payload: "ref123",
559
+ languageCode: "es",
560
+ });
561
+ const text = (result.content[0] as { type: "text"; text: string }).text;
562
+ expect(text).toContain("payload: ref123");
563
+ expect(text).toContain("language_code: es");
564
+ expect(text).toContain("warm, brief greeting");
565
+ });
566
+
567
+ test("does NOT inject start instructions for non-start commands", () => {
568
+ const result = injectChannelCommandContext(baseUserMessage, {
569
+ type: "help",
570
+ });
571
+ const text = (result.content[0] as { type: "text"; text: string }).text;
572
+ expect(text).toContain("command_type: help");
573
+ expect(text).not.toContain("warm, brief greeting");
574
+ expect(text).not.toContain("Treat /start as a hello");
575
+ });
576
+ });
577
+
557
578
  // ---------------------------------------------------------------------------
558
579
  // injectTemporalContext
559
580
  // ---------------------------------------------------------------------------
@@ -768,9 +789,13 @@ describe("injectInboundActorContext", () => {
768
789
  expect(text).toContain("trust_class: guardian");
769
790
  expect(text).toContain("source_channel: phone");
770
791
  expect(text).toContain("canonical_actor_identity: guardian-user-1");
792
+ // Display names differ from canonical, so they should appear
793
+ expect(text).toContain("actor_identifier: +15550001111");
771
794
  expect(text).toContain("actor_display_name: Guardian Name");
772
795
  expect(text).toContain("actor_sender_display_name: Guardian Name");
773
796
  expect(text).toContain("actor_member_display_name: Guardian Name");
797
+ // guardian_identity matches canonical, so it should be omitted
798
+ expect(text).not.toContain("guardian_identity:");
774
799
  expect(text).toContain("</inbound_actor_context>");
775
800
  });
776
801
 
@@ -798,6 +823,30 @@ describe("injectInboundActorContext", () => {
798
823
  );
799
824
  });
800
825
 
826
+ test("omits name_preference_note when member name matches canonical and is suppressed", () => {
827
+ const ctx: InboundActorContext = {
828
+ sourceChannel: "telegram",
829
+ canonicalActorIdentity: "Jeff",
830
+ actorIdentifier: "@jeff_handle",
831
+ actorDisplayName: "Jeff",
832
+ actorSenderDisplayName: "Jeffrey",
833
+ actorMemberDisplayName: "Jeff",
834
+ trustClass: "trusted_contact",
835
+ guardianIdentity: "guardian-user-1",
836
+ memberStatus: "active",
837
+ memberPolicy: "allow",
838
+ };
839
+
840
+ const result = injectInboundActorContext(baseUserMessage, ctx);
841
+ const text = (result.content[0] as { type: "text"; text: string }).text;
842
+ // actor_member_display_name matches canonical → omitted by differs() guard
843
+ expect(text).not.toContain("actor_member_display_name:");
844
+ // actor_sender_display_name differs from canonical → emitted
845
+ expect(text).toContain("actor_sender_display_name: Jeffrey");
846
+ // name_preference_note must NOT appear since actor_member_display_name was omitted
847
+ expect(text).not.toContain("name_preference_note:");
848
+ });
849
+
801
850
  test("sanitizes inline actor context values to prevent line injection", () => {
802
851
  const ctx: InboundActorContext = {
803
852
  sourceChannel: "telegram",
@@ -903,6 +952,38 @@ describe("injectInboundActorContext", () => {
903
952
  expect(text).not.toContain("non-guardian account");
904
953
  });
905
954
 
955
+ test("omits redundant fields when they match canonical_actor_identity", () => {
956
+ const uuid = "vellum-principal-b77e94f5-67c0-4599-8baa-871b925b3da8";
957
+ const ctx: InboundActorContext = {
958
+ sourceChannel: "vellum",
959
+ canonicalActorIdentity: uuid,
960
+ actorIdentifier: uuid,
961
+ actorDisplayName: uuid,
962
+ actorSenderDisplayName: undefined,
963
+ actorMemberDisplayName: uuid,
964
+ trustClass: "guardian",
965
+ guardianIdentity: uuid,
966
+ memberStatus: "active",
967
+ memberPolicy: "allow",
968
+ contactNotes: "guardian",
969
+ };
970
+
971
+ const result = injectInboundActorContext(baseUserMessage, ctx);
972
+ const text = (result.content[0] as { type: "text"; text: string }).text;
973
+ // Only essential fields should remain
974
+ expect(text).toContain("source_channel: vellum");
975
+ expect(text).toContain(`canonical_actor_identity: ${uuid}`);
976
+ expect(text).toContain("trust_class: guardian");
977
+ // Redundant fields should be omitted
978
+ expect(text).not.toContain("actor_identifier:");
979
+ expect(text).not.toContain("actor_display_name:");
980
+ expect(text).not.toContain("actor_sender_display_name:");
981
+ expect(text).not.toContain("actor_member_display_name:");
982
+ expect(text).not.toContain("guardian_identity:");
983
+ // contact_notes: "guardian" matches trust_class, should be omitted
984
+ expect(text).not.toContain("contact_notes:");
985
+ });
986
+
906
987
  test("omits member_status and member_policy when not provided", () => {
907
988
  const ctx: InboundActorContext = {
908
989
  sourceChannel: "phone",
@@ -969,46 +1050,51 @@ describe("applyRuntimeInjections with inboundActorContext", () => {
969
1050
  });
970
1051
 
971
1052
  // ---------------------------------------------------------------------------
972
- // buildChannelTurnContextBlock
1053
+ // buildTurnContextBlock (channel-only)
973
1054
  // ---------------------------------------------------------------------------
974
1055
 
975
- describe("buildChannelTurnContextBlock", () => {
976
- test("formats block with all three channel fields", () => {
977
- const block = buildChannelTurnContextBlock({
978
- turnContext: {
979
- userMessageChannel: "telegram",
980
- assistantMessageChannel: "telegram",
1056
+ describe("buildTurnContextBlock (channel-only)", () => {
1057
+ test("collapses to single field when all channels match", () => {
1058
+ const block = buildTurnContextBlock(
1059
+ {
1060
+ turnContext: {
1061
+ userMessageChannel: "telegram",
1062
+ assistantMessageChannel: "telegram",
1063
+ },
1064
+ conversationOriginChannel: "telegram",
981
1065
  },
982
- conversationOriginChannel: "telegram",
983
- });
1066
+ undefined,
1067
+ );
984
1068
  expect(block).toBe(
985
- "<channel_turn_context>\n" +
986
- "user_message_channel: telegram\n" +
987
- "assistant_message_channel: telegram\n" +
988
- "conversation_origin_channel: telegram\n" +
989
- "</channel_turn_context>",
1069
+ "<turn_context>\n" + "channel: telegram\n" + "</turn_context>",
990
1070
  );
991
1071
  });
992
1072
 
993
1073
  test('uses "unknown" when conversationOriginChannel is null', () => {
994
- const block = buildChannelTurnContextBlock({
995
- turnContext: {
996
- userMessageChannel: "vellum",
997
- assistantMessageChannel: "vellum",
1074
+ const block = buildTurnContextBlock(
1075
+ {
1076
+ turnContext: {
1077
+ userMessageChannel: "vellum",
1078
+ assistantMessageChannel: "vellum",
1079
+ },
1080
+ conversationOriginChannel: null,
998
1081
  },
999
- conversationOriginChannel: null,
1000
- });
1082
+ undefined,
1083
+ );
1001
1084
  expect(block).toContain("conversation_origin_channel: unknown");
1002
1085
  });
1003
1086
 
1004
1087
  test("handles mixed channels", () => {
1005
- const block = buildChannelTurnContextBlock({
1006
- turnContext: {
1007
- userMessageChannel: "telegram",
1008
- assistantMessageChannel: "vellum",
1088
+ const block = buildTurnContextBlock(
1089
+ {
1090
+ turnContext: {
1091
+ userMessageChannel: "telegram",
1092
+ assistantMessageChannel: "vellum",
1093
+ },
1094
+ conversationOriginChannel: "vellum",
1009
1095
  },
1010
- conversationOriginChannel: "vellum",
1011
- });
1096
+ undefined,
1097
+ );
1012
1098
  expect(block).toContain("user_message_channel: telegram");
1013
1099
  expect(block).toContain("assistant_message_channel: vellum");
1014
1100
  expect(block).toContain("conversation_origin_channel: vellum");
@@ -1016,10 +1102,10 @@ describe("buildChannelTurnContextBlock", () => {
1016
1102
  });
1017
1103
 
1018
1104
  // ---------------------------------------------------------------------------
1019
- // injectChannelTurnContext
1105
+ // injectTurnContext (channel-only)
1020
1106
  // ---------------------------------------------------------------------------
1021
1107
 
1022
- describe("injectChannelTurnContext", () => {
1108
+ describe("injectTurnContext (channel-only)", () => {
1023
1109
  const baseUserMessage: Message = {
1024
1110
  role: "user",
1025
1111
  content: [{ type: "text", text: "Hello from telegram" }],
@@ -1033,14 +1119,14 @@ describe("injectChannelTurnContext", () => {
1033
1119
  },
1034
1120
  conversationOriginChannel: "telegram",
1035
1121
  };
1036
- const result = injectChannelTurnContext(baseUserMessage, params);
1122
+ const result = injectTurnContext(baseUserMessage, params, undefined);
1037
1123
  expect(result.content.length).toBe(2);
1038
1124
  const injected = result.content[0];
1039
1125
  expect(injected.type).toBe("text");
1040
1126
  const text = (injected as { type: "text"; text: string }).text;
1041
- expect(text).toContain("<channel_turn_context>");
1042
- expect(text).toContain("user_message_channel: telegram");
1043
- expect(text).toContain("</channel_turn_context>");
1127
+ expect(text).toContain("<turn_context>");
1128
+ expect(text).toContain("channel: telegram");
1129
+ expect(text).toContain("</turn_context>");
1044
1130
  });
1045
1131
 
1046
1132
  test("preserves original message content", () => {
@@ -1051,7 +1137,7 @@ describe("injectChannelTurnContext", () => {
1051
1137
  },
1052
1138
  conversationOriginChannel: "vellum",
1053
1139
  };
1054
- const result = injectChannelTurnContext(baseUserMessage, params);
1140
+ const result = injectTurnContext(baseUserMessage, params, undefined);
1055
1141
  const lastBlock = result.content[result.content.length - 1];
1056
1142
  expect((lastBlock as { type: "text"; text: string }).text).toBe(
1057
1143
  "Hello from telegram",
@@ -1071,7 +1157,7 @@ describe("stripChannelTurnContext", () => {
1071
1157
  content: [
1072
1158
  {
1073
1159
  type: "text",
1074
- text: "<channel_turn_context>\nuser_message_channel: telegram\n</channel_turn_context>",
1160
+ text: "<turn_context>\nuser_message_channel: telegram\n</turn_context>",
1075
1161
  },
1076
1162
  { type: "text", text: "Hello" },
1077
1163
  ],
@@ -1099,7 +1185,7 @@ describe("stripChannelTurnContext", () => {
1099
1185
  content: [
1100
1186
  {
1101
1187
  type: "text",
1102
- text: "<channel_turn_context>\nuser_message_channel: macos\n</channel_turn_context>",
1188
+ text: "<turn_context>\nuser_message_channel: macos\n</turn_context>",
1103
1189
  },
1104
1190
  ],
1105
1191
  },
@@ -1152,7 +1238,7 @@ describe("applyRuntimeInjections with channelTurnContext", () => {
1152
1238
  expect(result[0].content.length).toBe(2);
1153
1239
  const injected = result[0].content[0];
1154
1240
  expect((injected as { type: "text"; text: string }).text).toContain(
1155
- "<channel_turn_context>",
1241
+ "<turn_context>",
1156
1242
  );
1157
1243
  });
1158
1244
 
@@ -1173,73 +1259,6 @@ describe("applyRuntimeInjections with channelTurnContext", () => {
1173
1259
  });
1174
1260
  });
1175
1261
 
1176
- // ---------------------------------------------------------------------------
1177
- // sanitizePttActivationKey
1178
- // ---------------------------------------------------------------------------
1179
-
1180
- describe("sanitizePttActivationKey", () => {
1181
- test("returns undefined for null/undefined input", () => {
1182
- expect(sanitizePttActivationKey(null)).toBeUndefined();
1183
- expect(sanitizePttActivationKey(undefined)).toBeUndefined();
1184
- });
1185
-
1186
- test("passes through valid JSON PTTActivator payloads", () => {
1187
- const modifierOnly = JSON.stringify({
1188
- kind: "modifierOnly",
1189
- modifierFlags: 8388608,
1190
- });
1191
- expect(sanitizePttActivationKey(modifierOnly)).toBe(modifierOnly);
1192
- const keyPayload = JSON.stringify({ kind: "key", keyCode: 49 });
1193
- expect(sanitizePttActivationKey(keyPayload)).toBe(keyPayload);
1194
- const nonePayload = JSON.stringify({ kind: "none" });
1195
- expect(sanitizePttActivationKey(nonePayload)).toBe(nonePayload);
1196
- });
1197
-
1198
- test("returns undefined for invalid keys", () => {
1199
- expect(
1200
- sanitizePttActivationKey("malicious\nprompt injection"),
1201
- ).toBeUndefined();
1202
- expect(sanitizePttActivationKey("arbitrary_value")).toBeUndefined();
1203
- expect(sanitizePttActivationKey("")).toBeUndefined();
1204
- });
1205
- });
1206
-
1207
- // ---------------------------------------------------------------------------
1208
- // resolveChannelCapabilities sanitizes pttActivationKey
1209
- // ---------------------------------------------------------------------------
1210
-
1211
- describe("resolveChannelCapabilities with PTT metadata", () => {
1212
- test("sanitizes valid JSON PTTActivator pttActivationKey", () => {
1213
- const key = JSON.stringify({
1214
- kind: "modifierOnly",
1215
- modifierFlags: 8388608,
1216
- });
1217
- const caps = resolveChannelCapabilities("macos", "macos", {
1218
- pttActivationKey: key,
1219
- });
1220
- expect(caps.pttActivationKey).toBe(key);
1221
- });
1222
-
1223
- test("sanitizes invalid pttActivationKey to undefined", () => {
1224
- const caps = resolveChannelCapabilities("macos", "macos", {
1225
- pttActivationKey: "evil\nprompt",
1226
- });
1227
- expect(caps.pttActivationKey).toBeUndefined();
1228
- });
1229
-
1230
- test("passes through microphonePermissionGranted", () => {
1231
- const key = JSON.stringify({
1232
- kind: "modifierOnly",
1233
- modifierFlags: 8388608,
1234
- });
1235
- const caps = resolveChannelCapabilities("macos", "macos", {
1236
- pttActivationKey: key,
1237
- microphonePermissionGranted: true,
1238
- });
1239
- expect(caps.microphonePermissionGranted).toBe(true);
1240
- });
1241
- });
1242
-
1243
1262
  // ---------------------------------------------------------------------------
1244
1263
  // applyRuntimeInjections — injection mode
1245
1264
  // ---------------------------------------------------------------------------
@@ -1299,8 +1318,8 @@ describe("applyRuntimeInjections — injection mode", () => {
1299
1318
  expect(allText).toContain("<channel_command_context>");
1300
1319
  expect(allText).toContain("<active_workspace>");
1301
1320
  expect(allText).toContain("<channel_capabilities>");
1302
- expect(allText).toContain("<channel_turn_context>");
1303
- expect(allText).toContain("<interface_turn_context>");
1321
+ expect(allText).toContain("<turn_context>");
1322
+ expect(allText).toContain("<turn_context>");
1304
1323
  expect(allText).toContain("<inbound_actor_context>");
1305
1324
  expect(allText).toContain("<non_interactive_context>");
1306
1325
  });
@@ -1349,8 +1368,8 @@ describe("applyRuntimeInjections — injection mode", () => {
1349
1368
  .join("\n");
1350
1369
 
1351
1370
  // Kept in minimal mode
1352
- expect(allText).toContain("<channel_turn_context>");
1353
- expect(allText).toContain("<interface_turn_context>");
1371
+ expect(allText).toContain("<turn_context>");
1372
+ expect(allText).toContain("<turn_context>");
1354
1373
  expect(allText).toContain("<inbound_actor_context>");
1355
1374
  expect(allText).toContain("<non_interactive_context>");
1356
1375
  expect(allText).toContain("<channel_capabilities>");