@vellumai/assistant 0.8.0 → 0.8.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 (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -1879,7 +1879,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
1879
1879
  },
1880
1880
  ];
1881
1881
 
1882
- const FLAT_REMINDER = buildPkbReminder([]);
1882
+ const FLAT_REMINDER = buildPkbReminder([], false);
1883
1883
 
1884
1884
  // Use a platform-agnostic absolute workspace root so the tests work on
1885
1885
  // macOS and Linux runners alike. `pkbRoot` sits under `pkbWorkingDir` to
@@ -2135,7 +2135,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2135
2135
  role: "user",
2136
2136
  content: [
2137
2137
  { type: "text", text: "hello" },
2138
- { type: "text", text: buildPkbReminder([]) },
2138
+ { type: "text", text: buildPkbReminder([], false) },
2139
2139
  ],
2140
2140
  };
2141
2141
  const hintedMessage: Message = {
@@ -2144,7 +2144,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2144
2144
  { type: "text", text: "hello" },
2145
2145
  {
2146
2146
  type: "text",
2147
- text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"]),
2147
+ text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"], false),
2148
2148
  },
2149
2149
  ],
2150
2150
  };
@@ -4760,7 +4760,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
4760
4760
  mode: "full",
4761
4761
  });
4762
4762
 
4763
- const expected = buildPkbReminder([]);
4763
+ const expected = buildPkbReminder([], false);
4764
4764
  expect(blocks.pkbSystemReminder).toBe(expected);
4765
4765
  });
4766
4766
 
@@ -1,10 +1,16 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { writeFileSync } from "node:fs";
2
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
3
 
4
+ import {
5
+ invalidateConfigCache,
6
+ loadRawConfig,
7
+ } from "../config/loader.js";
3
8
  import {
4
9
  classifySlash,
5
10
  resolveSlash,
6
11
  type SlashContext,
7
12
  } from "../daemon/conversation-slash.js";
13
+ import { getWorkspaceConfigPath } from "../util/platform.js";
8
14
 
9
15
  function makeSlashContext(overrides: Partial<SlashContext> = {}): SlashContext {
10
16
  return {
@@ -37,12 +43,12 @@ describe("resolveSlash /commands interface-aware help", () => {
37
43
  "/commands — List all available commands",
38
44
  "/compact — Force context compaction immediately",
39
45
  "/context — Show conversation context usage",
46
+ "/model — List or switch inference profile",
40
47
  "/models — List all available models",
41
48
  "/status — Show conversation status and context usage",
42
49
  "/btw — Ask a side question while the assistant is working",
43
50
  "/fork — Fork the current conversation into a new branch",
44
51
  ]);
45
- expect(lines).not.toContain("/model — Switch the active model");
46
52
  });
47
53
 
