@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -0,0 +1,417 @@
1
+ /**
2
+ * PR 23 — verifies that the daemon lazily backfills DM history the first
3
+ * time a Slack DM lands in a "cold" conversation (one with fewer than the
4
+ * warm-storage threshold of stored slackMeta messages).
5
+ *
6
+ * Behaviour under test (see `inbound-message-handler.ts`):
7
+ * - On a fresh DM, `backfillDm` is invoked exactly once and every returned
8
+ * message is persisted as a `messages` row with a `slackMeta` envelope.
9
+ * - Once warm storage exceeds the threshold, subsequent inbound DMs do
10
+ * not re-trigger backfill.
11
+ * - When `backfillDm` throws, the turn proceeds without a crash and
12
+ * nothing extra is persisted.
13
+ */
14
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Mocks (must precede module imports under test)
18
+ // ---------------------------------------------------------------------------
19
+
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, {
23
+ get: () => () => {},
24
+ }),
25
+ }));
26
+
27
+ mock.module("../config/env.js", () => ({
28
+ isHttpAuthDisabled: () => true,
29
+ getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
30
+ }));
31
+
32
+ mock.module("../tools/credentials/metadata-store.js", () => ({
33
+ getCredentialMetadata: () => undefined,
34
+ upsertCredentialMetadata: () => {},
35
+ deleteCredentialMetadata: () => {},
36
+ listCredentialMetadata: () => [],
37
+ }));
38
+
39
+ mock.module("../runtime/gateway-client.js", () => ({
40
+ deliverChannelReply: async () => {},
41
+ }));
42
+
43
+ import type { Message } from "../messaging/provider-types.js";
44
+
45
+ // `backfillDm` is the only piece of the slack provider surface this test
46
+ // needs to control. Mocking it directly keeps the test focused on the
47
+ // cold-start logic in the handler and avoids pulling in adapter wiring.
48
+ type BackfillDmFn = (
49
+ channelId: string,
50
+ opts?: { limit?: number; before?: string },
51
+ ) => Promise<Message[]>;
52
+
53
+ const backfillDmMock = mock<BackfillDmFn>(async () => []);
54
+ const backfillThreadMock = mock(async () => [] as Message[]);
55
+
56
+ mock.module("../messaging/providers/slack/backfill.js", () => ({
57
+ backfillDm: (channelId: string, opts?: { limit?: number; before?: string }) =>
58
+ backfillDmMock(channelId, opts),
59
+ backfillThread: () => backfillThreadMock(),
60
+ }));
61
+
62
+ import { upsertContactChannel } from "../contacts/contacts-write.js";
63
+ import { getDb, initializeDb } from "../memory/db.js";
64
+ import { messages } from "../memory/schema/conversations.js";
65
+ import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
66
+ import { handleChannelInbound } from "../runtime/routes/channel-routes.js";
67
+
68
+ initializeDb();
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Helpers
72
+ // ---------------------------------------------------------------------------
73
+
74
+ const TEST_BEARER_TOKEN = "test-token";
75
+ const SLACK_DM_CHANNEL_ID = "D0BACKFILL";
76
+ const SLACK_DM_USER_ID = "U_DM_USER";
77
+ const SLACK_DM_DISPLAY_NAME = "DM Sender";
78
+
79
+ function resetState(): void {
80
+ const db = getDb();
81
+ db.run("DELETE FROM messages");
82
+ db.run("DELETE FROM channel_inbound_events");
83
+ db.run("DELETE FROM conversations");
84
+ db.run("DELETE FROM contact_channels");
85
+ db.run("DELETE FROM contacts");
86
+ db.run("DELETE FROM external_conversation_bindings");
87
+ }
88
+
89
+ function seedActiveMember(): void {
90
+ upsertContactChannel({
91
+ sourceChannel: "slack",
92
+ externalUserId: SLACK_DM_USER_ID,
93
+ externalChatId: SLACK_DM_CHANNEL_ID,
94
+ status: "active",
95
+ policy: "allow",
96
+ displayName: SLACK_DM_DISPLAY_NAME,
97
+ });
98
+ }
99
+
100
+ let msgCounter = 0;
101
+
102
+ function buildDmRequest(
103
+ text: string,
104
+ overrides: Record<string, unknown> = {},
105
+ ): Request {
106
+ msgCounter++;
107
+ const ts = `1700000000.${String(100000 + msgCounter).padStart(6, "0")}`;
108
+ const body: Record<string, unknown> = {
109
+ sourceChannel: "slack",
110
+ interface: "slack",
111
+ conversationExternalId: SLACK_DM_CHANNEL_ID,
112
+ externalMessageId: `${SLACK_DM_CHANNEL_ID}:${ts}`,
113
+ content: text,
114
+ actorExternalId: SLACK_DM_USER_ID,
115
+ actorDisplayName: SLACK_DM_DISPLAY_NAME,
116
+ actorUsername: "dm_user",
117
+ replyCallbackUrl: "http://localhost:7830/deliver/slack",
118
+ sourceMetadata: {
119
+ messageId: ts,
120
+ // Critical — the cold-start trigger requires `chatType: "im"`.
121
+ chatType: "im",
122
+ },
123
+ ...overrides,
124
+ };
125
+ return new Request("http://localhost:8080/channels/inbound", {
126
+ method: "POST",
127
+ headers: {
128
+ "Content-Type": "application/json",
129
+ "X-Gateway-Origin": TEST_BEARER_TOKEN,
130
+ },
131
+ body: JSON.stringify(body),
132
+ });
133
+ }
134
+
135
+ function readPersistedSlackRows(): Array<{
136
+ role: string;
137
+ content: string;
138
+ metadata: string | null;
139
+ }> {
140
+ const db = getDb();
141
+ return db
142
+ .select({
143
+ role: messages.role,
144
+ content: messages.content,
145
+ metadata: messages.metadata,
146
+ })
147
+ .from(messages)
148
+ .all();
149
+ }
150
+
151
+ function makeBackfilledMessage(overrides: Partial<Message> = {}): Message {
152
+ return {
153
+ id: "1700000000.090000",
154
+ conversationId: SLACK_DM_CHANNEL_ID,
155
+ sender: { id: SLACK_DM_USER_ID, name: "Backfilled Sender" },
156
+ text: "older message",
157
+ timestamp: 1700000000_000,
158
+ platform: "slack",
159
+ ...overrides,
160
+ };
161
+ }
162
+
163
+ function noopProcessMessage(): Promise<{ messageId: string }> {
164
+ // The agent loop is fire-and-forget in production, so the handler never
165
+ // awaits a result. Returning a sentinel is enough to satisfy the type.
166
+ return Promise.resolve({ messageId: "agent-stub" });
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Tests
171
+ // ---------------------------------------------------------------------------
172
+
173
+ describe("PR 23 — Slack DM cold-start backfill", () => {
174
+ beforeEach(() => {
175
+ resetState();
176
+ seedActiveMember();
177
+ msgCounter = 0;
178
+ backfillDmMock.mockReset();
179
+ backfillDmMock.mockImplementation(async () => []);
180
+ backfillThreadMock.mockReset();
181
+ });
182
+
183
+ test("first DM in cold conversation triggers backfill exactly once and persists history", async () => {
184
+ const olderMessages: Message[] = [
185
+ makeBackfilledMessage({
186
+ id: "1700000000.000001",
187
+ text: "older A",
188
+ sender: { id: SLACK_DM_USER_ID, name: "Alice" },
189
+ }),
190
+ makeBackfilledMessage({
191
+ id: "1700000000.000002",
192
+ text: "older B",
193
+ sender: { id: SLACK_DM_USER_ID, name: "Alice" },
194
+ }),
195
+ makeBackfilledMessage({
196
+ id: "1700000000.000003",
197
+ text: "older C",
198
+ sender: { id: SLACK_DM_USER_ID, name: "Alice" },
199
+ }),
200
+ ];
201
+ backfillDmMock.mockImplementation(async () => olderMessages);
202
+
203
+ const req = buildDmRequest("live new DM");
204
+ const resp = await handleChannelInbound(
205
+ req,
206
+ noopProcessMessage,
207
+ TEST_BEARER_TOKEN,
208
+ );
209
+ const json = (await resp.json()) as Record<string, unknown>;
210
+
211
+ expect(json.accepted).toBe(true);
212
+ expect(backfillDmMock).toHaveBeenCalledTimes(1);
213
+ const [channelArg, optsArg] = backfillDmMock.mock.calls[0];
214
+ expect(channelArg).toBe(SLACK_DM_CHANNEL_ID);
215
+ // The webhook message's own ts must be passed as `before` so Slack's
216
+ // history window excludes it — otherwise backfill would re-insert the
217
+ // message that the live inbound path is already persisting.
218
+ expect(optsArg?.limit).toBe(50);
219
+ expect(typeof optsArg?.before).toBe("string");
220
+ expect(optsArg?.before).toMatch(/^1700000000\.10000/);
221
+
222
+ // All three backfilled rows are persisted with a slackMeta envelope.
223
+ // The live new DM's row is enqueued on the agent loop's persistence path
224
+ // (which is mocked out here via `noopProcessMessage`), so the rows we
225
+ // see in storage are exactly the backfilled ones.
226
+ const rows = readPersistedSlackRows();
227
+ expect(rows.length).toBe(3);
228
+
229
+ const persistedTs = rows.map((r) => {
230
+ const envelope = JSON.parse(r.metadata!) as Record<string, unknown>;
231
+ const meta = readSlackMetadata(envelope.slackMeta as string);
232
+ expect(meta).not.toBeNull();
233
+ expect(meta!.source).toBe("slack");
234
+ expect(meta!.eventKind).toBe("message");
235
+ expect(meta!.channelId).toBe(SLACK_DM_CHANNEL_ID);
236
+ return meta!.channelTs;
237
+ });
238
+ expect(new Set(persistedTs)).toEqual(
239
+ new Set(["1700000000.000001", "1700000000.000002", "1700000000.000003"]),
240
+ );
241
+
242
+ // Backfilled rows preserve their original text content so the renderer
243
+ // has something to display.
244
+ const texts = rows.map((r) => r.content).sort();
245
+ expect(texts).toEqual(["older A", "older B", "older C"]);
246
+ });
247
+
248
+ test("warm storage prevents re-trigger on subsequent DMs", async () => {
249
+ backfillDmMock.mockImplementation(async () => [
250
+ makeBackfilledMessage({ id: "1700000000.000001", text: "older A" }),
251
+ makeBackfilledMessage({ id: "1700000000.000002", text: "older B" }),
252
+ makeBackfilledMessage({ id: "1700000000.000003", text: "older C" }),
253
+ ]);
254
+
255
+ // First inbound: cold path, fires backfill.
256
+ await handleChannelInbound(
257
+ buildDmRequest("first live DM"),
258
+ noopProcessMessage,
259
+ TEST_BEARER_TOKEN,
260
+ );
261
+ expect(backfillDmMock).toHaveBeenCalledTimes(1);
262
+ const rowsAfterFirst = readPersistedSlackRows();
263
+ expect(rowsAfterFirst.length).toBe(3);
264
+
265
+ // Second inbound: storage now has three slackMeta-tagged rows from the
266
+ // backfill, which meets the warm threshold. Backfill MUST NOT be
267
+ // re-invoked.
268
+ const resp2 = await handleChannelInbound(
269
+ buildDmRequest("second live DM"),
270
+ noopProcessMessage,
271
+ TEST_BEARER_TOKEN,
272
+ );
273
+ expect(resp2.status).toBe(200);
274
+ expect(backfillDmMock).toHaveBeenCalledTimes(1);
275
+ expect(readPersistedSlackRows().length).toBe(3);
276
+ });
277
+
278
+ test("backfill failure is non-fatal: turn proceeds without crash", async () => {
279
+ backfillDmMock.mockImplementation(async () => {
280
+ throw new Error("Slack API error: rate_limited");
281
+ });
282
+
283
+ const resp = await handleChannelInbound(
284
+ buildDmRequest("live DM despite backfill failure"),
285
+ noopProcessMessage,
286
+ TEST_BEARER_TOKEN,
287
+ );
288
+ const json = (await resp.json()) as Record<string, unknown>;
289
+
290
+ expect(resp.status).toBe(200);
291
+ expect(json.accepted).toBe(true);
292
+ expect(json.duplicate).toBe(false);
293
+ expect(backfillDmMock).toHaveBeenCalledTimes(1);
294
+
295
+ // No backfilled rows persisted because the call threw.
296
+ const rows = readPersistedSlackRows();
297
+ expect(rows.length).toBe(0);
298
+ });
299
+
300
+ test("non-DM Slack inbound does not trigger backfill", async () => {
301
+ // chatType=channel must skip the cold-start branch entirely. This guards
302
+ // against the trigger accidentally widening to channel messages.
303
+ const req = buildDmRequest("channel message", {
304
+ sourceMetadata: {
305
+ messageId: "1700000000.999999",
306
+ chatType: "channel",
307
+ },
308
+ });
309
+ await handleChannelInbound(req, noopProcessMessage, TEST_BEARER_TOKEN);
310
+ expect(backfillDmMock).toHaveBeenCalledTimes(0);
311
+ });
312
+
313
+ test("concurrent cold DMs share a single backfill (no double-write)", async () => {
314
+ // Two near-simultaneous DMs into the same cold conversation must not
315
+ // each trigger their own backfill — the in-flight lock dedupes them so
316
+ // Slack history is fetched once and rows are written once.
317
+ let resolveBackfill: ((messages: Message[]) => void) | null = null;
318
+ backfillDmMock.mockImplementation(
319
+ () =>
320
+ new Promise<Message[]>((resolve) => {
321
+ resolveBackfill = resolve;
322
+ }),
323
+ );
324
+
325
+ const first = handleChannelInbound(
326
+ buildDmRequest("first concurrent DM"),
327
+ noopProcessMessage,
328
+ TEST_BEARER_TOKEN,
329
+ );
330
+ const second = handleChannelInbound(
331
+ buildDmRequest("second concurrent DM"),
332
+ noopProcessMessage,
333
+ TEST_BEARER_TOKEN,
334
+ );
335
+
336
+ // Wait for both handlers to register their await on the in-flight
337
+ // promise before resolving the underlying backfill fetch.
338
+ await new Promise((r) => setTimeout(r, 10));
339
+ expect(resolveBackfill).not.toBeNull();
340
+ resolveBackfill!([
341
+ makeBackfilledMessage({ id: "1700000000.000001", text: "older A" }),
342
+ makeBackfilledMessage({ id: "1700000000.000002", text: "older B" }),
343
+ ]);
344
+
345
+ await Promise.all([first, second]);
346
+
347
+ expect(backfillDmMock).toHaveBeenCalledTimes(1);
348
+ const rows = readPersistedSlackRows();
349
+ expect(rows.length).toBe(2);
350
+ const texts = rows.map((r) => r.content).sort();
351
+ expect(texts).toEqual(["older A", "older B"]);
352
+ });
353
+
354
+ test("bot-authored backfilled messages are persisted with role=assistant", async () => {
355
+ // Slack DM history includes our own prior bot replies. If those rows
356
+ // were rehydrated as `user` turns, the assistant would later treat its
357
+ // own output as new user input and speaker attribution would break.
358
+ backfillDmMock.mockImplementation(async () => [
359
+ makeBackfilledMessage({
360
+ id: "1700000000.000001",
361
+ text: "user reply",
362
+ sender: { id: SLACK_DM_USER_ID, name: "Alice" },
363
+ }),
364
+ makeBackfilledMessage({
365
+ id: "1700000000.000002",
366
+ text: "assistant reply",
367
+ sender: { id: "B_BOT", name: "assistant-bot" },
368
+ metadata: { isBot: true },
369
+ }),
370
+ ]);
371
+
372
+ await handleChannelInbound(
373
+ buildDmRequest("live new DM"),
374
+ noopProcessMessage,
375
+ TEST_BEARER_TOKEN,
376
+ );
377
+
378
+ const rows = readPersistedSlackRows();
379
+ expect(rows.length).toBe(2);
380
+ const byText = new Map(rows.map((r) => [r.content, r.role]));
381
+ expect(byText.get("user reply")).toBe("user");
382
+ expect(byText.get("assistant reply")).toBe("assistant");
383
+ });
384
+
385
+ test("backfill skips channelTs values already stored", async () => {
386
+ // First DM: backfill returns three rows.
387
+ backfillDmMock.mockImplementation(async () => [
388
+ makeBackfilledMessage({ id: "1700000000.000001", text: "older A" }),
389
+ makeBackfilledMessage({ id: "1700000000.000002", text: "older B" }),
390
+ ]);
391
+ await handleChannelInbound(
392
+ buildDmRequest("first live DM"),
393
+ noopProcessMessage,
394
+ TEST_BEARER_TOKEN,
395
+ );
396
+ expect(readPersistedSlackRows().length).toBe(2);
397
+
398
+ // Conversation now has 2 slackMeta rows — still under the warm
399
+ // threshold, so a second cold-path probe should fire. This time
400
+ // backfill returns an overlapping set; only the new ts is written.
401
+ backfillDmMock.mockImplementation(async () => [
402
+ makeBackfilledMessage({ id: "1700000000.000001", text: "older A again" }),
403
+ makeBackfilledMessage({ id: "1700000000.000004", text: "older D" }),
404
+ ]);
405
+ await handleChannelInbound(
406
+ buildDmRequest("second live DM"),
407
+ noopProcessMessage,
408
+ TEST_BEARER_TOKEN,
409
+ );
410
+ expect(backfillDmMock).toHaveBeenCalledTimes(2);
411
+
412
+ const rows = readPersistedSlackRows();
413
+ expect(rows.length).toBe(3);
414
+ const texts = rows.map((r) => r.content).sort();
415
+ expect(texts).toEqual(["older A", "older B", "older D"]);
416
+ });
417
+ });
@@ -0,0 +1,227 @@
1
+ /**
2
+ * PR 16 — verifies that Slack DMs ride the same persistence path as Slack
3
+ * channel messages. The DM case is structurally identical: `chatType: "im"`
4
+ * still maps to `userMessageChannel === "slack"` (the channel-vs-DM
5
+ * distinction lives on `ChannelCapabilities.chatType`, not `originChannel`),
6
+ * so the metadata enrichment in `persistQueuedMessageBody` is channel-
7
+ * agnostic for any Slack inbound.
8
+ *
9
+ * This test guards against a regression where someone tightens the slackMeta
10
+ * enrichment with a chatType-based guard (`chatType !== "im"` or similar)
11
+ * and silently drops DM rows back into the legacy JIT-hint path that PR 25
12
+ * is set to remove.
13
+ *
14
+ * The test exercises `persistQueuedMessageBody` directly — the same entry
15
+ * point used by `inbound-slack-persistence.test.ts` — to keep the assertion
16
+ * focused on the DM-vs-channel parity rather than the full HTTP plumbing.
17
+ */
18
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
19
+
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
23
+ }));
24
+
25
+ const addMessageCalls: Array<{
26
+ conversationId: string;
27
+ role: string;
28
+ content: string;
29
+ metadata?: Record<string, unknown>;
30
+ }> = [];
31
+
32
+ mock.module("../memory/conversation-crud.js", () => ({
33
+ addMessage: async (
34
+ conversationId: string,
35
+ role: string,
36
+ content: string,
37
+ metadata?: Record<string, unknown>,
38
+ ) => {
39
+ addMessageCalls.push({ conversationId, role, content, metadata });
40
+ return { id: `persisted-${addMessageCalls.length}` };
41
+ },
42
+ getConversation: () => null,
43
+ provenanceFromTrustContext: () => ({}),
44
+ setConversationOriginChannelIfUnset: () => {},
45
+ setConversationOriginInterfaceIfUnset: () => {},
46
+ }));
47
+
48
+ mock.module("../memory/conversation-disk-view.js", () => ({
49
+ syncMessageToDisk: () => {},
50
+ updateMetaFile: () => {},
51
+ }));
52
+
53
+ mock.module("../memory/attachments-store.js", () => ({
54
+ attachmentExists: () => false,
55
+ linkAttachmentToMessage: () => {},
56
+ attachInlineAttachmentToMessage: () => {},
57
+ validateAttachmentUpload: () => ({ ok: true }),
58
+ AttachmentUploadError: class extends Error {},
59
+ }));
60
+
61
+ import type {
62
+ TurnChannelContext,
63
+ TurnInterfaceContext,
64
+ } from "../channels/types.js";
65
+ import type { MessagingConversationContext } from "../daemon/conversation-messaging.js";
66
+ import { persistQueuedMessageBody } from "../daemon/conversation-messaging.js";
67
+ import type { MessageQueue } from "../daemon/conversation-queue-manager.js";
68
+ import {
69
+ readSlackMetadata,
70
+ type SlackMessageMetadata,
71
+ } from "../messaging/providers/slack/message-metadata.js";
72
+
73
+ function createSlackTurnContext(): MessagingConversationContext {
74
+ // DMs and channel messages both resolve to userMessageChannel === "slack"
75
+ // — the chatType ("im" vs "channel") is carried on ChannelCapabilities,
76
+ // not on the channel string itself. So the same context shape covers
77
+ // both surfaces.
78
+ const channel: TurnChannelContext = {
79
+ userMessageChannel: "slack",
80
+ assistantMessageChannel: "slack",
81
+ };
82
+ const iface: TurnInterfaceContext = {
83
+ userMessageInterface: "slack",
84
+ assistantMessageInterface: "slack",
85
+ };
86
+ const queueStub = {
87
+ push: () => true,
88
+ drain: () => [],
89
+ size: () => 0,
90
+ } as unknown as MessageQueue;
91
+ return {
92
+ conversationId: "conv-dm-test",
93
+ messages: [],
94
+ processing: false,
95
+ abortController: null,
96
+ queue: queueStub,
97
+ getTurnChannelContext: () => channel,
98
+ getTurnInterfaceContext: () => iface,
99
+ };
100
+ }
101
+
102
+ function lastPersistedSlackMeta(): SlackMessageMetadata | null {
103
+ expect(addMessageCalls.length).toBeGreaterThan(0);
104
+ const metadata = addMessageCalls.at(-1)?.metadata;
105
+ expect(metadata).toBeDefined();
106
+ const raw = metadata?.slackMeta;
107
+ if (raw === undefined) return null;
108
+ expect(typeof raw).toBe("string");
109
+ return readSlackMetadata(raw as string);
110
+ }
111
+
112
+ describe("PR 16 — Slack DM persistence parity", () => {
113
+ beforeEach(() => {
114
+ addMessageCalls.length = 0;
115
+ });
116
+
117
+ test("DM inbound persists slackMeta with channelId/channelTs and no threadTs", async () => {
118
+ // Simulate a Slack DM: gateway forwards `sourceMetadata.chatType: "im"`
119
+ // and never populates `threadId` because DMs don't have threads. The
120
+ // ingress handler builds a `slackInbound` with no `threadTs` and threads
121
+ // it through to persistence.
122
+ const ctx = createSlackTurnContext();
123
+ await persistQueuedMessageBody(
124
+ ctx,
125
+ "hello from DM",
126
+ [],
127
+ "req-dm",
128
+ {
129
+ slackInbound: {
130
+ channelId: "D0123DM",
131
+ channelTs: "1700000000.123456",
132
+ displayName: "Alice",
133
+ },
134
+ },
135
+ undefined,
136
+ );
137
+
138
+ const slackMeta = lastPersistedSlackMeta();
139
+ expect(slackMeta).not.toBeNull();
140
+ expect(slackMeta!.source).toBe("slack");
141
+ expect(slackMeta!.eventKind).toBe("message");
142
+ expect(slackMeta!.channelId).toBe("D0123DM");
143
+ expect(slackMeta!.channelTs).toBe("1700000000.123456");
144
+ expect(slackMeta!.displayName).toBe("Alice");
145
+ // DMs have no threads — `threadTs` must be absent rather than empty.
146
+ expect(slackMeta!.threadTs).toBeUndefined();
147
+
148
+ // The transient `slackInbound` carrier key must not leak into the stored
149
+ // metadata column — it's an in-memory plumbing field only.
150
+ const persistedMeta = addMessageCalls.at(-1)!.metadata;
151
+ expect(persistedMeta).toBeDefined();
152
+ expect(persistedMeta!.slackInbound).toBeUndefined();
153
+ });
154
+
155
+ test("DM persists slackMeta even when displayName is omitted", async () => {
156
+ // Some DM events arrive with no displayable sender label (e.g. when the
157
+ // gateway can't resolve the user). The envelope should still be written;
158
+ // only the optional displayName field is omitted.
159
+ const ctx = createSlackTurnContext();
160
+ await persistQueuedMessageBody(
161
+ ctx,
162
+ "anonymous DM",
163
+ [],
164
+ "req-dm-anon",
165
+ {
166
+ slackInbound: {
167
+ channelId: "D9999DM",
168
+ channelTs: "1700000000.555555",
169
+ },
170
+ },
171
+ undefined,
172
+ );
173
+
174
+ const slackMeta = lastPersistedSlackMeta();
175
+ expect(slackMeta).not.toBeNull();
176
+ expect(slackMeta!.channelId).toBe("D9999DM");
177
+ expect(slackMeta!.channelTs).toBe("1700000000.555555");
178
+ expect(slackMeta!.threadTs).toBeUndefined();
179
+ expect(slackMeta!.displayName).toBeUndefined();
180
+ });
181
+
182
+ test("DM and channel-message envelopes differ only by threadTs", async () => {
183
+ // Capture the channel-thread case first.
184
+ const ctx = createSlackTurnContext();
185
+ await persistQueuedMessageBody(
186
+ ctx,
187
+ "channel thread reply",
188
+ [],
189
+ "req-channel",
190
+ {
191
+ slackInbound: {
192
+ channelId: "C0123CHAN",
193
+ channelTs: "1700000000.999999",
194
+ threadTs: "1700000000.111111",
195
+ displayName: "Bob",
196
+ },
197
+ },
198
+ undefined,
199
+ );
200
+ const channelMeta = lastPersistedSlackMeta();
201
+ expect(channelMeta).not.toBeNull();
202
+ expect(channelMeta!.threadTs).toBe("1700000000.111111");
203
+
204
+ // Now dispatch a DM and assert that every shared field has the same
205
+ // shape — only `threadTs` (and the inputs themselves) differ.
206
+ addMessageCalls.length = 0;
207
+ await persistQueuedMessageBody(
208
+ ctx,
209
+ "DM reply",
210
+ [],
211
+ "req-dm-2",
212
+ {
213
+ slackInbound: {
214
+ channelId: "D9999DM",
215
+ channelTs: "1700000000.222222",
216
+ displayName: "Carol",
217
+ },
218
+ },
219
+ undefined,
220
+ );
221
+ const dmMeta = lastPersistedSlackMeta();
222
+ expect(dmMeta).not.toBeNull();
223
+ expect(dmMeta!.source).toBe(channelMeta!.source);
224
+ expect(dmMeta!.eventKind).toBe(channelMeta!.eventKind);
225
+ expect(dmMeta!.threadTs).toBeUndefined();
226
+ });
227
+ });