@vellumai/assistant 0.8.3 → 0.8.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 (665) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docker-entrypoint.sh +0 -1
  3. package/docs/browser-use-architecture-phase2.md +1 -1
  4. package/knip.json +2 -1
  5. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  6. package/openapi.yaml +1492 -100
  7. package/package.json +1 -1
  8. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  9. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +302 -33
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  14. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  15. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  16. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -3
  17. package/src/__tests__/btw-routes.test.ts +2 -3
  18. package/src/__tests__/call-controller.test.ts +0 -1
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  20. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  21. package/src/__tests__/channel-guardian.test.ts +3 -3
  22. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  23. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  24. package/src/__tests__/checker.test.ts +6 -15
  25. package/src/__tests__/compaction-events.test.ts +2 -1
  26. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  27. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  29. package/src/__tests__/computer-use-tools.test.ts +2 -4
  30. package/src/__tests__/config-watcher.test.ts +1 -1
  31. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  32. package/src/__tests__/context-token-estimator.test.ts +91 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
  37. package/src/__tests__/conversation-agent-loop.test.ts +188 -129
  38. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  39. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  40. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  41. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  42. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  43. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  44. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  45. package/src/__tests__/conversation-error.test.ts +31 -0
  46. package/src/__tests__/conversation-fork-crud.test.ts +324 -0
  47. package/src/__tests__/conversation-lifecycle.test.ts +53 -12
  48. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  49. package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
  50. package/src/__tests__/conversation-pairing.test.ts +2 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -1
  53. package/src/__tests__/conversation-queue.test.ts +1 -1
  54. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  57. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  58. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  59. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  60. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  61. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  62. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  63. package/src/__tests__/conversation-store.test.ts +1 -1
  64. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  65. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -1
  67. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  69. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  70. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  71. package/src/__tests__/credential-security-invariants.test.ts +7 -0
  72. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  73. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  74. package/src/__tests__/dm-backfill.test.ts +64 -0
  75. package/src/__tests__/dm-persistence.test.ts +33 -0
  76. package/src/__tests__/document-find-replace.test.ts +501 -0
  77. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  78. package/src/__tests__/email-html-renderer.test.ts +12 -0
  79. package/src/__tests__/first-greeting.test.ts +23 -2
  80. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  81. package/src/__tests__/gemini-provider.test.ts +78 -0
  82. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  83. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  84. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  85. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  87. package/src/__tests__/heartbeat-service.test.ts +4 -0
  88. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  89. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  90. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  91. package/src/__tests__/host-file-proxy.test.ts +8 -1
  92. package/src/__tests__/host-shell-tool.test.ts +1 -1
  93. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  94. package/src/__tests__/identity-routes.test.ts +57 -0
  95. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  96. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  97. package/src/__tests__/injector-chain.test.ts +2 -0
  98. package/src/__tests__/injector-document-comments.test.ts +378 -0
  99. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  100. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  102. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  103. package/src/__tests__/list-messages-tool-merge.test.ts +77 -17
  104. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  105. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  106. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  107. package/src/__tests__/llm-resolver.test.ts +161 -9
  108. package/src/__tests__/llm-usage-store.test.ts +66 -0
  109. package/src/__tests__/log-export-routes.test.ts +99 -2
  110. package/src/__tests__/logger.test.ts +89 -0
  111. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  112. package/src/__tests__/media-generate-image.test.ts +31 -0
  113. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  114. package/src/__tests__/message-queue-steer.test.ts +114 -0
  115. package/src/__tests__/model-intents.test.ts +2 -4
  116. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  117. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  118. package/src/__tests__/openai-provider.test.ts +151 -0
  119. package/src/__tests__/openai-responses-provider.test.ts +118 -16
  120. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  121. package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
  122. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  123. package/src/__tests__/platform.test.ts +2 -5
  124. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  125. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  126. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  127. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  128. package/src/__tests__/plugin-types.test.ts +3 -2
  129. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  130. package/src/__tests__/pricing.test.ts +12 -0
  131. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  132. package/src/__tests__/process-message-display-content.test.ts +21 -16
  133. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  134. package/src/__tests__/registry.test.ts +2 -8
  135. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  136. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  137. package/src/__tests__/server-history-render.test.ts +83 -4
  138. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  139. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  140. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  141. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  142. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  143. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  144. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  145. package/src/__tests__/suggestion-routes.test.ts +1 -0
  146. package/src/__tests__/sync-message-contract.test.ts +59 -0
  147. package/src/__tests__/system-prompt.test.ts +161 -124
  148. package/src/__tests__/terminal-tools.test.ts +12 -2
  149. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  150. package/src/__tests__/thread-backfill.test.ts +370 -22
  151. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  152. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  153. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  155. package/src/__tests__/tool-executor.test.ts +89 -53
  156. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  157. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  158. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  159. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  160. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  161. package/src/__tests__/twilio-routes.test.ts +1 -1
  162. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  163. package/src/__tests__/usage-routes.test.ts +3 -0
  164. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  165. package/src/__tests__/web-fetch.test.ts +2 -2
  166. package/src/__tests__/workspace-git-service.test.ts +94 -10
  167. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  168. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  169. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  170. package/src/acp/prepare-agent-env.ts +78 -0
  171. package/src/acp/session-manager.ts +1 -1
  172. package/src/agent/attachments.ts +1 -0
  173. package/src/agent/loop.ts +65 -20
  174. package/src/api/README.md +5 -0
  175. package/src/api/index.ts +4 -0
  176. package/src/api/package.json +10 -0
  177. package/src/background-wake/background-wake-routes.test.ts +233 -0
  178. package/src/background-wake/next-wake.test.ts +289 -0
  179. package/src/background-wake/next-wake.ts +172 -0
  180. package/src/background-wake/runtime-registry.ts +24 -0
  181. package/src/browser/operations.ts +15 -0
  182. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  183. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  184. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  185. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  186. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  187. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  188. package/src/cli/commands/__tests__/memory-v2.test.ts +10 -12
  189. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  190. package/src/cli/commands/browser.ts +247 -0
  191. package/src/cli/commands/conversations.ts +128 -1
  192. package/src/cli/commands/domain.ts +91 -41
  193. package/src/cli/commands/inference-providers.ts +147 -1
  194. package/src/cli/commands/inference.ts +93 -40
  195. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  196. package/src/cli/commands/memory-v2.ts +483 -0
  197. package/src/cli/commands/memory-v3-render.ts +344 -0
  198. package/src/cli/commands/memory-v3.ts +316 -0
  199. package/src/cli/commands/notifications.ts +24 -2
  200. package/src/cli/program.ts +2 -0
  201. package/src/cli/utils/conversation-id.ts +17 -5
  202. package/src/config/assistant-feature-flags.ts +21 -9
  203. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  204. package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
  205. package/src/config/bundled-skills/document-editor/TOOLS.json +258 -0
  206. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  207. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  208. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  209. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  210. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  211. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  212. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  213. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  214. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  215. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  216. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  217. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  218. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  219. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  220. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  221. package/src/config/bundled-tool-registry.ts +24 -12
  222. package/src/config/call-site-defaults.ts +20 -0
  223. package/src/config/feature-flag-registry.json +115 -3
  224. package/src/config/llm-resolver.ts +16 -2
  225. package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
  226. package/src/config/schemas/call-site-catalog.ts +35 -0
  227. package/src/config/schemas/llm.ts +14 -0
  228. package/src/config/schemas/memory-v2.ts +294 -1
  229. package/src/config/schemas/memory.ts +2 -1
  230. package/src/context/compactor.ts +60 -1
  231. package/src/context/token-estimator.ts +47 -4
  232. package/src/context/window-manager.ts +25 -0
  233. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  234. package/src/conversations/message-consolidation.ts +404 -0
  235. package/src/credential-health/credential-health-service.ts +34 -19
  236. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  237. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  238. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  239. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  240. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  241. package/src/daemon/conversation-agent-loop-handlers.ts +155 -36
  242. package/src/daemon/conversation-agent-loop.ts +307 -88
  243. package/src/daemon/conversation-error.ts +31 -1
  244. package/src/daemon/conversation-lifecycle.ts +149 -118
  245. package/src/daemon/conversation-messaging.ts +3 -0
  246. package/src/daemon/conversation-process.ts +273 -0
  247. package/src/daemon/conversation-queue-manager.ts +14 -0
  248. package/src/daemon/conversation-runtime-assembly.ts +145 -84
  249. package/src/daemon/conversation-slash.ts +37 -5
  250. package/src/daemon/conversation-surfaces.ts +45 -2
  251. package/src/daemon/conversation-tool-setup.ts +70 -3
  252. package/src/daemon/conversation-usage.ts +2 -0
  253. package/src/daemon/conversation.ts +54 -32
  254. package/src/daemon/disk-pressure-guard.ts +14 -2
  255. package/src/daemon/first-greeting.ts +10 -0
  256. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  257. package/src/daemon/handlers/config-a2a.ts +160 -0
  258. package/src/daemon/handlers/config-model.test.ts +2 -0
  259. package/src/daemon/handlers/conversations.ts +90 -3
  260. package/src/daemon/handlers/shared.ts +92 -29
  261. package/src/daemon/host-bash-proxy.ts +1 -1
  262. package/src/daemon/host-browser-proxy.ts +5 -5
  263. package/src/daemon/host-cu-proxy.ts +5 -5
  264. package/src/daemon/host-file-proxy.ts +5 -5
  265. package/src/daemon/host-proxy-base.ts +4 -4
  266. package/src/daemon/host-transfer-proxy.ts +11 -11
  267. package/src/daemon/lifecycle.ts +40 -23
  268. package/src/daemon/meet-manifest-loader.ts +1 -7
  269. package/src/daemon/message-protocol.ts +4 -0
  270. package/src/daemon/message-types/conversations.ts +14 -9
  271. package/src/daemon/message-types/document-comments.ts +50 -0
  272. package/src/daemon/message-types/home.ts +1 -13
  273. package/src/daemon/message-types/messages.ts +66 -7
  274. package/src/daemon/message-types/surfaces.ts +3 -1
  275. package/src/daemon/message-types/sync.ts +14 -0
  276. package/src/daemon/message-types/web-activity.ts +57 -0
  277. package/src/daemon/plugin-source-watcher.ts +135 -3
  278. package/src/daemon/process-message.ts +69 -12
  279. package/src/daemon/shutdown-handlers.ts +24 -5
  280. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  281. package/src/daemon/tool-setup-types.ts +13 -0
  282. package/src/daemon/trust-context.ts +6 -0
  283. package/src/documents/document-comments-store.test.ts +338 -0
  284. package/src/documents/document-comments-store.ts +237 -0
  285. package/src/documents/document-store.ts +202 -0
  286. package/src/events/relationship-state-updated.ts +25 -0
  287. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
  288. package/src/heartbeat/heartbeat-service.ts +1 -0
  289. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  290. package/src/home/feed-types.ts +6 -1
  291. package/src/home/home-content-refresh.ts +52 -0
  292. package/src/home/home-greeting-cache.ts +69 -0
  293. package/src/home/home-greeting.ts +85 -0
  294. package/src/home/suggested-prompts.ts +168 -9
  295. package/src/ipc/gateway-flag-listener.ts +123 -0
  296. package/src/ipc/skill-routes/registries.ts +8 -12
  297. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  298. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  299. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  300. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  301. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  302. package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
  303. package/src/memory/auto-analysis-enqueue.ts +5 -1
  304. package/src/memory/conversation-crud.ts +191 -100
  305. package/src/memory/conversation-starters-cadence.ts +3 -1
  306. package/src/memory/conversation-title-service.ts +19 -3
  307. package/src/memory/db-async-query.ts +214 -0
  308. package/src/memory/db-init.ts +26 -0
  309. package/src/memory/db-maintenance.ts +30 -21
  310. package/src/memory/delivery-crud.ts +41 -0
  311. package/src/memory/delivery-status.ts +141 -15
  312. package/src/memory/external-conversation-store.ts +32 -1
  313. package/src/memory/graph/bootstrap.ts +8 -1
  314. package/src/memory/graph/capability-seed.ts +7 -3
  315. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  316. package/src/memory/graph/extraction.ts +1 -5
  317. package/src/memory/graph/graph-search.ts +7 -1
  318. package/src/memory/indexer.ts +28 -18
  319. package/src/memory/job-handlers/cleanup.ts +76 -18
  320. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  321. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  322. package/src/memory/jobs-store.ts +14 -0
  323. package/src/memory/jobs-worker.ts +68 -15
  324. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  325. package/src/memory/llm-request-log-source-local.ts +7 -0
  326. package/src/memory/llm-request-log-source.ts +9 -2
  327. package/src/memory/llm-request-log-store.ts +43 -1
  328. package/src/memory/llm-usage-store.ts +24 -0
  329. package/src/memory/memory-retrospective-constants.ts +28 -0
  330. package/src/memory/memory-retrospective-enqueue.ts +11 -3
  331. package/src/memory/memory-retrospective-job.ts +413 -18
  332. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  333. package/src/memory/memory-v2-activation-log-store.ts +41 -14
  334. package/src/memory/migrations/100-core-tables.ts +1 -0
  335. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  336. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  337. package/src/memory/migrations/253-document-comments.ts +47 -0
  338. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  339. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  340. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  341. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  342. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  343. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  344. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  345. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  346. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  347. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  348. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  349. package/src/memory/migrations/index.ts +34 -0
  350. package/src/memory/migrations/registry.ts +58 -0
  351. package/src/memory/onboarding-events-store.ts +7 -0
  352. package/src/memory/schema/calls.ts +1 -0
  353. package/src/memory/schema/conversations.ts +3 -0
  354. package/src/memory/schema/infrastructure.ts +22 -0
  355. package/src/memory/tool-usage-store.ts +36 -8
  356. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  357. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  358. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  359. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  362. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  363. package/src/memory/v2/__tests__/injection.test.ts +158 -112
  364. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  365. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  366. package/src/memory/v2/__tests__/router.test.ts +660 -4
  367. package/src/memory/v2/consolidation-job.ts +14 -0
  368. package/src/memory/v2/harness/compare.ts +57 -0
  369. package/src/memory/v2/harness/metrics.ts +124 -0
  370. package/src/memory/v2/harness/oracle.ts +145 -0
  371. package/src/memory/v2/harness/replay-input.ts +224 -0
  372. package/src/memory/v2/harness/retriever.ts +74 -0
  373. package/src/memory/v2/harness/router-retriever.ts +43 -0
  374. package/src/memory/v2/harness/runner.ts +106 -0
  375. package/src/memory/v2/harness/trace.ts +58 -0
  376. package/src/memory/v2/injection-events.ts +101 -0
  377. package/src/memory/v2/injection.ts +42 -25
  378. package/src/memory/v2/page-index.ts +209 -7
  379. package/src/memory/v2/page-store.ts +18 -0
  380. package/src/memory/v2/prompts/router.ts +26 -1
  381. package/src/memory/v2/qdrant.ts +14 -2
  382. package/src/memory/v2/router.ts +369 -62
  383. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  384. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  385. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  386. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  387. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  388. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  389. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  390. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  391. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  392. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  393. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  394. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  395. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  396. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  397. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  398. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  399. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  400. package/src/memory/v3/auto-edges.ts +223 -0
  401. package/src/memory/v3/coactivation-store.ts +124 -0
  402. package/src/memory/v3/consolidation-job.ts +323 -0
  403. package/src/memory/v3/edge-learning-job.ts +160 -0
  404. package/src/memory/v3/edges.ts +249 -0
  405. package/src/memory/v3/filter.ts +281 -0
  406. package/src/memory/v3/gate.ts +334 -0
  407. package/src/memory/v3/index-composition.ts +113 -0
  408. package/src/memory/v3/llm-capture.ts +46 -0
  409. package/src/memory/v3/loop.ts +382 -0
  410. package/src/memory/v3/maintenance.ts +144 -0
  411. package/src/memory/v3/prompt-context.ts +33 -0
  412. package/src/memory/v3/prompts/consolidation.ts +458 -0
  413. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  414. package/src/memory/v3/retriever.ts +33 -0
  415. package/src/memory/v3/scouts.ts +420 -0
  416. package/src/memory/v3/shadow-middleware.ts +305 -0
  417. package/src/memory/v3/traversal.ts +206 -0
  418. package/src/memory/v3/tree-index.ts +237 -0
  419. package/src/memory/v3/tree-store.ts +394 -0
  420. package/src/memory/v3/tree-walk.ts +351 -0
  421. package/src/memory/v3/types.ts +65 -0
  422. package/src/memory/v3/validate.ts +300 -0
  423. package/src/messaging/providers/index.ts +7 -1
  424. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  425. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  426. package/src/messaging/providers/slack/adapter.ts +178 -25
  427. package/src/messaging/providers/slack/api.test.ts +54 -0
  428. package/src/messaging/providers/slack/api.ts +119 -3
  429. package/src/messaging/providers/slack/client.ts +12 -0
  430. package/src/messaging/providers/slack/deep-link.ts +20 -1
  431. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  432. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  433. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  434. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  435. package/src/messaging/providers/slack/send.test.ts +77 -0
  436. package/src/messaging/providers/slack/send.ts +8 -2
  437. package/src/messaging/providers/slack/types.ts +14 -0
  438. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  439. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  440. package/src/notifications/adapters/macos.ts +18 -1
  441. package/src/notifications/adapters/platform.ts +1 -1
  442. package/src/notifications/conversation-seed-composer.ts +14 -2
  443. package/src/notifications/decision-engine.ts +1 -4
  444. package/src/notifications/deferred-emit.ts +135 -0
  445. package/src/notifications/emit-signal.ts +38 -50
  446. package/src/notifications/home-feed-side-effect.ts +60 -30
  447. package/src/oauth/connect-orchestrator.ts +3 -0
  448. package/src/oauth/credential-token-resolver.ts +2 -0
  449. package/src/oauth/manual-token-connection.ts +19 -0
  450. package/src/oauth/oauth-store.ts +12 -0
  451. package/src/oauth/seed-providers.ts +22 -0
  452. package/src/permissions/prompter.ts +8 -5
  453. package/src/permissions/question-prompter.ts +5 -2
  454. package/src/permissions/secret-prompter.ts +6 -3
  455. package/src/plugin-api/index.ts +4 -0
  456. package/src/plugin-api/types.ts +7 -33
  457. package/src/plugins/defaults/index.ts +6 -0
  458. package/src/plugins/defaults/injectors.ts +100 -20
  459. package/src/plugins/external-plugin-loader.ts +5 -68
  460. package/src/plugins/types.ts +11 -16
  461. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  462. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  463. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  464. package/src/prompts/normalize-onboarding.ts +40 -0
  465. package/src/prompts/persona-resolver.ts +36 -21
  466. package/src/prompts/sections.ts +69 -19
  467. package/src/prompts/system-prompt.ts +118 -216
  468. package/src/prompts/template-detection.ts +37 -0
  469. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  470. package/src/prompts/templates/BOOTSTRAP.md +10 -2
  471. package/src/prompts/templates/VOICE.md +3 -0
  472. package/src/prompts/templates/system-sections.ts +281 -9
  473. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  474. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  475. package/src/providers/anthropic/client.ts +159 -66
  476. package/src/providers/call-site-routing.ts +14 -2
  477. package/src/providers/connection-model-compat.ts +38 -0
  478. package/src/providers/connection-resolution.ts +16 -2
  479. package/src/providers/fireworks/client.ts +20 -2
  480. package/src/providers/gemini/client.ts +49 -6
  481. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  482. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  483. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  484. package/src/providers/inference/adapter-factory.ts +18 -1
  485. package/src/providers/inference/auth.ts +3 -3
  486. package/src/providers/inference/codex-token-refresh.ts +128 -0
  487. package/src/providers/inference/resolve-auth.ts +49 -6
  488. package/src/providers/minimax/client.ts +106 -0
  489. package/src/providers/model-catalog.ts +91 -1
  490. package/src/providers/model-intents.ts +1 -1
  491. package/src/providers/openai/chat-completions-provider.ts +63 -23
  492. package/src/providers/openai/codex-models.ts +18 -0
  493. package/src/providers/openai/responses-provider.ts +86 -23
  494. package/src/providers/openrouter/client.ts +5 -1
  495. package/src/providers/provider-send-message.ts +7 -1
  496. package/src/providers/retry.ts +34 -3
  497. package/src/providers/thinking-config.ts +26 -1
  498. package/src/providers/types.ts +25 -0
  499. package/src/providers/usage-tracking.ts +2 -0
  500. package/src/runtime/AGENTS.md +2 -2
  501. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  502. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  503. package/src/runtime/agent-wake.ts +152 -56
  504. package/src/runtime/assistant-event-hub.ts +76 -6
  505. package/src/runtime/auth/route-policy.ts +43 -3
  506. package/src/runtime/background-job-runner.ts +26 -0
  507. package/src/runtime/btw-sidechain.ts +0 -6
  508. package/src/runtime/channel-reply-delivery.ts +182 -47
  509. package/src/runtime/channel-retry-sweep.ts +141 -16
  510. package/src/runtime/http-types.ts +7 -6
  511. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  512. package/src/runtime/pending-interactions.ts +50 -8
  513. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  514. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
  515. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  516. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
  517. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  518. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  519. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  520. package/src/runtime/routes/acp-routes.test.ts +255 -6
  521. package/src/runtime/routes/acp-routes.ts +8 -1
  522. package/src/runtime/routes/approval-routes.ts +4 -1
  523. package/src/runtime/routes/avatar-routes.ts +10 -10
  524. package/src/runtime/routes/background-wake-routes.ts +188 -0
  525. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  526. package/src/runtime/routes/btw-routes.ts +0 -6
  527. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  528. package/src/runtime/routes/content-source-routes.ts +78 -0
  529. package/src/runtime/routes/conversation-cli-routes.ts +147 -2
  530. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  531. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  532. package/src/runtime/routes/conversation-query-routes.ts +196 -31
  533. package/src/runtime/routes/conversation-routes.ts +472 -425
  534. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  535. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  536. package/src/runtime/routes/document-comments-routes.ts +287 -0
  537. package/src/runtime/routes/documents-routes.ts +33 -0
  538. package/src/runtime/routes/domain-routes.ts +60 -10
  539. package/src/runtime/routes/email-routes.ts +5 -2
  540. package/src/runtime/routes/events-routes.ts +54 -10
  541. package/src/runtime/routes/group-routes.ts +24 -8
  542. package/src/runtime/routes/home-feed-routes.ts +6 -3
  543. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  544. package/src/runtime/routes/host-browser-routes.ts +17 -2
  545. package/src/runtime/routes/host-cu-routes.ts +2 -2
  546. package/src/runtime/routes/identity-routes.ts +21 -0
  547. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  548. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  549. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  550. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  551. package/src/runtime/routes/index.ts +20 -4
  552. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  553. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  554. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  555. package/src/runtime/routes/integrations/a2a.ts +60 -1
  556. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  557. package/src/runtime/routes/log-export-routes.ts +39 -0
  558. package/src/runtime/routes/memory-item-routes.ts +8 -3
  559. package/src/runtime/routes/memory-v2-routes.ts +427 -0
  560. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  561. package/src/runtime/routes/migration-routes.ts +21 -24
  562. package/src/runtime/routes/notification-routes.ts +19 -2
  563. package/src/runtime/routes/plugins-routes.ts +337 -0
  564. package/src/runtime/routes/question-routes.ts +4 -1
  565. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  566. package/src/runtime/routes/sanity-routes.ts +159 -0
  567. package/src/runtime/routes/secret-routes.ts +25 -5
  568. package/src/runtime/routes/settings-routes.ts +12 -11
  569. package/src/runtime/routes/slack-channel-routes.ts +188 -0
  570. package/src/runtime/routes/workspace-routes.ts +25 -10
  571. package/src/runtime/services/conversation-serializer.ts +30 -4
  572. package/src/runtime/sync/resource-sync-events.ts +106 -38
  573. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  574. package/src/runtime/sync/sync-publisher.ts +2 -1
  575. package/src/runtime/verification-outbound-actions.ts +73 -1
  576. package/src/schedule/integration-status.ts +3 -1
  577. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  578. package/src/security/oauth2-device-code.ts +307 -0
  579. package/src/security/oauth2.ts +26 -9
  580. package/src/security/secure-keys.ts +5 -0
  581. package/src/skills/catalog-install.ts +6 -2
  582. package/src/telemetry/types.ts +12 -0
  583. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  584. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  585. package/src/tools/acp/spawn.test.ts +119 -0
  586. package/src/tools/acp/spawn.ts +15 -2
  587. package/src/tools/apps/definitions.ts +2 -8
  588. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  589. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  590. package/src/tools/browser/__tests__/pinned-tabs.test.ts +150 -0
  591. package/src/tools/browser/browser-execution.ts +106 -0
  592. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  593. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  594. package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
  595. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
  596. package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
  597. package/src/tools/browser/cdp-client/factory.ts +171 -4
  598. package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
  599. package/src/tools/browser/cdp-client/types.ts +101 -0
  600. package/src/tools/browser/pinned-tabs.ts +146 -0
  601. package/src/tools/computer-use/definitions.ts +22 -78
  602. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  603. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  604. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  605. package/src/tools/credentials/vault.ts +3 -9
  606. package/src/tools/document/document-comment-tool.test.ts +379 -0
  607. package/src/tools/document/document-comment-tool.ts +156 -0
  608. package/src/tools/document/document-tool.ts +187 -2
  609. package/src/tools/execution-target.ts +21 -23
  610. package/src/tools/executor.ts +6 -1
  611. package/src/tools/filesystem/edit.ts +3 -9
  612. package/src/tools/filesystem/list.ts +3 -9
  613. package/src/tools/filesystem/read.ts +3 -9
  614. package/src/tools/filesystem/write.ts +3 -9
  615. package/src/tools/host-filesystem/edit.ts +3 -9
  616. package/src/tools/host-filesystem/read.ts +3 -9
  617. package/src/tools/host-filesystem/transfer.ts +3 -9
  618. package/src/tools/host-filesystem/write.ts +3 -9
  619. package/src/tools/host-terminal/host-shell.ts +3 -9
  620. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  621. package/src/tools/memory/register.test.ts +1 -1
  622. package/src/tools/memory/register.ts +4 -9
  623. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  624. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  625. package/src/tools/network/domain-normalize.ts +17 -0
  626. package/src/tools/network/web-fetch.ts +216 -73
  627. package/src/tools/network/web-search.ts +216 -98
  628. package/src/tools/registry.ts +7 -23
  629. package/src/tools/schema-transforms.ts +1 -1
  630. package/src/tools/skills/execute.ts +3 -9
  631. package/src/tools/skills/load.ts +3 -9
  632. package/src/tools/skills/skill-tool-factory.ts +1 -8
  633. package/src/tools/subagent/notify-parent.ts +3 -9
  634. package/src/tools/system/request-permission.ts +3 -9
  635. package/src/tools/terminal/safe-env.ts +3 -2
  636. package/src/tools/terminal/shell.ts +3 -9
  637. package/src/tools/tool-approval-handler.ts +19 -12
  638. package/src/tools/tool-defaults.ts +94 -0
  639. package/src/tools/types.ts +31 -98
  640. package/src/tools/ui-surface/definitions.ts +9 -23
  641. package/src/types/onboarding-context.ts +4 -0
  642. package/src/usage/pricing.ts +23 -0
  643. package/src/usage/types.ts +12 -0
  644. package/src/util/__tests__/favicon.test.ts +84 -0
  645. package/src/util/favicon.ts +40 -0
  646. package/src/util/logger.ts +16 -7
  647. package/src/util/platform.ts +7 -7
  648. package/src/util/sqlite3-runtime.ts +65 -0
  649. package/src/workspace/git-service.ts +75 -4
  650. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  651. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  652. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  653. package/src/workspace/migrations/registry.ts +4 -0
  654. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  655. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  656. package/src/config/bundled-skills/document/SKILL.md +0 -54
  657. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  658. package/src/daemon/seed-files.ts +0 -18
  659. package/src/prompts/cache-boundary.ts +0 -8
  660. package/src/runtime/routes/interface-routes.ts +0 -43
  661. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  662. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  663. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  664. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  665. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Memory v3 — Tree structure validator.
