@vellumai/assistant 0.11.2 → 0.11.3-staging.2

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 (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. package/src/notifications/deferred-emit.ts +0 -147
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Guard against deriving a filesystem path from a `file:` URL's `.pathname`.
3
+ *
4
+ * `URL.pathname` is percent-encoded, so any path segment containing a space
5
+ * survives as `%20` and every downstream `existsSync` / spawn against it
6
+ * fails. This is not theoretical: macOS desktop installs live under
7
+ * `~/Library/Application Support/…`, so the encoded form broke every
8
+ * background worker spawn on that platform. `fileURLToPath()` decodes.
9
+ *
10
+ * Two shapes are checked:
11
+ * 1. `.pathname` read off a URL built from `import.meta.url`.
12
+ * 2. `.pathname` passed inside a `cmd: [...]` spawn array, the sink that
13
+ * turns an encoded path into a "Module not found" at runtime.
14
+ */
15
+
16
+ import { readdirSync, readFileSync } from "node:fs";
17
+ import { join, relative } from "node:path";
18
+ import { describe, expect, test } from "bun:test";
19
+
20
+ const SRC_DIR = join(import.meta.dir, "..");
21
+
22
+ /** This guard's own file, which necessarily contains the forbidden patterns. */
23
+ const SELF = "__tests__/file-url-path-guard.test.ts";
24
+
25
+ /** `.pathname` read off a URL derived from `import.meta.url`. */
26
+ const IMPORT_META_URL_PATHNAME =
27
+ /import\.meta\.url[\s\S]{0,80}?\)\s*\.pathname/;
28
+
29
+ /** A `cmd: [...]` spawn array (bounded so it can't run away over a file). */
30
+ const SPAWN_CMD_ARRAY = /cmd:\s*\[[\s\S]{0,300}?\]/g;
31
+
32
+ function collectSourceFiles(rootDir: string): string[] {
33
+ const pending = [rootDir];
34
+ const files: string[] = [];
35
+
36
+ while (pending.length > 0) {
37
+ const current = pending.pop();
38
+ if (!current) {
39
+ continue;
40
+ }
41
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
42
+ const fullPath = join(current, entry.name);
43
+ if (entry.isDirectory()) {
44
+ if (entry.name !== "node_modules") {
45
+ pending.push(fullPath);
46
+ }
47
+ continue;
48
+ }
49
+ if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
50
+ files.push(fullPath);
51
+ }
52
+ }
53
+ }
54
+
55
+ return files;
56
+ }
57
+
58
+ function offenders(predicate: (source: string) => boolean): string[] {
59
+ return collectSourceFiles(SRC_DIR)
60
+ .map((path) => ({ path, rel: relative(SRC_DIR, path) }))
61
+ .filter(({ rel }) => rel !== SELF)
62
+ .filter(({ path }) => predicate(readFileSync(path, "utf-8")))
63
+ .map(({ rel }) => rel)
64
+ .sort();
65
+ }
66
+
67
+ describe("file URL → path guard", () => {
68
+ test("no path is derived from import.meta.url via .pathname", () => {
69
+ expect(
70
+ offenders((source) => IMPORT_META_URL_PATHNAME.test(source)),
71
+ ).toEqual([]);
72
+ });
73
+
74
+ test("no .pathname is passed as a spawn argument", () => {
75
+ expect(
76
+ offenders((source) =>
77
+ [...source.matchAll(SPAWN_CMD_ARRAY)].some((match) =>
78
+ match[0].includes(".pathname"),
79
+ ),
80
+ ),
81
+ ).toEqual([]);
82
+ });
83
+ });
@@ -49,7 +49,7 @@ function sandboxPolicyFor(boundary: string): PathPolicy {
49
49
  // ===========================================================================
50
50
 
51
51
  describe("FileSystemOps symlink handling", () => {
52
- test("read blocks symlink pointing outside boundary", () => {
52
+ test("read blocks symlink pointing outside boundary", async () => {
53
53
  const boundary = makeTempDir();
54
54
  const outside = makeTempDir();
55
55
  const outsideFile = join(outside, "secret.txt");
@@ -58,7 +58,7 @@ describe("FileSystemOps symlink handling", () => {
58
58
  symlinkSync(outsideFile, join(boundary, "link.txt"));
59
59
  const ops = new FileSystemOps(sandboxPolicyFor(boundary));
60
60
 
61
- const result = ops.readFileSafe({ path: "link.txt" });
61
+ const result = await ops.readFileSafe({ path: "link.txt" });
62
62
  expect(result.ok).toBe(false);
63
63
  if (result.ok) {
64
64
  return;
@@ -66,14 +66,14 @@ describe("FileSystemOps symlink handling", () => {
66
66
  expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
67
67
  });
68
68
 
69
- test("read allows symlink within boundary", () => {
69
+ test("read allows symlink within boundary", async () => {
70
70
  const boundary = makeTempDir();
71
71
  const realFile = join(boundary, "real.txt");
72
72
  writeFileSync(realFile, "hello");
73
73
  symlinkSync(realFile, join(boundary, "link.txt"));
74
74
 
75
75
  const ops = new FileSystemOps(sandboxPolicyFor(boundary));
76
- const result = ops.readFileSafe({ path: "link.txt" });
76
+ const result = await ops.readFileSafe({ path: "link.txt" });
77
77
  expect(result.ok).toBe(true);
78
78
  if (!result.ok) {
79
79
  return;
@@ -81,13 +81,13 @@ describe("FileSystemOps symlink handling", () => {
81
81
  expect(result.value.content).toContain("hello");
82
82
  });
83
83
 
84
- test("write blocks creating file under symlinked dir pointing outside", () => {
84
+ test("write blocks creating file under symlinked dir pointing outside", async () => {
85
85
  const boundary = makeTempDir();
86
86
  const outside = makeTempDir();
87
87
  symlinkSync(outside, join(boundary, "link-dir"));
88
88
 
89
89
  const ops = new FileSystemOps(sandboxPolicyFor(boundary));
90
- const result = ops.writeFileSafe({
90
+ const result = await ops.writeFileSafe({
91
91
  path: "link-dir/evil.txt",
92
92
  content: "bad",
93
93
  });
@@ -100,7 +100,7 @@ describe("FileSystemOps symlink handling", () => {
100
100
  expect(existsSync(join(outside, "evil.txt"))).toBe(false);
101
101
  });
102
102
 
103
- test("edit blocks symlink pointing outside boundary", () => {
103
+ test("edit blocks symlink pointing outside boundary", async () => {
104
104
  const boundary = makeTempDir();
105
105
  const outside = makeTempDir();
106
106
  const outsideFile = join(outside, "target.txt");
@@ -108,7 +108,7 @@ describe("FileSystemOps symlink handling", () => {
108
108
  symlinkSync(outsideFile, join(boundary, "link.txt"));
109
109
 
110
110
  const ops = new FileSystemOps(sandboxPolicyFor(boundary));
111
- const result = ops.editFileSafe({
111
+ const result = await ops.editFileSafe({
112
112
  path: "link.txt",
113
113
  oldString: "original",
114
114
  newString: "modified",
@@ -129,12 +129,12 @@ describe("FileSystemOps symlink handling", () => {
129
129
  // ===========================================================================
130
130
 
131
131
  describe("FileSystemOps read offset/limit edge cases", () => {
132
- test("offset beyond file length returns empty content", () => {
132
+ test("offset beyond file length returns empty content", async () => {
133
133
  const dir = makeTempDir();
134
134
  writeFileSync(join(dir, "short.txt"), "a\nb\nc");
135
135
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
136
136
 
137
- const result = ops.readFileSafe({ path: "short.txt", offset: 100 });
137
+ const result = await ops.readFileSafe({ path: "short.txt", offset: 100 });
138
138
  expect(result.ok).toBe(true);
139
139
  if (!result.ok) {
140
140
  return;
@@ -142,12 +142,12 @@ describe("FileSystemOps read offset/limit edge cases", () => {
142
142
  expect(result.value.content).toBe("");
143
143
  });
144
144
 
145
- test("limit of zero returns empty content", () => {
145
+ test("limit of zero returns empty content", async () => {
146
146
  const dir = makeTempDir();
147
147
  writeFileSync(join(dir, "file.txt"), "a\nb\nc");
148
148
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
149
149
 
150
- const result = ops.readFileSafe({ path: "file.txt", limit: 0 });
150
+ const result = await ops.readFileSafe({ path: "file.txt", limit: 0 });
151
151
  expect(result.ok).toBe(true);
152
152
  if (!result.ok) {
153
153
  return;
@@ -155,12 +155,16 @@ describe("FileSystemOps read offset/limit edge cases", () => {
155
155
  expect(result.value.content).toBe("");
156
156
  });
157
157
 
158
- test("offset=1 reads from first line (1-indexed)", () => {
158
+ test("offset=1 reads from first line (1-indexed)", async () => {
159
159
  const dir = makeTempDir();
160
160
  writeFileSync(join(dir, "file.txt"), "first\nsecond\nthird");
161
161
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
162
162
 
163
- const result = ops.readFileSafe({ path: "file.txt", offset: 1, limit: 1 });
163
+ const result = await ops.readFileSafe({
164
+ path: "file.txt",
165
+ offset: 1,
166
+ limit: 1,
167
+ });
164
168
  expect(result.ok).toBe(true);
165
169
  if (!result.ok) {
166
170
  return;
@@ -169,12 +173,12 @@ describe("FileSystemOps read offset/limit edge cases", () => {
169
173
  expect(result.value.content).not.toContain("second");
170
174
  });
171
175
 
172
- test("limit exceeding file length returns all remaining lines", () => {
176
+ test("limit exceeding file length returns all remaining lines", async () => {
173
177
  const dir = makeTempDir();
174
178
  writeFileSync(join(dir, "file.txt"), "a\nb");
175
179
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
176
180
 
177
- const result = ops.readFileSafe({
181
+ const result = await ops.readFileSafe({
178
182
  path: "file.txt",
179
183
  offset: 1,
180
184
  limit: 1000,
@@ -187,12 +191,16 @@ describe("FileSystemOps read offset/limit edge cases", () => {
187
191
  expect(result.value.content).toContain("b");
188
192
  });
189
193
 
190
- test("read adds line numbers starting from offset", () => {
194
+ test("read adds line numbers starting from offset", async () => {
191
195
  const dir = makeTempDir();
192
196
  writeFileSync(join(dir, "file.txt"), "a\nb\nc\nd\ne");
193
197
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
194
198
 
195
- const result = ops.readFileSafe({ path: "file.txt", offset: 3, limit: 2 });
199
+ const result = await ops.readFileSafe({
200
+ path: "file.txt",
201
+ offset: 3,
202
+ limit: 2,
203
+ });
196
204
  expect(result.ok).toBe(true);
197
205
  if (!result.ok) {
198
206
  return;
@@ -210,7 +218,7 @@ describe("FileSystemOps read offset/limit edge cases", () => {
210
218
  // ===========================================================================
211
219
 
212
220
  describe("FileSystemOps edit match methods", () => {
213
- test("whitespace-normalized match succeeds", () => {
221
+ test("whitespace-normalized match succeeds", async () => {
214
222
  const dir = makeTempDir();
215
223
  writeFileSync(
216
224
  join(dir, "file.txt"),
@@ -218,7 +226,7 @@ describe("FileSystemOps edit match methods", () => {
218
226
  );
219
227
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
220
228
 
221
- const result = ops.editFileSafe({
229
+ const result = await ops.editFileSafe({
222
230
  path: "file.txt",
223
231
  oldString: "function foo() {\n return 1;\n}",
224
232
  newString: "function bar() {\n return 2;\n}",
@@ -233,12 +241,12 @@ describe("FileSystemOps edit match methods", () => {
233
241
  expect(result.value.newContent).toContain("bar");
234
242
  });
235
243
 
236
- test("fuzzy match succeeds with near-match", () => {
244
+ test("fuzzy match succeeds with near-match", async () => {
237
245
  const dir = makeTempDir();
238
246
  writeFileSync(join(dir, "file.txt"), "function fooo() {\n return 1;\n}\n");
239
247
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
240
248
 
241
- const result = ops.editFileSafe({
249
+ const result = await ops.editFileSafe({
242
250
  path: "file.txt",
243
251
  oldString: "function foo() {\n return 1;\n}",
244
252
  newString: "function bar() {\n return 2;\n}",
@@ -253,12 +261,12 @@ describe("FileSystemOps edit match methods", () => {
253
261
  expect(result.value.similarity).toBeLessThan(1);
254
262
  });
255
263
 
256
- test("edit returns actualOld and actualNew for fuzzy match", () => {
264
+ test("edit returns actualOld and actualNew for fuzzy match", async () => {
257
265
  const dir = makeTempDir();
258
266
  writeFileSync(join(dir, "file.txt"), "function fooo() {\n return 1;\n}\n");
259
267
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
260
268
 
261
- const result = ops.editFileSafe({
269
+ const result = await ops.editFileSafe({
262
270
  path: "file.txt",
263
271
  oldString: "function foo() {\n return 1;\n}",
264
272
  newString: "function bar() {\n return 2;\n}",
@@ -278,11 +286,14 @@ describe("FileSystemOps edit match methods", () => {
278
286
  // ===========================================================================
279
287
 
280
288
  describe("FileSystemOps write content tracking", () => {
281
- test("new file has empty oldContent", () => {
289
+ test("new file has empty oldContent", async () => {
282
290
  const dir = makeTempDir();
283
291
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
284
292
 
285
- const result = ops.writeFileSafe({ path: "brand-new.txt", content: "new" });
293
+ const result = await ops.writeFileSafe({
294
+ path: "brand-new.txt",
295
+ content: "new",
296
+ });
286
297
  expect(result.ok).toBe(true);
287
298
  if (!result.ok) {
288
299
  return;
@@ -291,12 +302,12 @@ describe("FileSystemOps write content tracking", () => {
291
302
  expect(result.value.isNewFile).toBe(true);
292
303
  });
293
304
 
294
- test("overwrite tracks oldContent and newContent", () => {
305
+ test("overwrite tracks oldContent and newContent", async () => {
295
306
  const dir = makeTempDir();
296
307
  writeFileSync(join(dir, "existing.txt"), "version 1");
297
308
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
298
309
 
299
- const result = ops.writeFileSafe({
310
+ const result = await ops.writeFileSafe({
300
311
  path: "existing.txt",
301
312
  content: "version 2",
302
313
  });
@@ -309,11 +320,14 @@ describe("FileSystemOps write content tracking", () => {
309
320
  expect(result.value.isNewFile).toBe(false);
310
321
  });
311
322
 
312
- test("write returns resolved absolute path", () => {
323
+ test("write returns resolved absolute path", async () => {
313
324
  const dir = makeTempDir();
314
325
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
315
326
 
316
- const result = ops.writeFileSafe({ path: "output.txt", content: "data" });
327
+ const result = await ops.writeFileSafe({
328
+ path: "output.txt",
329
+ content: "data",
330
+ });
317
331
  expect(result.ok).toBe(true);
318
332
  if (!result.ok) {
319
333
  return;
@@ -395,11 +409,11 @@ describe("formatWriteSummary", () => {
395
409
  // ===========================================================================
396
410
 
397
411
  describe("FileSystemOps path traversal prevention", () => {
398
- test("rejects absolute path outside boundary on read", () => {
412
+ test("rejects absolute path outside boundary on read", async () => {
399
413
  const dir = makeTempDir();
400
414
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
401
415
 
402
- const result = ops.readFileSafe({ path: "/etc/passwd" });
416
+ const result = await ops.readFileSafe({ path: "/etc/passwd" });
403
417
  expect(result.ok).toBe(false);
404
418
  if (result.ok) {
405
419
  return;
@@ -407,11 +421,11 @@ describe("FileSystemOps path traversal prevention", () => {
407
421
  expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
408
422
  });
409
423
 
410
- test("rejects absolute path outside boundary on write", () => {
424
+ test("rejects absolute path outside boundary on write", async () => {
411
425
  const dir = makeTempDir();
412
426
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
413
427
 
414
- const result = ops.writeFileSafe({
428
+ const result = await ops.writeFileSafe({
415
429
  path: "/tmp/evil-write.txt",
416
430
  content: "bad",
417
431
  });
@@ -422,11 +436,11 @@ describe("FileSystemOps path traversal prevention", () => {
422
436
  expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
423
437
  });
424
438
 
425
- test("rejects absolute path outside boundary on edit", () => {
439
+ test("rejects absolute path outside boundary on edit", async () => {
426
440
  const dir = makeTempDir();
427
441
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
428
442
 
429
- const result = ops.editFileSafe({
443
+ const result = await ops.editFileSafe({
430
444
  path: "/etc/hosts",
431
445
  oldString: "a",
432
446
  newString: "b",
@@ -439,12 +453,12 @@ describe("FileSystemOps path traversal prevention", () => {
439
453
  expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
440
454
  });
441
455
 
442
- test("rejects dot-dot traversal embedded in path on read", () => {
456
+ test("rejects dot-dot traversal embedded in path on read", async () => {
443
457
  const dir = makeTempDir();
444
458
  mkdirSync(join(dir, "sub"));
445
459
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
446
460
 
447
- const result = ops.readFileSafe({ path: "sub/../../etc/passwd" });
461
+ const result = await ops.readFileSafe({ path: "sub/../../etc/passwd" });
448
462
  expect(result.ok).toBe(false);
449
463
  if (result.ok) {
450
464
  return;
@@ -452,12 +466,12 @@ describe("FileSystemOps path traversal prevention", () => {
452
466
  expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
453
467
  });
454
468
 
455
- test("accepts absolute path inside boundary", () => {
469
+ test("accepts absolute path inside boundary", async () => {
456
470
  const dir = makeTempDir();
457
471
  writeFileSync(join(dir, "inside.txt"), "safe content");
458
472
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
459
473
 
460
- const result = ops.readFileSafe({ path: join(dir, "inside.txt") });
474
+ const result = await ops.readFileSafe({ path: join(dir, "inside.txt") });
461
475
  expect(result.ok).toBe(true);
462
476
  if (!result.ok) {
463
477
  return;
@@ -471,13 +485,13 @@ describe("FileSystemOps path traversal prevention", () => {
471
485
  // ===========================================================================
472
486
 
473
487
  describe("FileSystemOps binary file read", () => {
474
- test("reads binary content as utf-8 without crashing", () => {
488
+ test("reads binary content as utf-8 without crashing", async () => {
475
489
  const dir = makeTempDir();
476
490
  const binaryContent = Buffer.from([0x00, 0xff, 0x89, 0x50, 0x4e, 0x47]);
477
491
  writeFileSync(join(dir, "binary.bin"), binaryContent);
478
492
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
479
493
 
480
- const result = ops.readFileSafe({ path: "binary.bin" });
494
+ const result = await ops.readFileSafe({ path: "binary.bin" });
481
495
  // Should succeed — the file is readable, even if content has replacement chars
482
496
  expect(result.ok).toBe(true);
483
497
  });
@@ -488,12 +502,12 @@ describe("FileSystemOps binary file read", () => {
488
502
  // ===========================================================================
489
503
 
490
504
  describe("FileSystemOps empty file operations", () => {
491
- test("reads empty file successfully", () => {
505
+ test("reads empty file successfully", async () => {
492
506
  const dir = makeTempDir();
493
507
  writeFileSync(join(dir, "empty.txt"), "");
494
508
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
495
509
 
496
- const result = ops.readFileSafe({ path: "empty.txt" });
510
+ const result = await ops.readFileSafe({ path: "empty.txt" });
497
511
  expect(result.ok).toBe(true);
498
512
  if (!result.ok) {
499
513
  return;
@@ -502,11 +516,11 @@ describe("FileSystemOps empty file operations", () => {
502
516
  expect(result.value.content).toBeDefined();
503
517
  });
504
518
 
505
- test("write empty content creates empty file", () => {
519
+ test("write empty content creates empty file", async () => {
506
520
  const dir = makeTempDir();
507
521
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
508
522
 
509
- const result = ops.writeFileSafe({ path: "empty.txt", content: "" });
523
+ const result = await ops.writeFileSafe({ path: "empty.txt", content: "" });
510
524
  expect(result.ok).toBe(true);
511
525
  if (!result.ok) {
512
526
  return;
@@ -515,12 +529,12 @@ describe("FileSystemOps empty file operations", () => {
515
529
  expect(readFileSync(join(dir, "empty.txt"), "utf-8")).toBe("");
516
530
  });
517
531
 
518
- test("edit on empty file returns MATCH_NOT_FOUND", () => {
532
+ test("edit on empty file returns MATCH_NOT_FOUND", async () => {
519
533
  const dir = makeTempDir();
520
534
  writeFileSync(join(dir, "empty.txt"), "");
521
535
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
522
536
 
523
- const result = ops.editFileSafe({
537
+ const result = await ops.editFileSafe({
524
538
  path: "empty.txt",
525
539
  oldString: "something",
526
540
  newString: "else",
@@ -539,12 +553,12 @@ describe("FileSystemOps empty file operations", () => {
539
553
  // ===========================================================================
540
554
 
541
555
  describe("FileSystemOps /workspace path remapping", () => {
542
- test("read remaps /workspace/ path to boundary", () => {
556
+ test("read remaps /workspace/ path to boundary", async () => {
543
557
  const dir = makeTempDir();
544
558
  writeFileSync(join(dir, "file.txt"), "workspace content");
545
559
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
546
560
 
547
- const result = ops.readFileSafe({ path: "/workspace/file.txt" });
561
+ const result = await ops.readFileSafe({ path: "/workspace/file.txt" });
548
562
  expect(result.ok).toBe(true);
549
563
  if (!result.ok) {
550
564
  return;
@@ -552,11 +566,11 @@ describe("FileSystemOps /workspace path remapping", () => {
552
566
  expect(result.value.content).toContain("workspace content");
553
567
  });
554
568
 
555
- test("write remaps /workspace/ path to boundary", () => {
569
+ test("write remaps /workspace/ path to boundary", async () => {
556
570
  const dir = makeTempDir();
557
571
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
558
572
 
559
- const result = ops.writeFileSafe({
573
+ const result = await ops.writeFileSafe({
560
574
  path: "/workspace/new.txt",
561
575
  content: "remapped",
562
576
  });
@@ -568,12 +582,12 @@ describe("FileSystemOps /workspace path remapping", () => {
568
582
  expect(readFileSync(join(dir, "new.txt"), "utf-8")).toBe("remapped");
569
583
  });
570
584
 
571
- test("edit remaps /workspace/ path to boundary", () => {
585
+ test("edit remaps /workspace/ path to boundary", async () => {
572
586
  const dir = makeTempDir();
573
587
  writeFileSync(join(dir, "file.txt"), "old content");
574
588
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
575
589
 
576
- const result = ops.editFileSafe({
590
+ const result = await ops.editFileSafe({
577
591
  path: "/workspace/file.txt",
578
592
  oldString: "old content",
579
593
  newString: "new content",
@@ -587,11 +601,13 @@ describe("FileSystemOps /workspace path remapping", () => {
587
601
  expect(readFileSync(join(dir, "file.txt"), "utf-8")).toBe("new content");
588
602
  });
589
603
 
590
- test("/workspace traversal escape is blocked", () => {
604
+ test("/workspace traversal escape is blocked", async () => {
591
605
  const dir = makeTempDir();
592
606
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
593
607
 
594
- const result = ops.readFileSafe({ path: "/workspace/../../../etc/passwd" });
608
+ const result = await ops.readFileSafe({
609
+ path: "/workspace/../../../etc/passwd",
610
+ });
595
611
  expect(result.ok).toBe(false);
596
612
  if (result.ok) {
597
613
  return;
@@ -605,12 +621,12 @@ describe("FileSystemOps /workspace path remapping", () => {
605
621
  // ===========================================================================
606
622
 
607
623
  describe("FileSystemOps custom size limit", () => {
608
- test("read rejects file exceeding custom limit", () => {
624
+ test("read rejects file exceeding custom limit", async () => {
609
625
  const dir = makeTempDir();
610
626
  writeFileSync(join(dir, "big.txt"), "x".repeat(500));
611
627
  const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
612
628
 
613
- const result = ops.readFileSafe({ path: "big.txt" });
629
+ const result = await ops.readFileSafe({ path: "big.txt" });
614
630
  expect(result.ok).toBe(false);
615
631
  if (result.ok) {
616
632
  return;
@@ -618,20 +634,20 @@ describe("FileSystemOps custom size limit", () => {
618
634
  expect(result.error.code).toBe("SIZE_LIMIT_EXCEEDED");
619
635
  });
620
636
 
621
- test("read accepts file within custom limit", () => {
637
+ test("read accepts file within custom limit", async () => {
622
638
  const dir = makeTempDir();
623
639
  writeFileSync(join(dir, "small.txt"), "x".repeat(50));
624
640
  const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
625
641
 
626
- const result = ops.readFileSafe({ path: "small.txt" });
642
+ const result = await ops.readFileSafe({ path: "small.txt" });
627
643
  expect(result.ok).toBe(true);
628
644
  });
629
645
 
630
- test("write rejects content exceeding custom limit", () => {
646
+ test("write rejects content exceeding custom limit", async () => {
631
647
  const dir = makeTempDir();
632
648
  const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
633
649
 
634
- const result = ops.writeFileSafe({
650
+ const result = await ops.writeFileSafe({
635
651
  path: "big.txt",
636
652
  content: "x".repeat(500),
637
653
  });
@@ -642,12 +658,12 @@ describe("FileSystemOps custom size limit", () => {
642
658
  expect(result.error.code).toBe("SIZE_LIMIT_EXCEEDED");
643
659
  });
644
660
 
645
- test("edit rejects file exceeding custom limit", () => {
661
+ test("edit rejects file exceeding custom limit", async () => {
646
662
  const dir = makeTempDir();
647
663
  writeFileSync(join(dir, "big.txt"), "x".repeat(500));
648
664
  const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
649
665
 
650
- const result = ops.editFileSafe({
666
+ const result = await ops.editFileSafe({
651
667
  path: "big.txt",
652
668
  oldString: "x",
653
669
  newString: "y",
@@ -660,12 +676,12 @@ describe("FileSystemOps custom size limit", () => {
660
676
  expect(result.error.code).toBe("SIZE_LIMIT_EXCEEDED");
661
677
  });
662
678
 
663
- test("no size limit when not specified (defaults to 100MB)", () => {
679
+ test("no size limit when not specified (defaults to 100MB)", async () => {
664
680
  const dir = makeTempDir();
665
681
  writeFileSync(join(dir, "file.txt"), "x".repeat(1000));
666
682
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
667
683
 
668
- const result = ops.readFileSafe({ path: "file.txt" });
684
+ const result = await ops.readFileSafe({ path: "file.txt" });
669
685
  expect(result.ok).toBe(true);
670
686
  });
671
687
  });
@@ -440,7 +440,7 @@ describe("HTTP route: handleCreateVerificationSession (guardian path)", () => {
440
440
  test("creates inbound challenge when destination is absent", async () => {
441
441
  const result = (await handleCreateVerificationSession({
442
442
  body: { channel: "phone" },
443
- })) as Record<string, unknown>;
443
+ })) as unknown as Record<string, unknown>;
444
444
  expect(result.success).toBe(true);
445
445
  expect(result.channel).toBe("phone");
446
446
  });
@@ -448,7 +448,7 @@ describe("HTTP route: handleCreateVerificationSession (guardian path)", () => {
448
448
  test("returns success for valid voice start", async () => {
449
449
  const result = (await handleCreateVerificationSession({
450
450
  body: { channel: "phone", destination: "+15559999999" },
451
- })) as Record<string, unknown>;
451
+ })) as unknown as Record<string, unknown>;
452
452
  expect(result.success).toBe(true);
453
453
  expect(result.verificationSessionId).toBeDefined();
454
454
  });
@@ -471,7 +471,7 @@ describe("HTTP route: handleResendVerificationSession (guardian path)", () => {
471
471
  // Start a session first
472
472
  const startResult = (await handleCreateVerificationSession({
473
473
  body: { channel: "phone", destination: "+15556667777" },
474
- })) as Record<string, unknown>;
474
+ })) as unknown as Record<string, unknown>;
475
475
  expect(startResult.success).toBe(true);
476
476
 
477
477
  // Expire the cooldown so resend is allowed
@@ -489,7 +489,7 @@ describe("HTTP route: handleResendVerificationSession (guardian path)", () => {
489
489
  channel: "phone",
490
490
  originConversationId: "conv-resend-http-origin",
491
491
  },
492
- })) as Record<string, unknown>;
492
+ })) as unknown as Record<string, unknown>;
493
493
  expect(resendResult.success).toBe(true);
494
494
  expect(resendResult.originConversationId).toBe("conv-resend-http-origin");
495
495
  });
@@ -505,7 +505,7 @@ describe("HTTP route: handleCancelVerificationSession (guardian path)", () => {
505
505
  test("returns success even when no active session exists", async () => {
506
506
  const result = (await handleCancelVerificationSession({
507
507
  body: { channel: "phone" },
508
- })) as Record<string, unknown>;
508
+ })) as unknown as Record<string, unknown>;
509
509
  expect(result.success).toBe(true);
510
510
  });
511
511
 
@@ -513,13 +513,13 @@ describe("HTTP route: handleCancelVerificationSession (guardian path)", () => {
513
513
  // Start a session
514
514
  const startResult = (await handleCreateVerificationSession({
515
515
  body: { channel: "phone", destination: "+15558887777" },
516
- })) as Record<string, unknown>;
516
+ })) as unknown as Record<string, unknown>;
517
517
  expect(startResult.success).toBe(true);
518
518
 
519
519
  // Cancel it
520
520
  const cancelResult = (await handleCancelVerificationSession({
521
521
  body: { channel: "phone" },
522
- })) as Record<string, unknown>;
522
+ })) as unknown as Record<string, unknown>;
523
523
  expect(cancelResult.success).toBe(true);
524
524
  });
525
525
  });
@@ -575,7 +575,7 @@ describe("origin conversation linkage", () => {
575
575
  destination: "+15557776666",
576
576
  originConversationId: "conv-origin-http-test",
577
577
  },
578
- })) as Record<string, unknown>;
578
+ })) as unknown as Record<string, unknown>;
579
579
  expect(result.success).toBe(true);
580
580
  expect(result.originConversationId).toBe("conv-origin-http-test");
581
581
  });
@@ -139,9 +139,22 @@ export function createGatewayVerificationSessionsStub(options: {
139
139
  throwIfUnreachable("updateSessionDelivery");
140
140
  calls.updateSessionDelivery.push(args);
141
141
  },
142
- findActiveSession: async () => {
142
+ // Honours the caller's `expectedExternalUserId` filter the way the
143
+ // gateway does. Returning the session for every filter would make a
144
+ // "someone else is verifying" test pass against a lookup that, against
145
+ // the real gateway, would have found nothing.
146
+ findActiveSession: async (...args: unknown[]) => {
143
147
  calls.sessionReads.push("findActiveSession");
144
148
  throwIfUnreachable("findActiveSession");
149
+ const wantedActor = (
150
+ args[1] as { expectedExternalUserId?: string } | undefined
151
+ )?.expectedExternalUserId;
152
+ if (
153
+ wantedActor !== undefined &&
154
+ state.activeSession?.expectedExternalUserId !== wantedActor
155
+ ) {
156
+ return null;
157
+ }
145
158
  return state.activeSession;
146
159
  },
147
160
  getPendingSession: async () => {