48
54
  test("renders iOS command help with /fork", async () => {
@@ -53,6 +59,7 @@ describe("resolveSlash /commands interface-aware help", () => {
53
59
  "/commands — List all available commands",
54
60
  "/compact — Force context compaction immediately",
55
61
  "/context — Show conversation context usage",
62
+ "/model — List or switch inference profile",
56
63
  "/models — List all available models",
57
64
  "/status — Show conversation status and context usage",
58
65
  "/btw — Ask a side question while the assistant is working",
@@ -68,6 +75,7 @@ describe("resolveSlash /commands interface-aware help", () => {
68
75
  "/commands — List all available commands",
69
76
  "/compact — Force context compaction immediately",
70
77
  "/context — Show conversation context usage",
78
+ "/model — List or switch inference profile",
71
79
  "/models — List all available models",
72
80
  "/status — Show conversation status and context usage",
73
81
  "/btw — Ask a side question while the assistant is working",
@@ -80,6 +88,7 @@ describe("resolveSlash /commands interface-aware help", () => {
80
88
  "/commands — List all available commands",
81
89
  "/compact — Force context compaction immediately",
82
90
  "/context — Show conversation context usage",
91
+ "/model — List or switch inference profile",
83
92
  "/models — List all available models",
84
93
  "/status — Show conversation status and context usage",
85
94
  ]);
@@ -90,6 +99,7 @@ describe("resolveSlash /commands interface-aware help", () => {
90
99
  expect(lines).toEqual([
91
100
  "/commands — List all available commands",
92
101
  "/compact — Force context compaction immediately",
102
+ "/model — List or switch inference profile",
93
103
  "/models — List all available models",
94
104
  ]);
95
105
  });
@@ -128,6 +138,55 @@ describe("resolveSlash command contract", () => {
128
138
  });
129
139
  });
130
140
 
141
+ describe("resolveSlash /compact target override", () => {
142
+ test("plain /compact returns no override", async () => {
143
+ const result = await resolveSlash("/compact");
144
+ expect(result).toEqual({ kind: "compact" });
145
+ });
146
+
147
+ test("/compact <integer> sets explicit token target", async () => {
148
+ const result = await resolveSlash("/compact 30000");
149
+ expect(result).toEqual({
150
+ kind: "compact",
151
+ targetInputTokensOverride: 30000,
152
+ });
153
+ });
154
+
155
+ test("/compact <n>k expands to thousands", async () => {
156
+ const result = await resolveSlash("/compact 30k");
157
+ expect(result).toEqual({
158
+ kind: "compact",
159
+ targetInputTokensOverride: 30_000,
160
+ });
161
+ });
162
+
163
+ test("/compact <n>m expands to millions", async () => {
164
+ const result = await resolveSlash("/compact 1.5M");
165
+ expect(result).toEqual({
166
+ kind: "compact",
167
+ targetInputTokensOverride: 1_500_000,
168
+ });
169
+ });
170
+
171
+ test("/compact rejects malformed args with usage hint", async () => {
172
+ const result = await resolveSlash("/compact bogus");
173
+ expect(result.kind).toBe("unknown");
174
+ if (result.kind !== "unknown") throw new Error("expected unknown");
175
+ expect(result.message).toContain("`bogus`");
176
+ expect(result.message).toContain("/compact");
177
+ });
178
+
179
+ test("/compact rejects zero", async () => {
180
+ const result = await resolveSlash("/compact 0");
181
+ expect(result.kind).toBe("unknown");
182
+ });
183
+
184
+ test("/compact rejects negative numbers", async () => {
185
+ const result = await resolveSlash("/compact -50");
186
+ expect(result.kind).toBe("unknown");
187
+ });
188
+ });
189
+
131
190
  describe("classifySlash is a pure classifier matching resolveSlash kinds", () => {
132
191
  // Lookahead in `buildPassthroughBatch` must not run `resolveSlash`'s side
133
192
  // effects. The pure classifier is synchronous, takes no side-effecting
@@ -141,11 +200,16 @@ describe("classifySlash is a pure classifier matching resolveSlash kinds", () =>
141
200
  { input: "/status", kind: "unknown" },
142
201
  { input: "/commands", kind: "unknown" },
143
202
  { input: "/compact", kind: "compact" },
203
+ { input: "/compact 30000", kind: "compact" },
204
+ { input: "/compact 30k", kind: "compact" },
205
+ { input: "/compact 1.5M", kind: "compact" },
206
+ { input: "/compact bogus", kind: "unknown" },
144
207
  { input: "/model", kind: "unknown" },
145
208
  { input: "/model foo", kind: "unknown" },
146
209
  { input: "/opus", kind: "unknown" },
147
210
  { input: "hello", kind: "passthrough" },
148
211
  { input: " /compact ", kind: "compact" },
212
+ { input: " /compact 50k ", kind: "compact" },
149
213
  { input: "/models foo", kind: "passthrough" },
150
214
  ];
151
215
 
@@ -160,3 +224,131 @@ describe("classifySlash is a pure classifier matching resolveSlash kinds", () =>
160
224
  });
161
225
  }
162
226
  });
227
+
228
+ // ── /model — inference profile switcher ────────────────────────────
229
+
230
+ function writeFixtureConfig(config: Record<string, unknown>): void {
231
+ writeFileSync(getWorkspaceConfigPath(), JSON.stringify(config), "utf-8");
232
+ invalidateConfigCache();
233
+ }
234
+
235
+ describe("resolveSlash /model — inference profile switcher", () => {
236
+ beforeEach(() => {
237
+ writeFixtureConfig({
238
+ llm: {
239
+ profiles: {
240
+ balanced: {
241
+ label: "Balanced",
242
+ description: "Default mix of speed and quality",
243
+ },
244
+ "cost-optimized": {
245
+ label: "Cost-optimized",
246
+ description: "Cheaper models, slower",
247
+ },
248
+ "short-context": {
249
+ label: "Short context",
250
+ status: "disabled",
251
+ },
252
+ },
253
+ profileOrder: ["balanced", "cost-optimized", "short-context"],
254
+ activeProfile: "balanced",
255
+ },
256
+ });
257
+ });
258
+
259
+ afterEach(() => {
260
+ invalidateConfigCache();
261
+ });
262
+
263
+ test("`/model` lists profiles with current marker, status, and description", async () => {
264
+ const result = await resolveSlash("/model");
265
+ expect(result.kind).toBe("unknown");
266
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
267
+ expect(result.message).toContain("Inference profiles:");
268
+ expect(result.message).toContain(
269
+ "`balanced` (Balanced) **[current]** — Default mix of speed and quality",
270
+ );
271
+ expect(result.message).toContain(
272
+ "`cost-optimized` (Cost-optimized) — Cheaper models, slower",
273
+ );
274
+ expect(result.message).toContain(
275
+ "`short-context` (Short context) *(disabled)*",
276
+ );
277
+ expect(result.message).toContain("Switch with `/model <name>`.");
278
+ });
279
+
280
+ test("`/model <name>` switches the active profile and writes config to disk", async () => {
281
+ const result = await resolveSlash("/model cost-optimized");
282
+ expect(result.kind).toBe("unknown");
283
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
284
+ expect(result.message).toBe(
285
+ "Switched to profile `cost-optimized` (Cost-optimized).",
286
+ );
287
+
288
+ const persisted = loadRawConfig() as {
289
+ llm?: { activeProfile?: string };
290
+ };
291
+ expect(persisted.llm?.activeProfile).toBe("cost-optimized");
292
+ });
293
+
294
+ test("`/model <unknown>` returns an error with available profile names", async () => {
295
+ const result = await resolveSlash("/model gemini");
296
+ expect(result.kind).toBe("unknown");
297
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
298
+ expect(result.message).toContain("Profile `gemini` not found.");
299
+ expect(result.message).toContain("`balanced`");
300
+ expect(result.message).toContain("`cost-optimized`");
301
+ });
302
+
303
+ test("`/model <disabled>` refuses to switch and points at Settings", async () => {
304
+ const result = await resolveSlash("/model short-context");
305
+ expect(result.kind).toBe("unknown");
306
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
307
+ expect(result.message).toBe(
308
+ "Profile `short-context` is disabled. Enable it in **Settings → Models & Services** first.",
309
+ );
310
+ // Disk should NOT have been written.
311
+ const persisted = loadRawConfig() as {
312
+ llm?: { activeProfile?: string };
313
+ };
314
+ expect(persisted.llm?.activeProfile).toBe("balanced");
315
+ });
316
+
317
+ test("`/model <current>` is a no-op with a friendly message", async () => {
318
+ const result = await resolveSlash("/model balanced");
319
+ expect(result.kind).toBe("unknown");
320
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
321
+ expect(result.message).toBe(
322
+ "Already using profile `balanced` (Balanced).",
323
+ );
324
+ });
325
+
326
+ test("`/model` with no profiles defined points at Settings", async () => {
327
+ writeFixtureConfig({
328
+ llm: { profiles: {}, profileOrder: [] },
329
+ });
330
+ const result = await resolveSlash("/model");
331
+ expect(result.kind).toBe("unknown");
332
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
333
+ expect(result.message).toBe(
334
+ "No inference profiles are defined. Use **Settings → Models & Services** to create one.",
335
+ );
336
+ });
337
+
338
+ test("`/model <name>` trims surrounding whitespace from the argument", async () => {
339
+ const result = await resolveSlash("/model cost-optimized ");
340
+ expect(result.kind).toBe("unknown");
341
+ if (result.kind !== "unknown") throw new Error("expected unknown kind");
342
+ expect(result.message).toBe(
343
+ "Switched to profile `cost-optimized` (Cost-optimized).",
344
+ );
345
+ });
346
+
347
+ test("`/models` (plural) is not parsed as a /model invocation", async () => {
348
+ // /models is a separate command handled elsewhere; this test guards the
349
+ // boundary so we don't accidentally swallow it as a typo'd /model.
350
+ expect(classifySlash("/models")).toBe("unknown");
351
+ // /models foo is passthrough (existing behavior).
352
+ expect(classifySlash("/models foo")).toBe("passthrough");
353
+ });
354
+ });
@@ -11,6 +11,16 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
11
 
12
12
  const sentMessages: unknown[] = [];
13
13
  let mockHasClient = true;
14
+ // Default principal id used for both ctx.trustContext and clients in the
15
+ // existing single-user tests. Tests that exercise cross-user behaviour
16
+ // override this on individual clients and on the SurfaceConversationContext.
17
+ const DEFAULT_PRINCIPAL = "user-1";
18
+ type MockClient = {
19
+ clientId: string;
20
+ capabilities: string[];
21
+ actorPrincipalId?: string;
22
+ };
23
+ let mockHubClients: MockClient[] = [];
14
24
 
15
25
  mock.module("../runtime/assistant-event-hub.js", () => ({
16
26
  broadcastMessage: (msg: unknown) => sentMessages.push(msg),
@@ -19,6 +29,12 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
19
29
  cap === "host_app_control" && mockHasClient
20
30
  ? { id: "mock-client" }
21
31
  : null,
32
+ listClientsByCapability: (cap: string) =>
33
+ mockHubClients.filter((c) => c.capabilities.includes(cap)),
34
+ getClientById: (id: string) =>
35
+ mockHubClients.find((c) => c.clientId === id),
36
+ getActorPrincipalIdForClient: (id: string) =>
37
+ mockHubClients.find((c) => c.clientId === id)?.actorPrincipalId,
22
38
  },
23
39
  }));
24
40
 
@@ -56,9 +72,18 @@ function buildMockContext(
56
72
  setHostAppControlProxy?: (
57
73
  proxy: InstanceType<typeof HostAppControlProxy> | undefined,
58
74
  ) => void,
75
+ trustGuardianPrincipalId: string | null = DEFAULT_PRINCIPAL,
59
76
  ): SurfaceConversationContext {
60
77
  return {
61
78
  conversationId,
79
+ trustContext:
80
+ trustGuardianPrincipalId != null
81
+ ? {
82
+ sourceChannel: "vellum",
83
+ trustClass: "guardian",
84
+ guardianPrincipalId: trustGuardianPrincipalId,
85
+ }
86
+ : undefined,
62
87
  traceEmitter: { emit: () => {} },
63
88
  sendToClient: () => {},
64
89
  pendingSurfaceActions: new Map(),
@@ -86,11 +111,13 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
86
111
  beforeEach(() => {
87
112
  sentMessages.length = 0;
88
113
  mockHasClient = true;
114
+ mockHubClients = [];
89
115
  _resetActiveAppControlSession();
90
116
  });
91
117
 
92
118
  afterEach(() => {
93
119
  _resetActiveAppControlSession();
120
+ mockHubClients = [];
94
121
  });
95
122
 
96
123
  // -------------------------------------------------------------------------
@@ -130,15 +157,18 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
130
157
  proxy.dispose();
131
158
  });
132
159
 
133
- test("returns isError for app_control_stop when no proxy is attached", async () => {
160
+ test("app_control_stop succeeds idempotently when no proxy is attached", async () => {
161
+ // Stop is local-only and runs BEFORE the isAvailable() gate so a
162
+ // disconnected client cannot strand the singleton lock. With no proxy
163
+ // attached at all, it must still succeed as a no-op without dispatching.
134
164
  const ctx = buildMockContext();
135
165
 
136
166
  const result = await surfaceProxyResolver(ctx, "app_control_stop", {
137
167
  tool: "stop",
138
168
  });
139
169
 
140
- expect(result.isError).toBe(true);
141
- expect(result.content).toContain("not available");
170
+ expect(result.isError).toBe(false);
171
+ expect(result.content.toLowerCase()).toContain("stopped");
142
172
  expect(sentMessages).toHaveLength(0);
143
173
  });
144
174
  });
@@ -325,4 +355,294 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
325
355
  ownerProxy.dispose();
326
356
  });
