@vellumai/assistant 0.4.48 → 0.4.50

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 (423) hide show
  1. package/ARCHITECTURE.md +26 -35
  2. package/README.md +5 -26
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/memory.md +180 -119
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +2 -2
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/agent-loop.test.ts +3 -1
  12. package/src/__tests__/anthropic-provider.test.ts +249 -2
  13. package/src/__tests__/approval-cascade.test.ts +796 -0
  14. package/src/__tests__/approval-primitive.test.ts +0 -1
  15. package/src/__tests__/approval-routes-http.test.ts +4 -0
  16. package/src/__tests__/assistant-attachments.test.ts +12 -34
  17. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  18. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  22. package/src/__tests__/channel-guardian.test.ts +0 -2
  23. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  24. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  25. package/src/__tests__/checker.test.ts +13 -20
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-schema.test.ts +1 -68
  29. package/src/__tests__/config-watcher.test.ts +0 -1
  30. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  31. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  32. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  33. package/src/__tests__/context-token-estimator.test.ts +196 -13
  34. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  35. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  36. package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
  37. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  38. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  39. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  40. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  41. package/src/__tests__/credential-vault-unit.test.ts +284 -49
  42. package/src/__tests__/credential-vault.test.ts +150 -16
  43. package/src/__tests__/credentials-cli.test.ts +71 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  45. package/src/__tests__/date-context.test.ts +93 -77
  46. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  47. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  48. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  51. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  52. package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
  53. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  54. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  55. package/src/__tests__/heartbeat-service.test.ts +0 -1
  56. package/src/__tests__/history-repair.test.ts +245 -0
  57. package/src/__tests__/host-cu-proxy.test.ts +791 -0
  58. package/src/__tests__/host-shell-tool.test.ts +27 -15
  59. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  60. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  61. package/src/__tests__/integration-status.test.ts +32 -51
  62. package/src/__tests__/intent-routing.test.ts +0 -1
  63. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  64. package/src/__tests__/invite-routes-http.test.ts +10 -9
  65. package/src/__tests__/keychain-broker-client.test.ts +14 -46
  66. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  68. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  69. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  70. package/src/__tests__/memory-regressions.test.ts +477 -2841
  71. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  72. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  73. package/src/__tests__/mime-builder.test.ts +28 -0
  74. package/src/__tests__/native-web-search.test.ts +1 -0
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +941 -15
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +870 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  83. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  84. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  85. package/src/__tests__/recording-handler.test.ts +3 -4
  86. package/src/__tests__/registry.test.ts +2 -3
  87. package/src/__tests__/relay-server.test.ts +46 -1
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/schedule-tools.test.ts +32 -0
  91. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  95. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  96. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  97. package/src/__tests__/secure-keys.test.ts +7 -2
  98. package/src/__tests__/send-endpoint-busy.test.ts +24 -6
  99. package/src/__tests__/sequence-store.test.ts +0 -1
  100. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  101. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  102. package/src/__tests__/session-agent-loop.test.ts +19 -15
  103. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  104. package/src/__tests__/session-error.test.ts +124 -2
  105. package/src/__tests__/session-history-web-search.test.ts +918 -0
  106. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  107. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  108. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  109. package/src/__tests__/session-queue.test.ts +37 -27
  110. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  111. package/src/__tests__/session-slash-known.test.ts +1 -15
  112. package/src/__tests__/session-slash-queue.test.ts +1 -15
  113. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  114. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  115. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  116. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  117. package/src/__tests__/skill-include-graph.test.ts +66 -0
  118. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  119. package/src/__tests__/skill-load-tool.test.ts +149 -1
  120. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  121. package/src/__tests__/skills-install-extract.test.ts +93 -0
  122. package/src/__tests__/skills-uninstall.test.ts +1 -1
  123. package/src/__tests__/skills.test.ts +3 -3
  124. package/src/__tests__/skillssh-registry.test.ts +451 -0
  125. package/src/__tests__/slack-channel-config.test.ts +67 -3
  126. package/src/__tests__/slack-share-routes.test.ts +17 -19
  127. package/src/__tests__/system-prompt.test.ts +0 -1
  128. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  129. package/src/__tests__/terminal-tools.test.ts +4 -3
  130. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  131. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  132. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  135. package/src/__tests__/tool-executor.test.ts +0 -1
  136. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  137. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  138. package/src/__tests__/trust-store.test.ts +7 -13
  139. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  140. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  141. package/src/__tests__/twilio-routes.test.ts +0 -16
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  145. package/src/agent/ax-tree-compaction.test.ts +286 -0
  146. package/src/agent/loop.ts +104 -131
  147. package/src/approvals/AGENTS.md +1 -1
  148. package/src/approvals/guardian-request-resolvers.ts +14 -2
  149. package/src/bundler/compiler-tools.ts +66 -2
  150. package/src/calls/call-domain.ts +133 -6
  151. package/src/calls/call-store.ts +6 -0
  152. package/src/calls/relay-server.ts +52 -18
  153. package/src/calls/relay-setup-router.ts +17 -1
  154. package/src/calls/twilio-config.ts +3 -8
  155. package/src/calls/twilio-routes.ts +1 -2
  156. package/src/calls/types.ts +3 -1
  157. package/src/calls/voice-ingress-preflight.ts +1 -1
  158. package/src/cli/commands/browser-relay.ts +18 -12
  159. package/src/cli/commands/completions.ts +0 -3
  160. package/src/cli/commands/credentials.ts +101 -15
  161. package/src/cli/commands/doctor.ts +4 -3
  162. package/src/cli/commands/mcp.ts +46 -59
  163. package/src/cli/commands/memory.ts +16 -165
  164. package/src/cli/commands/oauth/apps.ts +284 -0
  165. package/src/cli/commands/oauth/connections.ts +633 -0
  166. package/src/cli/commands/oauth/index.ts +52 -0
  167. package/src/cli/commands/oauth/providers.ts +256 -0
  168. package/src/cli/commands/sessions.ts +5 -2
  169. package/src/cli/commands/skills.ts +177 -339
  170. package/src/cli/http-client.ts +0 -20
  171. package/src/cli/main-screen.tsx +2 -2
  172. package/src/cli/program.ts +6 -11
  173. package/src/cli/reference.ts +1 -3
  174. package/src/cli.ts +4 -10
  175. package/src/config/assistant-feature-flags.ts +0 -3
  176. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  177. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  178. package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
  179. package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
  180. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  181. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  182. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  183. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  184. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  185. package/src/config/bundled-tool-registry.ts +2 -5
  186. package/src/config/env-registry.ts +14 -83
  187. package/src/config/env.ts +11 -50
  188. package/src/config/feature-flag-registry.json +16 -16
  189. package/src/config/loader.ts +0 -6
  190. package/src/config/schema.ts +4 -13
  191. package/src/config/schemas/memory-lifecycle.ts +0 -9
  192. package/src/config/schemas/memory-processing.ts +0 -180
  193. package/src/config/schemas/memory-retrieval.ts +32 -104
  194. package/src/config/schemas/memory.ts +0 -10
  195. package/src/config/skills.ts +21 -2
  196. package/src/config/types.ts +0 -4
  197. package/src/context/image-dimensions.ts +229 -0
  198. package/src/context/token-estimator.ts +75 -12
  199. package/src/context/window-manager.ts +53 -11
  200. package/src/daemon/assistant-attachments.ts +1 -13
  201. package/src/daemon/config-watcher.ts +61 -3
  202. package/src/daemon/daemon-control.ts +1 -1
  203. package/src/daemon/date-context.ts +114 -31
  204. package/src/daemon/handlers/config-ingress.ts +8 -33
  205. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  206. package/src/daemon/handlers/config-telegram.ts +32 -16
  207. package/src/daemon/handlers/sessions.ts +27 -36
  208. package/src/daemon/handlers/shared.ts +0 -130
  209. package/src/daemon/handlers/skills.ts +20 -1
  210. package/src/daemon/history-repair.ts +72 -8
  211. package/src/daemon/host-cu-proxy.ts +430 -0
  212. package/src/daemon/lifecycle.ts +67 -71
  213. package/src/daemon/mcp-reload-service.ts +2 -2
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/computer-use.ts +1 -129
  216. package/src/daemon/message-types/host-cu.ts +19 -0
  217. package/src/daemon/message-types/memory.ts +4 -16
  218. package/src/daemon/message-types/messages.ts +4 -0
  219. package/src/daemon/message-types/sessions.ts +4 -0
  220. package/src/daemon/server.ts +25 -21
  221. package/src/daemon/session-agent-loop-handlers.ts +40 -0
  222. package/src/daemon/session-agent-loop.ts +334 -48
  223. package/src/daemon/session-attachments.ts +1 -2
  224. package/src/daemon/session-error.ts +89 -6
  225. package/src/daemon/session-history.ts +17 -7
  226. package/src/daemon/session-media-retry.ts +6 -2
  227. package/src/daemon/session-memory.ts +69 -149
  228. package/src/daemon/session-process.ts +10 -1
  229. package/src/daemon/session-runtime-assembly.ts +49 -19
  230. package/src/daemon/session-slash.ts +1 -1
  231. package/src/daemon/session-surfaces.ts +43 -28
  232. package/src/daemon/session-tool-setup.ts +9 -10
  233. package/src/daemon/session.ts +150 -17
  234. package/src/daemon/tool-side-effects.ts +2 -8
  235. package/src/daemon/watch-handler.ts +2 -2
  236. package/src/events/tool-metrics-listener.ts +2 -2
  237. package/src/hooks/manager.ts +1 -4
  238. package/src/inbound/public-ingress-urls.ts +7 -7
  239. package/src/instrument.ts +61 -1
  240. package/src/logfire.ts +16 -5
  241. package/src/memory/admin.ts +2 -191
  242. package/src/memory/canonical-guardian-store.ts +38 -2
  243. package/src/memory/conversation-crud.ts +0 -33
  244. package/src/memory/conversation-key-store.ts +21 -0
  245. package/src/memory/conversation-queries.ts +22 -3
  246. package/src/memory/db-init.ts +32 -0
  247. package/src/memory/embedding-backend.ts +84 -8
  248. package/src/memory/embedding-types.ts +9 -1
  249. package/src/memory/indexer.ts +7 -46
  250. package/src/memory/items-extractor.ts +274 -76
  251. package/src/memory/job-handlers/backfill.ts +2 -127
  252. package/src/memory/job-handlers/cleanup.ts +2 -16
  253. package/src/memory/job-handlers/extraction.ts +2 -138
  254. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  255. package/src/memory/job-handlers/summarization.ts +3 -148
  256. package/src/memory/job-utils.ts +21 -59
  257. package/src/memory/jobs-store.ts +1 -159
  258. package/src/memory/jobs-worker.ts +9 -52
  259. package/src/memory/migrations/104-core-indexes.ts +3 -3
  260. package/src/memory/migrations/149-oauth-tables.ts +62 -0
  261. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  262. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  263. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  264. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  265. package/src/memory/migrations/154-drop-fts.ts +20 -0
  266. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  267. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  268. package/src/memory/migrations/index.ts +8 -0
  269. package/src/memory/qdrant-client.ts +148 -51
  270. package/src/memory/raw-query.ts +1 -1
  271. package/src/memory/retriever.test.ts +294 -273
  272. package/src/memory/retriever.ts +421 -645
  273. package/src/memory/schema/calls.ts +2 -0
  274. package/src/memory/schema/index.ts +1 -0
  275. package/src/memory/schema/memory-core.ts +3 -48
  276. package/src/memory/schema/oauth.ts +67 -0
  277. package/src/memory/search/formatting.ts +263 -176
  278. package/src/memory/search/lexical.ts +1 -254
  279. package/src/memory/search/ranking.ts +0 -455
  280. package/src/memory/search/semantic.ts +100 -14
  281. package/src/memory/search/staleness.ts +47 -0
  282. package/src/memory/search/tier-classifier.ts +21 -0
  283. package/src/memory/search/types.ts +15 -77
  284. package/src/memory/task-memory-cleanup.ts +4 -6
  285. package/src/messaging/provider.ts +4 -4
  286. package/src/messaging/providers/gmail/client.ts +82 -2
  287. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  288. package/src/messaging/providers/gmail/people-client.ts +10 -10
  289. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  290. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  291. package/src/messaging/registry.ts +2 -32
  292. package/src/notifications/copy-composer.ts +0 -5
  293. package/src/notifications/signal.ts +4 -5
  294. package/src/oauth/byo-connection.test.ts +133 -25
  295. package/src/oauth/byo-connection.ts +22 -6
  296. package/src/oauth/connect-orchestrator.ts +113 -57
  297. package/src/oauth/connect-types.ts +17 -23
  298. package/src/oauth/connection-resolver.ts +35 -11
  299. package/src/oauth/connection.ts +1 -1
  300. package/src/oauth/manual-token-connection.ts +104 -0
  301. package/src/oauth/oauth-store.ts +582 -0
  302. package/src/oauth/platform-connection.test.ts +29 -0
  303. package/src/oauth/platform-connection.ts +6 -5
  304. package/src/oauth/provider-behaviors.ts +124 -0
  305. package/src/oauth/scope-policy.ts +9 -2
  306. package/src/oauth/seed-providers.ts +167 -0
  307. package/src/oauth/token-persistence.ts +81 -77
  308. package/src/permissions/checker.ts +3 -3
  309. package/src/permissions/defaults.ts +1 -1
  310. package/src/permissions/prompter.ts +10 -1
  311. package/src/permissions/trust-store.ts +36 -1
  312. package/src/playbooks/playbook-compiler.ts +1 -1
  313. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  314. package/src/prompts/system-prompt.ts +46 -42
  315. package/src/providers/anthropic/client.ts +59 -20
  316. package/src/providers/retry.ts +1 -27
  317. package/src/providers/types.ts +7 -1
  318. package/src/runtime/AGENTS.md +9 -0
  319. package/src/runtime/auth/route-policy.ts +6 -6
  320. package/src/runtime/channel-reply-delivery.ts +0 -40
  321. package/src/runtime/gateway-client.ts +0 -7
  322. package/src/runtime/guardian-reply-router.ts +24 -22
  323. package/src/runtime/http-server.ts +10 -8
  324. package/src/runtime/http-types.ts +2 -2
  325. package/src/runtime/invite-redemption-service.ts +19 -1
  326. package/src/runtime/invite-service.ts +25 -0
  327. package/src/runtime/middleware/twilio-validation.ts +1 -11
  328. package/src/runtime/pending-interactions.ts +14 -12
  329. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  330. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  331. package/src/runtime/routes/conversation-routes.ts +81 -19
  332. package/src/runtime/routes/events-routes.ts +21 -11
  333. package/src/runtime/routes/host-cu-routes.ts +97 -0
  334. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  335. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  336. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  337. package/src/runtime/routes/log-export-routes.ts +126 -8
  338. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  339. package/src/runtime/routes/memory-item-routes.ts +503 -0
  340. package/src/runtime/routes/session-management-routes.ts +3 -3
  341. package/src/runtime/routes/settings-routes.ts +55 -48
  342. package/src/runtime/routes/surface-action-routes.ts +1 -1
  343. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  344. package/src/runtime/routes/watch-routes.ts +128 -0
  345. package/src/runtime/routes/workspace-routes.ts +2 -1
  346. package/src/schedule/integration-status.ts +10 -9
  347. package/src/security/credential-key.ts +0 -156
  348. package/src/security/keychain-broker-client.ts +22 -10
  349. package/src/security/oauth2.ts +1 -1
  350. package/src/security/secure-keys.ts +25 -3
  351. package/src/security/token-manager.ts +137 -64
  352. package/src/skills/catalog-install.ts +414 -0
  353. package/src/skills/include-graph.ts +32 -0
  354. package/src/skills/skillssh-registry.ts +503 -0
  355. package/src/telegram/bot-username.ts +2 -3
  356. package/src/tools/assets/search.ts +5 -1
  357. package/src/tools/browser/network-recorder.ts +1 -1
  358. package/src/tools/browser/network-recording-types.ts +1 -1
  359. package/src/tools/computer-use/definitions.ts +36 -11
  360. package/src/tools/computer-use/registry.ts +5 -6
  361. package/src/tools/credentials/broker.ts +1 -2
  362. package/src/tools/credentials/metadata-store.ts +17 -121
  363. package/src/tools/credentials/vault.ts +92 -167
  364. package/src/tools/memory/definitions.ts +4 -13
  365. package/src/tools/memory/handlers.test.ts +83 -103
  366. package/src/tools/memory/handlers.ts +50 -85
  367. package/src/tools/registry.ts +2 -7
  368. package/src/tools/schedule/create.ts +8 -1
  369. package/src/tools/schedule/update.ts +8 -1
  370. package/src/tools/skills/load.ts +85 -3
  371. package/src/tools/watch/watch-state.ts +0 -12
  372. package/src/util/logger.ts +7 -41
  373. package/src/util/platform.ts +9 -28
  374. package/src/watcher/providers/google-calendar.ts +2 -1
  375. package/src/__tests__/clarification-resolver.test.ts +0 -193
  376. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  377. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  378. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  379. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  380. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  381. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  382. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  383. package/src/__tests__/conflict-policy.test.ts +0 -269
  384. package/src/__tests__/conflict-store.test.ts +0 -372
  385. package/src/__tests__/contradiction-checker.test.ts +0 -361
  386. package/src/__tests__/entity-extractor.test.ts +0 -211
  387. package/src/__tests__/entity-search.test.ts +0 -1117
  388. package/src/__tests__/profile-compiler.test.ts +0 -392
  389. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  390. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  391. package/src/__tests__/session-profile-injection.test.ts +0 -557
  392. package/src/cli/commands/dev.ts +0 -129
  393. package/src/cli/commands/map.ts +0 -391
  394. package/src/cli/commands/oauth.ts +0 -77
  395. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  396. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  397. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  398. package/src/daemon/computer-use-session.ts +0 -1026
  399. package/src/daemon/ride-shotgun-handler.ts +0 -569
  400. package/src/daemon/session-conflict-gate.ts +0 -167
  401. package/src/daemon/session-dynamic-profile.ts +0 -77
  402. package/src/memory/clarification-resolver.ts +0 -417
  403. package/src/memory/conflict-intent.ts +0 -205
  404. package/src/memory/conflict-policy.ts +0 -127
  405. package/src/memory/conflict-store.ts +0 -410
  406. package/src/memory/contradiction-checker.ts +0 -508
  407. package/src/memory/entity-extractor.ts +0 -535
  408. package/src/memory/format-recall.ts +0 -47
  409. package/src/memory/fts-reconciler.ts +0 -165
  410. package/src/memory/job-handlers/conflict.ts +0 -200
  411. package/src/memory/profile-compiler.ts +0 -195
  412. package/src/memory/recall-cache.ts +0 -117
  413. package/src/memory/search/entity.ts +0 -535
  414. package/src/memory/search/query-expansion.test.ts +0 -70
  415. package/src/memory/search/query-expansion.ts +0 -118
  416. package/src/oauth/provider-base-urls.ts +0 -21
  417. package/src/oauth/provider-profiles.ts +0 -192
  418. package/src/prompts/computer-use-prompt.ts +0 -98
  419. package/src/runtime/routes/computer-use-routes.ts +0 -641
  420. package/src/runtime/routes/mcp-routes.ts +0 -20
  421. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  422. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  423. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  buildTemporalContext,
