@vellumai/assistant 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/ARCHITECTURE.md +54 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop.test.ts +111 -0
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  8. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  9. package/src/__tests__/app-executors.test.ts +1 -291
  10. package/src/__tests__/app-git-history.test.ts +4 -4
  11. package/src/__tests__/app-routes-csp.test.ts +1 -0
  12. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
  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 +149 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +290 -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-queue.test.ts +36 -1
  37. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  38. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  39. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  41. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  42. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  43. package/src/__tests__/conversation-store.test.ts +24 -21
  44. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  45. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  46. package/src/__tests__/conversation-title-service.test.ts +137 -0
  47. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  48. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  49. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  51. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  52. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  53. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  54. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  55. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  56. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  57. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  58. package/src/__tests__/diagnostics-export.test.ts +70 -1
  59. package/src/__tests__/filesystem-tools.test.ts +4 -2
  60. package/src/__tests__/first-greeting.test.ts +80 -0
  61. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  63. package/src/__tests__/history-repair.test.ts +103 -10
  64. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  65. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  66. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  67. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  68. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  69. package/src/__tests__/media-generate-image.test.ts +47 -94
  70. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  71. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  72. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  73. package/src/__tests__/migration-export-http.test.ts +3 -1
  74. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  75. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  76. package/src/__tests__/mime-builder.test.ts +3 -2
  77. package/src/__tests__/non-member-access-request.test.ts +12 -1
  78. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  80. package/src/__tests__/oauth-store.test.ts +115 -0
  81. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  82. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  83. package/src/__tests__/recording-handler.test.ts +17 -0
  84. package/src/__tests__/registry.test.ts +3 -8
  85. package/src/__tests__/relay-server.test.ts +1 -1
  86. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  87. package/src/__tests__/schema-transforms.test.ts +165 -5
  88. package/src/__tests__/server-history-render.test.ts +2 -2
  89. package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
  90. package/src/__tests__/skill-feature-flags.test.ts +13 -13
  91. package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
  92. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  93. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  94. package/src/__tests__/starter-task-flow.test.ts +1 -0
  95. package/src/__tests__/suggestion-routes.test.ts +443 -0
  96. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  97. package/src/__tests__/swarm-recursion.test.ts +1 -0
  98. package/src/__tests__/swarm-tool.test.ts +1 -0
  99. package/src/__tests__/system-prompt.test.ts +8 -0
  100. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  101. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  102. package/src/__tests__/top-level-renderer.test.ts +22 -0
  103. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  104. package/src/__tests__/web-fetch.test.ts +6 -2
  105. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  106. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  107. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  108. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  109. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  110. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  111. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  112. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  113. package/src/agent/attachments.ts +27 -1
  114. package/src/agent/loop.ts +29 -1
  115. package/src/avatar/traits-png-sync.ts +80 -25
  116. package/src/bundler/app-bundler.ts +4 -4
  117. package/src/calls/call-domain.ts +1 -0
  118. package/src/calls/voice-session-bridge.ts +1 -0
  119. package/src/cli/commands/auth.ts +92 -0
  120. package/src/cli/commands/avatar.ts +7 -6
  121. package/src/cli/commands/config.ts +2 -0
  122. package/src/cli/commands/oauth/providers.ts +29 -0
  123. package/src/cli/program.ts +12 -0
  124. package/src/cli.ts +15 -48
  125. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  126. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  127. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  128. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  129. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  130. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  131. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  135. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  136. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  137. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  139. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  140. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  141. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  142. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  143. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  144. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  145. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  146. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  148. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  149. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  150. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  151. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  152. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  153. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  154. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  155. package/src/config/bundled-tool-registry.ts +2 -14
  156. package/src/config/feature-flag-registry.json +16 -0
  157. package/src/config/loader.ts +64 -0
  158. package/src/config/raw-config-utils.ts +30 -0
  159. package/src/config/schema-utils.ts +28 -7
  160. package/src/config/schema.ts +8 -0
  161. package/src/config/schemas/elevenlabs.ts +18 -0
  162. package/src/config/schemas/memory-lifecycle.ts +4 -2
  163. package/src/config/schemas/memory-storage.ts +1 -1
  164. package/src/config/schemas/services.ts +8 -6
  165. package/src/contacts/contact-store.ts +13 -6
  166. package/src/contacts/contacts-write.ts +0 -1
  167. package/src/context/window-manager.ts +13 -2
  168. package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
  169. package/src/daemon/conversation-agent-loop.ts +56 -19
  170. package/src/daemon/conversation-attachments.ts +18 -36
  171. package/src/daemon/conversation-error.ts +2 -1
  172. package/src/daemon/conversation-history.ts +18 -4
  173. package/src/daemon/conversation-lifecycle.ts +39 -15
  174. package/src/daemon/conversation-messaging.ts +70 -26
  175. package/src/daemon/conversation-process.ts +58 -34
  176. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  177. package/src/daemon/conversation-slash.ts +121 -256
  178. package/src/daemon/conversation-surfaces.ts +143 -20
  179. package/src/daemon/conversation-tool-setup.ts +0 -6
  180. package/src/daemon/conversation-workspace.ts +21 -1
  181. package/src/daemon/conversation.ts +51 -29
  182. package/src/daemon/first-greeting.ts +35 -0
  183. package/src/daemon/handlers/config-embeddings.ts +148 -0
  184. package/src/daemon/handlers/config-model.ts +71 -26
  185. package/src/daemon/handlers/conversations.ts +0 -23
  186. package/src/daemon/handlers/recording.ts +26 -21
  187. package/src/daemon/history-repair.ts +28 -8
  188. package/src/daemon/host-cu-proxy.ts +2 -2
  189. package/src/daemon/lifecycle.ts +106 -64
  190. package/src/daemon/message-protocol.ts +3 -0
  191. package/src/daemon/message-types/conversations.ts +19 -0
  192. package/src/daemon/message-types/messages.ts +1 -0
  193. package/src/daemon/message-types/shared.ts +2 -0
  194. package/src/daemon/message-types/surfaces.ts +2 -0
  195. package/src/daemon/message-types/upgrades.ts +23 -0
  196. package/src/daemon/server.ts +83 -12
  197. package/src/daemon/shutdown-handlers.ts +8 -5
  198. package/src/daemon/startup-error.ts +9 -0
  199. package/src/daemon/tool-side-effects.ts +11 -28
  200. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  201. package/src/instrument.ts +0 -4
  202. package/src/media/app-icon-generator.ts +2 -2
  203. package/src/memory/app-git-service.ts +28 -16
  204. package/src/memory/app-store.ts +230 -41
  205. package/src/memory/attachments-store.ts +558 -130
  206. package/src/memory/conversation-attention-store.ts +70 -0
  207. package/src/memory/conversation-crud.ts +442 -3
  208. package/src/memory/conversation-directories.ts +125 -0
  209. package/src/memory/conversation-disk-view.ts +390 -0
  210. package/src/memory/conversation-key-store.ts +17 -5
  211. package/src/memory/conversation-queries.ts +5 -1
  212. package/src/memory/conversation-title-service.ts +21 -49
  213. package/src/memory/db-init.ts +28 -0
  214. package/src/memory/embedding-backend.ts +42 -53
  215. package/src/memory/embedding-gemini.test.ts +4 -4
  216. package/src/memory/embedding-local.ts +1 -3
  217. package/src/memory/embedding-ollama.ts +1 -3
  218. package/src/memory/embedding-openai.ts +1 -3
  219. package/src/memory/indexer.ts +9 -7
  220. package/src/memory/items-extractor.ts +42 -13
  221. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  222. package/src/memory/job-handlers/embedding.test.ts +1 -4
  223. package/src/memory/llm-request-log-store.ts +100 -1
  224. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  225. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  226. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  227. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  228. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  229. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  230. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  231. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  232. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  233. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  234. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  235. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  236. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  237. package/src/memory/migrations/index.ts +7 -0
  238. package/src/memory/migrations/registry.ts +13 -0
  239. package/src/memory/retriever.test.ts +601 -2
  240. package/src/memory/retriever.ts +85 -9
  241. package/src/memory/schema/conversations.ts +6 -0
  242. package/src/memory/schema/infrastructure.ts +13 -7
  243. package/src/memory/schema/oauth.ts +6 -0
  244. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  245. package/src/notifications/copy-composer.ts +26 -0
  246. package/src/notifications/decision-engine.ts +14 -1
  247. package/src/notifications/emit-signal.ts +1 -1
  248. package/src/notifications/signal.ts +36 -0
  249. package/src/oauth/byo-connection.test.ts +1 -45
  250. package/src/oauth/byo-connection.ts +2 -8
  251. package/src/oauth/connect-orchestrator.ts +15 -11
  252. package/src/oauth/connection-resolver.test.ts +191 -0
  253. package/src/oauth/connection-resolver.ts +66 -38
  254. package/src/oauth/connection.ts +0 -1
  255. package/src/oauth/oauth-store.ts +97 -47
  256. package/src/oauth/platform-connection.test.ts +0 -1
  257. package/src/oauth/platform-connection.ts +11 -3
  258. package/src/oauth/seed-providers.ts +78 -3
  259. package/src/oauth/token-persistence.ts +16 -10
  260. package/src/permissions/checker.ts +62 -19
  261. package/src/prompts/system-prompt.ts +2 -0
  262. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  263. package/src/providers/anthropic/client.ts +8 -1
  264. package/src/providers/failover.ts +4 -1
  265. package/src/providers/gemini/client.ts +50 -0
  266. package/src/providers/model-catalog.ts +92 -0
  267. package/src/providers/model-intents.ts +29 -20
  268. package/src/providers/openai/client.ts +49 -0
  269. package/src/providers/types.ts +2 -0
  270. package/src/runtime/access-request-helper.ts +16 -7
  271. package/src/runtime/auth/credential-service.ts +3 -1
  272. package/src/runtime/auth/route-policy.ts +14 -1
  273. package/src/runtime/btw-sidechain.ts +101 -0
  274. package/src/runtime/channel-reply-delivery.ts +17 -1
  275. package/src/runtime/http-router.ts +3 -1
  276. package/src/runtime/http-server.ts +196 -141
  277. package/src/runtime/http-types.ts +1 -0
  278. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  279. package/src/runtime/routes/access-request-decision.ts +41 -0
  280. package/src/runtime/routes/app-management-routes.ts +6 -3
  281. package/src/runtime/routes/app-routes.ts +7 -3
  282. package/src/runtime/routes/approval-routes.ts +1 -0
  283. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  284. package/src/runtime/routes/attachment-routes.ts +45 -15
  285. package/src/runtime/routes/btw-routes.ts +21 -61
  286. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  287. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  288. package/src/runtime/routes/conversation-routes.ts +222 -28
  289. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  290. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  291. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  292. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  293. package/src/runtime/routes/log-export-routes.ts +3 -0
  294. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  295. package/src/runtime/routes/memory-item-routes.ts +4 -0
  296. package/src/runtime/routes/migration-routes.ts +4 -1
  297. package/src/runtime/routes/oauth-apps.ts +291 -0
  298. package/src/runtime/routes/secret-routes.ts +28 -1
  299. package/src/runtime/routes/settings-routes.ts +14 -0
  300. package/src/runtime/routes/trace-event-routes.ts +4 -1
  301. package/src/schedule/schedule-store.ts +9 -21
  302. package/src/security/secure-keys.ts +21 -0
  303. package/src/signals/bash.ts +1 -1
  304. package/src/swarm/backend-claude-code.ts +3 -6
  305. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  306. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  307. package/src/tools/AGENTS.md +6 -10
  308. package/src/tools/apps/executors.ts +17 -232
  309. package/src/tools/claude-code/claude-code.ts +2 -3
  310. package/src/tools/credentials/vault.ts +7 -12
  311. package/src/tools/host-filesystem/read.ts +13 -10
  312. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  313. package/src/tools/schedule/list.ts +2 -7
  314. package/src/tools/schema-transforms.ts +5 -0
  315. package/src/tools/shared/filesystem/format-diff.ts +4 -21
  316. package/src/tools/skills/execute.ts +1 -1
  317. package/src/tools/tool-manifest.ts +0 -6
  318. package/src/tools/ui-surface/definitions.ts +2 -2
  319. package/src/util/device-id.ts +28 -5
  320. package/src/util/platform.ts +6 -0
  321. package/src/util/pricing.ts +1 -0
  322. package/src/util/retry.ts +1 -3
  323. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  324. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  325. package/src/workspace/migrations/006-services-config.ts +5 -0
  326. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  327. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  328. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  329. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  330. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  331. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  332. package/src/workspace/migrations/registry.ts +10 -0
  333. package/src/workspace/top-level-renderer.ts +12 -0
  334. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  335. package/src/__tests__/asset-search-tool.test.ts +0 -536
  336. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  337. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  338. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  339. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  340. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  341. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  342. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  343. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  344. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  345. package/src/daemon/media-visibility-policy.ts +0 -59
  346. package/src/tools/assets/materialize.ts +0 -248
  347. package/src/tools/assets/search.ts +0 -400
