@vellumai/assistant 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -0,0 +1,1035 @@
1
+ /**
2
+ * Regression tests locking the exact payload shapes emitted by the
3
+ * standalone surface lifecycle (showStandaloneSurface + handleSurfaceAction).
4
+ *
5
+ * These tests verify that daemon-driven standalone surfaces (not LLM tool
6
+ * invocations) produce wire messages matching the contracts that macOS/iOS/web
7
+ * clients decode. Any payload shape drift here is a client compatibility break.
8
+ *
9
+ * Surface lifecycle under test:
10
+ * 1. `ui_surface_show` — daemon → client (showStandaloneSurface)
11
+ * 2. `ui_surface_action` — client → daemon (user click)
12
+ * 3. `ui_surface_complete` — daemon → client (handleSurfaceAction resolving standalone)
13
+ *
14
+ * The Swift client decodes these via:
15
+ * - UiSurfaceShowMessage (MessageTypes.swift)
16
+ * - UiSurfaceCompleteMessage (MessageTypes.swift)
17
+ * and dispatches them in ChatActionHandler → ChatViewModel+SurfaceHandling.
18
+ */
19
+
20
+ import { describe, expect, test } from "bun:test";
21
+
22
+ import {
23
+ buildCompletionSummary,
24
+ handleSurfaceAction,
25
+ showStandaloneSurface,
26
+ type SurfaceConversationContext,
27
+ } from "../daemon/conversation-surfaces.js";
28
+ import type { ServerMessage } from "../daemon/message-protocol.js";
29
+
30
+ // ── Helpers ──────────────────────────────────────────────────────────
31
+
32
+ function createMockContext(
33
+ overrides?: Partial<{
34
+ hasNoClient: boolean;
35
+ supportsDynamicUi: boolean;
36
+ channel: string;
37
+ }>,
38
+ ): SurfaceConversationContext & {
39
+ sentMessages: ServerMessage[];
40
+ enqueuedMessages: Array<{ content: string; requestId: string }>;
41
+ } {
42
+ const sentMessages: ServerMessage[] = [];
43
+ const enqueuedMessages: Array<{ content: string; requestId: string }> = [];
44
+
45
+ return {
46
+ conversationId: "payload-test-conv",
47
+ assistantId: undefined,
48
+ trustContext: undefined,
49
+ channelCapabilities: overrides?.channel
50
+ ? {
51
+ channel: overrides.channel,
52
+ supportsDynamicUi: overrides.supportsDynamicUi ?? true,
53
+ }
54
+ : undefined,
55
+ traceEmitter: { emit: () => {} },
56
+ sendToClient: (msg: ServerMessage) => sentMessages.push(msg),
57
+ broadcastToAllClients: (msg: ServerMessage) => sentMessages.push(msg),
58
+ pendingSurfaceActions: new Map(),
59
+ lastSurfaceAction: new Map(),
60
+ surfaceState: new Map(),
61
+ surfaceUndoStacks: new Map(),
62
+ accumulatedSurfaceState: new Map(),
63
+ surfaceActionRequestIds: new Set(),
64
+ pendingStandaloneSurfaces: new Map(),
65
+ currentTurnSurfaces: [],
66
+ hostCuProxy: undefined,
67
+ hasNoClient: overrides?.hasNoClient ?? false,
68
+ isProcessing: () => false,
69
+ enqueueMessage: (content, _attachments, _onEvent, requestId) => {
70
+ enqueuedMessages.push({ content, requestId });
71
+ return { queued: false, requestId };
72
+ },
73
+ getQueueDepth: () => 0,
74
+ processMessage: async () => "msg-id",
75
+ withSurface: async <T>(_surfaceId: string, fn: () => T | Promise<T>) =>
76
+ fn(),
77
+ sentMessages,
78
+ enqueuedMessages,
79
+ };
80
+ }
81
+
82
+ type AnyRecord = Record<string, unknown>;
83
+
84
+ function findByType(
85
+ messages: ServerMessage[],
86
+ type: string,
87
+ ): AnyRecord | undefined {
88
+ return messages.find(
89
+ (m) => (m as unknown as AnyRecord).type === type,
90
+ ) as unknown as AnyRecord | undefined;
91
+ }
92
+
93
+ function findAllByType(messages: ServerMessage[], type: string): AnyRecord[] {
94
+ return messages.filter(
95
+ (m) => (m as unknown as AnyRecord).type === type,
96
+ ) as unknown as AnyRecord[];
97
+ }
98
+
99
+ // ── Confirmation surface payload shapes ──────────────────────────────
100
+
101
+ describe("standalone confirmation surface payload shapes", () => {
102
+ test("ui_surface_show payload matches UiSurfaceShowMessage contract", async () => {
103
+ const ctx = createMockContext();
104
+
105
+ const resultPromise = showStandaloneSurface(
106
+ ctx,
107
+ {
108
+ conversationId: "payload-test-conv",
109
+ surfaceType: "confirmation",
110
+ title: "Delete project?",
111
+ data: {
112
+ message: "This will permanently delete the project and all data.",
113
+ detail: "This action cannot be undone.",
114
+ confirmLabel: "Delete",
115
+ cancelLabel: "Keep",
116
+ destructive: true,
117
+ },
118
+ actions: [
119
+ { id: "confirm", label: "Delete", variant: "danger" },
120
+ { id: "cancel", label: "Keep", variant: "secondary" },
121
+ ],
122
+ timeoutMs: 60_000,
123
+ },
124
+ "payload-surf-1",
125
+ );
126
+
127
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
128
+ expect(showMsg).toBeDefined();
129
+
130
+ // ── Fields the Swift UiSurfaceShowMessage struct decodes ──
131
+ // These are the exact keys the client expects. Missing or renamed
132
+ // keys will cause a decoding failure on the client.
133
+ expect(showMsg!.type).toBe("ui_surface_show");
134
+ expect(showMsg!.conversationId).toBe("payload-test-conv");
135
+ expect(showMsg!.surfaceId).toBe("payload-surf-1");
136
+ expect(showMsg!.surfaceType).toBe("confirmation");
137
+ expect(showMsg!.title).toBe("Delete project?");
138
+ expect(showMsg!.display).toBe("inline");
139
+
140
+ // ── data field: ConfirmationSurfaceData ──
141
+ const data = showMsg!.data as AnyRecord;
142
+ expect(data.message).toBe(
143
+ "This will permanently delete the project and all data.",
144
+ );
145
+ expect(data.detail).toBe("This action cannot be undone.");
146
+ expect(data.confirmLabel).toBe("Delete");
147
+ expect(data.cancelLabel).toBe("Keep");
148
+ expect(data.destructive).toBe(true);
149
+
150
+ // ── actions array: SurfaceAction[] ──
151
+ const actions = showMsg!.actions as Array<AnyRecord>;
152
+ expect(actions).toHaveLength(2);
153
+ expect(actions[0].id).toBe("confirm");
154
+ expect(actions[0].label).toBe("Delete");
155
+ expect(actions[0].style).toBe("destructive"); // "danger" maps to "destructive"
156
+ expect(actions[1].id).toBe("cancel");
157
+ expect(actions[1].label).toBe("Keep");
158
+ expect(actions[1].style).toBe("secondary");
159
+
160
+ // Resolve to avoid dangling timer
161
+ await handleSurfaceAction(ctx, "payload-surf-1", "confirm");
162
+ await resultPromise;
163
+ });
164
+
165
+ test("ui_surface_complete payload on confirm matches UiSurfaceCompleteMessage contract", async () => {
166
+ const ctx = createMockContext();
167
+
168
+ const resultPromise = showStandaloneSurface(
169
+ ctx,
170
+ {
171
+ conversationId: "payload-test-conv",
172
+ surfaceType: "confirmation",
173
+ data: {
174
+ message: "Proceed with deployment?",
175
+ confirmLabel: "Deploy",
176
+ cancelLabel: "Abort",
177
+ },
178
+ timeoutMs: 60_000,
179
+ },
180
+ "payload-surf-2",
181
+ );
182
+
183
+ // Clear show messages
184
+ ctx.sentMessages.length = 0;
185
+
186
+ // Simulate user clicking confirm with submitted data
187
+ await handleSurfaceAction(ctx, "payload-surf-2", "confirm", {
188
+ environment: "production",
189
+ });
190
+ await resultPromise;
191
+
192
+ const completeMsg = findByType(ctx.sentMessages, "ui_surface_complete");
193
+ expect(completeMsg).toBeDefined();
194
+
195
+ // ── Fields the Swift UiSurfaceCompleteMessage struct decodes ──
196
+ expect(completeMsg!.type).toBe("ui_surface_complete");
197
+ expect(completeMsg!.conversationId).toBe("payload-test-conv");
198
+ expect(completeMsg!.surfaceId).toBe("payload-surf-2");
199
+ expect(typeof completeMsg!.summary).toBe("string");
200
+ expect(completeMsg!.summary).toBe('User chose: "Deploy"');
201
+ // submittedData should contain the action data from the user click
202
+ expect(completeMsg!.submittedData).toEqual({ environment: "production" });
203
+ });
204
+
205
+ test("ui_surface_complete payload on cancel matches UiSurfaceCompleteMessage contract", async () => {
206
+ const ctx = createMockContext();
207
+
208
+ const resultPromise = showStandaloneSurface(
209
+ ctx,
210
+ {
211
+ conversationId: "payload-test-conv",
212
+ surfaceType: "confirmation",
213
+ data: {
214
+ message: "Discard changes?",
215
+ cancelLabel: "Keep editing",
216
+ },
217
+ timeoutMs: 60_000,
218
+ },
219
+ "payload-surf-3",
220
+ );
221
+
222
+ ctx.sentMessages.length = 0;
223
+
224
+ await handleSurfaceAction(ctx, "payload-surf-3", "cancel");
225
+ await resultPromise;
226
+
227
+ const completeMsg = findByType(ctx.sentMessages, "ui_surface_complete");
228
+ expect(completeMsg).toBeDefined();
229
+
230
+ expect(completeMsg!.type).toBe("ui_surface_complete");
231
+ expect(completeMsg!.conversationId).toBe("payload-test-conv");
232
+ expect(completeMsg!.surfaceId).toBe("payload-surf-3");
233
+ expect(completeMsg!.summary).toBe('User chose: "Keep editing"');
234
+ // No submittedData on cancel without explicit data
235
+ expect(completeMsg!.submittedData).toBeUndefined();
236
+ });
237
+
238
+ test("ui_surface_complete on timeout matches UiSurfaceCompleteMessage contract", async () => {
239
+ const ctx = createMockContext();
240
+
241
+ const resultPromise = showStandaloneSurface(
242
+ ctx,
243
+ {
244
+ conversationId: "payload-test-conv",
245
+ surfaceType: "confirmation",
246
+ data: { message: "Quick confirm" },
247
+ timeoutMs: 50,
248
+ },
249
+ "payload-surf-4",
250
+ );
251
+
252
+ await resultPromise;
253
+
254
+ const completeMsg = findByType(ctx.sentMessages, "ui_surface_complete");
255
+ expect(completeMsg).toBeDefined();
256
+
257
+ expect(completeMsg!.type).toBe("ui_surface_complete");
258
+ expect(completeMsg!.conversationId).toBe("payload-test-conv");
259
+ expect(completeMsg!.surfaceId).toBe("payload-surf-4");
260
+ expect(completeMsg!.summary).toBe("Timed out");
261
+ // No submittedData on timeout
262
+ expect(completeMsg!).not.toHaveProperty("submittedData");
263
+ });
264
+ });
265
+
266
+ // ── Form surface payload shapes ──────────────────────────────────────
267
+
268
+ describe("standalone form surface payload shapes", () => {
269
+ test("ui_surface_show payload for form matches UiSurfaceShowMessage contract", async () => {
270
+ const ctx = createMockContext();
271
+
272
+ const resultPromise = showStandaloneSurface(
273
+ ctx,
274
+ {
275
+ conversationId: "payload-test-conv",
276
+ surfaceType: "form",
277
+ title: "Configure settings",
278
+ data: {
279
+ description: "Adjust your preferences below.",
280
+ fields: [
281
+ {
282
+ id: "name",
283
+ type: "text",
284
+ label: "Display Name",
285
+ placeholder: "Enter your name",
286
+ required: true,
287
+ },
288
+ {
289
+ id: "theme",
290
+ type: "select",
291
+ label: "Theme",
292
+ options: [
293
+ { label: "Light", value: "light" },
294
+ { label: "Dark", value: "dark" },
295
+ ],
296
+ defaultValue: "dark",
297
+ },
298
+ {
299
+ id: "notifications",
300
+ type: "toggle",
301
+ label: "Enable notifications",
302
+ defaultValue: true,
303
+ },
304
+ ],
305
+ submitLabel: "Save",
306
+ },
307
+ timeoutMs: 60_000,
308
+ },
309
+ "payload-surf-form-1",
310
+ );
311
+
312
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
313
+ expect(showMsg).toBeDefined();
314
+
315
+ // ── Core wire fields ──
316
+ expect(showMsg!.type).toBe("ui_surface_show");
317
+ expect(showMsg!.conversationId).toBe("payload-test-conv");
318
+ expect(showMsg!.surfaceId).toBe("payload-surf-form-1");
319
+ expect(showMsg!.surfaceType).toBe("form");
320
+ expect(showMsg!.title).toBe("Configure settings");
321
+ expect(showMsg!.display).toBe("inline");
322
+
323
+ // ── data field: FormSurfaceData ──
324
+ const data = showMsg!.data as AnyRecord;
325
+ expect(data.description).toBe("Adjust your preferences below.");
326
+ expect(data.submitLabel).toBe("Save");
327
+
328
+ const fields = data.fields as Array<AnyRecord>;
329
+ expect(fields).toHaveLength(3);
330
+
331
+ // Text field
332
+ expect(fields[0].id).toBe("name");
333
+ expect(fields[0].type).toBe("text");
334
+ expect(fields[0].label).toBe("Display Name");
335
+ expect(fields[0].placeholder).toBe("Enter your name");
336
+ expect(fields[0].required).toBe(true);
337
+
338
+ // Select field
339
+ expect(fields[1].id).toBe("theme");
340
+ expect(fields[1].type).toBe("select");
341
+ expect(fields[1].label).toBe("Theme");
342
+ expect(fields[1].options).toEqual([
343
+ { label: "Light", value: "light" },
344
+ { label: "Dark", value: "dark" },
345
+ ]);
346
+ expect(fields[1].defaultValue).toBe("dark");
347
+
348
+ // Toggle field
349
+ expect(fields[2].id).toBe("notifications");
350
+ expect(fields[2].type).toBe("toggle");
351
+ expect(fields[2].label).toBe("Enable notifications");
352
+ expect(fields[2].defaultValue).toBe(true);
353
+
354
+ // Resolve to avoid dangling timer
355
+ await handleSurfaceAction(ctx, "payload-surf-form-1", "submit", {
356
+ name: "Alice",
357
+ });
358
+ await resultPromise;
359
+ });
360
+
361
+ test("ui_surface_complete payload on form submit matches UiSurfaceCompleteMessage contract", async () => {
362
+ const ctx = createMockContext();
363
+
364
+ const resultPromise = showStandaloneSurface(
365
+ ctx,
366
+ {
367
+ conversationId: "payload-test-conv",
368
+ surfaceType: "form",
369
+ title: "User info",
370
+ data: {
371
+ fields: [
372
+ { id: "email", type: "text", label: "Email", required: true },
373
+ { id: "age", type: "number", label: "Age" },
374
+ ],
375
+ },
376
+ timeoutMs: 60_000,
377
+ },
378
+ "payload-surf-form-2",
379
+ );
380
+
381
+ ctx.sentMessages.length = 0;
382
+
383
+ await handleSurfaceAction(ctx, "payload-surf-form-2", "submit", {
384
+ email: "alice@example.com",
385
+ age: 30,
386
+ });
387
+ await resultPromise;
388
+
389
+ const completeMsg = findByType(ctx.sentMessages, "ui_surface_complete");
390
+ expect(completeMsg).toBeDefined();
391
+
392
+ expect(completeMsg!.type).toBe("ui_surface_complete");
393
+ expect(completeMsg!.conversationId).toBe("payload-test-conv");
394
+ expect(completeMsg!.surfaceId).toBe("payload-surf-form-2");
395
+ expect(completeMsg!.summary).toBe("Submitted");
396
+ expect(completeMsg!.submittedData).toEqual({
397
+ email: "alice@example.com",
398
+ age: 30,
399
+ });
400
+ });
401
+
402
+ test("form dismiss action resolves as cancelled with correct payload", async () => {
403
+ const ctx = createMockContext();
404
+
405
+ const resultPromise = showStandaloneSurface(
406
+ ctx,
407
+ {
408
+ conversationId: "payload-test-conv",
409
+ surfaceType: "form",
410
+ data: { fields: [{ id: "x", type: "text", label: "X" }] },
411
+ timeoutMs: 60_000,
412
+ },
413
+ "payload-surf-form-3",
414
+ );
415
+
416
+ ctx.sentMessages.length = 0;
417
+
418
+ await handleSurfaceAction(ctx, "payload-surf-form-3", "dismiss");
419
+ const result = await resultPromise;
420
+
421
+ // Standalone result
422
+ expect(result.status).toBe("cancelled");
423
+ expect(result.surfaceId).toBe("payload-surf-form-3");
424
+ expect(result.actionId).toBe("dismiss");
425
+
426
+ // Client-facing ui_surface_complete
427
+ const completeMsg = findByType(ctx.sentMessages, "ui_surface_complete");
428
+ expect(completeMsg).toBeDefined();
429
+ expect(completeMsg!.surfaceId).toBe("payload-surf-form-3");
430
+ expect(typeof completeMsg!.summary).toBe("string");
431
+ });
432
+ });
433
+
434
+ // ── Cross-surface contract invariants ────────────────────────────────
435
+
436
+ describe("standalone surface contract invariants", () => {
437
+ test("standalone surfaces never enqueue LLM messages", async () => {
438
+ const ctx = createMockContext();
439
+
440
+ // Confirmation flow
441
+ const p1 = showStandaloneSurface(
442
+ ctx,
443
+ {
444
+ conversationId: "payload-test-conv",
445
+ surfaceType: "confirmation",
446
+ data: { message: "Yes?" },
447
+ timeoutMs: 60_000,
448
+ },
449
+ "invariant-surf-1",
450
+ );
451
+ await handleSurfaceAction(ctx, "invariant-surf-1", "confirm");
452
+ await p1;
453
+
454
+ // Form flow
455
+ const p2 = showStandaloneSurface(
456
+ ctx,
457
+ {
458
+ conversationId: "payload-test-conv",
459
+ surfaceType: "form",
460
+ data: { fields: [] },
461
+ timeoutMs: 60_000,
462
+ },
463
+ "invariant-surf-2",
464
+ );
465
+ await handleSurfaceAction(ctx, "invariant-surf-2", "submit", {
466
+ val: "test",
467
+ });
468
+ await p2;
469
+
470
+ // No messages should have been enqueued to the LLM for standalone surfaces
471
+ expect(ctx.enqueuedMessages).toHaveLength(0);
472
+ });
473
+
474
+ test("every ui_surface_show has required fields for Swift deserialization", async () => {
475
+ const ctx = createMockContext();
476
+
477
+ const p1 = showStandaloneSurface(
478
+ ctx,
479
+ {
480
+ conversationId: "payload-test-conv",
481
+ surfaceType: "confirmation",
482
+ data: { message: "A?" },
483
+ timeoutMs: 60_000,
484
+ },
485
+ "schema-surf-1",
486
+ );
487
+
488
+ const p2 = showStandaloneSurface(
489
+ ctx,
490
+ {
491
+ conversationId: "payload-test-conv",
492
+ surfaceType: "form",
493
+ data: { fields: [] },
494
+ timeoutMs: 60_000,
495
+ },
496
+ "schema-surf-2",
497
+ );
498
+
499
+ const showMessages = findAllByType(ctx.sentMessages, "ui_surface_show");
500
+ expect(showMessages).toHaveLength(2);
501
+
502
+ for (const msg of showMessages) {
503
+ // Required fields per UiSurfaceShowMessage(Decodable) in MessageTypes.swift:
504
+ // conversationId: String? — present (nullable but present)
505
+ // surfaceId: String — required
506
+ // surfaceType: String — required
507
+ // data: AnyCodable — required
508
+ expect(msg).toHaveProperty("conversationId");
509
+ expect(typeof msg.surfaceId).toBe("string");
510
+ expect(typeof msg.surfaceType).toBe("string");
511
+ expect(msg.data).toBeDefined();
512
+ expect(msg.data).not.toBeNull();
513
+ }
514
+
515
+ // Cleanup
516
+ await handleSurfaceAction(ctx, "schema-surf-1", "confirm");
517
+ await handleSurfaceAction(ctx, "schema-surf-2", "submit", {});
518
+ await p1;
519
+ await p2;
520
+ });
521
+
522
+ test("every ui_surface_complete has required fields for Swift deserialization", async () => {
523
+ const ctx = createMockContext();
524
+
525
+ const p1 = showStandaloneSurface(
526
+ ctx,
527
+ {
528
+ conversationId: "payload-test-conv",
529
+ surfaceType: "confirmation",
530
+ data: { message: "B?" },
531
+ timeoutMs: 60_000,
532
+ },
533
+ "schema-surf-3",
534
+ );
535
+ await handleSurfaceAction(ctx, "schema-surf-3", "confirm");
536
+ await p1;
537
+
538
+ const p2 = showStandaloneSurface(
539
+ ctx,
540
+ {
541
+ conversationId: "payload-test-conv",
542
+ surfaceType: "form",
543
+ data: { fields: [] },
544
+ timeoutMs: 60_000,
545
+ },
546
+ "schema-surf-4",
547
+ );
548
+ await handleSurfaceAction(ctx, "schema-surf-4", "submit", { k: "v" });
549
+ await p2;
550
+
551
+ const completeMessages = findAllByType(
552
+ ctx.sentMessages,
553
+ "ui_surface_complete",
554
+ );
555
+ expect(completeMessages.length).toBeGreaterThanOrEqual(2);
556
+
557
+ for (const msg of completeMessages) {
558
+ // Required fields per UiSurfaceCompleteMessage(Decodable) in MessageTypes.swift:
559
+ // conversationId: String? — present (nullable but present)
560
+ // surfaceId: String — required
561
+ // summary: String — required
562
+ // submittedData: [String: AnyCodable]? — optional
563
+ expect(msg).toHaveProperty("conversationId");
564
+ expect(typeof msg.surfaceId).toBe("string");
565
+ expect(typeof msg.summary).toBe("string");
566
+ expect(msg.summary).not.toBe("");
567
+ }
568
+ });
569
+
570
+ test("standalone surface cleanup leaves no stale state", async () => {
571
+ const ctx = createMockContext();
572
+
573
+ const resultPromise = showStandaloneSurface(
574
+ ctx,
575
+ {
576
+ conversationId: "payload-test-conv",
577
+ surfaceType: "confirmation",
578
+ data: { message: "Clean?" },
579
+ timeoutMs: 60_000,
580
+ },
581
+ "cleanup-surf-1",
582
+ );
583
+
584
+ // Verify state exists before action
585
+ expect(ctx.pendingStandaloneSurfaces!.has("cleanup-surf-1")).toBe(true);
586
+ expect(ctx.surfaceState.has("cleanup-surf-1")).toBe(true);
587
+
588
+ await handleSurfaceAction(ctx, "cleanup-surf-1", "confirm");
589
+ await resultPromise;
590
+
591
+ // After resolution, all related state maps should be clean
592
+ expect(ctx.pendingStandaloneSurfaces!.has("cleanup-surf-1")).toBe(false);
593
+ expect(ctx.surfaceState.has("cleanup-surf-1")).toBe(false);
594
+ expect(ctx.pendingSurfaceActions.has("cleanup-surf-1")).toBe(false);
595
+ expect(ctx.lastSurfaceAction.has("cleanup-surf-1")).toBe(false);
596
+ expect(ctx.accumulatedSurfaceState.has("cleanup-surf-1")).toBe(false);
597
+ expect(ctx.surfaceUndoStacks.has("cleanup-surf-1")).toBe(false);
598
+ });
599
+
600
+ test("action variant mapping: danger → destructive, unset → secondary", async () => {
601
+ const ctx = createMockContext();
602
+
603
+ const resultPromise = showStandaloneSurface(
604
+ ctx,
605
+ {
606
+ conversationId: "payload-test-conv",
607
+ surfaceType: "confirmation",
608
+ data: { message: "Variants?" },
609
+ actions: [
610
+ { id: "a", label: "Primary", variant: "primary" },
611
+ { id: "b", label: "Danger", variant: "danger" },
612
+ { id: "c", label: "Secondary", variant: "secondary" },
613
+ { id: "d", label: "Default" }, // no variant
614
+ ],
615
+ timeoutMs: 60_000,
616
+ },
617
+ "variant-surf-1",
618
+ );
619
+
620
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
621
+ const actions = showMsg!.actions as Array<AnyRecord>;
622
+
623
+ // Verify the mapping matches what Swift SurfaceActionButton expects:
624
+ // "primary" → "primary"
625
+ // "danger" → "destructive"
626
+ // "secondary" → "secondary"
627
+ // undefined → "secondary" (default)
628
+ expect(actions[0].style).toBe("primary");
629
+ expect(actions[1].style).toBe("destructive");
630
+ expect(actions[2].style).toBe("secondary");
631
+ expect(actions[3].style).toBe("secondary");
632
+
633
+ await handleSurfaceAction(ctx, "variant-surf-1", "a");
634
+ await resultPromise;
635
+ });
636
+ });
637
+
638
+ // ── Completion summary consistency ───────────────────────────────────
639
+
640
+ describe("buildCompletionSummary for standalone surfaces", () => {
641
+ test("confirmation confirm with custom label", () => {
642
+ expect(
643
+ buildCompletionSummary(
644
+ "confirmation",
645
+ "confirm",
646
+ {},
647
+ { confirmLabel: "Yes, proceed" },
648
+ ),
649
+ ).toBe('User chose: "Yes, proceed"');
650
+ });
651
+
652
+ test("confirmation confirm without custom label", () => {
653
+ expect(buildCompletionSummary("confirmation", "confirm", {}, {})).toBe(
654
+ "Confirmed",
655
+ );
656
+ });
657
+
658
+ test("confirmation cancel with custom label", () => {
659
+ expect(
660
+ buildCompletionSummary(
661
+ "confirmation",
662
+ "cancel",
663
+ {},
664
+ { cancelLabel: "Never mind" },
665
+ ),
666
+ ).toBe('User chose: "Never mind"');
667
+ });
668
+
669
+ test("confirmation cancel without custom label", () => {
670
+ expect(buildCompletionSummary("confirmation", "cancel", {}, {})).toBe(
671
+ "Cancelled",
672
+ );
673
+ });
674
+
675
+ test("form submit", () => {
676
+ expect(buildCompletionSummary("form", "submit", { k: "v" })).toBe(
677
+ "Submitted",
678
+ );
679
+ });
680
+
681
+ test("confirmation deny with custom cancelLabel uses the label", () => {
682
+ expect(
683
+ buildCompletionSummary(
684
+ "confirmation",
685
+ "deny",
686
+ {},
687
+ { cancelLabel: "Keep" },
688
+ ),
689
+ ).toBe('User chose: "Keep"');
690
+ });
691
+
692
+ test("confirmation deny without custom label returns Denied", () => {
693
+ expect(buildCompletionSummary("confirmation", "deny", {}, {})).toBe(
694
+ "Denied",
695
+ );
696
+ });
697
+
698
+ test("unknown action ID is passed through", () => {
699
+ expect(buildCompletionSummary("confirmation", "reject", {}, {})).toBe(
700
+ "User selected: reject",
701
+ );
702
+ });
703
+ });
704
+
705
+ // ── Multi-page form payload shapes ───────────────────────────────────
706
+
707
+ describe("standalone multi-page form payload shapes", () => {
708
+ test("multi-page form preserves pages and pageLabels in emitted payload", async () => {
709
+ const ctx = createMockContext();
710
+
711
+ const resultPromise = showStandaloneSurface(
712
+ ctx,
713
+ {
714
+ conversationId: "payload-test-conv",
715
+ surfaceType: "form",
716
+ title: "Setup Wizard",
717
+ data: {
718
+ description: "Complete the setup steps.",
719
+ fields: [],
720
+ pages: [
721
+ {
722
+ id: "page-1",
723
+ title: "Personal Info",
724
+ description: "Enter your personal details.",
725
+ fields: [
726
+ {
727
+ id: "name",
728
+ type: "text",
729
+ label: "Full Name",
730
+ required: true,
731
+ },
732
+ { id: "email", type: "text", label: "Email", required: true },
733
+ ],
734
+ },
735
+ {
736
+ id: "page-2",
737
+ title: "Preferences",
738
+ fields: [
739
+ {
740
+ id: "theme",
741
+ type: "select",
742
+ label: "Theme",
743
+ options: [
744
+ { label: "Light", value: "light" },
745
+ { label: "Dark", value: "dark" },
746
+ ],
747
+ },
748
+ {
749
+ id: "notifications",
750
+ type: "toggle",
751
+ label: "Enable notifications",
752
+ },
753
+ ],
754
+ },
755
+ ],
756
+ pageLabels: {
757
+ next: "Continue",
758
+ back: "Go Back",
759
+ submit: "Finish Setup",
760
+ },
761
+ submitLabel: "Finish Setup",
762
+ },
763
+ timeoutMs: 60_000,
764
+ },
765
+ "payload-surf-multipage-1",
766
+ );
767
+
768
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
769
+ expect(showMsg).toBeDefined();
770
+
771
+ // Core wire fields
772
+ expect(showMsg!.surfaceType).toBe("form");
773
+ expect(showMsg!.title).toBe("Setup Wizard");
774
+
775
+ // data field: FormSurfaceData with pages
776
+ const data = showMsg!.data as AnyRecord;
777
+ expect(data.description).toBe("Complete the setup steps.");
778
+ expect(data.submitLabel).toBe("Finish Setup");
779
+
780
+ // pages should be preserved exactly
781
+ const pages = data.pages as Array<AnyRecord>;
782
+ expect(pages).toBeDefined();
783
+ expect(pages).toHaveLength(2);
784
+ expect(pages[0].id).toBe("page-1");
785
+ expect(pages[0].title).toBe("Personal Info");
786
+ expect(pages[0].description).toBe("Enter your personal details.");
787
+ expect(pages[0].fields as Array<AnyRecord>).toHaveLength(2);
788
+ expect(pages[1].id).toBe("page-2");
789
+ expect(pages[1].title).toBe("Preferences");
790
+ expect(pages[1].fields as Array<AnyRecord>).toHaveLength(2);
791
+
792
+ // pageLabels should be preserved exactly
793
+ const pageLabels = data.pageLabels as AnyRecord;
794
+ expect(pageLabels).toBeDefined();
795
+ expect(pageLabels.next).toBe("Continue");
796
+ expect(pageLabels.back).toBe("Go Back");
797
+ expect(pageLabels.submit).toBe("Finish Setup");
798
+
799
+ // fields should still be a valid array (defensive normalization)
800
+ expect(Array.isArray(data.fields)).toBe(true);
801
+
802
+ // Resolve to avoid dangling timer
803
+ await handleSurfaceAction(ctx, "payload-surf-multipage-1", "submit", {
804
+ name: "Alice",
805
+ email: "alice@example.com",
806
+ theme: "dark",
807
+ notifications: true,
808
+ });
809
+ await resultPromise;
810
+ });
811
+
812
+ test("pages-only form (no top-level fields) normalizes fields to empty array", async () => {
813
+ const ctx = createMockContext();
814
+
815
+ const resultPromise = showStandaloneSurface(
816
+ ctx,
817
+ {
818
+ conversationId: "payload-test-conv",
819
+ surfaceType: "form",
820
+ title: "Wizard",
821
+ data: {
822
+ pages: [
823
+ {
824
+ id: "p1",
825
+ title: "Step 1",
826
+ fields: [{ id: "x", type: "text", label: "X" }],
827
+ },
828
+ ],
829
+ pageLabels: { next: "Next", submit: "Done" },
830
+ },
831
+ timeoutMs: 60_000,
832
+ },
833
+ "payload-surf-pages-only",
834
+ );
835
+
836
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
837
+ const data = showMsg!.data as AnyRecord;
838
+
839
+ // pages should be preserved
840
+ expect(data.pages).toBeDefined();
841
+ expect(data.pages as Array<AnyRecord>).toHaveLength(1);
842
+
843
+ // pageLabels should be preserved
844
+ expect(data.pageLabels).toEqual({ next: "Next", submit: "Done" });
845
+
846
+ // fields should default to empty array (defensive normalization)
847
+ expect(data.fields).toEqual([]);
848
+
849
+ // Resolve to avoid dangling timer
850
+ await handleSurfaceAction(ctx, "payload-surf-pages-only", "submit", {
851
+ x: "val",
852
+ });
853
+ await resultPromise;
854
+ });
855
+
856
+ test("multi-page form submit resolves correctly end-to-end", async () => {
857
+ const ctx = createMockContext();
858
+
859
+ const resultPromise = showStandaloneSurface(
860
+ ctx,
861
+ {
862
+ conversationId: "payload-test-conv",
863
+ surfaceType: "form",
864
+ title: "Multi-Step",
865
+ data: {
866
+ pages: [
867
+ {
868
+ id: "p1",
869
+ title: "Step 1",
870
+ fields: [{ id: "a", type: "text", label: "A" }],
871
+ },
872
+ {
873
+ id: "p2",
874
+ title: "Step 2",
875
+ fields: [{ id: "b", type: "number", label: "B" }],
876
+ },
877
+ ],
878
+ pageLabels: { next: "Next", back: "Previous", submit: "Complete" },
879
+ },
880
+ timeoutMs: 60_000,
881
+ },
882
+ "payload-surf-multipage-submit",
883
+ );
884
+
885
+ ctx.sentMessages.length = 0;
886
+
887
+ await handleSurfaceAction(ctx, "payload-surf-multipage-submit", "submit", {
888
+ a: "hello",
889
+ b: 42,
890
+ });
891
+ const result = await resultPromise;
892
+
893
+ // Result should be submitted with the form data
894
+ expect(result.status).toBe("submitted");
895
+ expect(result.submittedData).toEqual({ a: "hello", b: 42 });
896
+
897
+ // ui_surface_complete should have been emitted
898
+ const completeMsg = findByType(ctx.sentMessages, "ui_surface_complete");
899
+ expect(completeMsg).toBeDefined();
900
+ expect(completeMsg!.summary).toBe("Submitted");
901
+ expect(completeMsg!.submittedData).toEqual({ a: "hello", b: 42 });
902
+ });
903
+ });
904
+
905
+ // ── Forward-compatible additive keys (regression) ────────────────────
906
+
907
+ describe("standalone form forward-compatible payload preservation", () => {
908
+ test("additive keys not in FormSurfaceData are preserved through the pipeline", async () => {
909
+ // Regression test: the form surface pipeline must preserve all keys from
910
+ // the input data — including ones not declared in FormSurfaceData (e.g.
911
+ // keys added in newer protocol versions) — so that forward-compatible
912
+ // clients can consume them.
913
+ const ctx = createMockContext();
914
+
915
+ const resultPromise = showStandaloneSurface(
916
+ ctx,
917
+ {
918
+ conversationId: "payload-test-conv",
919
+ surfaceType: "form",
920
+ title: "Future Form",
921
+ data: {
922
+ description: "A form with future keys.",
923
+ fields: [{ id: "f1", type: "text", label: "Field 1" }],
924
+ submitLabel: "Go",
925
+ // Hypothetical future keys that the protocol may add
926
+ futureStringField: "hello",
927
+ futureNumberField: 99,
928
+ futureBooleanField: true,
929
+ futureObjectField: { nested: "value", count: 3 },
930
+ futureArrayField: ["a", "b", "c"],
931
+ },
932
+ timeoutMs: 60_000,
933
+ },
934
+ "payload-surf-forward-compat",
935
+ );
936
+
937
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
938
+ const data = showMsg!.data as AnyRecord;
939
+
940
+ // Known FormSurfaceData fields should be present
941
+ expect(data.description).toBe("A form with future keys.");
942
+ expect(data.submitLabel).toBe("Go");
943
+ expect(data.fields as Array<AnyRecord>).toHaveLength(1);
944
+
945
+ // Future additive keys must NOT be dropped
946
+ expect(data.futureStringField).toBe("hello");
947
+ expect(data.futureNumberField).toBe(99);
948
+ expect(data.futureBooleanField).toBe(true);
949
+ expect(data.futureObjectField).toEqual({ nested: "value", count: 3 });
950
+ expect(data.futureArrayField).toEqual(["a", "b", "c"]);
951
+
952
+ // Resolve to avoid dangling timer
953
+ await handleSurfaceAction(ctx, "payload-surf-forward-compat", "submit", {});
954
+ await resultPromise;
955
+ });
956
+
957
+ test("existing single-page form behavior is unchanged", async () => {
958
+ // Ensure the refactored code does not regress the basic single-page
959
+ // form path that existed before the pages/pageLabels fix.
960
+ const ctx = createMockContext();
961
+
962
+ const resultPromise = showStandaloneSurface(
963
+ ctx,
964
+ {
965
+ conversationId: "payload-test-conv",
966
+ surfaceType: "form",
967
+ title: "Simple Form",
968
+ data: {
969
+ description: "A basic form.",
970
+ fields: [
971
+ { id: "name", type: "text", label: "Name", required: true },
972
+ { id: "age", type: "number", label: "Age" },
973
+ ],
974
+ submitLabel: "Submit",
975
+ },
976
+ timeoutMs: 60_000,
977
+ },
978
+ "payload-surf-simple-form",
979
+ );
980
+
981
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
982
+ const data = showMsg!.data as AnyRecord;
983
+
984
+ expect(data.description).toBe("A basic form.");
985
+ expect(data.submitLabel).toBe("Submit");
986
+ expect(data.fields as Array<AnyRecord>).toHaveLength(2);
987
+ expect((data.fields as Array<AnyRecord>)[0].id).toBe("name");
988
+ expect((data.fields as Array<AnyRecord>)[0].required).toBe(true);
989
+ expect((data.fields as Array<AnyRecord>)[1].id).toBe("age");
990
+
991
+ // pages/pageLabels should not be present for single-page forms
992
+ expect(data.pages).toBeUndefined();
993
+ expect(data.pageLabels).toBeUndefined();
994
+
995
+ // Resolve to avoid dangling timer
996
+ await handleSurfaceAction(ctx, "payload-surf-simple-form", "submit", {
997
+ name: "Bob",
998
+ age: 25,
999
+ });
1000
+ await resultPromise;
1001
+ });
1002
+
1003
+ test("form with neither fields nor pages normalizes fields to empty array", async () => {
1004
+ // Defensive normalization: an empty/missing form payload should still
1005
+ // produce a valid FormSurfaceData with an empty fields array rather
1006
+ // than undefined or a missing key.
1007
+ const ctx = createMockContext();
1008
+
1009
+ const resultPromise = showStandaloneSurface(
1010
+ ctx,
1011
+ {
1012
+ conversationId: "payload-test-conv",
1013
+ surfaceType: "form",
1014
+ title: "Empty Form",
1015
+ data: {
1016
+ description: "No fields at all.",
1017
+ },
1018
+ timeoutMs: 60_000,
1019
+ },
1020
+ "payload-surf-empty-form",
1021
+ );
1022
+
1023
+ const showMsg = findByType(ctx.sentMessages, "ui_surface_show");
1024
+ const data = showMsg!.data as AnyRecord;
1025
+
1026
+ expect(data.description).toBe("No fields at all.");
1027
+ // fields must always be a valid array — never undefined
1028
+ expect(Array.isArray(data.fields)).toBe(true);
1029
+ expect(data.fields).toEqual([]);
1030
+
1031
+ // Resolve to avoid dangling timer
1032
+ await handleSurfaceAction(ctx, "payload-surf-empty-form", "submit", {});
1033
+ await resultPromise;
1034
+ });
1035
+ });