@vellumai/assistant 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Remove a plugin previously materialized under `<workspaceDir>/plugins/`.
3
+ *
4
+ * Symmetric to {@link ./install-from-github.installPlugin}. The CLI
5
+ * command `assistant plugins uninstall <name>` is a thin wrapper that
6
+ * supplies the live workspace directory and formats the result.
7
+ *
8
+ * The operation is destructive — a successful return means the plugin
9
+ * directory and everything beneath it have been removed from disk.
10
+ * Callers needing a confirmation prompt should run it before invoking
11
+ * this function (the CLI command does this via `--force`).
12
+ */
13
+
14
+ import { existsSync, rmSync, statSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ import { getWorkspacePluginsDir } from "../../util/platform.js";
18
+ import {
19
+ InvalidPluginNameError,
20
+ sanitizePluginName,
21
+ } from "./install-from-github.js";
22
+
23
+ /** Plugin is not present in the workspace plugins directory. */
24
+ export class PluginNotInstalledError extends Error {
25
+ constructor(
26
+ readonly pluginName: string,
27
+ readonly target: string,
28
+ ) {
29
+ super(`Plugin "${pluginName}" is not installed at ${target}.`);
30
+ this.name = "PluginNotInstalledError";
31
+ }
32
+ }
33
+
34
+ /** Options accepted by {@link uninstallPlugin}. */
35
+ export interface UninstallPluginOptions {
36
+ readonly name: string;
37
+ /** Override the workspace plugins directory. Falls back to {@link getWorkspacePluginsDir}. */
38
+ readonly workspacePluginsDir?: string;
39
+ }
40
+
41
+ /** Result of a successful uninstall. */
42
+ export interface UninstallPluginResult {
43
+ readonly name: string;
44
+ /** Absolute path that was removed. */
45
+ readonly target: string;
46
+ }
47
+
48
+ /**
49
+ * Validate the name, confirm the plugin exists, then recursively remove
50
+ * the install target. Throws {@link InvalidPluginNameError} if the name
51
+ * fails sanitization or {@link PluginNotInstalledError} if no directory
52
+ * (or symlink to a directory) is present at the resolved target.
53
+ *
54
+ * The name check is performed up front so an attacker-supplied
55
+ * `../../etc/passwd` style argument never reaches `rmSync` — even
56
+ * though commander typically prevents it at the argv level, defense in
57
+ * depth.
58
+ */
59
+ export function uninstallPlugin(
60
+ opts: UninstallPluginOptions,
61
+ ): UninstallPluginResult {
62
+ const name = sanitizePluginName(opts.name);
63
+ const pluginsDir = opts.workspacePluginsDir ?? getWorkspacePluginsDir();
64
+ const target = join(pluginsDir, name);
65
+
66
+ if (!existsSync(target)) {
67
+ throw new PluginNotInstalledError(name, target);
68
+ }
69
+
70
+ // `existsSync` follows symlinks; guard against a stray file with the
71
+ // plugin's name (which would be surprising rather than dangerous —
72
+ // we'd refuse to delete it).
73
+ const stats = statSync(target);
74
+ if (!stats.isDirectory()) {
75
+ throw new PluginNotInstalledError(name, target);
76
+ }
77
+
78
+ rmSync(target, { recursive: true, force: true });
79
+ return { name, target };
80
+ }
81
+
82
+ export { InvalidPluginNameError };
@@ -0,0 +1,111 @@
1
+ import type { Command } from "commander";
2
+
3
+ /**
4
+ * Result of detecting an unknown top-level subcommand. `suggestion` is set
5
+ * when a near-match is found (Levenshtein distance ≤ 40% of the longer name).
6
+ */
7
+ export interface UnknownCommandHit {
8
+ readonly token: string;
9
+ readonly suggestion?: string;
10
+ }
11
+
12
+ /**
13
+ * Collect every top-level subcommand name and alias registered on `program`.
14
+ */
15
+ export function knownCommandNames(program: Command): Set<string> {
16
+ const names = new Set<string>();
17
+ for (const cmd of program.commands) {
18
+ names.add(cmd.name());
19
+ for (const alias of cmd.aliases()) {
20
+ names.add(alias);
21
+ }
22
+ }
23
+ return names;
24
+ }
25
+
26
+ /**
27
+ * Pre-parse scan: returns the first positional argv token that doesn't match
28
+ * any known subcommand/alias, or null when the args look valid.
29
+ *
30
+ * Commander processes `--help` / `--version` before any action or hook runs,
31
+ * so `assistant invalid --help` would otherwise dump the root help instead of
32
+ * surfacing the unknown command. Callers should run this before `parse()` so
33
+ * the error wins over the help short-circuit.
34
+ *
35
+ * The first non-flag token is treated as the subcommand candidate. Flags are
36
+ * skipped wholesale; the root program has no value-taking options today.
37
+ */
38
+ export function detectUnknownCommand(
39
+ program: Command,
40
+ argv: readonly string[],
41
+ ): UnknownCommandHit | null {
42
+ const firstPositional = argv.find((token) => !token.startsWith("-"));
43
+ if (!firstPositional) return null;
44
+
45
+ const known = knownCommandNames(program);
46
+ if (known.has(firstPositional)) return null;
47
+
48
+ const suggestion = findClosestCommand(firstPositional, [...known]);
49
+ return suggestion ? { token: firstPositional, suggestion } : { token: firstPositional };
50
+ }
51
+
52
+ /**
53
+ * Format the unknown-command error as a multi-line message. Kept as a pure
54
+ * function so it can be reused by `default-action`'s in-parse path (when the
55
+ * user runs `assistant invalid` with no `--help`) and by the pre-parse path.
56
+ */
57
+ export function formatUnknownCommandMessage(hit: UnknownCommandHit): string {
58
+ const lines = [`unknown command '${hit.token}'`];
59
+ if (hit.suggestion) {
60
+ lines.push(`(Did you mean '${hit.suggestion}'?)`);
61
+ }
62
+ lines.push(`Run 'assistant --help' to see a list of available commands.`);
63
+ return lines.join("\n");
64
+ }
65
+
66
+ /**
67
+ * Find the closest matching command name using Levenshtein distance.
68
+ * Returns the best match if the distance is ≤ 40% of the longer string's
69
+ * length, otherwise returns undefined.
70
+ */
71
+ export function findClosestCommand(
72
+ input: string,
73
+ candidates: readonly string[],
74
+ ): string | undefined {
75
+ let best: string | undefined;
76
+ let bestDist = Infinity;
77
+ const lowered = input.toLowerCase();
78
+
79
+ for (const name of candidates) {
80
+ const dist = levenshtein(lowered, name.toLowerCase());
81
+ if (dist < bestDist) {
82
+ bestDist = dist;
83
+ best = name;
84
+ }
85
+ }
86
+
87
+ const maxLen = Math.max(input.length, best?.length ?? 0);
88
+ if (best && bestDist <= Math.ceil(maxLen * 0.4)) {
89
+ return best;
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ function levenshtein(a: string, b: string): number {
95
+ const m = a.length;
96
+ const n = b.length;
97
+ const dp: number[][] = Array.from({ length: m + 1 }, () =>
98
+ Array(n + 1).fill(0),
99
+ );
100
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
101
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
102
+ for (let i = 1; i <= m; i++) {
103
+ for (let j = 1; j <= n; j++) {
104
+ dp[i][j] =
105
+ a[i - 1] === b[j - 1]
106
+ ? dp[i - 1][j - 1]
107
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
108
+ }
109
+ }
110
+ return dp[m][n];
111
+ }
@@ -5,6 +5,7 @@ import { Command } from "commander";
5
5
  import { initFeatureFlagOverrides } from "../config/assistant-feature-flags.js";
6
6
  import { getConfigReadOnly } from "../config/loader.js";
7
7
  import { isEmailEnabled } from "../email/feature-gate.js";
8
+ import { isExternalPluginsEnabled } from "../plugins/feature-gate.js";
8
9
  import { getWorkspaceDir } from "../util/platform.js";
9
10
  import { APP_VERSION } from "../version.js";
10
11
  import { registerAttachmentCommand } from "./commands/attachment.js";
@@ -37,18 +38,22 @@ import { registerNotificationsCommand } from "./commands/notifications.js";
37
38
  import { registerOAuthCommand } from "./commands/oauth/index.js";
38
39
  import { registerPendingCommand } from "./commands/pending.js";
39
40
  import { registerPlatformCommand } from "./commands/platform/index.js";
41
+ import { registerPluginsCommand } from "./commands/plugins.js";
40
42
  import { registerRoutesCommand } from "./commands/routes.js";
43
+ import { registerSchedulesCommand } from "./commands/schedules.js";
41
44
  import { registerSequenceCommand } from "./commands/sequence.js";
42
45
  import { registerSkillsCommand } from "./commands/skills.js";
43
46
  import { registerStatusCommand } from "./commands/status.js";
44
47
  import { registerSttCommand } from "./commands/stt.js";
45
48
  import { registerTaskCommand } from "./commands/task.js";
49
+ import { registerTelemetryCommand } from "./commands/telemetry.js";
46
50
  import { registerTrustCommand } from "./commands/trust.js";
47
51
  import { registerTtsCommand } from "./commands/tts.js";
48
52
  import { registerUiCommand } from "./commands/ui.js";
49
53
  import { registerUsageCommand } from "./commands/usage.js";
50
54
  import { registerWatchersCommand } from "./commands/watchers.js";
51
55
  import { registerWebhooksCommand } from "./commands/webhooks.js";
56
+ import { red } from "./lib/cli-colors.js";
52
57
  import { log } from "./logger.js";
53
58
 
54
59
  /**
@@ -57,6 +62,20 @@ import { log } from "./logger.js";
57
62
  */
58
63
  export async function buildCliProgram(): Promise<Command> {
59
64
  await initFeatureFlagOverrides({ retryBackoffsMs: [], callTimeoutMs: 200 });
65
+ return buildCliProgramTree();
66
+ }
67
+
68
+ /**
69
+ * Synchronously build the CLI program tree without pre-populating the
70
+ * feature-flag cache. Use this from inside the daemon, where flags are
71
+ * already initialized — calling `buildCliProgram` from there would round-trip
72
+ * to the gateway unnecessarily.
73
+ *
74
+ * Same shape as `buildCliProgram` minus the async feature-flag init: registers
75
+ * the full subcommand set (conditionally gated on email / external-plugins
76
+ * flags via `getConfigReadOnly()`) and installs the workspace-preAction hook.
77
+ */
78
+ export function buildCliProgramTree(): Command {
60
79
  const program = new Command();
61
80
 
62
81
  program
@@ -65,6 +84,13 @@ export async function buildCliProgram(): Promise<Command> {
65
84
  .version(APP_VERSION)
66
85
  .allowExcessArguments(true);
67
86
 
87
+ // Color Commander-emitted error output red (unknown options, missing args,
88
+ // cmd.error() calls). Plain success output and --help text are untouched.
89
+ // The `red` helper is a no-op when stderr isn't a TTY or NO_COLOR is set.
90
+ program.configureOutput({
91
+ outputError: (str, write) => write(red(str)),
92
+ });
93
+
68
94
  program.addHelpText(
69
95
  "after",
70
96
  `
@@ -107,12 +133,17 @@ Examples:
107
133
  registerOAuthCommand(program);
108
134
  registerPendingCommand(program);
109
135
  registerPlatformCommand(program);
136
+ if (isExternalPluginsEnabled(getConfigReadOnly())) {
137
+ registerPluginsCommand(program);
138
+ }
110
139
  registerRoutesCommand(program);
140
+ registerSchedulesCommand(program);
111
141
  registerSequenceCommand(program);
112
142
  registerStatusCommand(program);
113
143
  registerSkillsCommand(program);
114
144
  registerSttCommand(program);
115
145
  registerTaskCommand(program);
146
+ registerTelemetryCommand(program);
116
147
  registerTrustCommand(program);
117
148
  registerTtsCommand(program);
118
149
  registerUiCommand(program);
@@ -126,9 +157,28 @@ Examples:
126
157
  // remain available even without a workspace.
127
158
  // Workspace-independent commands are exempt:
128
159
  // completions — pure shell-script generation, no workspace files needed
129
- const workspaceExemptCommands = new Set(["completions", "status"]);
160
+ // status — diagnostic; should run even when the workspace is broken
161
+ // changelog — pure read-only network surface backed by GitHub Releases;
162
+ // its on-disk cache is best-effort and tolerates a missing
163
+ // workspace dir (see changelog.ts:writeCache)
164
+ const workspaceExemptCommands = new Set([
165
+ "completions",
166
+ "status",
167
+ "changelog",
168
+ ]);
169
+ // An action command's `.name()` returns the leaf (e.g. "show" for
170
+ // `changelog show <ver>`), so we walk up the parent chain to see whether
171
+ // any ancestor — typically the top-level subcommand — is exempt.
172
+ const isExemptFromWorkspaceCheck = (command: Command): boolean => {
173
+ let current: Command | null | undefined = command;
174
+ while (current && current !== program) {
175
+ if (workspaceExemptCommands.has(current.name())) return true;
176
+ current = current.parent;
177
+ }
178
+ return false;
179
+ };
130
180
  program.hook("preAction", (_thisCommand, actionCommand) => {
131
- if (workspaceExemptCommands.has(actionCommand.name())) {
181
+ if (isExemptFromWorkspaceCheck(actionCommand)) {
132
182
  return;
133
183
  }
134
184
  const workspaceDir = getWorkspaceDir();
@@ -110,20 +110,22 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
110
110
  }
111
111
 
112
112
  // ---------------------------------------------------------------------------
113
- // Override loading — reads from gateway IPC socket or local file
113
+ // Override loading — reads from gateway IPC socket
114
114
  // ---------------------------------------------------------------------------
115
115
 
116
116
  /**
117
- * Module-level cache of feature flag override values. Populated lazily on
118
- * first access, invalidated by `clearFeatureFlagOverridesCache()`.
117
+ * Module-level cache of feature flag override values. Populated by
118
+ * `initFeatureFlagOverrides()` at startup, invalidated by
119
+ * `clearFeatureFlagOverridesCache()`.
119
120
  */
120
121
  let cachedOverrides: Record<string, boolean> | null = null;
121
122
 
122
123
  /**
123
- * True when `cachedOverrides` was populated by the gateway IPC fetch (or
124
- * preseeded by a test). False/unset when the cache was populated by the sync
125
- * file fallback in `loadOverrides()`, which must not prevent a subsequent
126
- * authoritative gateway fetch from running.
124
+ * True when `cachedOverrides` was populated by the gateway IPC fetch or
125
+ * preseeded by a test via `_setOverridesForTesting()`. Guards
126
+ * `initFeatureFlagOverrides()` from clobbering an existing populated cache
127
+ * when called a second time (e.g. by a CLI entry point after the daemon
128
+ * has already initialized).
127
129
  */
128
130
  let cachedOverridesFromGateway = false;
129
131
 
@@ -247,59 +249,30 @@ function loadOverrides(): Record<string, boolean> {
247
249
  return cachedOverrides ?? {};
248
250
  }
249
251
 
250
- // ---------------------------------------------------------------------------
251
- // Remote values — platform-pushed flags cached in a local JSON file
252
- // ---------------------------------------------------------------------------
253
-
254
252
  /**
255
- * Module-level cache of remote feature flag values. Populated lazily on
256
- * first access, invalidated by `clearFeatureFlagOverridesCache()`.
257
- */
258
- let cachedRemoteValues: Record<string, boolean> | null = null;
259
-
260
- /**
261
- * Load remote values with module-level caching.
253
+ * Invalidate the cached overrides so the next call to
254
+ * `isAssistantFeatureFlagEnabled` re-reads from the gateway.
262
255
  *
263
- * Remote values are now always included in the gateway IPC response (merged
264
- * server-side), so this only returns the injected test cache. In production,
265
- * remote values flow through the overrides cache.
266
- */
267
- function loadRemoteValues(): Record<string, boolean> {
268
- return cachedRemoteValues ?? {};
269
- }
270
-
271
- /**
272
- * Invalidate the cached override and remote values so the next call to
273
- * `isAssistantFeatureFlagEnabled` re-reads from the source.
274
- *
275
- * Called by the config watcher when the feature-flags file changes.
256
+ * Used by tests between cases to reset module state.
276
257
  */
277
258
  export function clearFeatureFlagOverridesCache(): void {
278
259
  cachedOverrides = null;
279
260
  cachedOverridesFromGateway = false;
280
- cachedRemoteValues = null;
281
261
  }
282
262
 
283
263
  /**
284
264
  * Directly inject override values into the module-level cache.
285
265
  *
286
- * **Test-only** — bypasses file/gateway loading so unit tests can control
287
- * flag state without writing to disk. Production code should never call this;
288
- * use `clearFeatureFlagOverridesCache()` instead and let the resolver
289
- * re-read from the appropriate source.
290
- *
291
- * Forces `cachedRemoteValues` to an empty record (not `null`) so the resolver
292
- * does not fall through to reading `feature-flags-remote.json` from disk. This
293
- * matters because a developer's local remote-cache file can leak platform-set
294
- * values into the test environment (e.g. `email-channel: true`), defeating
295
- * test isolation.
266
+ * **Test-only** — bypasses the gateway IPC fetch so unit tests can control
267
+ * flag state without standing up a real gateway. Production code should
268
+ * never call this; use `clearFeatureFlagOverridesCache()` instead and let
269
+ * the resolver re-read from the gateway.
296
270
  */
297
271
  export function _setOverridesForTesting(
298
272
  overrides: Record<string, boolean>,
299
273
  ): void {
300
274
  cachedOverrides = { ...overrides };
301
275
  cachedOverridesFromGateway = true;
302
- cachedRemoteValues = {};
303
276
  }
304
277
 
305
278
  // ---------------------------------------------------------------------------
@@ -310,9 +283,11 @@ export function _setOverridesForTesting(
310
283
  * Resolve whether an assistant feature flag is enabled.
311
284
  *
312
285
  * Resolution order:
313
- * 1. Override from gateway IPC socket
314
- * 2. defaults registry `defaultEnabled` (for declared assistant-scope keys)
315
- * 3. `true` (for undeclared keys with no override)
286
+ * 1. Override from the gateway IPC fetch (includes platform-pushed remote
287
+ * values, which the gateway merges server-side: persisted > remote >
288
+ * registry)
289
+ * 2. Registry `defaultEnabled` (for declared assistant-scope keys)
290
+ * 3. `true` (for undeclared keys with no override)
316
291
  */
317
292
  export function isAssistantFeatureFlagEnabled(
318
293
  key: string,
@@ -322,18 +297,13 @@ export function isAssistantFeatureFlagEnabled(
322
297
  const declared = defaults[key];
323
298
  const overrides = loadOverrides();
324
299
 
325
- // 1. Check overrides from gateway / local file
300
+ // 1. Check overrides from the gateway IPC cache.
326
301
  const explicit = overrides[key];
327
302
  if (typeof explicit === "boolean") return explicit;
328
303
 
329
- // 2. Check remote values (platform-pushed, cached locally)
330
- const remote = loadRemoteValues();
331
- const remoteValue = remote[key];
332
- if (typeof remoteValue === "boolean") return remoteValue;
333
-
334
- // 3. For declared keys, use the registry default
304
+ // 2. For declared keys, use the registry default.
335
305
  if (declared) return declared.defaultEnabled;
336
306
 
337
- // 4. Undeclared keys with no persisted override default to enabled
307
+ // 3. Undeclared keys with no override default to enabled.
338
308
  return true;
339
309
  }
@@ -54,8 +54,114 @@ Each record is a JSON file at `<slug>/records/<uuid>.json` with shape:
54
54
 
55
55
  All new apps use `formatVersion: 2`: source files live under `src/` and compiled output lives under `dist/`. The build system compiles TSX to JS automatically when `app_refresh` is called.
56
56
 
57
+ ## Responsive Baseline & Mobile-First Mode
58
+
59
+ Every app must be responsive across the full width range — phone (~360px) to desktop (~1400px+). The conversation context's `<turn_context>` block carries an `interface:` field. Visual interfaces are `macos`, `ios`, and `web`; the field doesn't toggle responsiveness on or off — it shifts the **design priority**. Non-visual values like `phone` represent voice channels that can't render apps at all and don't need to be considered here.
60
+
61
+ - **`interface: ios`** (or any future mobile-web / android identifier) — mobile-first build. Design the narrow viewport first and progressively enhance upward at wider widths.
62
+ - **`interface: macos` / `web`** — desktop-first build. Design the larger composition first; the narrow-width fallback must still meet the universal baseline below but doesn't need to feel like a native mobile app.
63
+ - **Field absent or ambiguous** — default to desktop-first unless the user's request itself implies phone use ("for my iPhone home screen", "a tap-tracker I'll use on the go").
64
+
65
+ ### Universal baseline (every build, regardless of interface)
66
+
67
+ These rules aren't mobile-specific — they're touch / responsive a11y baselines that any user-resizable WebView needs.
68
+
69
+ **Viewport & safe areas**
70
+
71
+ - Viewport meta: `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">`. Never set `user-scalable=no` — it blocks accessibility zoom.
72
+ - Pad the root container with `env(safe-area-inset-*)` so content clears the notch / home indicator when the app is opened on a notched device: `padding-top: max(var(--v-spacing-lg), env(safe-area-inset-top))`, mirrored for `-bottom`/`-left`/`-right`. On desktop the env vars resolve to `0` and the `max()` falls through to the design-system value — no-op.
73
+ - Use `100dvh` (dynamic viewport height), not `100vh`, for full-height containers. `100vh` creates a scroll-jump on every mobile browser regardless of build mode.
74
+
75
+ **Form controls**
76
+
77
+ - `<input>`, `<textarea>`, `<select>` must be `font-size: 16px` or larger, or iOS Safari will zoom on focus and break the layout. This applies to every build — anyone may open a desktop-built app on their phone.
78
+ - Add `inputmode` to text fields with structured input: `numeric` for integers, `decimal` for amounts, `email`, `tel`, `url`. Add matching `autocomplete` and `autocapitalize` hints where appropriate.
79
+
80
+ **Touch & hover**
81
+
82
+ - Interactive elements (buttons, list rows, nav items, toggles, icon buttons) must be ≥44×44pt. `.v-button` already meets this; for custom controls, set `min-height: 44px` explicitly.
83
+ - Gate hover affordances behind `@media (hover: hover)` so they don't stick on touch devices visiting a desktop-built app.
84
+ - Disable text selection on app chrome (headers, nav, buttons) with `user-select: none; -webkit-user-select: none` so long-press doesn't pop the iOS selection menu over interactive elements.
85
+
86
+ **Layout fluidity**
87
+
88
+ - Fluid widths only — no fixed-pixel layouts. Use `%`, `fr`, `minmax`, `clamp()` instead of `px` on container widths.
89
+ - Horizontal-scroll tables don't work on narrow screens. At narrow widths, collapse rows into stacked cards with labels and values arranged vertically. (Mobile-first builds can use cards everywhere; desktop-first builds can keep the table at wide widths and switch to cards below a breakpoint.)
90
+ - `vellum.widgets.*` chart containers should be sized in `vw`/`%`, not fixed `px`. Prefer simpler chart types (sparkline, bar) at narrow widths — dense multi-series charts lose detail.
91
+
92
+ ### Mobile-first priorities (`interface: ios` or future mobile identifier)
93
+
94
+ These are the **design priority differences** that mobile-first builds adopt on top of the universal baseline. They reflect "narrow viewport is the primary experience, wider widths progressively enhance."
95
+
96
+ **Typography**
97
+
98
+ - Default body text to `--v-font-size-lg` (17px), not `--v-font-size-base` (14px) — the desktop base is too small to read comfortably on a phone. At wider widths the same 17px reads fine.
99
+
100
+ **Spacing**
101
+
102
+ - Bump default vertical rhythm one step (e.g. `--v-spacing-md` → `--v-spacing-lg` between cards and sections) so users can comfortably scroll-stop on each item.
103
+
104
+ **Layout**
105
+
106
+ - One column as the **default**, not as a narrow-width fallback. `flex-direction: column` first; opt into a multi-column grid only above a width breakpoint (`@media (min-width: 720px)`). No side rails, no two-pane master/detail, no fixed-width sidebars in the default view.
107
+ - Bottom-anchor the primary action (e.g. "Add", "Save") so the thumb can reach it: `position: sticky; bottom: env(safe-area-inset-bottom)` over the scrolling list. On wider widths you may re-flow it back inline.
108
+ - Replace side modals and popovers with bottom sheets that animate up from the bottom edge.
109
+
110
+ **Interaction**
111
+
112
+ - Skip the Tab/Enter/Esc keyboard pattern from "Interaction Standards" as the primary affordance — on mobile, focus comes from taps, submit from the soft keyboard's `return`, dismissal from a swipe down on bottom sheets. Keyboard support is still allowed (external-keyboard users exist on iPad) but isn't the design driver.
113
+
114
+ ### Desktop-first priorities (`interface: macos` / `web`)
115
+
116
+ The default behaviour the rest of this skill describes — multi-column composition, hover-rich affordances, denser information, side modals, inline primary actions. The universal baseline above is the floor: the narrow-width view must still work and follow the touch / responsive a11y rules, but it doesn't need to feel native to mobile.
117
+
118
+ Everything else in this skill applies unchanged.
119
+
57
120
  ## Workflow
58
121
 
122
+ ### 0. Preflight — Pin to a high-quality model
123
+
124
+ App building is design-heavy judgment work — color palettes, layout decisions, component architecture, micro-interactions. A stronger model produces meaningfully better apps: more creative visual directions, cleaner component boundaries, fewer generic patterns. Before building, check whether the conversation is already pinned to the quality profile:
125
+
126
+ ```
127
+ assistant inference session list
128
+ ```
129
+
130
+ If no session is active, check the current default profile:
131
+
132
+ ```
133
+ assistant config get llm.default.profile
134
+ ```
135
+
136
+ If the profile is already `quality-optimized`, skip the rest of this step and proceed to Step 1.
137
+
138
+ **If the active profile is `balanced`, `cost-optimized`, or any non-quality profile, you MUST ask the user for permission before switching. Do NOT open an inference session without explicit user confirmation.** Use `assistant ui confirm`:
139
+
140
+ ```
141
+ assistant ui confirm --message "App building works best with a high-quality model — it makes better design decisions, writes cleaner components, and produces more visually polished results. Switch to the quality profile for this build? (You can switch back after.)"
142
+ ```
143
+
144
+ If `assistant ui confirm` isn't available on this binary, ask the user directly in conversation instead. **Either way, wait for the user's answer before proceeding.**
145
+
146
+ **Only if the user confirms**, open an inference session:
147
+
148
+ ```
149
+ assistant inference session open quality-optimized --ttl 1h
150
+ ```
151
+
152
+ If `quality-optimized` isn't a profile name on this workspace, list the available profiles and open against the highest-quality one:
153
+
154
+ ```
155
+ assistant config get llm.profiles
156
+ assistant inference session open <profile-name> --ttl 1h
157
+ ```
158
+
159
+ The `--ttl 1h` gives comfortable headroom for a typical app build without leaving a forever-pinned session if the close in Step 6 is skipped.
160
+
161
+ **If the user declines, do not switch profiles.** Proceed with the current profile — the build still works, the model just won't be pinned. Skip the close in Step 6 too.
162
+
163
+ If `assistant inference session` isn't available on this binary, proceed without it.
164
+
59
165
  ### 1. Gather Requirements
60
166
 
61
167
  **Default: just build.** When a user says "build me a habit tracker," don't ask what colors they want or how many fields to include. Immediately:
@@ -177,27 +283,31 @@ useEffect(() => {
177
283
  }, []);
178
284
  ```
179
285
 
180
- **File workflow:** Call `app_create` first to create the app record and scaffold, use `file_write` for each source file under `src/`, then call `app_refresh` once to compile and refresh the UI.
286
+ **File workflow:** Pass all source files inline via the `source_files` parameter of `app_create`. This writes and compiles the real app in a single call — no scaffold placeholder, no separate `file_write` or `app_refresh` needed for initial creation. For subsequent edits, use `file_edit`/`file_write` then call `app_refresh` once.
181
287
 
182
288
  **Allowed third-party packages:** `date-fns`, `chart.js`, `lodash-es`, `zod`, `clsx`, `lucide`. Import them directly - esbuild resolves them at build time. No CDN imports. Note: `lucide` is the vanilla JS icon library (not `lucide-react`). Use its `createElement` or `createIcons` API, or manually inline SVG - do not import JSX icon components.
183
289
 
184
- **Example - creating a multi-file project** (assuming app slug is `project-tracker`):
290
+ **Example - creating a multi-file project:**
185
291
 
186
292
  ```
187
- file_write("{workspaceDir}/data/apps/project-tracker/src/index.html", `<!DOCTYPE html>
293
+ app_create({
294
+ name: "Project Tracker",
295
+ description: "Track projects with status and priority",
296
+ schema_json: '{"type":"object","properties":{"title":{"type":"string"},"status":{"type":"string"}},"required":["title"]}',
297
+ preview: { title: "Project Tracker", icon: "📋" },
298
+ source_files: {
299
+ "src/index.html": `<!DOCTYPE html>
188
300
  <html lang="en">
189
301
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
190
302
  <title>Project Tracker</title></head>
191
303
  <body><div id="app"></div></body>
192
- </html>`)
193
-
194
- file_write("{workspaceDir}/data/apps/project-tracker/src/main.tsx", `import { render } from 'preact';
304
+ </html>`,
305
+ "src/main.tsx": `import { render } from 'preact';
195
306
  import { App } from './components/App';
196
307
  import './styles.css';
197
308
 
198
- render(<App />, document.getElementById('app')!);`)
199
-
200
- file_write("{workspaceDir}/data/apps/project-tracker/src/components/App.tsx", `import { FunctionComponent } from 'preact';
309
+ render(<App />, document.getElementById('app')!);`,
310
+ "src/components/App.tsx": `import { FunctionComponent } from 'preact';
201
311
  import { useState, useEffect } from 'preact/hooks';
202
312
  import { Header } from './Header';
203
313
 
@@ -217,9 +327,8 @@ export const App: FunctionComponent = () => {
217
327
  {/* ... */}
218
328
  </div>
219
329
  );
220
- };`)
221
-
222
- file_write("{workspaceDir}/data/apps/project-tracker/src/components/Header.tsx", `import { FunctionComponent } from 'preact';
330
+ };`,
331
+ "src/components/Header.tsx": `import { FunctionComponent } from 'preact';
223
332
 
224
333
  interface HeaderProps {
225
334
  title: string;
@@ -231,14 +340,12 @@ export const Header: FunctionComponent<HeaderProps> = ({ title, count }) => (
231
340
  <h1>{title}</h1>
232
341
  <span className="badge">{count} items</span>
233
342
  </header>
234
- );`)
235
-
236
- file_write("{workspaceDir}/data/apps/project-tracker/src/styles.css", `.app { padding: var(--v-spacing-lg); }
343
+ );`,
344
+ "src/styles.css": `.app { padding: var(--v-spacing-lg); }
237
345
  .header { display: flex; justify-content: space-between; align-items: center; }
238
- .badge { background: var(--v-accent); color: var(--v-aux-white); padding: var(--v-spacing-xs) var(--v-spacing-sm); border-radius: var(--v-radius-pill); }`)
239
-
240
- # After all files are written, compile and refresh:
241
- app_refresh(app_id)
346
+ .badge { background: var(--v-accent); color: var(--v-aux-white); padding: var(--v-spacing-xs) var(--v-spacing-sm); border-radius: var(--v-radius-pill); }`
347
+ }
348
+ })
242
349
  ```
243
350
 
244
351
  **Technical constraints (multi-file):**
@@ -329,10 +436,11 @@ Call `app_create` with:
329
436
  - `name`: Short descriptive name
330
437
  - `description`: One-sentence summary
331
438
  - `schema_json`: JSON schema as string
332
- - `auto_open`: (optional, defaults to `true`) Shows an inline preview card in chat
439
+ - `source_files`: Map of relative file paths to contents (e.g. `{"src/main.tsx": "...", "src/styles.css": "..."}`). **Always include this** with the complete app source — it writes, compiles, and opens the real app in a single call.
440
+ - `auto_open`: (optional, defaults to `true`) Shows an inline preview card in chat after the app is built. Only fires when real source files are provided (not for scaffold-only apps).
333
441
  - `preview`: Always include - `title` (required), `subtitle`, `description`, `icon` (image URL preferred, emoji fallback), `metrics` (up to 3 key-value pills)
334
442
 
335
- Do not pass `html` or `pages` to `app_create`; those single-file shortcuts are retired. After `app_create` returns the app ID, write the real app source under `src/` and call `app_refresh`.
443
+ Do not pass `html` or `pages` to `app_create`; those single-file shortcuts are retired.
336
444
 
337
445
  The app is NOT opened in a workspace panel automatically - users open it via the 'Open App' button on the inline card.
338
446
 
@@ -349,6 +457,16 @@ After making all file changes, call `app_refresh(app_id)` once to compile and re
349
457
 
350
458
  Apps should have multiple source files under `src/` (`styles.css`, components, helpers, etc.). Import CSS and modules from TSX so esbuild includes them in the compiled output.
351
459
 
460
+ ### 6. Close the inference session
461
+
462
+ If you opened an inference session in Step 0, close it now:
463
+
464
+ ```
465
+ assistant inference session close
466
+ ```
467
+
468
+ If you skipped the open in Step 0 (because the user declined, the CLI didn't have the command, or the profile was already quality), skip this step too.
469
+
352
470
  ## Interaction Standards
353
471
 
354
472
  Every app must meet these baselines:
@@ -357,7 +475,7 @@ Every app must meet these baselines:
357
475
  - **Confirmation for destructive actions:** Use `window.vellum.confirm(title, message)` before deleting or resetting. Returns `Promise<boolean>`.
358
476
  - **Form validation:** Validate before submit, show errors inline, disable submit during async operations.
359
477
  - **Loading states:** Never show a blank screen while data loads. Use skeleton shimmer or spinners.
360
- - **Keyboard navigation:** `Tab` between elements, `Enter` to submit, `Escape` to close/cancel.
478
+ - **Keyboard navigation:** `Tab` between elements, `Enter` to submit, `Escape` to close/cancel. *(De-prioritised on mobile-first builds — see [Responsive Baseline & Mobile-First Mode](#responsive-baseline--mobile-first-mode).)*
361
479
 
362
480
  ## Presentation Slide Design
363
481