5
- extractUserTimeZoneFromDynamicProfile,
5
+ extractUserTimeZoneFromRecall,
6
6
  } from "../daemon/date-context.js";
7
7
 
8
8
  // Fixed timestamps for deterministic assertions (all UTC midday to avoid DST edge cases).
@@ -152,82 +152,6 @@ describe("buildTemporalContext", () => {
152
152
  });
153
153
  });
154
154
 
155
- describe("extractUserTimeZoneFromDynamicProfile", () => {
156
- test("extracts canonical timezone from explicit timezone profile line", () => {
157
- const profile = [
158
- "<dynamic-user-profile>",
159
- "- timezone: Timezone is America/New_York.",
160
- "</dynamic-user-profile>",
161
- ].join("\n");
162
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe(
163
- "America/New_York",
164
- );
165
- });
166
-
167
- test("extracts timezone token from generic profile text when explicit line is absent", () => {
168
- const profile = [
169
- "<dynamic-user-profile>",
170
- "- location: Travels often between Europe and Asia (currently Europe/Paris).",
171
- "</dynamic-user-profile>",
172
- ].join("\n");
173
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe("Europe/Paris");
174
- });
175
-
176
- test("returns null when no valid timezone is present", () => {
177
- const profile = [
178
- "<dynamic-user-profile>",
179
- "- timezone: Pacific time",
180
- "</dynamic-user-profile>",
181
- ].join("\n");
182
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBeNull();
183
- });
184
-
185
- test("extracts UTC/GMT offset tokens from explicit timezone profile line", () => {
186
- const profile = [
187
- "<dynamic-user-profile>",
188
- "- timezone: UTC+2",
189
- "</dynamic-user-profile>",
190
- ].join("\n");
191
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe("Etc/GMT-2");
192
- });
193
-
194
- test("extracts GMT negative offset tokens from generic profile text", () => {
195
- const profile = [
196
- "<dynamic-user-profile>",
197
- "- preferences: schedule notifications in GMT-5 whenever possible.",
198
- "</dynamic-user-profile>",
199
- ].join("\n");
200
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe("Etc/GMT+5");
201
- });
202
-
203
- test("extracts fractional UTC offset tokens from explicit timezone profile line", () => {
204
- const profile = [
205
- "<dynamic-user-profile>",
206
- "- timezone: UTC+5:30",
207
- "</dynamic-user-profile>",
208
- ].join("\n");
209
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe("+05:30");
210
- });
211
-
212
- test("extracts fractional GMT offset tokens from generic profile text", () => {
213
- const profile = [
214
- "<dynamic-user-profile>",
215
- "- preferences: default reminders to GMT+5:45.",
216
- "</dynamic-user-profile>",
217
- ].join("\n");
218
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe("+05:45");
219
- });
220
-
221
- test("prefers IANA timezone tokens over UTC/GMT offsets in the same profile line", () => {
222
- const profile = [
223
- "<dynamic-user-profile>",
224
- "- timezone: UTC+1 (Europe/Paris)",
225
- "</dynamic-user-profile>",
226
- ].join("\n");
227
- expect(extractUserTimeZoneFromDynamicProfile(profile)).toBe("Europe/Paris");
228
- });
229
- });
230
-
231
155
  // ---------------------------------------------------------------------------
