@vellumai/assistant 0.5.1 → 0.5.3

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 (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Unit tests for the GET /v1/suggestion endpoint (handleGetSuggestion).
3
+ *
4
+ * Validates happy path, all null-return paths, caching, staleness check,
5
+ * quote stripping, word-boundary truncation, empty response rejection,
6
+ * and modelIntent verification.
7
+ */
8
+
9
+ import { describe, expect, mock, test } from "bun:test";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Mocks — must be defined before importing the module under test
13
+ // ---------------------------------------------------------------------------
14
+
15
+ mock.module("../util/logger.js", () => ({
16
+ getLogger: () =>
17
+ new Proxy({} as Record<string, unknown>, {
18
+ get: () => () => {},
19
+ }),
20
+ }));
21
+
22
+ const mockGetConversationByKey = mock(
23
+ (_key: string): { conversationId: string } | null => ({
24
+ conversationId: "conv-test",
25
+ }),
26
+ );
27
+
28
+ mock.module("../memory/conversation-key-store.js", () => ({
29
+ getConversationByKey: mockGetConversationByKey,
30
+ }));
31
+
32
+ const mockGetMessages = mock((_conversationId: string) => [
33
+ {
34
+ id: "msg-user-1",
35
+ conversationId: "conv-test",
36
+ role: "user",
37
+ content: JSON.stringify([{ type: "text", text: "Hello there" }]),
38
+ createdAt: Date.now() - 2000,
39
+ metadata: null,
40
+ },
41
+ {
42
+ id: "msg-asst-1",
43
+ conversationId: "conv-test",
44
+ role: "assistant",
45
+ content: JSON.stringify([
46
+ { type: "text", text: "Hi! How can I help you today?" },
47
+ ]),
48
+ createdAt: Date.now() - 1000,
49
+ metadata: null,
50
+ },
51
+ ]);
52
+
53
+ mock.module("../memory/conversation-crud.js", () => ({
54
+ getMessages: mockGetMessages,
55
+ }));
56
+
57
+ const mockGetConfiguredProvider = mock(async () => ({
58
+ name: "test-provider",
59
+ sendMessage: mock(async () => ({
60
+ content: [{ type: "text", text: "Let's do round two!" }],
61
+ model: "test",
62
+ usage: { inputTokens: 1, outputTokens: 1 },
63
+ stopReason: "end_turn",
64
+ })),
65
+ }));
66
+
67
+ mock.module("../providers/provider-send-message.js", () => ({
68
+ getConfiguredProvider: mockGetConfiguredProvider,
69
+ }));
70
+
71
+ mock.module("../daemon/handlers/shared.js", () => ({
72
+ renderHistoryContent: (content: unknown) => {
73
+ // Extract text from content blocks, mirroring the real function
74
+ if (Array.isArray(content)) {
75
+ const texts = content
76
+ .filter((b: { type: string }) => b.type === "text" && "text" in b)
77
+ .map((b: { text: string }) => b.text);
78
+ return {
79
+ text: texts.join("\n"),
80
+ toolCalls: [],
81
+ toolCallsBeforeText: false,
82
+ textSegments: [],
83
+ contentOrder: [],
84
+ surfaces: [],
85
+ };
86
+ }
87
+ return {
88
+ text: typeof content === "string" ? content : "",
89
+ toolCalls: [],
90
+ toolCallsBeforeText: false,
91
+ textSegments: [],
92
+ contentOrder: [],
93
+ surfaces: [],
94
+ };
95
+ },
96
+ }));
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Imports (after mocks)
100
+ // ---------------------------------------------------------------------------
101
+
102
+ import { handleGetSuggestion } from "../runtime/routes/conversation-routes.js";
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Helpers
106
+ // ---------------------------------------------------------------------------
107
+
108
+ function makeUrl(params: {
109
+ conversationKey?: string;
110
+ messageId?: string;
111
+ }): URL {
112
+ const url = new URL("http://localhost/v1/suggestion");
113
+ if (params.conversationKey) {
114
+ url.searchParams.set("conversationKey", params.conversationKey);
115
+ }
116
+ if (params.messageId) {
117
+ url.searchParams.set("messageId", params.messageId);
118
+ }
119
+ return url;
120
+ }
121
+
122
+ function makeDeps() {
123
+ return {
124
+ suggestionCache: new Map<string, string>(),
125
+ suggestionInFlight: new Map<string, Promise<string | null>>(),
126
+ };
127
+ }
128
+
129
+ function makeMockProvider(text: string) {
130
+ return {
131
+ name: "test-provider",
132
+ sendMessage: mock(async () => ({
133
+ content: [{ type: "text" as const, text }],
134
+ model: "test",
135
+ usage: { inputTokens: 1, outputTokens: 1 },
136
+ stopReason: "end_turn",
137
+ })),
138
+ };
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Tests
143
+ // ---------------------------------------------------------------------------
144
+
145
+ describe("GET /v1/suggestion", () => {
146
+ test("returns suggestion from LLM", async () => {
147
+ const provider = makeMockProvider("Let's do round two!");
148
+ mockGetConfiguredProvider.mockImplementation(async () => provider);
149
+ mockGetConversationByKey.mockImplementation(() => ({
150
+ conversationId: "conv-test",
151
+ }));
152
+ mockGetMessages.mockImplementation(() => [
153
+ {
154
+ id: "msg-user-1",
155
+ conversationId: "conv-test",
156
+ role: "user",
157
+ content: JSON.stringify([{ type: "text", text: "Hello" }]),
158
+ createdAt: Date.now() - 2000,
159
+ metadata: null,
160
+ },
161
+ {
162
+ id: "msg-asst-1",
163
+ conversationId: "conv-test",
164
+ role: "assistant",
165
+ content: JSON.stringify([
166
+ { type: "text", text: "Hi! How can I help you today?" },
167
+ ]),
168
+ createdAt: Date.now() - 1000,
169
+ metadata: null,
170
+ },
171
+ ]);
172
+
173
+ const url = makeUrl({ conversationKey: "test-key" });
174
+ const deps = makeDeps();
175
+ const res = await handleGetSuggestion(url, deps);
176
+ const body = (await res.json()) as {
177
+ suggestion: string;
178
+ source: string;
179
+ };
180
+
181
+ expect(body.suggestion).toBe("Let's do round two!");
182
+ expect(body.source).toBe("llm");
183
+ });
184
+
185
+ test("returns null when no conversation found", async () => {
186
+ mockGetConversationByKey.mockImplementation(() => null);
187
+
188
+ const url = makeUrl({ conversationKey: "nonexistent-key" });
189
+ const deps = makeDeps();
190
+ const res = await handleGetSuggestion(url, deps);
191
+ const body = (await res.json()) as { suggestion: string | null };
192
+
193
+ expect(body.suggestion).toBeNull();
194
+ });
195
+
196
+ test("returns null when no messages", async () => {
197
+ mockGetConversationByKey.mockImplementation(() => ({
198
+ conversationId: "conv-test",
199
+ }));
200
+ mockGetMessages.mockImplementation(() => []);
201
+
202
+ const url = makeUrl({ conversationKey: "test-key" });
203
+ const deps = makeDeps();
204
+ const res = await handleGetSuggestion(url, deps);
205
+ const body = (await res.json()) as { suggestion: string | null };
206
+
207
+ expect(body.suggestion).toBeNull();
208
+ });
209
+
210
+ test("returns null when provider unavailable", async () => {
211
+ mockGetConversationByKey.mockImplementation(() => ({
212
+ conversationId: "conv-test",
213
+ }));
214
+ mockGetMessages.mockImplementation(() => [
215
+ {
216
+ id: "msg-asst-1",
217
+ conversationId: "conv-test",
218
+ role: "assistant",
219
+ content: JSON.stringify([{ type: "text", text: "Hello there" }]),
220
+ createdAt: Date.now(),
221
+ metadata: null,
222
+ },
223
+ ]);
224
+ mockGetConfiguredProvider.mockImplementation(
225
+ async () =>
226
+ null as unknown as Awaited<
227
+ ReturnType<typeof mockGetConfiguredProvider>
228
+ >,
229
+ );
230
+
231
+ const url = makeUrl({ conversationKey: "test-key" });
232
+ const deps = makeDeps();
233
+ const res = await handleGetSuggestion(url, deps);
234
+ const body = (await res.json()) as { suggestion: string | null };
235
+
236
+ expect(body.suggestion).toBeNull();
237
+ });
238
+
239
+ test("returns null when provider throws", async () => {
240
+ const throwingProvider = {
241
+ name: "test-provider",
242
+ sendMessage: mock(async () => {
243
+ throw new Error("Provider error");
244
+ }),
245
+ };
246
+ mockGetConfiguredProvider.mockImplementation(async () => throwingProvider);
247
+ mockGetConversationByKey.mockImplementation(() => ({
248
+ conversationId: "conv-test",
249
+ }));
250
+ mockGetMessages.mockImplementation(() => [
251
+ {
252
+ id: "msg-asst-1",
253
+ conversationId: "conv-test",
254
+ role: "assistant",
255
+ content: JSON.stringify([{ type: "text", text: "Hello there" }]),
256
+ createdAt: Date.now(),
257
+ metadata: null,
258
+ },
259
+ ]);
260
+
261
+ const url = makeUrl({ conversationKey: "test-key" });
262
+ const deps = makeDeps();
263
+ const res = await handleGetSuggestion(url, deps);
264
+
265
+ // Should return null gracefully, not a 500
266
+ expect(res.status).toBe(200);
267
+ const body = (await res.json()) as { suggestion: string | null };
268
+ expect(body.suggestion).toBeNull();
269
+ });
270
+
271
+ test("strips quotes from LLM response", async () => {
272
+ const provider = makeMockProvider('"Sure, let\'s go!"');
273
+ mockGetConfiguredProvider.mockImplementation(async () => provider);
274
+ mockGetConversationByKey.mockImplementation(() => ({
275
+ conversationId: "conv-test",
276
+ }));
277
+ mockGetMessages.mockImplementation(() => [
278
+ {
279
+ id: "msg-asst-1",
280
+ conversationId: "conv-test",
281
+ role: "assistant",
282
+ content: JSON.stringify([{ type: "text", text: "Hello there" }]),
283
+ createdAt: Date.now(),
284
+ metadata: null,
285
+ },
286
+ ]);
287
+
288
+ const url = makeUrl({ conversationKey: "test-key" });
289
+ const deps = makeDeps();
290
+ const res = await handleGetSuggestion(url, deps);
291
+ const body = (await res.json()) as { suggestion: string };
292
+
293
+ expect(body.suggestion).toBe("Sure, let's go!");
294
+ });
295
+
296
+ test("truncates long suggestions at word boundary", async () => {
297
+ // A 60-char string that will exceed the 50-char limit
298
+ const longText =
299
+ "This is a really long suggestion that goes well beyond fifty chars";
300
+ const provider = makeMockProvider(longText);
301
+ mockGetConfiguredProvider.mockImplementation(async () => provider);
302
+ mockGetConversationByKey.mockImplementation(() => ({
303
+ conversationId: "conv-test",
304
+ }));
305
+ mockGetMessages.mockImplementation(() => [
306
+ {
307
+ id: "msg-asst-1",
308
+ conversationId: "conv-test",
309
+ role: "assistant",
310
+ content: JSON.stringify([{ type: "text", text: "Hello there" }]),
311
+ createdAt: Date.now(),
312
+ metadata: null,
313
+ },
314
+ ]);
315
+
316
+ const url = makeUrl({ conversationKey: "test-key" });
317
+ const deps = makeDeps();
318
+ const res = await handleGetSuggestion(url, deps);
319
+ const body = (await res.json()) as { suggestion: string };
320
+
321
+ expect(body.suggestion.length).toBeLessThanOrEqual(50);
322
+ // Should end at a word boundary (no partial words)
323
+ expect(body.suggestion).not.toMatch(/\s$/);
324
+ });
325
+
326
+ test("rejects empty LLM response", async () => {
327
+ const provider = makeMockProvider("");
328
+ mockGetConfiguredProvider.mockImplementation(async () => provider);
329
+ mockGetConversationByKey.mockImplementation(() => ({
330
+ conversationId: "conv-test",
331
+ }));
332
+ mockGetMessages.mockImplementation(() => [
333
+ {
334
+ id: "msg-asst-1",
335
+ conversationId: "conv-test",
336
+ role: "assistant",
337
+ content: JSON.stringify([{ type: "text", text: "Hello there" }]),
338
+ createdAt: Date.now(),
339
+ metadata: null,
340
+ },
341
+ ]);
342
+
343
+ const url = makeUrl({ conversationKey: "test-key" });
344
+ const deps = makeDeps();
345
+ const res = await handleGetSuggestion(url, deps);
346
+ const body = (await res.json()) as { suggestion: string | null };
347
+
348
+ expect(body.suggestion).toBeNull();
349
+ });
350
+
351
+ test("returns cached suggestion", async () => {
352
+ const provider = makeMockProvider("Fresh suggestion");
353
+ mockGetConfiguredProvider.mockImplementation(async () => provider);
354
+ mockGetConversationByKey.mockImplementation(() => ({
355
+ conversationId: "conv-test",
356
+ }));
357
+ mockGetMessages.mockImplementation(() => [
358
+ {
359
+ id: "msg-asst-cache",
360
+ conversationId: "conv-test",
361
+ role: "assistant",
362
+ content: JSON.stringify([{ type: "text", text: "Some response" }]),
363
+ createdAt: Date.now(),
364
+ metadata: null,
365
+ },
366
+ ]);
367
+
368
+ const url = makeUrl({ conversationKey: "test-key" });
369
+ const deps = makeDeps();
370
+
371
+ // First call — should hit the LLM
372
+ const res1 = await handleGetSuggestion(url, deps);
373
+ const body1 = (await res1.json()) as { suggestion: string };
374
+ expect(body1.suggestion).toBe("Fresh suggestion");
375
+
376
+ // Second call — should return from cache
377
+ const res2 = await handleGetSuggestion(url, deps);
378
+ const body2 = (await res2.json()) as { suggestion: string };
379
+ expect(body2.suggestion).toBe("Fresh suggestion");
380
+
381
+ // Provider sendMessage should have been called only once
382
+ expect(provider.sendMessage).toHaveBeenCalledTimes(1);
383
+ });
384
+
385
+ test("returns stale when messageId doesn't match", async () => {
386
+ mockGetConversationByKey.mockImplementation(() => ({
387
+ conversationId: "conv-test",
388
+ }));
389
+ mockGetMessages.mockImplementation(() => [
390
+ {
391
+ id: "msg-asst-latest",
392
+ conversationId: "conv-test",
393
+ role: "assistant",
394
+ content: JSON.stringify([{ type: "text", text: "Latest response" }]),
395
+ createdAt: Date.now(),
396
+ metadata: null,
397
+ },
398
+ ]);
399
+
400
+ const url = makeUrl({
401
+ conversationKey: "test-key",
402
+ messageId: "msg-asst-old",
403
+ });
404
+ const deps = makeDeps();
405
+ const res = await handleGetSuggestion(url, deps);
406
+ const body = (await res.json()) as {
407
+ suggestion: string | null;
408
+ stale: boolean;
409
+ };
410
+
411
+ expect(body.stale).toBe(true);
412
+ expect(body.suggestion).toBeNull();
413
+ });
414
+
415
+ test("uses latency-optimized model intent", async () => {
416
+ const provider = makeMockProvider("Quick reply");
417
+ mockGetConfiguredProvider.mockImplementation(async () => provider);
418
+ mockGetConversationByKey.mockImplementation(() => ({
419
+ conversationId: "conv-test",
420
+ }));
421
+ mockGetMessages.mockImplementation(() => [
422
+ {
423
+ id: "msg-asst-intent",
424
+ conversationId: "conv-test",
425
+ role: "assistant",
426
+ content: JSON.stringify([{ type: "text", text: "Hello!" }]),
427
+ createdAt: Date.now(),
428
+ metadata: null,
429
+ },
430
+ ]);
431
+
432
+ const url = makeUrl({ conversationKey: "test-key" });
433
+ const deps = makeDeps();
434
+ await handleGetSuggestion(url, deps);
435
+
436
+ expect(provider.sendMessage).toHaveBeenCalledTimes(1);
437
+ const callArgs = provider.sendMessage.mock.calls[0] as unknown[];
438
+ const options = callArgs[3] as
439
+ | { config?: { modelIntent?: string } }
440
+ | undefined;
441
+ expect(options?.config?.modelIntent).toBe("latency-optimized");
442
+ });
443
+ });
@@ -70,6 +70,7 @@ const mockTestProvider = {
70
70
  let mockAnthropicKey: string | undefined = "test-api-key";
71
71
  mock.module("../security/secure-keys.js", () => ({
72
72
  getSecureKeyAsync: async () => mockAnthropicKey,
73
+ getProviderKeyAsync: async () => mockAnthropicKey,
73
74
  }));
74
75
 
75
76
  mock.module("../providers/registry.js", () => ({
@@ -81,6 +81,7 @@ const mockProvider = {
81
81
  };
82
82
  mock.module("../security/secure-keys.js", () => ({
83
83
  getSecureKeyAsync: async () => "test-api-key",
84
+ getProviderKeyAsync: async () => "test-api-key",
84
85
  }));
85
86
 
86
87
  mock.module("../providers/registry.js", () => ({
@@ -76,6 +76,7 @@ const mockProvider = {
76
76
  };
77
77
  mock.module("../security/secure-keys.js", () => ({
78
78
  getSecureKeyAsync: async () => "test-api-key",
79
+ getProviderKeyAsync: async () => "test-api-key",
79
80
  }));
80
81
 
81
82
  mock.module("../providers/registry.js", () => ({
@@ -79,6 +79,7 @@ mock.module("../util/platform.js", () => ({
79
79
  getRootDir: () => "/tmp",
80
80
  getDataDir: () => "/tmp",
81
81
  getWorkspaceDir: () => "/tmp/workspace",
82
+ getConversationsDir: () => "/tmp/workspace/conversations",
82
83
  getDbPath: () => "/tmp/assistant.db",
83
84
  ensureDataDir: () => {},
84
85
  }));
@@ -68,6 +68,7 @@ mock.module("../memory/conversation-crud.js", () => ({
68
68
 
69
69
  mock.module("../memory/llm-request-log-store.js", () => ({
70
70
  recordRequestLog: () => {},
71
+ backfillMessageIdOnLogs: () => {},
71
72
  }));
72
73
 
73
74
  // ── Imports (after mocks) ─────────────────────────────────────────────────────
@@ -238,13 +239,13 @@ describe("tool preview lifecycle", () => {
238
239
  expect(activity.statusText).toMatch(/^Preparing/);
239
240
  });
240
241
 
241
- test("handleInputJsonDelta includes toolUseId", () => {
242
+ test("handleInputJsonDelta includes toolUseId for app tools", () => {
242
243
  const collector = createEventCollector();
243
244
  const deps = createMockDeps({ onEvent: collector.onEvent });
244
245
 
245
246
  handleInputJsonDelta(state, deps, {
246
247
  type: "input_json_delta",
247
- toolName: "bash",
248
+ toolName: "app_create",
248
249
  toolUseId: "toolu_delta456",
249
250
  accumulatedJson: '{"command": "ls"}',
250
251
  });
@@ -253,7 +254,7 @@ describe("tool preview lifecycle", () => {
253
254
  const emitted = collector.events[0];
254
255
  expect(emitted.type).toBe("tool_input_delta");
255
256
  expect((emitted as any).toolUseId).toBe("toolu_delta456");
256
- expect((emitted as any).toolName).toBe("bash");
257
+ expect((emitted as any).toolName).toBe("app_create");
257
258
  expect((emitted as any).content).toBe('{"command": "ls"}');
258
259
  expect((emitted as any).conversationId).toBe("test-session-id");
259
260
  });
@@ -305,7 +306,8 @@ describe("tool preview lifecycle", () => {
305
306
  });
306
307
 
307
308
  const toolUseId = "toolu_ordering_test";
308
- const toolName = "file_read";
309
+ // Use an app tool so input_json_delta is forwarded to the client
310
+ const toolName = "app_create";
309
311
 
310
312
  // Step 1: tool_use_preview_start (emitted by provider on content_block_start)
311
313
  handleToolUsePreviewStart(state, deps, {
@@ -344,6 +346,30 @@ describe("tool preview lifecycle", () => {
344
346
  }
345
347
  });
346
348
 
349
+ test("non-app tool input_json_delta events are not forwarded to client", () => {
350
+ const collector = createEventCollector();
351
+ const deps = createMockDeps({
352
+ onEvent: collector.onEvent,
353
+ ctx: {
354
+ ...createMockDeps().ctx,
355
+ emitActivityState: collector.emitActivityState,
356
+ } as unknown as EventHandlerDeps["ctx"],
357
+ });
358
+
359
+ const toolUseId = "toolu_non_app_delta";
360
+ const toolName = "file_read";
361
+
362
+ handleInputJsonDelta(state, deps, {
363
+ type: "input_json_delta",
364
+ toolName,
365
+ toolUseId,
366
+ accumulatedJson: '{"path": "/test"}',
367
+ });
368
+
369
+ // Non-app tools should not emit tool_input_delta to the client
370
+ expect(collector.events).toEqual([]);
371
+ });
372
+
347
373
  test("full lifecycle: preview_start → input_delta → tool_use → tool_result", () => {
348
374
  const collector = createEventCollector();
349
375
  const deps = createMockDeps({
@@ -355,7 +381,8 @@ describe("tool preview lifecycle", () => {
355
381
  });
356
382
 
357
383
  const toolUseId = "toolu_full_lifecycle";
358
- const toolName = "bash";
384
+ // Use an app tool so input_json_delta is forwarded to the client
385
+ const toolName = "app_create";
359
386
 
360
387
  // 1. Preview start
361
388
  handleToolUsePreviewStart(state, deps, {
@@ -123,4 +123,26 @@ describe("renderWorkspaceTopLevelContext", () => {
123
123
  expect(result).toContain("Directories: ");
124
124
  expect(result).toContain("Files: a.txt, b.txt");
125
125
  });
126
+
127
+ test("renders current conversation and attachment paths when provided", () => {
128
+ const snapshot: TopLevelSnapshot = {
129
+ rootPath: "/sandbox",
130
+ directories: ["src"],
131
+ files: ["package.json"],
132
+ truncated: false,
133
+ };
134
+
135
+ const result = renderWorkspaceTopLevelContext(snapshot, {
136
+ currentConversationPath: "conversations/2026-03-19T12-00-00.000Z_conv-1/",
137
+ currentConversationAttachmentsPath:
138
+ "conversations/2026-03-19T12-00-00.000Z_conv-1/attachments/",
139
+ });
140
+
141
+ expect(result).toContain(
142
+ "Current conversation folder: conversations/2026-03-19T12-00-00.000Z_conv-1/",
143
+ );
144
+ expect(result).toContain(
145
+ "Attachment files: conversations/2026-03-19T12-00-00.000Z_conv-1/attachments/",
146
+ );
147
+ });
126
148
  });