@@ -1,190 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- type AttachmentContext,
5
- filterVisibleAttachments,
6
- isAttachmentVisible,
7
- } from "../daemon/media-visibility-policy.js";
8
-
9
- // ---------------------------------------------------------------------------
10
- // isAttachmentVisible
11
- // ---------------------------------------------------------------------------
12
-
13
- describe("isAttachmentVisible", () => {
14
- const standardAttachment: AttachmentContext = {
15
- conversationId: "conv-standard-001",
16
- isPrivate: false,
17
- };
18
-
19
- const privateAttachmentA: AttachmentContext = {
20
- conversationId: "conv-private-aaa",
21
- isPrivate: true,
22
- };
23
-
24
- const privateAttachmentB: AttachmentContext = {
25
- conversationId: "conv-private-bbb",
26
- isPrivate: true,
27
- };
28
-
29
- describe("standard conversation attachments", () => {
30
- test("visible from a standard conversation", () => {
31
- const ctx: AttachmentContext = {
32
- conversationId: "conv-other",
33
- isPrivate: false,
34
- };
35
- expect(isAttachmentVisible(standardAttachment, ctx)).toBe(true);
36
- });
37
-
38
- test("visible from a different standard conversation", () => {
39
- const ctx: AttachmentContext = {
40
- conversationId: "conv-standard-002",
41
- isPrivate: false,
42
- };
43
- expect(isAttachmentVisible(standardAttachment, ctx)).toBe(true);
44
- });
45
-
46
- test("visible from a private conversation", () => {
47
- const ctx: AttachmentContext = {
48
- conversationId: "conv-private-xyz",
49
- isPrivate: true,
50
- };
51
- expect(isAttachmentVisible(standardAttachment, ctx)).toBe(true);
52
- });
53
-
54
- test("visible when isPrivate is explicitly false", () => {
55
- const attachment: AttachmentContext = {
56
- conversationId: "conv-001",
57
- isPrivate: false,
58
- };
59
- const ctx: AttachmentContext = {
60
- conversationId: "conv-002",
61
- isPrivate: false,
62
- };
63
- expect(isAttachmentVisible(attachment, ctx)).toBe(true);
64
- });
65
- });
66
-
67
- describe("private conversation attachments", () => {
68
- test("visible from the same private conversation", () => {
69
- const ctx: AttachmentContext = {
70
- conversationId: privateAttachmentA.conversationId,
71
- isPrivate: true,
72
- };
73
- expect(isAttachmentVisible(privateAttachmentA, ctx)).toBe(true);
74
- });
75
-
76
- test("NOT visible from a different private conversation", () => {
77
- const ctx: AttachmentContext = {
78
- conversationId: privateAttachmentB.conversationId,
79
- isPrivate: true,
80
- };
81
- expect(isAttachmentVisible(privateAttachmentA, ctx)).toBe(false);
82
- });
83
-
84
- test("NOT visible from a standard conversation", () => {
85
- const ctx: AttachmentContext = {
86
- conversationId: "conv-standard-001",
87
- isPrivate: false,
88
- };
89
- expect(isAttachmentVisible(privateAttachmentA, ctx)).toBe(false);
90
- });
91
-
92
- test("NOT visible when context is standard (isPrivate false)", () => {
93
- const ctx: AttachmentContext = {
94
- conversationId: privateAttachmentA.conversationId,
95
- isPrivate: false,
96
- };
97
- expect(isAttachmentVisible(privateAttachmentA, ctx)).toBe(false);
98
- });
99
-
100
- test("NOT visible from standard conversation even with same conversationId", () => {
101
- // Edge case: same conversationId but the context is not marked as private
102
- const ctx: AttachmentContext = {
103
- conversationId: privateAttachmentA.conversationId,
104
- isPrivate: false,
105
- };
106
- expect(isAttachmentVisible(privateAttachmentA, ctx)).toBe(false);
107
- });
108
- });
109
- });
110
-
111
- // ---------------------------------------------------------------------------
112
- // filterVisibleAttachments
113
- // ---------------------------------------------------------------------------
114
-
115
- describe("filterVisibleAttachments", () => {
116
- interface TestAttachment {
117
- id: string;
118
- conversationId: string;
119
- isPrivate: boolean;
120
- }
121
-
122
- const getContext = (a: TestAttachment): AttachmentContext => ({
123
- conversationId: a.conversationId,
124
- isPrivate: a.isPrivate,
125
- });
126
-
127
- const standardAtt: TestAttachment = {
128
- id: "att-1",
129
- conversationId: "conv-std",
130
- isPrivate: false,
131
- };
132
- const privateAttA: TestAttachment = {
133
- id: "att-2",
134
- conversationId: "conv-priv-a",
135
- isPrivate: true,
136
- };
137
- const privateAttB: TestAttachment = {
138
- id: "att-3",
139
- conversationId: "conv-priv-b",
140
- isPrivate: true,
141
- };
142
-
143
- const allAttachments = [standardAtt, privateAttA, privateAttB];
144
-
145
- test("returns all standard attachments regardless of context", () => {
146
- const ctx: AttachmentContext = {
147
- conversationId: "conv-unrelated",
148
- isPrivate: false,
149
- };
150
- const result = filterVisibleAttachments(allAttachments, ctx, getContext);
151
- expect(result).toEqual([standardAtt]);
152
- });
153
-
154
- test("includes matching private attachment when in same private conversation", () => {
155
- const ctx: AttachmentContext = {
156
- conversationId: "conv-priv-a",
157
- isPrivate: true,
158
- };
159
- const result = filterVisibleAttachments(allAttachments, ctx, getContext);
160
- expect(result).toEqual([standardAtt, privateAttA]);
161
- });
162
-
163
- test("includes only the private attachment for the current thread, not others", () => {
164
- const ctx: AttachmentContext = {
165
- conversationId: "conv-priv-b",
166
- isPrivate: true,
167
- };
168
- const result = filterVisibleAttachments(allAttachments, ctx, getContext);
169
- expect(result).toEqual([standardAtt, privateAttB]);
170
- });
171
-
172
- test("returns empty array when input is empty", () => {
173
- const ctx: AttachmentContext = {
174
- conversationId: "conv-std",
175
- isPrivate: false,
176
- };
177
- const result = filterVisibleAttachments([], ctx, getContext);
178
- expect(result).toEqual([]);
179
- });
180
-
181
- test("preserves original attachment objects (referential identity)", () => {
182
- const ctx: AttachmentContext = {
183
- conversationId: "conv-priv-a",
184
- isPrivate: true,
185
- };
186
- const result = filterVisibleAttachments(allAttachments, ctx, getContext);
187
- expect(result[0]).toBe(standardAtt);
188
- expect(result[1]).toBe(privateAttA);
189
- });
190
- });
@@ -1,14 +0,0 @@
1
- import * as appStore from "../../../../memory/app-store.js";
2
- import type { AppFileEditInput } from "../../../../tools/apps/executors.js";
3
- import { executeAppFileEdit } from "../../../../tools/apps/executors.js";
4
- import type {
5
- ToolContext,
6
- ToolExecutionResult,
7
- } from "../../../../tools/types.js";
8
-
9
- export async function run(
10
- input: Record<string, unknown>,
11
- _context: ToolContext,
12
- ): Promise<ToolExecutionResult> {
13
- return executeAppFileEdit(input as unknown as AppFileEditInput, appStore);
14
- }
@@ -1,13 +0,0 @@
1
- import * as appStore from "../../../../memory/app-store.js";
2
- import { executeAppFileList } from "../../../../tools/apps/executors.js";
3
- import type {
4
- ToolContext,
5
- ToolExecutionResult,
6
- } from "../../../../tools/types.js";
7
-
8
- export async function run(
9
- input: Record<string, unknown>,
10
- _context: ToolContext,
11
- ): Promise<ToolExecutionResult> {
12
- return executeAppFileList({ app_id: input.app_id as string }, appStore);
13
- }
@@ -1,21 +0,0 @@
1
- import * as appStore from "../../../../memory/app-store.js";
2
- import { executeAppFileRead } from "../../../../tools/apps/executors.js";
3
- import type {
4
- ToolContext,
5
- ToolExecutionResult,
6
- } from "../../../../tools/types.js";
7
-
8
- export async function run(
9
- input: Record<string, unknown>,
10
- _context: ToolContext,
11
- ): Promise<ToolExecutionResult> {
12
- return executeAppFileRead(
13
- {
14
- app_id: input.app_id as string,
15
- path: input.path as string,
16
- offset: input.offset as number | undefined,
17
- limit: input.limit as number | undefined,
18
- },
19
- appStore,
20
- );
21
- }
@@ -1,14 +0,0 @@
1
- import * as appStore from "../../../../memory/app-store.js";
2
- import type { AppFileWriteInput } from "../../../../tools/apps/executors.js";
3
- import { executeAppFileWrite } from "../../../../tools/apps/executors.js";
4
- import type {
5
- ToolContext,
6
- ToolExecutionResult,
7
- } from "../../../../tools/types.js";
8
-
9
- export async function run(
10
- input: Record<string, unknown>,
11
- _context: ToolContext,
12
- ): Promise<ToolExecutionResult> {
13
- return executeAppFileWrite(input as unknown as AppFileWriteInput, appStore);
14
- }
@@ -1,13 +0,0 @@
1
- import * as appStore from "../../../../memory/app-store.js";
2
- import { executeAppList } from "../../../../tools/apps/executors.js";
3
- import type {
4
- ToolContext,
5
- ToolExecutionResult,
6
- } from "../../../../tools/types.js";
7
-
8
- export async function run(
9
- _input: Record<string, unknown>,
10
- _context: ToolContext,
11
- ): Promise<ToolExecutionResult> {
12
- return executeAppList(appStore);
13
- }
@@ -1,23 +0,0 @@
1
- import * as appStore from "../../../../memory/app-store.js";
2
- import { executeAppUpdate } from "../../../../tools/apps/executors.js";
3
- import type {
4
- ToolContext,
5
- ToolExecutionResult,
6
- } from "../../../../tools/types.js";
7
-
8
- export async function run(
9
- input: Record<string, unknown>,
10
- _context: ToolContext,
11
- ): Promise<ToolExecutionResult> {
12
- return executeAppUpdate(
13
- {
14
- app_id: input.app_id as string,
15
- name: input.name as string | undefined,
16
- description: input.description as string | undefined,
17
- schema_json: input.schema_json as string | undefined,
18
- html: input.html as string | undefined,
19
- pages: input.pages as Record<string, string> | undefined,
20
- },
21
- appStore,
22
- );
23
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * Centralized policy for attachment visibility across conversation types.
3
- *
4
- * Private-conversation attachments are scoped: they are only visible when the
5
- * current context is the *same* private conversation. Standard (non-private)
6
- * conversation attachments are visible everywhere.
7
- *
8
- * This is a pure policy module -- no IO, no database queries, just logic.
9
- * Actual enforcement in tools happens downstream (e.g., attachment-list
10
- * and attachment-retrieve tools check this before returning results).
11
- */
12
-
13
- export interface AttachmentContext {
14
- conversationId: string;
15
- /** Callers must always resolve this — omitting it would silently fail open. */
16
- isPrivate: boolean;
17
- }
18
-
19
- /**
20
- * Determine whether a single attachment is visible from the given context.
21
- *
22
- * Rules:
23
- * 1. Attachments from standard (non-private) conversations are always visible.
24
- * 2. Attachments from private conversations are only visible when the current
25
- * context matches the same private conversation (same conversationId).
26
- */
27
- export function isAttachmentVisible(
28
- attachment: AttachmentContext,
29
- currentContext: AttachmentContext,
30
- ): boolean {
31
- // Standard conversation attachments are universally visible
32
- if (!attachment.isPrivate) {
33
- return true;
34
- }
35
-
36
- // Private conversation attachments require the viewer to be in the same private conversation
37
- return (
38
- currentContext.isPrivate === true &&
39
- currentContext.conversationId === attachment.conversationId
40
- );
41
- }
42
-
43
- /**
44
- * Filter a list of attachments to only those visible from the current context.
45
- *
46
- * @param attachments - The full list of attachments to filter.
47
- * @param currentContext - The conversation context from which visibility is evaluated.
48
- * @param getContext - Extracts the conversation context from each attachment
49
- * record (since the caller's attachment type may differ from AttachmentContext).
50
- */
51
- export function filterVisibleAttachments<T>(
52
- attachments: T[],
53
- currentContext: AttachmentContext,
54
- getContext: (attachment: T) => AttachmentContext,
55
- ): T[] {
56
- return attachments.filter((a) =>
57
- isAttachmentVisible(getContext(a), currentContext),
58
- );
59
- }
@@ -1,248 +0,0 @@
1
- /**
2
- * asset_materialize - write a stored attachment to a sandbox file path.
3
- *
4
- * Accepts an attachment ID (from asset_search) and a destination path
5
- * within the sandbox working directory. Decodes the base64 content and
6
- * writes it to disk so scripts and other tools can consume it as a
7
- * regular file.
8
- */
9
-
10
- import { copyFileSync, mkdirSync, writeFileSync } from "node:fs";
11
- import { dirname } from "node:path";
12
-
13
- import { eq } from "drizzle-orm";
14
-
15
- import {
16
- type AttachmentContext,
17
- isAttachmentVisible,
18
- } from "../../daemon/media-visibility-policy.js";
19
- import {
20
- getAttachmentContent,
21
- getFilePathForAttachment,
22
- } from "../../memory/attachments-store.js";
23
- import { getConversationType } from "../../memory/conversation-crud.js";
24
- import { getDb } from "../../memory/db.js";
25
- import { attachments } from "../../memory/schema.js";
26
- import { RiskLevel } from "../../permissions/types.js";
27
- import type { ToolDefinition } from "../../providers/types.js";
28
- import { registerTool } from "../registry.js";
29
- import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
30
- import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
31
- import { getAttachmentSourceConversations } from "./search.js";
32
-
33
- // ---------------------------------------------------------------------------
34
- // Size limit - prevent materializing excessively large attachments
35
- // ---------------------------------------------------------------------------
36
-
37
- /** 100 MB ceiling for materialized files. */
38
- export const MAX_MATERIALIZE_BYTES = 100 * 1024 * 1024;
39
-
40
- // ---------------------------------------------------------------------------
41
- // Helpers
42
- // ---------------------------------------------------------------------------
43
-
44
- function formatBytes(bytes: number): string {
45
- if (bytes < 1024) return `${bytes} B`;
46
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
47
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
48
- }
49
-
50
- /**
51
- * Load attachment metadata by its primary key.
52
- *
53
- * Not scoped by assistantId because attachment access is enforced by
54
- * conversation visibility checks in execute().
55
- */
56
- function loadAttachmentById(attachmentId: string): {
57
- id: string;
58
- originalFilename: string;
59
- mimeType: string;
60
- sizeBytes: number;
61
- } | null {
62
- const db = getDb();
63
- const row = db
64
- .select({
65
- id: attachments.id,
66
- originalFilename: attachments.originalFilename,
67
- mimeType: attachments.mimeType,
68
- sizeBytes: attachments.sizeBytes,
69
- })
70
- .from(attachments)
71
- .where(eq(attachments.id, attachmentId))
72
- .get();
73
-
74
- return row ?? null;
75
- }
76
-
77
- // ---------------------------------------------------------------------------
78
- // Tool definition
79
- // ---------------------------------------------------------------------------
80
-
81
- const definition: ToolDefinition = {
82
- name: "asset_materialize",
83
- description:
84
- "Copy a stored attachment to a file on disk inside the sandbox directory. " +
85
- "Use attachment IDs from asset_search results. The file can then be used " +
86
- "as input to scripts, tools, or other processing.",
87
- input_schema: {
88
- type: "object",
89
- properties: {
90
- attachment_id: {
91
- type: "string",
92
- description:
93
- "The ID of the attachment to materialize (from asset_search results).",
94
- },
95
- destination_path: {
96
- type: "string",
97
- description:
98
- "Path where the file should be written, relative to (or inside) the sandbox working directory.",
99
- },
100
- },
101
- required: ["attachment_id", "destination_path"],
102
- },
103
- };
104
-
105
- // ---------------------------------------------------------------------------
106
- // Tool class
107
- // ---------------------------------------------------------------------------
108
-
109
- class AssetMaterializeTool implements Tool {
110
- name = "asset_materialize";
111
- description = definition.description;
112
- category = "assets";
113
- defaultRiskLevel = RiskLevel.Low;
114
-
115
- getDefinition(): ToolDefinition {
116
- return definition;
117
- }
118
-
119
- async execute(
120
- input: Record<string, unknown>,
121
- context: ToolContext,
122
- ): Promise<ToolExecutionResult> {
123
- const attachmentId = input.attachment_id as string | undefined;
124
- const destinationPath = input.destination_path as string | undefined;
125
-
126
- // --- Input validation ---------------------------------------------------
127
-
128
- if (!attachmentId || typeof attachmentId !== "string") {
129
- return {
130
- content: "Error: attachment_id is required and must be a string.",
131
- isError: true,
132
- };
133
- }
134
-
135
- if (!destinationPath || typeof destinationPath !== "string") {
136
- return {
137
- content: "Error: destination_path is required and must be a string.",
138
- isError: true,
139
- };
140
- }
141
-
142
- // --- Sandbox path enforcement -------------------------------------------
143
-
144
- const pathCheck = sandboxPolicy(destinationPath, context.workingDir, {
145
- mustExist: false,
146
- });
147
- if (!pathCheck.ok) {
148
- return {
149
- content: `Error: ${pathCheck.error}`,
150
- isError: true,
151
- };
152
- }
153
-
154
- const resolvedPath = pathCheck.resolved;
155
-
156
- // --- Load attachment ----------------------------------------------------
157
-
158
- const attachment = loadAttachmentById(attachmentId);
159
- if (!attachment) {
160
- return {
161
- content: `Error: Attachment "${attachmentId}" not found.`,
162
- isError: true,
163
- };
164
- }
165
-
166
- // --- Visibility check ---------------------------------------------------
167
- // Reject materialization of attachments from private conversations the caller
168
- // does not belong to. This prevents cross-conversation data leakage.
169
-
170
- const currentConversationType = getConversationType(context.conversationId);
171
- const currentContext: AttachmentContext = {
172
- conversationId: context.conversationId,
173
- isPrivate: currentConversationType === "private",
174
- };
175
-
176
- const sources = getAttachmentSourceConversations(attachmentId);
177
- if (sources.length > 0) {
178
- const hasStandard = sources.some((s) => s.conversationType !== "private");
179
- if (!hasStandard) {
180
- // All sources are private - check if the caller is in any of those conversations
181
- const callerInSourceConversation = sources.some((s) =>
182
- isAttachmentVisible(
183
- { conversationId: s.conversationId, isPrivate: true },
184
- currentContext,
185
- ),
186
- );
187
- if (!callerInSourceConversation) {
188
- return {
189
- content:
190
- `Error: Attachment "${attachment.originalFilename}" is from a private conversation ` +
191
- `and cannot be accessed from this context. You can only access this ` +
192
- `attachment from within the private conversation where it was shared.`,
193
- isError: true,
194
- };
195
- }
196
- }
197
- }
198
-
199
- // --- Size check ---------------------------------------------------------
200
-
201
- if (attachment.sizeBytes > MAX_MATERIALIZE_BYTES) {
202
- return {
203
- content:
204
- `Error: Attachment "${attachment.originalFilename}" is ${formatBytes(attachment.sizeBytes)}, ` +
205
- `which exceeds the ${formatBytes(MAX_MATERIALIZE_BYTES)} materialization limit.`,
206
- isError: true,
207
- };
208
- }
209
-
210
- // --- Write to disk -------------------------------------------------------
211
-
212
- try {
213
- // Ensure parent directories exist
214
- mkdirSync(dirname(resolvedPath), { recursive: true });
215
-
216
- // For file-backed attachments, copy directly from the on-disk path
217
- // to avoid reading potentially large files into memory. For inline
218
- // attachments, decode the base64 content from the database.
219
- const filePath = getFilePathForAttachment(attachmentId);
220
- if (filePath) {
221
- copyFileSync(filePath, resolvedPath);
222
- } else {
223
- const buffer = getAttachmentContent(attachmentId);
224
- if (!buffer) {
225
- return {
226
- content: `Error: Could not read content for attachment "${attachmentId}".`,
227
- isError: true,
228
- };
229
- }
230
- writeFileSync(resolvedPath, buffer);
231
- }
232
-
233
- return {
234
- content:
235
- `Materialized "${attachment.originalFilename}" (${attachment.mimeType}, ` +
236
- `${formatBytes(attachment.sizeBytes)}) to ${resolvedPath}`,
237
- isError: false,
238
- };
239
- } catch (err) {
240
- const msg = err instanceof Error ? err.message : String(err);
241
- return { content: `Error writing file: ${msg}`, isError: true };
242
- }
243
- }
244
- }
245
-
246
- export const assetMaterializeTool = new AssetMaterializeTool();
247
-
248
- registerTool(assetMaterializeTool);