232
156
  // Weekday baseline — today is Wednesday
233
157
  // ---------------------------------------------------------------------------
@@ -615,3 +539,95 @@ describe("trip-planning: timezone-shifted weekend anchors", () => {
615
539
  expect(result).toContain("Next weekend: 2026-02-28 – 2026-03-01");
616
540
  });
617
541
  });
542
+
543
+ // ---------------------------------------------------------------------------
544
+ // extractUserTimeZoneFromRecall
545
+ // ---------------------------------------------------------------------------
546
+
547
+ describe("extractUserTimeZoneFromRecall", () => {
548
+ test("returns null for empty input", () => {
549
+ expect(extractUserTimeZoneFromRecall("")).toBeNull();
550
+ expect(extractUserTimeZoneFromRecall(" ")).toBeNull();
551
+ });
552
+
553
+ test("extracts IANA timezone from user_identity section", () => {
554
+ const text = `<memory_context>
555
+
556
+ <user_identity>
557
+ User's timezone is America/New_York
558
+ User works as a software engineer
559
+ </user_identity>
560
+
561
+ </memory_context>`;
562
+ expect(extractUserTimeZoneFromRecall(text)).toBe("America/New_York");
563
+ });
564
+
565
+ test("extracts timezone from 'timezone: ...' line in identity", () => {
566
+ const text = `<memory_context>
567
+
568
+ <user_identity>
569
+ - name: Alice
570
+ - timezone: Europe/London
571
+ - role: designer
572
+ </user_identity>
573
+
574
+ </memory_context>`;
575
+ expect(extractUserTimeZoneFromRecall(text)).toBe("Europe/London");
576
+ });
577
+
578
+ test("extracts UTC offset timezone", () => {
579
+ const text = `<memory_context>
580
+
581
+ <user_identity>
582
+ User's time zone is UTC+5:30
583
+ </user_identity>
584
+
585
+ </memory_context>`;
586
+ const result = extractUserTimeZoneFromRecall(text);
587
+ expect(result).not.toBeNull();
588
+ // UTC+5:30 should canonicalize to +05:30
589
+ expect(result).toBe("+05:30");
590
+ });
591
+
592
+ test("falls back to scanning full text when no identity section", () => {
593
+ const text = `<memory_context>
594
+
595
+ <relevant_context>
596
+ <episode source="Mar 5">
597
+ User mentioned their timezone is Asia/Tokyo
598
+ </episode>
599
+ </relevant_context>
600
+
601
+ </memory_context>`;
602
+ expect(extractUserTimeZoneFromRecall(text)).toBe("Asia/Tokyo");
603
+ });
604
+
605
+ test("returns null when no timezone info present", () => {
606
+ const text = `<memory_context>
607
+
608
+ <user_identity>
609
+ User's name is Bob
610
+ User works at Acme Corp
611
+ </user_identity>
612
+
613
+ </memory_context>`;
614
+ expect(extractUserTimeZoneFromRecall(text)).toBeNull();
615
+ });
616
+
617
+ test("prefers identity section over other sections", () => {
618
+ const text = `<memory_context>
619
+
620
+ <user_identity>
621
+ User's timezone is America/Chicago
622
+ </user_identity>
623
+
624
+ <relevant_context>
625
+ <episode source="Mar 5">
626
+ Discussed timezone America/Los_Angeles for the deployment
627
+ </episode>
628
+ </relevant_context>
629
+
630
+ </memory_context>`;
631
+ expect(extractUserTimeZoneFromRecall(text)).toBe("America/Chicago");
632
+ });
633
+ });
@@ -399,4 +399,68 @@ describe("Verification control messages are deterministic (guard)", () => {
399
399
  globalThis.fetch = originalFetch;
400
400
  }
401
401
  });