327
357
  });
358
+
359
+ // -------------------------------------------------------------------------
360
+ // target_client_id validation — mirrors host_cu's targetClientId tests in
361
+ // cu-unified-flow.test.ts. The resolver validates the explicit target
362
+ // before recordAction-equivalents so an invalid or cross-user id never
363
+ // reaches the proxy.
364
+ // -------------------------------------------------------------------------
365
+
366
+ describe("target_client_id validation", () => {
367
+ test("returns fast error when target_client_id does not match any connected client", async () => {
368
+ mockHubClients = [
369
+ {
370
+ clientId: "client-a",
371
+ capabilities: ["host_app_control"],
372
+ actorPrincipalId: DEFAULT_PRINCIPAL,
373
+ },
374
+ ];
375
+ const proxy = new HostAppControlProxy("conv-1");
376
+ const ctx = buildMockContext(proxy, "conv-1");
377
+ _setActiveAppControlSession({
378
+ conversationId: "conv-1",
379
+ app: "com.example.editor",
380
+ });
381
+
382
+ const result = await surfaceProxyResolver(ctx, "app_control_observe", {
383
+ app: "com.example.editor",
384
+ target_client_id: "missing-client",
385
+ });
386
+
387
+ expect(result.isError).toBe(true);
388
+ expect(result.content).toContain("missing-client");
389
+ expect(result.content).toContain("host_app_control");
390
+ // No envelope dispatched — fail-fast before request().
391
+ expect(sentMessages).toHaveLength(0);
392
+
393
+ proxy.dispose();
394
+ });
395
+
396
+ test("returns fast error when target_client_id points to a client without host_app_control capability", async () => {
397
+ mockHubClients = [
398
+ {
399
+ clientId: "wrong-cap-client",
400
+ capabilities: ["host_bash"], // not host_app_control
401
+ actorPrincipalId: DEFAULT_PRINCIPAL,
402
+ },
403
+ ];
404
+ const proxy = new HostAppControlProxy("conv-1");
405
+ const ctx = buildMockContext(proxy, "conv-1");
406
+ _setActiveAppControlSession({
407
+ conversationId: "conv-1",
408
+ app: "com.example.editor",
409
+ });
410
+
411
+ const result = await surfaceProxyResolver(ctx, "app_control_observe", {
412
+ app: "com.example.editor",
413
+ target_client_id: "wrong-cap-client",
414
+ });
415
+
416
+ expect(result.isError).toBe(true);
417
+ expect(result.content).toContain("wrong-cap-client");
418
+ expect(result.content).toContain("does not support host_app_control");
419
+ expect(sentMessages).toHaveLength(0);
420
+
421
+ proxy.dispose();
422
+ });
423
+
424
+ test("dispatches with targetClientId when target_client_id is valid", async () => {
425
+ mockHubClients = [
426
+ {
427
+ clientId: "client-a",
428
+ capabilities: ["host_app_control"],
429
+ actorPrincipalId: DEFAULT_PRINCIPAL,
430
+ },
431
+ {
432
+ clientId: "client-b",
433
+ capabilities: ["host_app_control"],
434
+ actorPrincipalId: DEFAULT_PRINCIPAL,
435
+ },
436
+ ];
437
+ const proxy = new HostAppControlProxy("conv-1");
438
+ const ctx = buildMockContext(proxy, "conv-1");
439
+ _setActiveAppControlSession({
440
+ conversationId: "conv-1",
441
+ app: "com.example.editor",
442
+ });
443
+
444
+ const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
445
+ app: "com.example.editor",
446
+ target_client_id: "client-b",
447
+ });
448
+
449
+ // Exactly one envelope dispatched, addressed to client-b.
450
+ expect(sentMessages).toHaveLength(1);
451
+ const sent = sentMessages[0] as Record<string, unknown>;
452
+ expect(sent.type).toBe("host_app_control_request");
453
+ expect(sent.targetClientId).toBe("client-b");
454
+
455
+ proxy.resolve(sent.requestId as string, {
456
+ requestId: "ignored-by-proxy",
457
+ state: "running",
458
+ });
459
+ const result = await resultPromise;
460
+ expect(result.isError).toBe(false);
461
+
462
+ proxy.dispose();
463
+ });
464
+ });
465
+
466
+ // -------------------------------------------------------------------------
467
+ // Multi-client ambiguity guard — when the LLM omits target_client_id and
468
+ // multiple same-user host_app_control clients are connected, the resolver
469
+ // must error rather than broadcast (one app-control session per client).
470
+ // -------------------------------------------------------------------------
471
+
472
+ describe("multi-client ambiguity guard", () => {
473
+ test("errors when multiple same-user clients connected and no target_client_id given", async () => {
474
+ mockHubClients = [
475
+ {
476
+ clientId: "client-a",
477
+ capabilities: ["host_app_control"],
478
+ actorPrincipalId: DEFAULT_PRINCIPAL,
479
+ },
480
+ {
481
+ clientId: "client-b",
482
+ capabilities: ["host_app_control"],
483
+ actorPrincipalId: DEFAULT_PRINCIPAL,
484
+ },
485
+ ];
486
+ const proxy = new HostAppControlProxy("conv-1");
487
+ const ctx = buildMockContext(proxy, "conv-1");
488
+ _setActiveAppControlSession({
489
+ conversationId: "conv-1",
490
+ app: "com.example.editor",
491
+ });
492
+
493
+ const result = await surfaceProxyResolver(ctx, "app_control_observe", {
494
+ app: "com.example.editor",
495
+ });
496
+
497
+ expect(result.isError).toBe(true);
498
+ expect(result.content).toContain(
499
+ "multiple clients support host_app_control",
500
+ );
501
+ expect(result.content).toContain("target_client_id");
502
+ // No envelope dispatched.
503
+ expect(sentMessages).toHaveLength(0);
504
+
505
+ proxy.dispose();
506
+ });
507
+
508
+ test("auto-resolves to the unique same-user client when cross-user clients are also present", async () => {
509
+ mockHubClients = [
510
+ {
511
+ clientId: "client-mine",
512
+ capabilities: ["host_app_control"],
513
+ actorPrincipalId: DEFAULT_PRINCIPAL,
514
+ },
515
+ {
516
+ clientId: "client-other",
517
+ capabilities: ["host_app_control"],
518
+ actorPrincipalId: "user-2",
519
+ },
520
+ ];
521
+ const proxy = new HostAppControlProxy("conv-1");
522
+ const ctx = buildMockContext(proxy, "conv-1");
523
+ _setActiveAppControlSession({
524
+ conversationId: "conv-1",
525
+ app: "com.example.editor",
526
+ });
527
+
528
+ const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
529
+ app: "com.example.editor",
530
+ });
531
+
532
+ // Resolver must explicitly target the same-user client to prevent the
533
+ // proxy from broadcasting the action across the cross-user client too.
534
+ expect(sentMessages).toHaveLength(1);
535
+ const sent = sentMessages[0] as Record<string, unknown>;
536
+ expect(sent.targetClientId).toBe("client-mine");
537
+
538
+ proxy.resolve(sent.requestId as string, {
539
+ requestId: "ignored-by-proxy",
540
+ state: "running",
541
+ });
542
+ const result = await resultPromise;
543
+ expect(result.isError).toBe(false);
544
+
545
+ proxy.dispose();
546
+ });
547
+
548
+ test("single same-user client with no target proceeds without forcing targetClientId", async () => {
549
+ mockHubClients = [
550
+ {
551
+ clientId: "only-client",
552
+ capabilities: ["host_app_control"],
553
+ actorPrincipalId: DEFAULT_PRINCIPAL,
554
+ },
555
+ ];
556
+ const proxy = new HostAppControlProxy("conv-1");
557
+ const ctx = buildMockContext(proxy, "conv-1");
558
+ _setActiveAppControlSession({
559
+ conversationId: "conv-1",
560
+ app: "com.example.editor",
561
+ });
562
+
563
+ const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
564
+ app: "com.example.editor",
565
+ });
566
+
567
+ expect(sentMessages).toHaveLength(1);
568
+ const sent = sentMessages[0] as Record<string, unknown>;
569
+ // No cross-user ambiguity → resolver leaves targetClientId undefined,
570
+ // letting the proxy use its existing single-client routing.
571
+ expect(sent.targetClientId).toBeUndefined();
572
+
573
+ proxy.resolve(sent.requestId as string, {
574
+ requestId: "ignored-by-proxy",
575
+ state: "running",
576
+ });
577
+ const result = await resultPromise;
578
+ expect(result.isError).toBe(false);
579
+
580
+ proxy.dispose();
581
+ });
582
+ });
583
+
584
+ // -------------------------------------------------------------------------
585
+ // Same-user enforcement — even when target_client_id is provided, a
586
+ // cross-user client can never be addressed.
587
+ // -------------------------------------------------------------------------
588
+
589
+ describe("same-user enforcement", () => {
590
+ test("rejects targeted dispatch from a different actor principal", async () => {
591
+ mockHubClients = [
592
+ {
593
+ clientId: "other-user-client",
594
+ capabilities: ["host_app_control"],
595
+ actorPrincipalId: "user-2",
596
+ },
597
+ ];
598
+ const proxy = new HostAppControlProxy("conv-1");
599
+ const ctx = buildMockContext(proxy, "conv-1");
600
+ _setActiveAppControlSession({
601
+ conversationId: "conv-1",
602
+ app: "com.example.editor",
603
+ });
604
+
605
+ const result = await surfaceProxyResolver(ctx, "app_control_observe", {
606
+ app: "com.example.editor",
607
+ target_client_id: "other-user-client",
608
+ });
609
+
610
+ expect(result.isError).toBe(true);
611
+ // No envelope dispatched.
612
+ expect(sentMessages).toHaveLength(0);
613
+
614
+ proxy.dispose();
615
+ });
616
+
617
+ test("rejects when the conversation has no source actor principal", async () => {
618
+ mockHubClients = [
619
+ {
620
+ clientId: "client-a",
621
+ capabilities: ["host_app_control"],
622
+ actorPrincipalId: DEFAULT_PRINCIPAL,
623
+ },
624
+ ];
625
+ const proxy = new HostAppControlProxy("conv-1");
626
+ const ctx = buildMockContext(
627
+ proxy,
628
+ "conv-1",
629
+ undefined,
630
+ /* trustGuardianPrincipalId */ null,
631
+ );
632
+ _setActiveAppControlSession({
633
+ conversationId: "conv-1",
634
+ app: "com.example.editor",
635
+ });
636
+
637
+ const result = await surfaceProxyResolver(ctx, "app_control_observe", {
638
+ app: "com.example.editor",
639
+ target_client_id: "client-a",
640
+ });
641
+
642
+ expect(result.isError).toBe(true);
643
+ expect(sentMessages).toHaveLength(0);
644
+
645
+ proxy.dispose();
646
+ });
647
+ });
328
648
  });