@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,18 +1,14 @@
1
1
  # Tools - Agent Instructions
2
2
 
3
- ## No New Tools Policy
3
+ ## New Non-Skill Tools Are Strongly Discouraged
4
4
 
5
- **New tool registrations require approval from Team Jarvis.**
5
+ **Prefer skills over new non-skill tool registrations.** Non-skill tools require approval from Team Jarvis.
6
6
 
7
- The tool registration system (`class ... implements Tool` + `registerTool()`) is being phased out in favor of skill-based approaches. Before adding a new tool, contact Team Jarvis for approval.
7
+ Skills are the preferred approach for adding new capabilities they are progressively disclosed into context, more portable, and can be iterated on independently. New non-skill tool registrations (`class ... implements Tool` + `registerTool()`) carry additional costs:
8
8
 
9
- ## Why This Policy Exists
9
+ 1. **Context overhead** Each registered tool adds to the system prompt and increases token usage for every conversation.
10
10
 
11
- 1. **Skills are preferred** - The project direction is to teach the assistant CLI tools via skills rather than hardcoding tool implementations. Skills are progressively disclosed into context, are more portable, and are often self-contained.
12
-
13
- 2. **Context overhead** - Each registered tool adds to the system prompt and increases token usage for every conversation.
14
-
15
- 3. **Maintenance burden** - Tools require ongoing maintenance, testing, and security review. Skills can be iterated on independently.
11
+ 2. **Maintenance burden** Tools require ongoing maintenance, testing, and security review.
16
12
 
17
13
  ## What To Do Instead
18
14
 
@@ -26,7 +22,7 @@ Instead of creating a new tool, consider:
26
22
 
27
23
  ## Approved Exception: Credential Execution Service (CES) Tools
28
24
 
29
- The following three CES tools are the only approved exception to the no-new-tools policy:
25
+ The following three CES tools are approved exceptions that justify tool registrations over skills:
30
26
 
31
27
  | Tool | Purpose |
32
28
  | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -10,11 +10,8 @@
10
10
 
11
11
  import { compileApp } from "../../bundler/app-compiler.js";
12
12
  import { generateAppIcon } from "../../media/app-icon-generator.js";
13
- import type {
14
- AppDefinition,
15
- EditEngineResult,
16
- } from "../../memory/app-store.js";
17
- import { getAppsDir, isMultifileApp } from "../../memory/app-store.js";
13
+ import type { AppDefinition } from "../../memory/app-store.js";
14
+ import { getAppDirPath } from "../../memory/app-store.js";
18
15
 
19
16
  // ---------------------------------------------------------------------------
20
17
  // Shared result type