402
+
403
+ test("handleChannelInbound does not allow blocked members to bootstrap with /start gv_<token>", async () => {
404
+ const { createHash, randomBytes } = await import("node:crypto");
405
+ const { handleChannelInbound } =
406
+ await import("../runtime/routes/inbound-message-handler.js");
407
+ const { createOutboundSession } =
408
+ await import("../runtime/channel-verification-service.js");
409
+ const { upsertContactChannel } =
410
+ await import("../contacts/contacts-write.js");
411
+
412
+ const blockedIdentity = {
413
+ sourceChannel: "telegram",
414
+ externalUserId: "user-blocked-bootstrap",
415
+ externalChatId: "chat-blocked-bootstrap",
416
+ displayName: "Blocked Bootstrap User",
417
+ status: "blocked",
418
+ policy: "deny",
419
+ } as const;
420
+ upsertContactChannel(blockedIdentity);
421
+
422
+ const bootstrapToken = randomBytes(16).toString("hex");
423
+ const bootstrapTokenHash = createHash("sha256")
424
+ .update(bootstrapToken)
425
+ .digest("hex");
426
+
427
+ createOutboundSession({
428
+ channel: "telegram",
429
+ identityBindingStatus: "pending_bootstrap",
430
+ destinationAddress: blockedIdentity.externalUserId,
431
+ bootstrapTokenHash,
432
+ });
433
+
434
+ let processMessageCalled = false;
435
+ const processMessage = async () => {
436
+ processMessageCalled = true;
437
+ return { messageId: "msg-1" };
438
+ };
439
+
440
+ const req = new Request("http://localhost/channels/inbound", {
441
+ method: "POST",
442
+ headers: { "Content-Type": "application/json" },
443
+ body: JSON.stringify({
444
+ sourceChannel: "telegram",
445
+ interface: "telegram",
446
+ conversationExternalId: blockedIdentity.externalChatId,
447
+ externalMessageId: `msg-blocked-bootstrap-${Date.now()}`,
448
+ content: `/start gv_${bootstrapToken}`,
449
+ actorExternalId: blockedIdentity.externalUserId,
450
+ actorDisplayName: blockedIdentity.displayName,
451
+ sourceMetadata: {
452
+ commandIntent: { type: "start", payload: `gv_${bootstrapToken}` },
453
+ },
454
+ }),
455
+ });
456
+
457
+ const response = await handleChannelInbound(req, processMessage);
458
+ const body = (await response.json()) as Record<string, unknown>;
459
+
460
+ expect(body.accepted).toBe(true);
461
+ expect(body.denied).toBe(true);
462
+ expect(body.reason).toBe("member_blocked");
463
+ expect(body.verificationOutcome).toBeUndefined();
464
+ expect(processMessageCalled).toBe(false);
465
+ });
402
466
  });