3
+ *
4
+ * The v3 tree is hand-authored by a data-migration during the v2 → v3 rollout
5
+ * (nodes reference pages and sub-nodes by `page:`/`node:` refs). Because the
6
+ * structure is authored, not derived, it can drift: a ref can dangle, a page
7
+ * can be left unwired, two nodes can reference each other into a cycle, a
8
+ * parent node's compositional summary can fall behind a freshly-edited child,
9
+ * or a page `edges:` entry can point at a slug with no page.
10
+ *
11
+ * `validateTree` is the read-only report the migration (and any later
12
+ * structure-health probe) runs to surface those defects. It is deliberately
13
+ * **non-throwing**: the migration is in progress, so an incomplete tree is
14
+ * expected — the report is informational, and the caller decides what (if
15
+ * anything) is fatal. It builds the three indices it needs (tree, page, edge),
16
+ * walks the DAG, and returns counts plus the offending ids for each category.
17
+ *
18
+ * Categories:
19
+ * - `danglingChildRefs` — a node `children` entry (`node:`/`page:`) whose
20
+ * target node/page does not exist on disk.
21
+ * - `orphanPages` — concept pages present in the page index but not reachable
22
+ * from the tree root by descending every `node:` child. Informational while
23
+ * the migration is mid-flight (not every page is wired in yet). Synthetic
24
+ * page-index entries (skills, CLI commands) are excluded — they are never
25
+ * tree members.
26
+ * - `cycles` — back-edges found during a full DFS over `node:` adjacency
27
+ * (A → B → A). A cycle would make a naive descent loop forever.
28
+ * - `staleIndex` — a node whose own file mtime predates one of its `node:`
29
+ * children's mtime, hinting its compositional index/summary may be out of
30
+ * date relative to the child it composes.
31
+ * - `unknownEdgeTargets` — page `edges:` targets with no corresponding page
32
+ * index slug, reusing v2's `validateEdgeTargets`.
33
+ */
34
+
35
+ import { CLI_COMMAND_SLUG_PREFIX } from "../v2/cli-command-store.js";
36
+ import { getEdgeIndex, validateEdgeTargets } from "../v2/edge-index.js";
37
+ import { getPageIndex } from "../v2/page-index.js";
38
+ import { SKILL_SLUG_PREFIX } from "../v2/skill-store.js";
39
+ import { getTreeIndex, type TreeIndex } from "./tree-index.js";
40
+ import { getNodeMtimeMs } from "./tree-store.js";
41
+
42
+ /**
43
+ * A `node:` child whose mtime is newer than the parent node that composes it.
44
+ * `node` is the parent, `child` the fresher child, and the two `*MtimeMs`
45
+ * fields are their epoch-ms mtimes (parent < child triggers the report).
46
+ */
47
+ export interface StaleIndexEntry {
48
+ node: string;
49
+ child: string;
50
+ nodeMtimeMs: number;
51
+ childMtimeMs: number;
52
+ }
53
+
54
+ /**
55
+ * Read-only health report over the v3 tree + its referenced pages/edges.
56
+ * Every list is sorted for deterministic output; `*Count` fields mirror the
57
+ * corresponding list length so callers can summarize without re-counting.
58
+ */
59
+ export interface TreeValidationReport {
60
+ /** `node:`/`page:` children whose target does not exist. */
61
+ danglingChildRefs: Array<{
62
+ node: string;
63
+ ref: string;
64
+ kind: "node" | "page";
65
+ }>;
66
+ danglingChildRefCount: number;
67
+ /** Concept pages not reachable from the root by descending all node children. */
68
+ orphanPages: string[];
69
+ orphanPageCount: number;
70
+ /** Back-edges (`from → to`) closing a cycle during the full DFS descent. */
71
+ cycles: Array<{ from: string; to: string }>;
72
+ cycleCount: number;
73
+ /** Nodes whose mtime predates a child node's mtime. */
74
+ staleIndex: StaleIndexEntry[];
75
+ staleIndexCount: number;
76
+ /** Page `edges:` targets with no corresponding page-index slug. */
77
+ unknownEdgeTargets: Array<{ from: string; to: string }>;
78
+ unknownEdgeTargetCount: number;
79
+ }
80
+
81
+ /** True when a page-index slug is a synthetic (non-concept-page) entry. */
82
+ function isSyntheticSlug(slug: string): boolean {
83
+ return (
84
+ slug.startsWith(SKILL_SLUG_PREFIX) ||
85
+ slug.startsWith(CLI_COMMAND_SLUG_PREFIX)
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Collect dangling `node:`/`page:` child refs: every node child whose target
91
+ * node id is absent from `tree.nodes`, and every page child whose slug is
92
+ * absent from `knownPageSlugs`. Sorted by `(node, kind, ref)`.
93
+ */
94
+ function collectDanglingChildRefs(
95
+ tree: TreeIndex,
96
+ knownPageSlugs: ReadonlySet<string>,
97
+ ): Array<{ node: string; ref: string; kind: "node" | "page" }> {
98
+ const dangling: Array<{ node: string; ref: string; kind: "node" | "page" }> =
99
+ [];
100
+ for (const [nodeId, children] of tree.childrenByNode) {
101
+ for (const child of children) {
102
+ const exists =
103
+ child.kind === "node"
104
+ ? tree.nodes.has(child.ref)
105
+ : knownPageSlugs.has(child.ref);
106
+ if (!exists) {
107
+ dangling.push({ node: nodeId, ref: child.ref, kind: child.kind });
108
+ }
109
+ }
110
+ }
111
+ dangling.sort(
112
+ (a, b) =>
113
+ a.node.localeCompare(b.node) ||
114
+ a.kind.localeCompare(b.kind) ||
115
+ a.ref.localeCompare(b.ref),
116
+ );
117
+ return dangling;
118
+ }
119
+
120
+ /**
121
+ * Resolve the existing `node:` children of `nodeId`, in `children` order. Refs
122
+ * to absent nodes are skipped (those are reported separately as dangling) so
123
+ * the descent never recurses into a node that isn't on disk.
124
+ */
125
+ function nodeChildrenOf(tree: TreeIndex, nodeId: string): string[] {
126
+ const children = tree.childrenByNode.get(nodeId) ?? [];
127
+ const out: string[] = [];
128
+ for (const child of children) {
129
+ if (child.kind === "node" && tree.nodes.has(child.ref)) {
130
+ out.push(child.ref);
131
+ }
132
+ }
133
+ return out;
134
+ }
135
+
136
+ /**
137
+ * Full DFS over `node:` adjacency from `tree.root`. Returns the set of
138
+ * reachable node ids (for orphan-page reachability) and the back-edges that
139
+ * close a cycle. A back-edge is an edge into a node still on the active
140
+ * recursion stack (classic gray-node cycle detection); `visited` (black)
141
+ * prevents re-walking shared DAG sub-nodes.
142
+ */
143
+ function descend(tree: TreeIndex): {
144
+ reachableNodes: Set<string>;
145
+ cycles: Array<{ from: string; to: string }>;
146
+ } {
147
+ const reachableNodes = new Set<string>();
148
+ const onStack = new Set<string>();
149
+ const cycles: Array<{ from: string; to: string }> = [];
150
+
151
+ // Iterative DFS with an explicit stack so deep trees don't blow the call
152
+ // stack. Each frame tracks its child cursor; we push a child frame, and on
153
+ // exhaustion pop the parent off the recursion stack (`onStack`).
154
+ type Frame = { node: string; children: string[]; cursor: number };
155
+ const stack: Frame[] = [];
156
+
157
+ function enter(nodeId: string): void {
158
+ reachableNodes.add(nodeId);
159
+ onStack.add(nodeId);
160
+ stack.push({
161
+ node: nodeId,
162
+ children: nodeChildrenOf(tree, nodeId),
163
+ cursor: 0,
164
+ });
165
+ }
166
+
167
+ if (tree.nodes.has(tree.root)) {
168
+ enter(tree.root);
169
+ }
170
+
171
+ while (stack.length > 0) {
172
+ const frame = stack[stack.length - 1];
173
+ if (frame.cursor >= frame.children.length) {
174
+ onStack.delete(frame.node);
175
+ stack.pop();
176
+ continue;
177
+ }
178
+ const child = frame.children[frame.cursor++];
179
+ if (onStack.has(child)) {
180
+ // Edge into an ancestor still on the stack → cycle-closing back-edge.
181
+ cycles.push({ from: frame.node, to: child });
182
+ continue;
183
+ }
184
+ if (reachableNodes.has(child)) {
185
+ // Already fully explored via another parent (shared DAG sub-node).
186
+ continue;
187
+ }
188
+ enter(child);
189
+ }
190
+
191
+ cycles.sort(
192
+ (a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to),
193
+ );
194
+ return { reachableNodes, cycles };
195
+ }
196
+
197
+ /**
198
+ * Concept pages reachable from the tree: every `page:` child of a reachable
199
+ * node. Pages hanging off unreachable nodes are *not* counted reachable — they
200
+ * only become reachable once their parent chain links back to the root.
201
+ */
202
+ function reachablePages(
203
+ tree: TreeIndex,
204
+ reachableNodes: ReadonlySet<string>,
205
+ ): Set<string> {
206
+ const pages = new Set<string>();
207
+ for (const nodeId of reachableNodes) {
208
+ for (const child of tree.childrenByNode.get(nodeId) ?? []) {
209
+ if (child.kind === "page") pages.add(child.ref);
210
+ }
211
+ }
212
+ return pages;
213
+ }
214
+
215
+ /**
216
+ * Nodes whose own mtime predates one of their `node:` children's mtime. A
217
+ * missing node file reads as mtime 0 (oldest), so the check never flags a
218
+ * parent against an absent child. Sorted by `(node, child)`.
219
+ */
220
+ async function collectStaleIndex(
221
+ workspaceDir: string,
222
+ tree: TreeIndex,
223
+ ): Promise<StaleIndexEntry[]> {
224
+ const ids = [...tree.nodes.keys()];
225
+ const mtimes = new Map<string, number>();
226
+ await Promise.all(
227
+ ids.map(async (id) => {
228
+ mtimes.set(id, await getNodeMtimeMs(workspaceDir, id));
229
+ }),
230
+ );
231
+
232
+ const stale: StaleIndexEntry[] = [];
233
+ for (const node of ids) {
234
+ const nodeMtimeMs = mtimes.get(node) ?? 0;
235
+ for (const child of nodeChildrenOf(tree, node)) {
236
+ const childMtimeMs = mtimes.get(child) ?? 0;
237
+ if (nodeMtimeMs < childMtimeMs) {
238
+ stale.push({ node, child, nodeMtimeMs, childMtimeMs });
239
+ }
240
+ }
241
+ }
242
+ stale.sort(
243
+ (a, b) => a.node.localeCompare(b.node) || a.child.localeCompare(b.child),
244
+ );
245
+ return stale;
246
+ }
247
+
248
+ /**
249
+ * Validate the hand-authored v3 tree structure for `workspaceDir` and return a
250
+ * {@link TreeValidationReport}. Builds the tree, page, and edge indices, walks
251
+ * the DAG from the root, and reports the five defect categories. Never throws —
252
+ * it is a report, not an assertion.
253
+ */
254
+ export async function validateTree(
255
+ workspaceDir: string,
256
+ ): Promise<TreeValidationReport> {
257
+ const [tree, pageIndex, edgeIndex] = await Promise.all([
258
+ getTreeIndex(workspaceDir),
259
+ getPageIndex(workspaceDir),
260
+ getEdgeIndex(workspaceDir),
261
+ ]);
262
+
263
+ const knownPageSlugs = new Set(pageIndex.bySlug.keys());
264
+
265
+ // Kick off the stale-index mtime stats up front — it only depends on the
266
+ // tree, not on the DAG walk below — so its filesystem reads overlap the
267
+ // (synchronous) descent rather than running strictly after it.
268
+ const staleIndexPromise = collectStaleIndex(workspaceDir, tree);
269
+
270
+ const danglingChildRefs = collectDanglingChildRefs(tree, knownPageSlugs);
271
+
272
+ const { reachableNodes, cycles } = descend(tree);
273
+
274
+ const reached = reachablePages(tree, reachableNodes);
275
+ const orphanPages = [...knownPageSlugs]
276
+ .filter((slug) => !isSyntheticSlug(slug) && !reached.has(slug))
277
+ .sort();
278
+
279
+ const staleIndex = await staleIndexPromise;
280
+
281
+ // Edge graph is page-only; knownSlugs is the full page-index slug set so an
282
+ // edge pointing at a skill/CLI entry is not spuriously flagged unknown.
283
+ const unknownEdgeTargets = validateEdgeTargets(
284
+ edgeIndex,
285
+ knownPageSlugs,
286
+ ).missing;
287
+
288
+ return {
289
+ danglingChildRefs,
290
+ danglingChildRefCount: danglingChildRefs.length,
291
+ orphanPages,
292
+ orphanPageCount: orphanPages.length,
293
+ cycles,
294
+ cycleCount: cycles.length,
295
+ staleIndex,
296
+ staleIndexCount: staleIndex.length,
297
+ unknownEdgeTargets,
298
+ unknownEdgeTargetCount: unknownEdgeTargets.length,
299
+ };
300
+ }
@@ -174,8 +174,14 @@ async function deliverSlack(
174
174
  channel,
175
175
  threadTs: statusThreadTs,
176
176
  status,
177
+ loadingMessages,
177
178
  } = payload.assistantThreadStatus;
178
- await sendSlackAssistantThreadStatus(channel, statusThreadTs, status);
179
+ await sendSlackAssistantThreadStatus(
180
+ channel,
181
+ statusThreadTs,
182
+ status,
183
+ loadingMessages,
184
+ );
179
185
  return { ok: true };
180
186
  }
181
187
 
@@ -21,7 +21,7 @@ mock.module("../../../../oauth/oauth-store.js", () => ({
21
21
  isProviderConnected: async () => false,
22
22
  }));
23
23
 
24
- const findContactChannelMock = mock(() => undefined);
24
+ const findContactChannelMock = mock((): unknown => undefined);
25
25
  const upsertContactChannelMock = mock(() => {});
26
26
  mock.module("../../../../contacts/contact-store.js", () => ({
27
27
  findContactChannel: findContactChannelMock,
@@ -30,9 +30,13 @@ mock.module("../../../../contacts/contacts-write.js", () => ({
30
30
  upsertContactChannel: upsertContactChannelMock,
31
31
  }));
32
32
 
33
- import { slackProvider } from "../adapter.js";
33
+ import {
34
+ __resetSlackUserInfoCacheForTests,
35
+ slackProvider,
36
+ } from "../adapter.js";
34
37
 
35
38
  const originalFetch = globalThis.fetch;
39
+ let userInfoCalls: string[] = [];
36
40
 
37
41
  function installFetchStub() {
38
42
  globalThis.fetch = (async (
@@ -60,6 +64,73 @@ function fakeSlackResponse(url: string): Record<string, unknown> {
60
64
  const method = parsed.pathname.split("/").at(-1);
61
65
 
62
66
  if (method === "conversations.history") {
67
+ if (parsed.searchParams.get("channel") === "C_USERINFO_FAIL") {
68
+ return {
69
+ ok: true,
70
+ has_more: false,
71
+ messages: [
72
+ {
73
+ type: "message",
74
+ ts: "1700000006.000700",
75
+ user: "UMISSING",
76
+ text: "Fallback sender message",
77
+ },
78
+ ],
79
+ };
80
+ }
81
+
82
+ if (parsed.searchParams.get("channel") === "C_USERINFO_RETRY") {
83
+ return {
84
+ ok: true,
85
+ has_more: false,
86
+ messages: [
87
+ {
88
+ type: "message",
89
+ ts: "1700000007.000800",
90
+ user: "URETRY",
91
+ text: "Retry sender message",
92
+ },
93
+ ],
94
+ };
95
+ }
96
+
97
+ if (parsed.searchParams.get("channel") === "C_TIMEZONE_CACHE") {
98
+ return {
99
+ ok: true,
100
+ has_more: false,
101
+ messages: [
102
+ {
103
+ type: "message",
104
+ ts: "1700000004.000500",
105
+ user: "USENDER",
106
+ text: "First timezone-bearing message",
107
+ },
108
+ {
109
+ type: "message",
110
+ ts: "1700000005.000600",
111
+ user: "USENDER",
112
+ text: "Second timezone-bearing message",
113
+ },
114
+ ],
115
+ };
116
+ }
117
+
118
+ if (parsed.searchParams.get("channel") === "C_BOT_HISTORY") {
119
+ return {
120
+ ok: true,
121
+ has_more: false,
122
+ messages: [
123
+ {
124
+ type: "message",
125
+ subtype: "bot_message",
126
+ ts: "1700000003.000400",
127
+ bot_id: "B_ASSISTANT",
128
+ text: "Bot-authored history",
129
+ },
130
+ ],
131
+ };
132
+ }
133
+
63
134
  return {
64
135
  ok: true,
65
136
  has_more: false,
@@ -117,7 +188,9 @@ function fakeSlackResponse(url: string): Record<string, unknown> {
117
188
  }
118
189
 
119
190
  if (method === "users.info") {
120
- return fakeUserInfoResponse(parsed.searchParams.get("user") ?? "");
191
+ const userId = parsed.searchParams.get("user") ?? "";
192
+ userInfoCalls.push(userId);
193
+ return fakeUserInfoResponse(userId);
121
194
  }
122
195
 
123
196
  return { ok: true };
@@ -141,6 +214,9 @@ function fakeUserInfoResponse(userId: string): Record<string, unknown> {
141
214
  user: {
142
215
  id: "USENDER",
143
216
  name: "sender",
217
+ tz: "America/New_York",
218
+ tz_label: "Eastern Time",
219
+ tz_offset: -18000,
144
220
  profile: { display_name: "Sender" },
145
221
  },
146
222
  };
@@ -157,11 +233,93 @@ function fakeUserInfoResponse(userId: string): Record<string, unknown> {
157
233
  };
158
234
  }
159
235
 
236
+ if (userId === "URETRY") {
237
+ const retryCalls = userInfoCalls.filter((call) => call === userId).length;
238
+ if (retryCalls === 1) {
239
+ return { ok: false, error: "temporarily_unavailable" };
240
+ }
241
+ return {
242
+ ok: true,
243
+ user: {
244
+ id: "URETRY",
245
+ name: "retry_sender",
246
+ tz: "America/Chicago",
247
+ tz_label: "Central Time",
248
+ tz_offset: -21600,
249
+ profile: { display_name: "Retry Sender" },
250
+ },
251
+ };
252
+ }
253
+
160
254
  return { ok: false, error: "user_not_found" };
161
255
  }
162
256
 
257
+ function makeOAuthConnection(
258
+ id: string,
259
+ accountInfo: string,
260
+ displayName: string,
261
+ timezone: string,
262
+ timezoneLabel: string,
263
+ timezoneOffsetSeconds: number,
264
+ ): OAuthConnection {
265
+ return {
266
+ id,
267
+ provider: "slack",
268
+ accountInfo,
269
+ request: async (req) => {
270
+ if (req.path === "/conversations.history") {
271
+ return {
272
+ status: 200,
273
+ headers: {},
274
+ body: {
275
+ ok: true,
276
+ has_more: false,
277
+ messages: [
278
+ {
279
+ type: "message",
280
+ ts: "1700000008.000900",
281
+ user: "USAME",
282
+ text: "Account scoped sender",
283
+ },
284
+ ],
285
+ },
286
+ };
287
+ }
288
+ if (req.path === "/users.info") {
289
+ const userId = req.query?.user;
290
+ userInfoCalls.push(`${id}:${userId}`);
291
+ return {
292
+ status: 200,
293
+ headers: {},
294
+ body: {
295
+ ok: true,
296
+ user: {
297
+ id: "USAME",
298
+ name: displayName.toLowerCase().replaceAll(" ", "_"),
299
+ tz: timezone,
300
+ tz_label: timezoneLabel,
301
+ tz_offset: timezoneOffsetSeconds,
302
+ profile: { display_name: displayName },
303
+ },
304
+ },
305
+ };
306
+ }
307
+ return {
308
+ status: 200,
309
+ headers: {},
310
+ body: { ok: true },
311
+ };
312
+ },
313
+ withToken: async <T>(_fn: (token: string) => Promise<T>): Promise<T> => {
314
+ throw new Error("withToken was not expected");
315
+ },
316
+ };
317
+ }
318
+
163
319
  describe("Slack adapter mention rendering", () => {
164
320
  beforeEach(async () => {
321
+ __resetSlackUserInfoCacheForTests();
322
+ userInfoCalls = [];
165
323
  getSecureKeyAsyncMock.mockReset();
166
324
  getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
167
325
  if (key === credentialKey("slack_channel", "bot_token")) {
@@ -190,6 +348,174 @@ describe("Slack adapter mention rendering", () => {
190
348
  expect(messages[0].reactions).toEqual([{ name: "eyes", count: 1 }]);
191
349
  });
192
350
 
351
+ test("getHistory preserves bot ids for bot-authored Slack messages", async () => {
352
+ const messages = await slackProvider.getHistory(undefined, "C_BOT_HISTORY");
353
+
354
+ expect(messages).toHaveLength(1);
355
+ expect(messages[0].sender).toEqual({ id: "B_ASSISTANT", name: "unknown" });
356
+ expect(messages[0].metadata).toEqual({
357
+ isBot: true,
358
+ slackBotId: "B_ASSISTANT",
359
+ });
360
+ });
361
+
362
+ test("getHistory caches Slack user info and maps timezone metadata", async () => {
363
+ const messages = await slackProvider.getHistory(
364
+ undefined,
365
+ "C_TIMEZONE_CACHE",
366
+ );
367
+
368
+ expect(messages).toHaveLength(2);
369
+ expect(userInfoCalls.filter((userId) => userId === "USENDER")).toHaveLength(
370
+ 1,
371
+ );
372
+ expect(messages.map((message) => message.sender)).toEqual([
373
+ { id: "USENDER", name: "Sender" },
374
+ { id: "USENDER", name: "Sender" },
375
+ ]);
376
+ expect(messages.map((message) => message.metadata)).toEqual([
377
+ {
378
+ actorTimezone: "America/New_York",
379
+ actorTimezoneLabel: "Eastern Time",
380
+ actorTimezoneOffsetSeconds: -18000,
381
+ },
382
+ {
383
+ actorTimezone: "America/New_York",
384
+ actorTimezoneLabel: "Eastern Time",
385
+ actorTimezoneOffsetSeconds: -18000,
386
+ },
387
+ ]);
388
+ });
389
+
390
+ test("getHistory prefers contact display names while still fetching Slack timezone facts", async () => {
391
+ findContactChannelMock.mockImplementationOnce(() => ({
392
+ contact: { displayName: "Saved Sender" },
393
+ }));
394
+
395
+ const messages = await slackProvider.getHistory(
396
+ undefined,
397
+ "C_TIMEZONE_CACHE",
398
+ );
399
+
400
+ expect(userInfoCalls.filter((userId) => userId === "USENDER")).toHaveLength(
401
+ 1,
402
+ );
403
+ expect(messages[0].sender).toEqual({
404
+ id: "USENDER",
405
+ name: "Saved Sender",
406
+ });
407
+ expect(messages[0].metadata).toEqual({
408
+ actorTimezone: "America/New_York",
409
+ actorTimezoneLabel: "Eastern Time",
410
+ actorTimezoneOffsetSeconds: -18000,
411
+ });
412
+ });
413
+
414
+ test("getHistory caches fallback user info for permanent users.info failures", async () => {
415
+ const firstMessages = await slackProvider.getHistory(
416
+ undefined,
417
+ "C_USERINFO_FAIL",
418
+ );
419
+ const secondMessages = await slackProvider.getHistory(
420
+ undefined,
421
+ "C_USERINFO_FAIL",
422
+ );
423
+
424
+ expect(firstMessages).toHaveLength(1);
425
+ expect(secondMessages).toHaveLength(1);
426
+ expect(
427
+ userInfoCalls.filter((userId) => userId === "UMISSING"),
428
+ ).toHaveLength(1);
429
+ expect(firstMessages[0].sender).toEqual({
430
+ id: "UMISSING",
431
+ name: "UMISSING",
432
+ });
433
+ expect(secondMessages[0].sender).toEqual({
434
+ id: "UMISSING",
435
+ name: "UMISSING",
436
+ });
437
+ expect(firstMessages[0].metadata).toBeUndefined();
438
+ expect(secondMessages[0].metadata).toBeUndefined();
439
+ });
440
+
441
+ test("getHistory does not cache fallback user info after transient users.info failures", async () => {
442
+ const firstMessages = await slackProvider.getHistory(
443
+ undefined,
444
+ "C_USERINFO_RETRY",
445
+ );
446
+ const secondMessages = await slackProvider.getHistory(
447
+ undefined,
448
+ "C_USERINFO_RETRY",
449
+ );
450
+
451
+ expect(userInfoCalls.filter((userId) => userId === "URETRY")).toHaveLength(
452
+ 2,
453
+ );
454
+ expect(firstMessages[0].sender).toEqual({
455
+ id: "URETRY",
456
+ name: "URETRY",
457
+ });
458
+ expect(firstMessages[0].metadata).toBeUndefined();
459
+ expect(secondMessages[0].sender).toEqual({
460
+ id: "URETRY",
461
+ name: "Retry Sender",
462
+ });
463
+ expect(secondMessages[0].metadata).toEqual({
464
+ actorTimezone: "America/Chicago",
465
+ actorTimezoneLabel: "Central Time",
466
+ actorTimezoneOffsetSeconds: -21600,
467
+ });
468
+ });
469
+
470
+ test("getHistory scopes Slack user info cache by OAuth connection", async () => {
471
+ const workspaceA = makeOAuthConnection(
472
+ "conn-workspace-a",
473
+ "workspace-a",
474
+ "Workspace A Sender",
475
+ "America/Los_Angeles",
476
+ "Pacific Time",
477
+ -28800,
478
+ );
479
+ const workspaceB = makeOAuthConnection(
480
+ "conn-workspace-b",
481
+ "workspace-b",
482
+ "Workspace B Sender",
483
+ "Europe/London",
484
+ "Greenwich Mean Time",
485
+ 0,
486
+ );
487
+
488
+ const messagesA = await slackProvider.getHistory(
489
+ workspaceA,
490
+ "C_ACCOUNT_SCOPE",
491
+ );
492
+ const messagesB = await slackProvider.getHistory(
493
+ workspaceB,
494
+ "C_ACCOUNT_SCOPE",
495
+ );
496
+
497
+ expect(userInfoCalls).toContain("conn-workspace-a:USAME");
498
+ expect(userInfoCalls).toContain("conn-workspace-b:USAME");
499
+ expect(messagesA[0].sender).toEqual({
500
+ id: "USAME",
501
+ name: "Workspace A Sender",
502
+ });
503
+ expect(messagesA[0].metadata).toEqual({
504
+ actorTimezone: "America/Los_Angeles",
505
+ actorTimezoneLabel: "Pacific Time",
506
+ actorTimezoneOffsetSeconds: -28800,
507
+ });
508
+ expect(messagesB[0].sender).toEqual({
509
+ id: "USAME",
510
+ name: "Workspace B Sender",
511
+ });
512
+ expect(messagesB[0].metadata).toEqual({
513
+ actorTimezone: "Europe/London",
514
+ actorTimezoneLabel: "Greenwich Mean Time",
515
+ actorTimezoneOffsetSeconds: 0,
516
+ });
517
+ });
518
+
193
519
  test("getThreadReplies renders Slack user mentions for model-facing text without changing sender identity", async () => {
194
520
  const messages = await slackProvider.getThreadReplies!(
195
521
  undefined,