@@ -35,9 +32,6 @@ export interface ExecutorResult {
35
32
  export interface AppStoreReader {
36
33
  getApp(id: string): AppDefinition | null;
37
34
  listApps(): AppDefinition[];
38
- queryAppRecords(appId: string): unknown[];
39
- listAppFiles(appId: string): string[];
40
- readAppFile(appId: string, path: string): string;
41
35
  }
42
36
 
43
37
  export interface AppStoreWriter {
@@ -61,13 +55,6 @@ export interface AppStoreWriter {
61
55
  ): AppDefinition;
62
56
  deleteApp(id: string): void;
63
57
  writeAppFile(appId: string, path: string, content: string): void;
64
- editAppFile(
65
- appId: string,
66
- path: string,
67
- oldString: string,
68
- newString: string,
69
- replaceAll?: boolean,
70
- ): EditEngineResult;
71
58
  }
72
59
 
73
60
  export type AppStore = AppStoreReader & AppStoreWriter;
@@ -81,28 +68,6 @@ export type ProxyResolver = (
81
68
  input: Record<string, unknown>,
82
69
  ) => Promise<ExecutorResult>;
83
70
 
84
- // ---------------------------------------------------------------------------
85
- // Path resolution - multifile apps default to src/ for file operations
86
- // ---------------------------------------------------------------------------
87
-
88
- /**
89
- * For multifile (formatVersion 2) apps, prepend `src/` to paths that don't
90
- * already target a known top-level directory (src/, dist/, records/).
91
- * Legacy apps pass through unchanged.
92
- */
93
- export function resolveAppFilePath(app: AppDefinition, path: string): string {
94
- if (!isMultifileApp(app)) return path;
95
- const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
96
- if (
97
- normalized.startsWith("src/") ||
98
- normalized.startsWith("dist/") ||
99
- normalized.startsWith("records/")
100
- ) {
101
- return normalized;
102
- }
103
- return `src/${normalized}`;
104
- }
105
-
106
71
  // ---------------------------------------------------------------------------
107
72
  // app_create
108
73
  // ---------------------------------------------------------------------------
@@ -222,8 +187,7 @@ render(<App />, document.getElementById('app')!);
222
187
  store.writeAppFile(app.id, "src/main.tsx", mainTsx);
223
188
 
224
189
  // Compile src/ → dist/
225
- const { join } = await import("node:path");
226
- const appDir = join(getAppsDir(), app.id);
190
+ const appDir = getAppDirPath(app.id);
227
191
  const compileResult = await compileApp(appDir);
228
192
  if (!compileResult.ok) {
229
193
  return {
@@ -286,72 +250,6 @@ render(<App />, document.getElementById('app')!);
286
250
  return { content: JSON.stringify(app), isError: false };
287
251
  }
288
252
 
289
- // ---------------------------------------------------------------------------
290
- // app_list
291
- // ---------------------------------------------------------------------------
292
-
293
- export function executeAppList(store: AppStoreReader): ExecutorResult {
294
- const apps = store.listApps().map((a) => ({
295
- id: a.id,
296
- name: a.name,
297
- description: a.description,
298
- updatedAt: a.updatedAt,
299
- }));
300
- return { content: JSON.stringify(apps), isError: false };
301
- }
302
-
303
- // ---------------------------------------------------------------------------
304
- // app_query
305
- // ---------------------------------------------------------------------------
306
-
307
- export interface AppQueryInput {
308
- app_id: string;
309
- }
310
-
311
- export function executeAppQuery(
312
- input: AppQueryInput,
313
- store: AppStoreReader,
314
- ): ExecutorResult {
315
- const records = store.queryAppRecords(input.app_id);
316
- return { content: JSON.stringify(records), isError: false };
317
- }
318
-
319
- // ---------------------------------------------------------------------------
320
- // app_update
321
- // ---------------------------------------------------------------------------
322
-
323
- export interface AppUpdateInput {
324
- app_id: string;
325
- name?: string;
326
- description?: string;
327
- schema_json?: string;
328
- html?: string;
329
- pages?: Record<string, string>;
330
- }
331
-
332
- export function executeAppUpdate(
333
- input: AppUpdateInput,
334
- store: AppStore,
335
- ): ExecutorResult {
336
- const updates: Partial<
337
- Pick<
338
- AppDefinition,
339
- "name" | "description" | "schemaJson" | "htmlDefinition" | "pages"
340
- >
341
- > = {};
342
- if (typeof input.name === "string") updates.name = input.name;
343
- if (typeof input.description === "string")
344
- updates.description = input.description;
345
- if (typeof input.schema_json === "string")
346
- updates.schemaJson = input.schema_json;
347
- if (typeof input.html === "string") updates.htmlDefinition = input.html;
348
- if (input.pages && typeof input.pages === "object")
349
- updates.pages = input.pages;
350
-
351
- const app = store.updateApp(input.app_id, updates);
352
- return { content: JSON.stringify(app), isError: false };
353
- }
354
-
355
253
  // ---------------------------------------------------------------------------
356
254
  // app_delete
357
255
  // ---------------------------------------------------------------------------
@@ -372,131 +270,15 @@ export function executeAppDelete(
372
270
  }
373
271
 
374
272
  // ---------------------------------------------------------------------------
375
- // app_file_list
376
- // ---------------------------------------------------------------------------
377
-
378
- export interface AppFileListInput {
379
- app_id: string;
380
- }
381
-
382
- export function executeAppFileList(
383
- input: AppFileListInput,
384
- store: AppStoreReader,
385
- ): ExecutorResult {
386
- const files = store.listAppFiles(input.app_id);
387
- const app = store.getApp(input.app_id);
388
-
389
- if (app && isMultifileApp(app)) {
390
- // Separate build output paths from source paths without mutating the
391
- // file path strings - consumers need clean paths for subsequent tool calls.
392
- const buildOutputPaths = files.filter((f) =>
393
- f.replace(/\\/g, "/").startsWith("dist/"),
394
- );
395
- return {
396
- content: JSON.stringify({
397
- files,
398
- buildOutput: buildOutputPaths,
399
- }),
400
- isError: false,
401
- };
402
- }
403
-
404
- return { content: JSON.stringify(files), isError: false };
405
- }
406
-
407
- // ---------------------------------------------------------------------------
408
- // app_file_read
409
- // ---------------------------------------------------------------------------
410
-
411
- export interface AppFileReadInput {
412
- app_id: string;
413
- path: string;
414
- offset?: number;
415
- limit?: number;
416
- }
417
-
418
- export function executeAppFileRead(
419
- input: AppFileReadInput,
420
- store: AppStoreReader,
421
- ): ExecutorResult {
422
- const offset = input.offset ?? 1;
423
- const limit = input.limit;
424
-
425
- const app = store.getApp(input.app_id);
426
- const resolvedPath = app ? resolveAppFilePath(app, input.path) : input.path;
427
- const raw = store.readAppFile(input.app_id, resolvedPath);
428
- const allLines = raw.split("\n");
429
- const startIndex = Math.max(0, offset - 1);
430
- const sliced =
431
- limit != null
432
- ? allLines.slice(startIndex, startIndex + limit)
433
- : allLines.slice(startIndex);
434
-
435
- const formatted = sliced
436
- .map((line, i) => {
437
- const lineNum = startIndex + i + 1;
438
- return `${String(lineNum).padStart(6)}\t${line}`;
439
- })
440
- .join("\n");
441
-
442
- return { content: formatted, isError: false };
443
- }
444
-
445
- // ---------------------------------------------------------------------------
446
- // app_file_edit
447
- // ---------------------------------------------------------------------------
448
-
449
- export interface AppFileEditInput {
450
- app_id: string;
451
- path: string;
452
- old_string: string;
453
- new_string: string;
454
- replace_all?: boolean;
455
- status?: string;
456
- }
457
-
458
- export function executeAppFileEdit(
459
- input: AppFileEditInput,
460
- store: AppStore,
461
- ): ExecutorResult {
462
- if (!input.old_string) {
463
- return {
464
- content: JSON.stringify({ error: "old_string must not be empty" }),
465
- isError: true,
466
- };
467
- }
468
-
469
- const app = store.getApp(input.app_id);
470
- const resolvedPath = app ? resolveAppFilePath(app, input.path) : input.path;
471
-
472
- const replaceAll = input.replace_all ?? false;
473
- const result = store.editAppFile(
474
- input.app_id,
475
- resolvedPath,
476
- input.old_string,
477
- input.new_string,
478
- replaceAll,
479
- );
480
- return {
481
- content: JSON.stringify(result),
482
- isError: false,
483
- status: input.status,
484
- };
485
- }
486
-
487
- // ---------------------------------------------------------------------------
488
- // app_file_write
273
+ // app_refresh
489
274
  // ---------------------------------------------------------------------------
490
275
 
491
- export interface AppFileWriteInput {
276
+ export interface AppRefreshInput {
492
277
  app_id: string;
493
- path: string;
494
- content: string;
495
- status?: string;
496
278
  }
497
279
 
498
- export function executeAppFileWrite(
499
- input: AppFileWriteInput,
280
+ export function executeAppRefresh(
281
+ input: AppRefreshInput,
500
282
  store: AppStore,
501
283
  ): ExecutorResult {
502
284
  const app = store.getApp(input.app_id);
@@ -507,12 +289,16 @@ export function executeAppFileWrite(
507
289
  };
508
290
  }
509
291
 
510
- const resolvedPath = resolveAppFilePath(app, input.path);
511
- store.writeAppFile(input.app_id, resolvedPath, input.content);
292
+ // Empty update bumps updatedAt timestamp, triggering recompilation and
293
+ // surface refresh on the client side.
294
+ const updated = store.updateApp(input.app_id, {});
512
295
  return {
513
- content: JSON.stringify({ written: true, path: resolvedPath }),
296
+ content: JSON.stringify({
297
+ refreshed: true,
298
+ appId: updated.id,
299
+ name: updated.name,
300
+ }),
514
301
  isError: false,
515
- status: input.status,
516
302
  };
517
303
  }
518
304
 
@@ -541,9 +327,8 @@ export async function executeAppGenerateIcon(
541
327
  // destroying an existing icon if generation fails.
542
328
  const { existsSync, renameSync, unlinkSync } = await import("node:fs");
543
329
  const { join } = await import("node:path");
544
- const { getAppsDir } = await import("../../memory/app-store.js");
545
- const iconPath = join(getAppsDir(), input.app_id, "icon.png");
546
- const tempPath = join(getAppsDir(), input.app_id, "icon.tmp.png");
330
+ const iconPath = join(getAppDirPath(input.app_id), "icon.png");
331
+ const tempPath = join(getAppDirPath(input.app_id), "icon.tmp.png");
547
332
 
548
333
  // Temporarily move existing icon aside so generateAppIcon doesn't skip
549
334
  if (existsSync(iconPath)) {
@@ -4,7 +4,7 @@ import {
4
4
  } from "../../commands/cc-command-registry.js";
5
5
  import { RiskLevel } from "../../permissions/types.js";
6
6
  import type { ToolDefinition } from "../../providers/types.js";
7
- import { getSecureKeyAsync } from "../../security/secure-keys.js";
7
+ import { getProviderKeyAsync } from "../../security/secure-keys.js";
8
8
  import type { WorkerProfile } from "../../swarm/worker-backend.js";
9
9
  import { getProfilePolicy } from "../../swarm/worker-backend.js";
10
10
  import { getLogger } from "../../util/logger.js";
@@ -203,8 +203,7 @@ export const claudeCodeTool: Tool = {
203
203
  const profilePolicy = getProfilePolicy(profileName);
204
204
 
205
205
  // Validate API key
206
- const apiKey =
207
- (await getSecureKeyAsync("anthropic")) ?? process.env.ANTHROPIC_API_KEY;
206
+ const apiKey = await getProviderKeyAsync("anthropic");
208
207
  if (!apiKey) {
209
208
  return {
210
209
  content:
@@ -7,8 +7,8 @@ import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
7
7
  import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
8
8
  import {
9
9
  disconnectOAuthProvider,
10
+ getActiveConnection,
10
11
  getAppByProviderAndClientId,
11
- getConnectionByProviderAndAccount,
12
12
  getMostRecentAppByProvider,
13
13
  getProvider,
14
14
  } from "../../oauth/oauth-store.js";
@@ -66,9 +66,7 @@ async function storeSlackChannelCredential(
66
66
  : setSlackChannelConfig(undefined, value);
67
67
  }
68
68
 
69
- function formatSlackChannelStatus(
70
- result: SlackChannelConfigResult,
71
- ): string {
69
+ function formatSlackChannelStatus(result: SlackChannelConfigResult): string {
72
70
  if (result.connected) {
73
71
  const teamLabel = result.teamName ?? "Slack";
74
72
  const botLabel = result.botUsername ? ` (@${result.botUsername})` : "";
@@ -366,8 +364,7 @@ class CredentialStoreTool implements Tool {
366
364
  if (!slackChannelResult.success) {
367
365
  return {
368
366
  content: `Error: ${
369
- slackChannelResult.error ??
370
- "failed to configure Slack channel"
367
+ slackChannelResult.error ?? "failed to configure Slack channel"
371
368
  }`,
372
369
  isError: true,
373
370
  };
@@ -523,10 +520,9 @@ class CredentialStoreTool implements Tool {
523
520
  const accountHint = input.account as string | undefined;
524
521
  let oauthResult: "disconnected" | "not-found" | "error";
525
522
  if (accountHint) {
526
- const targetConn = getConnectionByProviderAndAccount(
527
- service,
528
- accountHint,
529
- );
523
+ const targetConn = getActiveConnection(service, {
524
+ account: accountHint,
525
+ });
530
526
  oauthResult = targetConn
531
527
  ? await disconnectOAuthProvider(service, undefined, targetConn.id)
532
528
  : "not-found";
@@ -778,8 +774,7 @@ class CredentialStoreTool implements Tool {
778
774
  if (!slackChannelResult.success) {
779
775
  return {
780
776
  content: `Error: ${
781
- slackChannelResult.error ??
782
- "failed to configure Slack channel"
777
+ slackChannelResult.error ?? "failed to configure Slack channel"
783
778
  }`,
784
779
  isError: true,
785
780
  };
@@ -54,6 +54,19 @@ class HostFileReadTool implements Tool {
54
54
  };
55
55
  }
56
56
 
57
+ // Image files must be handled locally — the host-file proxy protocol
58
+ // only carries {content, isError} and cannot transport contentBlocks
59
+ // (base64 image data). Check for image extensions before the proxy
60
+ // short-circuit so image reads work in managed/macOS+iOS sessions.
61
+ const ext = extname(rawPath).toLowerCase();
62
+ if (IMAGE_EXTENSIONS.has(ext)) {
63
+ const pathCheck = hostPolicy(rawPath);
64
+ if (!pathCheck.ok) {
65
+ return { content: `Error: ${pathCheck.error}`, isError: true };
66
+ }
67
+ return readImageFile(pathCheck.resolved);
68
+ }
69
+
57
70
  // Proxy to connected client for execution on the user's machine
58
71
  // when a capable client is available (managed/cloud-hosted mode).
59
72
  if (context.hostFileProxy?.isAvailable()) {
@@ -69,16 +82,6 @@ class HostFileReadTool implements Tool {
69
82
  );
70
83
  }
71
84
 
72
- // For image files, delegate to the shared image reader.
73
- const ext = extname(rawPath).toLowerCase();
74
- if (IMAGE_EXTENSIONS.has(ext)) {
75
- const pathCheck = hostPolicy(rawPath);
76
- if (!pathCheck.ok) {
77
- return { content: `Error: ${pathCheck.error}`, isError: true };
78
- }
79
- return readImageFile(pathCheck.resolved);
80
- }
81
-
82
85
  const ops = new FileSystemOps(hostPolicy);
83
86
 
84
87
  const result = ops.readFileSafe({
@@ -16,12 +16,14 @@ mock.module("../../registry.js", () => ({
16
16
 
17
17
  mock.module("../../../config/loader.js", () => ({
18
18
  getConfig: () => ({
19
- webSearchProvider: mockWebSearchProvider,
19
+ services: {
20
+ "web-search": { provider: mockWebSearchProvider },
21
+ },
20
22
  }),
21
23
  }));
22
24
 
23
25
  mock.module("../../../security/secure-keys.js", () => ({
24
- getSecureKeyAsync: async (provider: string) => {
26
+ getProviderKeyAsync: async (provider: string) => {
25
27
  if (provider === "brave") return mockBraveSecureKey;
26
28
  if (provider === "perplexity") return mockPerplexitySecureKey;
27
29
  return undefined;
@@ -62,15 +62,10 @@ export async function executeScheduleList(
62
62
  );
63
63
  }
64
64
 
65
- lines.push(
66
- ` Enabled: ${job.enabled}`,
67
- ` Message: ${job.message}`,
68
- );
65
+ lines.push(` Enabled: ${job.enabled}`, ` Message: ${job.message}`);
69
66
 
70
67
  if (!oneShot) {
71
- lines.push(
72
- ` Next run: ${formatLocalDate(job.nextRunAt)}`,
73
- );
68
+ lines.push(` Next run: ${formatLocalDate(job.nextRunAt)}`);
74
69
  }
75
70
 
76
71
  lines.push(
@@ -29,7 +29,12 @@ export function injectActivityField(
29
29
  }
30
30
 
31
31
  const properties = schema.properties as Record<string, unknown>;
32
+
32
33
  if (schemaDefinesProperty(schema, "activity")) {
34
+ // Activity is already defined somewhere in the schema (top-level properties
35
+ // or composite sub-schemas). Don't modify schemas we don't own — MCP tools
36
+ // may define activity as intentionally optional or with server-specific
37
+ // semantics.
33
38
  return def;
34
39
  }
35
40
 
@@ -1,22 +1,12 @@
1
- const MAX_DIFF_LINES = 8;
2
-
3
1
  /**
4
- * Build a compact inline diff from an old→new string replacement.
5
- * Lines are prefixed with - / + and truncated if the change is large.
2
+ * Build an inline diff from an old→new string replacement.
3
+ * Lines are prefixed with - / +.
6
4
  */
7
5
  export function formatEditDiff(oldString: string, newString: string): string {
8
6
  const removed =
9
- oldString.length > 0
10
- ? truncateLines(oldString.split("\n"), MAX_DIFF_LINES).map(
11
- (l) => `- ${l}`,
12
- )
13
- : [];
7
+ oldString.length > 0 ? oldString.split("\n").map((l) => `- ${l}`) : [];
14
8
  const added =
15
- newString.length > 0
16
- ? truncateLines(newString.split("\n"), MAX_DIFF_LINES).map(
17
- (l) => `+ ${l}`,
18
- )
19
- : [];
9
+ newString.length > 0 ? newString.split("\n").map((l) => `+ ${l}`) : [];
20
10
 
21
11
  return [...removed, ...added].join("\n");
22
12
  }
@@ -36,10 +26,3 @@ export function formatWriteSummary(
36
26
  const oldLineCount = oldContent.split("\n").length;
37
27
  return `(${oldLineCount} → ${newLineCount} lines)`;
38
28
  }
39
-
40
- function truncateLines(lines: string[], max: number): string[] {
41
- if (lines.length <= max) return lines;
42
- const kept = lines.slice(0, max);
43
- kept.push(`... (${lines.length - max} more lines)`);
44
- return kept;
45
- }
@@ -33,7 +33,7 @@ export class SkillExecuteTool implements Tool {
33
33
  "Brief non-technical explanation of what you are doing and why, shown to the user as a status update.",
34
34
  },
35
35
  },
36
- required: ["tool", "input"],
36
+ required: ["tool", "input", "activity"],
37
37
  },
38
38
  };
39
39
  }
@@ -11,8 +11,6 @@ import {
11
11
  isCesSecureInstallEnabled,
12
12
  isCesToolsEnabled,
13
13
  } from "../credential-execution/feature-gates.js";
14
- import { assetMaterializeTool } from "./assets/materialize.js";
15
- import { assetSearchTool } from "./assets/search.js";
16
14
  import { makeAuthenticatedRequestTool } from "./credential-execution/make-authenticated-request.js";
17
15
  import { manageSecureCommandTool } from "./credential-execution/manage-secure-command-tool.js";
18
16
  import { runAuthenticatedCommandTool } from "./credential-execution/run-authenticated-command.js";
@@ -63,8 +61,6 @@ export const eagerModuleToolNames: string[] = [
63
61
  "skill_execute",
64
62
  "skill_load",
65
63
  "request_system_permission",
66
- "asset_search",
67
- "asset_materialize",
68
64
  ];
69
65
 
70
66
  // ── Explicit tool instances ─────────────────────────────────────────
@@ -85,8 +81,6 @@ export const explicitTools: Tool[] = [
85
81
  skillExecuteTool,
86
82
  skillLoadTool,
87
83
  requestSystemPermissionTool,
88
- assetSearchTool,
89
- assetMaterializeTool,
90
84
  // Always-explicit tools
91
85
  memoryManageTool,
92
86
  memoryRecallTool,
@@ -30,7 +30,7 @@ export const uiShowTool: Tool = {
30
30
  description:
31
31
  "Show structured data or UI to the user. For long-form writing use the document skill; for interactive apps use the app-builder skill.\n\n" +
32
32
  "Surface types (data shapes):\n" +
33
- "- card: { title, subtitle?, body, metadata?: [{ label, value }], template?, templateData? }. Templates: \"weather_forecast\" (native weather widget), \"task_progress\" (live step tracker - update via ui_update on data.templateData; shape: { title, status: \"in_progress\"|\"completed\"|\"failed\", steps: [{ label, status: \"pending\"|\"in_progress\"|\"completed\"|\"failed\", detail? }] })\n" +
33
+ '- card: { title, subtitle?, body, metadata?: [{ label, value }], template?, templateData? }. Templates: "weather_forecast" (native weather widget), "task_progress" (live step tracker - update via ui_update on data.templateData; shape: { title, status: "in_progress"|"completed"|"failed", steps: [{ label, status: "pending"|"in_progress"|"completed"|"failed", detail? }] })\n' +
34
34
  '- table: { columns: [{ id, label, width? }], rows: [{ id, cells: Record<id, string | { text, icon?, iconColor?: "success"|"warning"|"error"|"muted" }>, selectable?, selected? }], selectionMode?: "none"|"single"|"multiple", caption? }\n' +
35
35
  '- form: { description?, fields: [{ id, type: "text"|"textarea"|"select"|"toggle"|"number"|"password", label, placeholder?, required?, defaultValue?, options?: [{ label, value }] }], submitLabel? }. Multi-page: { pages: [{ id, title, description?, fields }], pageLabels?: { next?, back?, submit? }, submitLabel? }\n' +
36
36
  '- list: { items: [{ id, title, subtitle?, icon?, selected? }], selectionMode: "single"|"multiple"|"none" }\n' +
@@ -91,7 +91,7 @@ export const uiShowTool: Tool = {
91
91
  type: "string",
92
92
  enum: ["inline", "panel"],
93
93
  description:
94
- 'Where to render the surface. "inline" embeds it in the chat message. "panel" shows a floating window. Defaults to "inline".',
94
+ 'Where to render the surface. "inline" embeds it in the chat message. "panel" shows a floating window. Defaults to "inline". Prefer inline — only use panel when the user explicitly asks for a separate window.',
95
95
  },
96
96
  await_action: {
97
97
  type: "boolean",
@@ -1,9 +1,16 @@
1
1
  /**
2
2
  * Device ID resolver.
3
3
  *
4
- * Reads or creates a stable per-device UUID stored in ~/.vellum/device.json.
5
- * The file is a JSON object (`{ "deviceId": "<uuid>" }`) extensible for
6
- * future per-device metadata.
4
+ * Reads or creates a stable per-device UUID stored in device.json under the
5
+ * Vellum config directory. The file is a JSON object (`{ "deviceId": "<uuid>" }`)
6
+ * extensible for future per-device metadata.
7
+ *
8
+ * Path resolution:
9
+ * - Containerized (IS_CONTAINERIZED=true): uses BASE_DATA_DIR, which maps to a
10
+ * persistent volume. Each container is effectively its own "device."
11
+ * - Local (single or multi-instance): uses homedir() so all instances on the
12
+ * same machine share a single device ID, even when BASE_DATA_DIR is set to
13
+ * an instance-scoped directory.
7
14
  *
8
15
  * The value is cached in memory after the first successful read/write.
9
16
  * Falls back to a generated UUID if the file cannot be read or written.
@@ -14,18 +21,34 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
21
  import { homedir } from "node:os";
15
22
  import { join } from "node:path";
16
23
 
24
+ import { getBaseDataDir, getIsContainerized } from "../config/env-registry.js";
17
25
  import { getLogger } from "./logger.js";
18
26
 
19
27
  const log = getLogger("device-id");
20
28
 
21
29
  let cached: string | undefined;
22
30
 
31
+ /**
32
+ * Resolve the base directory for device.json.
33
+ *
34
+ * In containerized environments, BASE_DATA_DIR points to a persistent volume
35
+ * and homedir() is ephemeral, so we must use BASE_DATA_DIR.
36
+ * In local environments (including multi-instance), homedir() is stable and
37
+ * shared across instances, giving a true per-machine device ID.
38
+ */
39
+ export function getDeviceIdBaseDir(): string {
40
+ if (getIsContainerized()) {
41
+ return getBaseDataDir() || homedir();
42
+ }
43
+ return homedir();
44
+ }
45
+
23
46
  /**
24
47
  * Get the stable device ID for this machine.
25
48
  *
26
49
  * Resolution order:
27
50
  * 1. Cached in-memory value (populated on first call)
28
- * 2. `deviceId` field from ~/.vellum/device.json
51
+ * 2. `deviceId` field from device.json
29
52
  * 3. Generate a new UUID, persist it to device.json, and return it
30
53
  *
31
54
  * On any read/write error the generated UUID is still cached so the
@@ -36,7 +59,7 @@ export function getDeviceId(): string {
36
59
  return cached;
37
60
  }
38
61
 
39
- const vellumDir = join(homedir(), ".vellum");
62
+ const vellumDir = join(getDeviceIdBaseDir(), ".vellum");
40
63
  const filePath = join(vellumDir, "device.json");
41
64
  const generated = randomUUID();
42
65