@@ -46,7 +46,6 @@ mock.module("../util/logger.js", () => ({
46
46
  ...realLogger,
47
47
  getLogger: () => noopLogger,
48
48
  getCliLogger: () => noopLogger,
49
- isDebug: () => false,
50
49
  truncateForLog: (v: string) => v,
51
50
  initLogger: () => {},
52
51
  pruneOldLogFiles: () => 0,
@@ -328,11 +328,11 @@ describe("ephemeral-permissions", () => {
328
328
  testConfig.permissions.mode = "workspace";
329
329
  });
330
330
 
331
- test("workspace mode auto-allows workspace-scoped file_write (medium risk)", async () => {
331
+ test("workspace mode prompts for workspace-scoped file_write (medium risk)", async () => {
332
332
  const filePath = join(testDir, "workspace-test-file.txt");
333
333
  const result = await check("file_write", { path: filePath }, testDir);
334
- expect(result.decision).toBe("allow");
335
- expect(result.reason).toContain("Workspace mode");
334
+ expect(result.decision).toBe("prompt");
335
+ expect(result.reason).toContain("medium risk");
336
336
  });
337
337
 
338
338
  test("workspace mode still prompts for file_write outside workspace", async () => {
@@ -37,7 +37,6 @@ const ALLOWLIST = new Set([
37
37
  // --- Documentation and comments that mention the port for explanatory purposes ---
38
38
  "AGENTS.md", // documents the gateway-only rule itself
39
39
  "ARCHITECTURE.md", // architecture overview with port references
40
- "assistant/docs/runbook-trusted-contacts.md", // operator runbook targeting runtime-only /v1/contacts endpoints
41
40
  "assistant/src/runtime/middleware/twilio-validation.ts", // comment explaining proxy URL rewriting
42
41
  ]);
43
42
 
@@ -35,7 +35,6 @@ mock.module("../util/logger.js", () => ({
35
35
  new Proxy({} as Record<string, unknown>, {
36
36
  get: () => () => {},
37
37
  }),
38
- isDebug: () => false,
39
38
  truncateForLog: (value: string) => value,
40
39
  }));
41
40
 
@@ -21,7 +21,6 @@ mock.module("../util/logger.js", () => ({
21
21
  new Proxy({} as Record<string, unknown>, {
22
22
  get: () => () => {},
23
23
  }),
24
- isDebug: () => false,
25
24
  truncateForLog: (value: string) => value,
26
25
  }));
27
26
 
@@ -42,7 +42,6 @@ mock.module("../util/logger.js", () => ({
42
42
  new Proxy({} as Record<string, unknown>, {
43
43
  get: () => () => {},
44
44
  }),
45
- isDebug: () => false,
46
45
  truncateForLog: (value: string) => value,
47
46
  }));
48
47
 
@@ -1428,3 +1427,96 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1428
1427
  expect(resolved!.status).toBe("approved");
1429
1428
  });
1430
1429
  });
1430
+
1431
+ // ===========================================================================
1432
+ // SECTION 11: Expired requests are excluded from routing
1433
+ // ===========================================================================
1434
+
1435
+ describe("routing invariant: expired requests are excluded from pending discovery", () => {
1436
+ beforeEach(() => resetTables());
1437
+
1438
+ test("expired request with hinted IDs is excluded from disambiguation", async () => {
1439
+ const expired = createCanonicalGuardianRequest({
1440
+ kind: "tool_approval",
1441
+ sourceType: "channel",
1442
+ conversationId: "conv-1",
1443
+ guardianExternalUserId: "guardian-1",
1444
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1445
+ requestCode: "EXP001",
1446
+ toolName: "shell",
1447
+ expiresAt: new Date(Date.now() - 10_000).toISOString(),
1448
+ });
1449
+
1450
+ const active = createCanonicalGuardianRequest({
1451
+ kind: "tool_approval",
1452
+ sourceType: "channel",
1453
+ conversationId: "conv-1",
1454
+ guardianExternalUserId: "guardian-1",
1455
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1456
+ requestCode: "ACT001",
1457
+ toolName: "file_write",
1458
+ expiresAt: new Date(Date.now() + 60_000).toISOString(),
1459
+ });
1460
+ registerPendingToolApprovalInteraction(active.id, "conv-1", "file_write");
1461
+
1462
+ // Both IDs are hinted but only the active one should be considered
1463
+ const result = await routeGuardianReply(
1464
+ replyCtx({
1465
+ messageText: "approve",
1466
+ conversationId: "conv-guardian-thread",
1467
+ pendingRequestIds: [expired.id, active.id],
1468
+ approvalConversationGenerator: undefined,
1469
+ }),
1470
+ );
1471
+
1472
+ // Single active request — should apply directly, no disambiguation
1473
+ expect(result.consumed).toBe(true);
1474
+ expect(result.type).toBe("canonical_decision_applied");
1475
+ expect(result.decisionApplied).toBe(true);
1476
+
1477
+ const resolvedActive = getCanonicalGuardianRequest(active.id);
1478
+ expect(resolvedActive!.status).toBe("approved");
1479
+
1480
+ // Expired request untouched
1481
+ const resolvedExpired = getCanonicalGuardianRequest(expired.id);
1482
+ expect(resolvedExpired!.status).toBe("pending");
1483
+ });
1484
+
1485
+ test("all expired hinted requests means no pending found — not consumed", async () => {
1486
+ const expired1 = createCanonicalGuardianRequest({
1487
+ kind: "tool_approval",
1488
+ sourceType: "channel",
1489
+ conversationId: "conv-1",
1490
+ guardianExternalUserId: "guardian-1",
1491
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1492
+ requestCode: "EXP002",
1493
+ toolName: "shell",
1494
+ expiresAt: new Date(Date.now() - 10_000).toISOString(),
1495
+ });
1496
+
1497
+ const expired2 = createCanonicalGuardianRequest({
1498
+ kind: "tool_approval",
1499
+ sourceType: "channel",
1500
+ conversationId: "conv-1",
1501
+ guardianExternalUserId: "guardian-1",
1502
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1503
+ requestCode: "EXP003",
1504
+ toolName: "file_write",
1505
+ expiresAt: new Date(Date.now() - 5_000).toISOString(),
1506
+ });
1507
+
1508
+ const result = await routeGuardianReply(
1509
+ replyCtx({
1510
+ messageText: "approve",
1511
+ conversationId: "conv-guardian-thread",
1512
+ pendingRequestIds: [expired1.id, expired2.id],
1513
+ approvalConversationGenerator: undefined,
1514
+ }),
1515
+ );
1516
+
1517
+ // No active pending requests — falls through
1518
+ expect(result.consumed).toBe(false);
1519
+ expect(result.type).toBe("not_consumed");
1520
+ expect(result.decisionApplied).toBe(false);
1521
+ });
1522
+ });
@@ -57,7 +57,6 @@ mock.module("../security/secure-keys.js", () => ({
57
57
 
58
58
  mock.module("../config/env.js", () => ({
59
59
  isHttpAuthDisabled: () => true,
60
- getTwilioUserPhoneNumber: () => null,
61
60
  }));
62
61
 
63
62
  mock.module("../inbound/public-ingress-urls.js", () => ({
@@ -147,7 +147,6 @@ import { handleConfirmationResponse } from "../daemon/handlers/sessions.js";
147
147
 
148
148
  interface TestSession {
149
149
  messages: Array<{ role: string; content: unknown[] }>;
150
- hasEscalationHandler: () => boolean;
151
150
  setChannelCapabilities: (caps: unknown) => void;
152
151
  isProcessing: () => boolean;
153
152
  hasPendingConfirmation: (requestId: string) => boolean;
@@ -181,8 +180,6 @@ function createContext(session: TestSession): {
181
180
  const sent: ServerMessage[] = [];
182
181
  const ctx: HandlerContext = {
183
182
  sessions: new Map(),
184
- cuSessions: new Map(),
185
- cuObservationParseSequence: new Map(),
186
183
  sharedRequestTimestamps: [],
187
184
  debounceTimers: new DebouncerMap({ defaultDelayMs: 100 }),
188
185
  suppressConfigReload: false,
@@ -202,7 +199,6 @@ function createContext(session: TestSession): {
202
199
  function makeSession(overrides: Partial<TestSession> = {}): TestSession {
203
200
  return {
204
201
  messages: [],
205
- hasEscalationHandler: () => true,
206
202
  setChannelCapabilities: () => {},
207
203
  isProcessing: () => false,
208
204
  hasPendingConfirmation: () => true,
@@ -277,39 +273,4 @@ describe("handleConfirmationResponse canonical status sync", () => {
277
273
  );
278
274
  expect(resolveMock).toHaveBeenCalledWith("req-confirm-allow");
279
275
  });
280
-
281
- test("syncs canonical status to denied for deny decisions in CU sessions", () => {
282
- const cuSession = {
283
- hasPendingConfirmation: (requestId: string) =>
284
- requestId === "req-confirm-deny",
285
- handleConfirmationResponse: mock(() => {}),
286
- };
287
- const { ctx } = createContext(
288
- makeSession({
289
- hasPendingConfirmation: () => false,
290
- }),
291
- );
292
- ctx.cuSessions.set("cu-1", cuSession as any);
293
-
294
- const msg: ConfirmationResponse = {
295
- type: "confirmation_response",
296
- requestId: "req-confirm-deny",
297
- decision: "always_deny",
298
- };
299
-
300
- handleConfirmationResponse(msg, ctx);
301
-
302
- expect(
303
- (cuSession.handleConfirmationResponse as any).mock.calls.length,
304
- ).toBe(1);
305
- expect((cuSession.handleConfirmationResponse as any).mock.calls[0]).toEqual(
306
- ["req-confirm-deny", "always_deny", undefined, undefined],
307
- );
308
- expect(resolveCanonicalGuardianRequestMock).toHaveBeenCalledWith(
309
- "req-confirm-deny",
310
- "pending",
311
- { status: "denied" },
312
- );
313
- expect(resolveMock).toHaveBeenCalledWith("req-confirm-deny");
314
- });
315
276
  });
@@ -67,7 +67,6 @@ mock.module("../util/logger.js", () => ({
67
67
  warn: () => {},
68
68
  error: () => {},
69
69
  }),
70
- isDebug: () => false,
71
70
  }));
72
71
 
73
72
  // Mock conversation title service