@vellumai/assistant 0.12.1 → 0.12.2-staging.1

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 (548) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -9,6 +9,30 @@ import { migrateProviderBaseUrl, seedProviders } from "./oauth-store.js";
9
9
  */
10
10
  const STALE_GOOGLE_BASE_URL = "https://gmail.googleapis.com/gmail/v1/users/me";
11
11
 
12
+ /**
13
+ * The hosts a Slack credential may be sent to, shared by the person-identity
14
+ * `slack` provider and the `slack_channel` bot: both read messages, and a file
15
+ * shared in a message is fetched from the file object's `url_private`,
16
+ * `url_private_download`, or `thumb_*` URL on `files.slack.com` with the same
17
+ * Bearer token as the Web API. Slack documents that one file host by name.
18
+ */
19
+ const SLACK_PROVIDER_INJECTION_TEMPLATES: NonNullable<
20
+ (typeof PROVIDER_SEED_DATA)[string]["injectionTemplates"]
21
+ > = [
22
+ {
23
+ hostPattern: "slack.com",
24
+ injectionType: "header",
25
+ headerName: "Authorization",
26
+ valuePrefix: "Bearer ",
27
+ },
28
+ {
29
+ hostPattern: "files.slack.com",
30
+ injectionType: "header",
31
+ headerName: "Authorization",
32
+ valuePrefix: "Bearer ",
33
+ },
34
+ ];
35
+
12
36
  /**
13
37
  * Protocol-level seed data for each well-known OAuth provider.
14
38
  *
@@ -183,14 +207,13 @@ export const PROVIDER_SEED_DATA: Record<
183
207
  clientIdPlaceholder: null,
184
208
  logoUrl:
185
209
  "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/slack/default.svg",
186
- // Sent as the bot `scope` parameter, while this flow persists the
187
- // `authed_user` token and stores `authed_user.scope` as `grantedScopes`.
188
- // Credential health compares this list against that grant, so a scope
189
- // listed here that `user_scope` does not also request reports as
190
- // `missing_scopes`, a hard failure that disables the provider's tools.
191
- // The wider bot set belongs to Socket Mode installs, which verify the live
192
- // `x-oauth-scopes` header against SLACK_REQUIRED_BOT_SCOPES in
193
- // channel-readiness-service.ts.
210
+ // Sent as the bot `scope` parameter on the same authorize request. This
211
+ // flow persists the `authed_user` token and stores `authed_user.scope` as
212
+ // `grantedScopes`; credential health measures that grant against
213
+ // `user_scope` below, never against this list, so a scope the stored
214
+ // token needs belongs in `user_scope`. The wider bot set belongs to Socket
215
+ // Mode installs, which verify the live `x-oauth-scopes` header against
216
+ // SLACK_REQUIRED_BOT_SCOPES in channel-readiness-service.ts.
194
217
  defaultScopes: [
195
218
  "channels:join",
196
219
  "channels:read",
@@ -212,19 +235,17 @@ export const PROVIDER_SEED_DATA: Record<
212
235
  // only credential this flow stores, so it acts as the installer for every
213
236
  // call including `chat.postMessage`. The manifest's user list is read-only
214
237
  // instead, since a Socket Mode install keeps a bot token beside it.
238
+ // `files:read` is what lets that token fetch a file shared in a message
239
+ // from files.slack.com. Credential health measures every stored grant
240
+ // against this list, so a scope added here reports each existing
241
+ // connection as `missing_scopes` until it reconnects: that is how a newly
242
+ // required scope reaches an install.
215
243
  authorizeParams: {
216
244
  user_scope:
217
- "channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write",
245
+ "channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write,files:read",
218
246
  },
219
247
  loopbackPort: 17322,
220
- injectionTemplates: [
221
- {
222
- hostPattern: "slack.com",
223
- injectionType: "header",
224
- headerName: "Authorization",
225
- valuePrefix: "Bearer ",
226
- },
227
- ],
248
+ injectionTemplates: SLACK_PROVIDER_INJECTION_TEMPLATES,
228
249
  appType: "Slack App",
229
250
  identityUrl: "https://slack.com/api/auth.test",
230
251
  identityOkField: "ok",
@@ -1194,14 +1215,7 @@ export const PROVIDER_SEED_DATA: Record<
1194
1215
  logoUrl:
1195
1216
  "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/slack/default.svg",
1196
1217
  defaultScopes: [],
1197
- injectionTemplates: [
1198
- {
1199
- hostPattern: "slack.com",
1200
- injectionType: "header",
1201
- headerName: "Authorization",
1202
- valuePrefix: "Bearer ",
1203
- },
1204
- ],
1218
+ injectionTemplates: SLACK_PROVIDER_INJECTION_TEMPLATES,
1205
1219
  },
1206
1220
 
1207
1221
  // The bot that sits in a server and talks to people there, which is a
@@ -1329,6 +1343,171 @@ export const PROVIDER_SEED_DATA: Record<
1329
1343
  identityUrl: "https://api.link.com/userinfo",
1330
1344
  identityResponsePaths: ["email", "phone"],
1331
1345
  },
1346
+ shopify: {
1347
+ provider: "shopify",
1348
+ // Shopify is per-tenant: every OAuth endpoint lives on the merchant's own
1349
+ // myshopify.com domain, so these carry a {tenant_host} placeholder the
1350
+ // platform fills in from the shop domain the user supplies at connect
1351
+ // time. There is no global Shopify authorization host to point at.
1352
+ authorizeUrl: "https://{tenant_host}/admin/oauth/authorize",
1353
+ tokenExchangeUrl: "https://{tenant_host}/admin/oauth/access_token",
1354
+ // Requesting `expiring=1` at token exchange yields a short-lived access
1355
+ // token plus a refresh token, which is refreshed against the same
1356
+ // per-shop endpoint.
1357
+ refreshUrl: "https://{tenant_host}/admin/oauth/access_token",
1358
+ pingUrl: "https://{tenant_host}/admin/api/2026-07/shop.json",
1359
+ baseUrl: "https://{tenant_host}",
1360
+ displayLabel: "Shopify",
1361
+ description: "Products, orders, customers, and inventory",
1362
+ dashboardUrl: "https://shopify.dev/dashboard",
1363
+ clientIdPlaceholder: null,
1364
+ logoUrl: "https://cdn.simpleicons.org/shopify",
1365
+ // Shopify parses `scope` as a comma-separated list. A space-separated
1366
+ // list is read as one malformed scope and the request is rejected.
1367
+ scopeSeparator: ",",
1368
+ // Admin API scopes only: Storefront, Customer Account and Shop APIs are
1369
+ // buyer-facing surfaces an assistant has no use for. Every scope here is
1370
+ // grantable without Shopify approval -- the approval-gated ones
1371
+ // (read_all_orders, customer payment methods, subscription contracts,
1372
+ // dispute evidence) are deliberately absent, because Shopify rejects the
1373
+ // whole authorization request if the app cannot grant a requested scope.
1374
+ // A write_* grant implies its read_* counterpart, so the response may
1375
+ // name only the write scope.
1376
+ defaultScopes: [
1377
+ "read_products",
1378
+ "write_products",
1379
+ "read_orders",
1380
+ "write_orders",
1381
+ "read_draft_orders",
1382
+ "write_draft_orders",
1383
+ "read_customers",
1384
+ "write_customers",
1385
+ "read_inventory",
1386
+ "read_locations",
1387
+ "read_fulfillments",
1388
+ "read_discounts",
1389
+ "write_discounts",
1390
+ "read_price_rules",
1391
+ ],
1392
+ availableScopes: [
1393
+ {
1394
+ scope: "read_products",
1395
+ description: "Read products, variants, and collections",
1396
+ },
1397
+ {
1398
+ scope: "write_products",
1399
+ description: "Create and update products, variants, and collections",
1400
+ },
1401
+ {
1402
+ scope: "read_orders",
1403
+ description: "Read orders, transactions, and abandoned checkouts",
1404
+ },
1405
+ {
1406
+ scope: "write_orders",
1407
+ description: "Create and update orders and fulfillments",
1408
+ },
1409
+ { scope: "read_draft_orders", description: "Read draft orders" },
1410
+ {
1411
+ scope: "write_draft_orders",
1412
+ description: "Create and update draft orders",
1413
+ },
1414
+ {
1415
+ scope: "read_customers",
1416
+ description: "Read customers, segments, and companies",
1417
+ },
1418
+ {
1419
+ scope: "write_customers",
1420
+ description: "Create and update customers and segments",
1421
+ },
1422
+ {
1423
+ scope: "read_inventory",
1424
+ description: "Read inventory levels and items",
1425
+ },
1426
+ {
1427
+ scope: "write_inventory",
1428
+ description: "Adjust inventory levels and items",
1429
+ },
1430
+ { scope: "read_locations", description: "Read store locations" },
1431
+ { scope: "read_fulfillments", description: "Read fulfillment services" },
1432
+ {
1433
+ scope: "write_fulfillments",
1434
+ description: "Create and update fulfillment services",
1435
+ },
1436
+ { scope: "read_discounts", description: "Read discounts" },
1437
+ { scope: "write_discounts", description: "Create and update discounts" },
1438
+ { scope: "read_price_rules", description: "Read price rules" },
1439
+ {
1440
+ scope: "write_price_rules",
1441
+ description: "Create and update price rules",
1442
+ },
1443
+ { scope: "read_files", description: "Read files uploaded to the store" },
1444
+ { scope: "write_files", description: "Upload and update files" },
1445
+ { scope: "read_gift_cards", description: "Read gift cards" },
1446
+ {
1447
+ scope: "write_gift_cards",
1448
+ description: "Create and update gift cards",
1449
+ },
1450
+ { scope: "read_returns", description: "Read returns" },
1451
+ { scope: "write_returns", description: "Create and update returns" },
1452
+ { scope: "read_markets", description: "Read markets" },
1453
+ { scope: "read_locales", description: "Read shop locales" },
1454
+ {
1455
+ scope: "read_shipping",
1456
+ description: "Read shipping and delivery carrier services",
1457
+ },
1458
+ { scope: "read_analytics", description: "Read analytics and reports" },
1459
+ {
1460
+ scope: "read_marketing_events",
1461
+ description: "Read marketing events and activities",
1462
+ },
1463
+ { scope: "read_order_edits", description: "Read order edits" },
1464
+ {
1465
+ scope: "write_order_edits",
1466
+ description: "Create and apply order edits",
1467
+ },
1468
+ {
1469
+ scope: "read_metaobjects",
1470
+ description: "Read metaobjects and their definitions",
1471
+ },
1472
+ {
1473
+ scope: "write_metaobjects",
1474
+ description: "Create and update metaobjects",
1475
+ },
1476
+ { scope: "read_translations", description: "Read translated content" },
1477
+ {
1478
+ scope: "read_shopify_payments_payouts",
1479
+ description: "Read Shopify Payments payouts and balance",
1480
+ },
1481
+ {
1482
+ scope: "read_shopify_payments_disputes",
1483
+ description: "Read Shopify Payments disputes",
1484
+ },
1485
+ ],
1486
+ loopbackPort: 17341,
1487
+ managedServiceConfigKey: "shopify-oauth",
1488
+ injectionTemplates: [
1489
+ {
1490
+ hostPattern: "*.myshopify.com",
1491
+ injectionType: "header",
1492
+ headerName: "X-Shopify-Access-Token",
1493
+ valuePrefix: "",
1494
+ },
1495
+ ],
1496
+ appType: "App",
1497
+ setupNotes: [
1498
+ "Shopify OAuth is per-shop: the authorize, token and API endpoints all live on the merchant's own myshopify.com domain, so connecting requires the shop domain (for example your-store.myshopify.com) alongside the usual credentials.",
1499
+ "The Admin API authenticates with the X-Shopify-Access-Token header rather than an Authorization Bearer header.",
1500
+ ],
1501
+ // The Admin API's shop endpoint nests its payload under "shop"; the
1502
+ // myshopify domain is the stable, human-recognisable handle.
1503
+ identityUrl: "https://{tenant_host}/admin/api/2026-07/shop.json",
1504
+ identityResponsePaths: ["shop.myshopify_domain", "shop.name"],
1505
+ // Gated like monday/figma/stripe-link: the platform side and the
1506
+ // shop-domain input in the clients land separately, and until both are
1507
+ // in place a visible Shopify tile would offer a connect flow that
1508
+ // cannot complete.
1509
+ featureFlag: "shopify-oauth",
1510
+ },
1332
1511
  };
1333
1512
 
1334
1513
  export const SEEDED_PROVIDER_KEYS = new Set(Object.keys(PROVIDER_SEED_DATA));
@@ -2,6 +2,8 @@ import { X509Certificate } from "node:crypto";
2
2
  import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
 
5
+ import { writeCombinedCABundle } from "../util/ca-bundle.js";
6
+
5
7
  const CA_CERT_FILENAME = "ca.pem";
6
8
  const CA_KEY_FILENAME = "ca-key.pem";
7
9
  const COMBINED_CA_FILENAME = "combined-ca-bundle.pem";
@@ -263,11 +265,7 @@ export async function ensureCombinedCABundle(
263
265
  }
264
266
 
265
267
  try {
266
- const [systemCAs, proxyCACert] = await Promise.all([
267
- readFile(systemBundlePath, "utf-8"),
268
- readFile(caCertPath, "utf-8"),
269
- ]);
270
- await writeFile(combinedPath, systemCAs + "\n" + proxyCACert);
268
+ await writeCombinedCABundle(systemBundlePath, caCertPath, combinedPath);
271
269
  return combinedPath;
272
270
  } catch {
273
271
  return null;
@@ -15,13 +15,16 @@ mock.module("../config/assistant-feature-flags.js", () => ({
15
15
  }));
16
16
 
17
17
  // `buildPolicyContext` (used by the integration tests below) precomputes the
18
- // proc-to-skills gate via `isV3TierActive`. Drive it through this slot so a
19
- // test can put the production threading path in the active / inactive state.
18
+ // retrospective skill-improvement gate. Keep the v3 tier active while the
19
+ // dedicated gate changes so the production threading test covers the new
20
+ // opt-out rather than the old v3 condition.
20
21
  let mockV3TierActive = true;
22
+ let mockSkillImprovementActive = true;
21
23
  mock.module("../config/memory-v3-gate.js", () => ({
22
24
  isMemoryEnabled: (config?: { memory?: { enabled?: boolean } }) =>
23
25
  config?.memory?.enabled !== false,
24
26
  isV3TierActive: () => mockV3TierActive,
27
+ isSkillImprovementActive: () => mockSkillImprovementActive,
25
28
  isMemoryV3Live: () => mockV3TierActive,
26
29
  usesConceptPageMemory: (memory?: {
27
30
  enabled?: boolean;
@@ -219,6 +222,7 @@ describe("Permission Checker (gateway IPC)", () => {
219
222
  mockCachedThreshold = "low";
220
223
  mockRefreshedThreshold = null;
221
224
  mockV3TierActive = true;
225
+ mockSkillImprovementActive = true;
222
226
  thresholdCallLog.length = 0;
223
227
  mockSkillCatalog = [];
224
228
  mockResolvedSkill = null;
@@ -1173,10 +1177,11 @@ describe("Permission Checker (gateway IPC)", () => {
1173
1177
  });
1174
1178
 
1175
1179
  test("grant does NOT fire when proc-to-skills is inactive, even for the retrospective turn", async () => {
1176
- // Same retrospective ToolContext, but the feature is inactive (flag off
1177
- // or v3 not live). buildPolicyContext stamps `procToSkillsActive: false`,
1178
- // so the grant is dead and the high-risk scaffold prompts.
1179
- mockV3TierActive = false;
1180
+ // The retrospective keeps its live v3 tier, but this independent flag is
1181
+ // off. buildPolicyContext stamps `procToSkillsActive: false`, so the
1182
+ // grant is dead and the high-risk scaffold prompts.
1183
+ mockV3TierActive = true;
1184
+ mockSkillImprovementActive = false;
1180
1185
  mockIpcClassifyRiskResult = {
1181
1186
  risk: "high",
1182
1187
  reason: "Skill scaffold",
@@ -26,6 +26,7 @@
26
26
  */
27
27
  import type { AgentEvent } from "../agent/loop.js";
28
28
  import { getConfig } from "../config/loader.js";
29
+ import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
29
30
  import type { CompactionLogsClickHouseConfig } from "../config/schemas/compaction-logs.js";
30
31
  import { credentialKey } from "../security/credential-key.js";
31
32
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -136,7 +137,7 @@ export interface ClickHouseCompactionLogStoreDeps {
136
137
  resolveUrl?: () => Promise<string | null>;
137
138
  /** Override the credential read for `clickhouse:password`. */
138
139
  resolvePassword?: () => Promise<string | null>;
139
- /** Override the credential read for `vellum:platform_assistant_id`. */
140
+ /** Override the in-memory platform assistant id. */
140
141
  resolveAssistantId?: () => Promise<string | null>;
141
142
  /** Override fetch for testing. */
142
143
  fetchImpl?: CompactionLogFetch;
@@ -163,8 +164,7 @@ export class ClickHouseCompactionLogStore {
163
164
  deps.resolvePassword ??
164
165
  (() => readCredentialOrNull("clickhouse", "password"));
165
166
  this.resolveAssistantId =
166
- deps.resolveAssistantId ??
167
- (() => readCredentialOrNull("vellum", "platform_assistant_id"));
167
+ deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
168
168
  this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
169
169
  }
170
170
 
@@ -35,7 +35,10 @@ import { findConversation } from "../daemon/conversation-registry.js";
35
35
  import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
36
36
  import type { TrustContext } from "../daemon/trust-context-types.js";
37
37
  import { clearAllConversationIds } from "../home/feed-writer.js";
38
- import type { ConversationDeletedInputContext } from "../hooks/types.js";
38
+ import type {
39
+ ConversationDeletedInputContext,
40
+ MessageDeletedInputContext,
41
+ } from "../hooks/types.js";
39
42
  import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
40
43
  import { HOOKS } from "../plugin-api/constants.js";
41
44
  import { forkConversationMemory } from "../plugins/defaults/memory/fork-conversation-memory.js";
@@ -81,7 +84,6 @@ import { ensureDisplayOrderMigration } from "./conversation-display-order-migrat
81
84
  import { ensureGroupMigration } from "./conversation-group-migration.js";
82
85
  import {
83
86
  isReferentialFork,
84
- type LineageBound,
85
87
  type LineageConversationRow,
86
88
  lineageMessageFilter,
87
89
  lineageMessagesAfterFilter,
@@ -121,6 +123,11 @@ import {
121
123
  } from "./job-handlers/message-lexical.js";
122
124
  import { buildLifecycleTelemetryEvent } from "./lifecycle-events-store.js";
123
125
  import { resolveMessageContentBlocks } from "./message-content-file.js";
126
+ import {
127
+ loadMessageBound,
128
+ type MessagesAfterRef,
129
+ resolveMessagesAfterBound,
130
+ } from "./message-cursor.js";
124
131
  import { mergeMessageMetadata } from "./message-metadata.js";
125
132
  import {
126
133
  rawAll,
@@ -2641,16 +2648,6 @@ function loadLineageRow(id: string): LineageConversationRow | null {
2641
2648
  return row ?? null;
2642
2649
  }
2643
2650
 
2644
- /** The `(createdAt, id)` bound of a single message, or null when it is gone. */
2645
- function loadMessageBound(messageId: string): LineageBound | null {
2646
- const row = getDb()
2647
- .select({ createdAt: messages.createdAt, id: messages.id })
2648
- .from(messages)
2649
- .where(eq(messages.id, messageId))
2650
- .get();
2651
- return row ?? null;
2652
- }
2653
-
2654
2651
  /**
2655
2652
  * The segments making up a conversation's logical message set. Non-forks and
2656
2653
  * copied forks resolve to a single unbounded segment after one row lookup, so
@@ -2972,81 +2969,67 @@ export function selectNewestSightFrameCapture(
2972
2969
  }
2973
2970
 
2974
2971
  /**
2975
- * Count messages in a conversation that were created strictly after the
2976
- * `afterMessageId` reference message. If `afterMessageId` is `null` or empty,
2977
- * counts all messages in the conversation. If the referenced message no
2978
- * longer exists (e.g. deleted by a separate flow), returns 0 — callers
2979
- * decide how to react to a vanished reference, and the conservative answer
2980
- * here is "no new work."
2972
+ * Count messages in a conversation created strictly after the `after`
2973
+ * reference. `null`, `""`, or a cursor with an empty id counts every message.
2974
+ * The reference resolves through `resolveMessagesAfterBound`: a live row's
2975
+ * `(createdAt, id)` is authoritative, a `MessageCursor` whose row has been
2976
+ * deleted still bounds the count from the `createdAt` it carries, and a bare
2977
+ * id whose row is gone counts 0: a caller holding only an id has no
2978
+ * defensible starting point, and the conservative answer is "no new work."
2981
2979
  *
2982
2980
  * Used by the memory-retrospective trigger check to decide whether to fire
2983
2981
  * the message-count trigger without loading message bodies.
2984
2982
  */
2985
2983
  export function countMessagesAfter(
2986
2984
  conversationId: string,
2987
- afterMessageId: string | null,
2985
+ after: MessagesAfterRef,
2988
2986
  ): number {
2989
2987
  const db = getDb();
2990
2988
  const segments = resolveLineage(conversationId);
2991
- if (afterMessageId === null || afterMessageId === "") {
2992
- const row = db
2993
- .select({ c: count() })
2994
- .from(messages)
2995
- .where(lineageMessageFilter(segments))
2996
- .get();
2997
- return row?.c ?? 0;
2998
- }
2999
- const ref = loadMessageBound(afterMessageId);
3000
- if (!ref) {
2989
+ const start = resolveMessagesAfterBound(after);
2990
+ if (start.kind === "vanished") {
3001
2991
  return 0;
3002
2992
  }
3003
- // Tie-breaker on `messages.id` so rows that share a millisecond timestamp
3004
- // with the reference are not permanently skipped. Mirrors the
3005
- // `(createdAt, id)` cursor pattern used by the backfill job-handler and
3006
- // turn-events-store.
3007
2993
  const row = db
3008
2994
  .select({ c: count() })
3009
2995
  .from(messages)
3010
- .where(lineageMessagesAfterFilter(segments, ref))
2996
+ .where(
2997
+ start.kind === "all"
2998
+ ? lineageMessageFilter(segments)
2999
+ : lineageMessagesAfterFilter(segments, start.bound),
3000
+ )
3011
3001
  .get();
3012
3002
  return row?.c ?? 0;
3013
3003
  }
3014
3004
 
3015
3005
  /**
3016
- * Return messages in a conversation created strictly after the
3017
- * `afterMessageId` reference. If the reference is `null`/empty, returns all
3018
- * messages. If the reference doesn't exist, returns an empty array (mirrors
3019
- * `countMessagesAfter`'s conservative semantics). Used by the
3020
- * memory-retrospective job handler to load the message slice it processes.
3006
+ * Return messages in a conversation created strictly after the `after`
3007
+ * reference, in `(createdAt, id)` order. Resolves the reference exactly as
3008
+ * `countMessagesAfter` does, returning an empty array where that returns 0.
3009
+ * Used by the memory-retrospective job handler to load the message slice it
3010
+ * processes.
3021
3011
  */
3022
3012
  export function getMessagesAfter(
3023
3013
  conversationId: string,
3024
- afterMessageId: string | null,
3014
+ after: MessagesAfterRef,
3025
3015
  ): MessageRow[] {
3026
3016
  const db = getDb();
3027
3017
  const segments = resolveLineage(conversationId);
3028
- if (afterMessageId === null || afterMessageId === "") {
3029
- // Secondary `asc(messages.id)` matches the non-null path's cursor
3030
- // ordering, so callers tracking `cutoffMessageId` across runs see a
3031
- // consistent ordering when multiple rows share a millisecond timestamp.
3032
- return db
3033
- .select()
3034
- .from(messages)
3035
- .where(lineageMessageFilter(segments))
3036
- .orderBy(asc(messages.createdAt), asc(messages.id))
3037
- .all()
3038
- .map(parseMessage);
3039
- }
3040
- const ref = loadMessageBound(afterMessageId);
3041
- if (!ref) {
3018
+ const start = resolveMessagesAfterBound(after);
3019
+ if (start.kind === "vanished") {
3042
3020
  return [];
3043
3021
  }
3044
- // Same `(createdAt, id)` cursor as `countMessagesAfter` rows sharing
3045
- // the reference's millisecond timestamp would otherwise be skipped.
3022
+ // Secondary `asc(messages.id)` mirrors the bound's tie-breaker, so callers
3023
+ // tracking `cutoffMessageId` across runs see a consistent ordering when
3024
+ // multiple rows share a millisecond timestamp.
3046
3025
  return db
3047
3026
  .select()
3048
3027
  .from(messages)
3049
- .where(lineageMessagesAfterFilter(segments, ref))
3028
+ .where(
3029
+ start.kind === "all"
3030
+ ? lineageMessageFilter(segments)
3031
+ : lineageMessagesAfterFilter(segments, start.bound),
3032
+ )
3050
3033
  .orderBy(asc(messages.createdAt), asc(messages.id))
3051
3034
  .all()
3052
3035
  .map(parseMessage);
@@ -4304,12 +4287,12 @@ export function deleteLastExchange(conversationId: string): number {
4304
4287
 
4305
4288
  // Collect attachment IDs linked to the messages being deleted so we can
4306
4289
  // scope orphan cleanup to only those candidates (not freshly uploaded ones).
4307
- const messageIds = db
4308
- .select({ id: messages.id })
4290
+ const deletedRows = db
4291
+ .select({ id: messages.id, createdAt: messages.createdAt })
4309
4292
  .from(messages)
4310
4293
  .where(condition)
4311
- .all()
4312
- .map((r) => r.id);
4294
+ .all();
4295
+ const messageIds = deletedRows.map((r) => r.id);
4313
4296
  const candidateAttachmentIds =
4314
4297
  messageIds.length > 0
4315
4298
  ? db
@@ -4356,6 +4339,17 @@ export function deleteLastExchange(conversationId: string): number {
4356
4339
  enqueueDeleteMessageLexical(deletedMessageId);
4357
4340
  }
4358
4341
 
4342
+ // Notify `message-deleted` hooks for each removed row, as the
4343
+ // single-message primitive does: undo removes the same tail a regenerate
4344
+ // does, and a hook keeping a cursor on one of these rows needs its position.
4345
+ for (const row of deletedRows) {
4346
+ void runHook(HOOKS.MESSAGE_DELETED, {
4347
+ conversationId,
4348
+ messageId: row.id,
4349
+ createdAt: row.createdAt,
4350
+ } satisfies MessageDeletedInputContext);
4351
+ }
4352
+
4359
4353
  return deleted;
4360
4354
  }
4361
4355
 
@@ -4594,9 +4588,13 @@ export function deleteMessageById(
4594
4588
  .map((r) => r.attachmentId)
4595
4589
  .filter((id): id is string => id !== undefined);
4596
4590
 
4597
- // Look up the conversation before the transaction so we can recalculate lastMessageAt.
4591
+ // Look up the conversation before the transaction so we can recalculate
4592
+ // lastMessageAt, and the row's createdAt for the `message-deleted` hook.
4598
4593
  const msgRow = db
4599
- .select({ conversationId: messages.conversationId })
4594
+ .select({
4595
+ conversationId: messages.conversationId,
4596
+ createdAt: messages.createdAt,
4597
+ })
4600
4598
  .from(messages)
4601
4599
  .where(eq(messages.id, messageId))
4602
4600
  .get();
@@ -4652,6 +4650,16 @@ export function deleteMessageById(
4652
4650
  // not go through the conversation-level purge.
4653
4651
  if (msgRow) {
4654
4652
  enqueueDeleteMessageLexical(messageId);
4653
+
4654
+ // Notify `message-deleted` hooks (e.g. the memory plugin stamping a
4655
+ // retrospective cursor that sat on this row). Fire-and-forget from this
4656
+ // synchronous primitive, like `conversation-deleted`; the context carries
4657
+ // the row's position because the row itself is gone.
4658
+ void runHook(HOOKS.MESSAGE_DELETED, {
4659
+ conversationId: msgRow.conversationId,
4660
+ messageId,
4661
+ createdAt: msgRow.createdAt,
4662
+ } satisfies MessageDeletedInputContext);
4655
4663
  }
4656
4664
 
4657
4665
  return result;
@@ -227,9 +227,17 @@ export function lineageMessagesAfterFilter(
227
227
  ): SQL {
228
228
  return and(
229
229
  lineageMessageFilter(segments),
230
- or(
231
- gt(messages.createdAt, after.createdAt),
232
- and(eq(messages.createdAt, after.createdAt), gt(messages.id, after.id)),
233
- ),
230
+ messagesAfterBoundFilter(after),
231
+ ) as SQL;
232
+ }
233
+
234
+ /**
235
+ * Predicate for rows strictly after a `(createdAt, id)` bound, with the id
236
+ * tie-breaker so rows sharing the bound's millisecond keep their order.
237
+ */
238
+ export function messagesAfterBoundFilter(after: LineageBound): SQL {
239
+ return or(
240
+ gt(messages.createdAt, after.createdAt),
241
+ and(eq(messages.createdAt, after.createdAt), gt(messages.id, after.id)),
234
242
  ) as SQL;
235
243
  }
@@ -17,6 +17,7 @@ import {
17
17
  wrapUntrustedContent,
18
18
  } from "../security/untrusted-content.js";
19
19
  import { getLogger } from "../util/logger.js";
20
+ import { safeStringSlice } from "../util/unicode.js";
20
21
  import { isLexicalBackfillComplete } from "./checkpoints.js";
21
22
  import { unseenAttentionStateConditions } from "./conversation-attention-store.js";
22
23
  import type { ConversationRow } from "./conversation-crud.js";
@@ -1443,8 +1444,7 @@ function buildExcerptFromText(
1443
1444
  if (!match) {
1444
1445
  // Neither the query nor any of its tokens is present (e.g. the lexical
1445
1446
  // index matched JSON structure instead); fall back to the text start.
1446
- return text
1447
- .slice(0, EXCERPT_WINDOW * 2)
1447
+ return safeStringSlice(text, 0, EXCERPT_WINDOW * 2)
1448
1448
  .replace(/\s+/g, " ")
1449
1449
  .trim();
1450
1450
  }
@@ -0,0 +1,23 @@
1
+ import { isMessageKey } from "../i18n/index.js";
2
+
3
+ const REPLACEABLE_PATTERNS = [
4
+ /^Runtime:\s/,
5
+ /^New Conversation$/,
6
+ /^Untitled$/,
7
+ /^Untitled Conversation$/,
8
+ /^Generating title\.\.\.$/,
9
+ ];
10
+
11
+ /**
12
+ * Check whether a title is a system-generated placeholder that can be
13
+ * replaced or omitted without discarding user-provided copy.
14
+ */
15
+ export function isReplaceableTitle(title: string | null): boolean {
16
+ if (title == null || title.trim() === "") {
17
+ return true;
18
+ }
19
+ if (isMessageKey(title.trim())) {
20
+ return true;
21
+ }
22
+ return REPLACEABLE_PATTERNS.some((pattern) => pattern.test(title));
23
+ }