@vellumai/assistant 0.6.0 → 0.6.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 (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -238,7 +238,7 @@ export const Header: FunctionComponent<HeaderProps> = ({ title, count }) => (
238
238
 
239
239
  file_write("{workspaceDir}/data/apps/project-tracker/src/styles.css", `.app { padding: var(--v-spacing-lg); }
240
240
  .header { display: flex; justify-content: space-between; align-items: center; }
241
- .badge { background: var(--v-accent); color: white; padding: var(--v-spacing-xs) var(--v-spacing-sm); border-radius: var(--v-radius-pill); }`)
241
+ .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); }`)
242
242
 
243
243
  # After all files are written, compile and refresh:
244
244
  app_refresh(app_id)
@@ -290,9 +290,12 @@ Available design tokens:
290
290
  | **Typography** | `--v-font-family`, `--v-font-mono`, `--v-font-size-xs` (10px) / `-sm` (11px) / `-base` (14px) / `-lg` (17px) / `-xl` (22px) / `-2xl` (26px), `--v-line-height` |
291
291
  | **Animation** | `--v-duration-fast` (0.15s) / `-standard` (0.25s) / `-slow` (0.4s) |
292
292
  | **Palettes** | `--v-slate-{950..50}`, `--v-emerald-*`, `--v-violet-*`, `--v-indigo-*`, `--v-rose-*`, `--v-amber-*` |
293
+ | **Constant** | `--v-aux-white` (always `#FFFFFF` in both modes — use for text on filled/accent backgrounds) |
293
294
 
294
295
  Utility classes: `.v-button` (`.secondary`/`.danger`/`.ghost`), `.v-card`, `.v-list`/`.v-list-item`, `.v-badge` (`.success`/`.warning`/`.danger`), `.v-input-row`, `.v-empty-state`, `.v-toggle`.
295
296
 
297
+ **Never hardcode `color: white` or `color: #fff`.** Use `var(--v-aux-white)` for text on filled/accent backgrounds, or `var(--v-text)` / `var(--v-text-secondary)` for text on surface backgrounds. Hardcoded white causes invisible text on light surfaces.
298
+
296
299
  **Custom themes:** When the user wants a specific branded look, write complete CSS with hardcoded colors and `@media (prefers-color-scheme: dark)` for dark variants. Don't mix `--v-*` auto-switching variables with hardcoded colors in the same element.
297
300
 
298
301
  **Theme detection in JavaScript:**
@@ -445,9 +448,91 @@ Important:
445
448
  - All operations are async - use `async/await`
446
449
  - Wrap all calls in `try/catch`
447
450
 
451
+ #### Custom route handlers (user-defined routes)
452
+
453
+ When the app needs server-side persistence, custom API logic, or workspace file access, use **user-defined routes**. Route handlers are TypeScript or JavaScript files that live in the workspace `routes/` directory and are served under the `/v1/x/` URL path.
454
+
455
+ **Common use cases:** CRUD storage, file-based persistence, search/aggregation, external API proxying, webhook receivers.
456
+
457
+ **Handler file convention:**
458
+
459
+ Each handler file exports named functions for the HTTP methods it supports (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). Handlers use the standard Web API `Request`/`Response` signature.
460
+
461
+ ```
462
+ {workspaceDir}/routes/
463
+ items.ts # Handles /v1/x/items
464
+ items/
465
+ [id].ts # Not supported — use query params instead
466
+ index.ts # Also handles /v1/x/items (index convention)
467
+ ```
468
+
469
+ **Example handler — JSON file persistence:**
470
+
471
+ ```typescript
472
+ // routes/items.ts
473
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
474
+ import { join } from "node:path";
475
+
476
+ export const description = "Item CRUD — stores records as a JSON file";
477
+
478
+ const DATA_DIR = join(process.env.VELLUM_WORKSPACE_DIR!, "data");
479
+ const DATA_FILE = join(DATA_DIR, "items.json");
480
+
481
+ function loadItems(): Array<Record<string, unknown>> {
482
+ mkdirSync(DATA_DIR, { recursive: true });
483
+ if (!existsSync(DATA_FILE)) return [];
484
+ return JSON.parse(readFileSync(DATA_FILE, "utf-8"));
485
+ }
486
+
487
+ function saveItems(items: Array<Record<string, unknown>>): void {
488
+ mkdirSync(DATA_DIR, { recursive: true });
489
+ writeFileSync(DATA_FILE, JSON.stringify(items, null, 2));
490
+ }
491
+
492
+ export function GET(): Response {
493
+ return Response.json(loadItems());
494
+ }
495
+
496
+ export async function POST(request: Request): Promise<Response> {
497
+ const body = await request.json();
498
+ const items = loadItems();
499
+ const item = { id: crypto.randomUUID(), ...body, createdAt: new Date().toISOString() };
500
+ items.push(item);
501
+ saveItems(items);
502
+ return Response.json(item, { status: 201 });
503
+ }
504
+ ```
505
+
506
+ **Calling routes from the app frontend:**
507
+
508
+ Apps call custom routes via `fetch()` using the `/v1/x/` prefix. The assistant's runtime HTTP server requires the `/v1/` namespace for all API requests.
509
+
510
+ ```typescript
511
+ // In a TSX component or HTML script
512
+ const res = await fetch("/v1/x/items");
513
+ const items = await res.json();
514
+
515
+ // Create a new item
516
+ await fetch("/v1/x/items", {
517
+ method: "POST",
518
+ headers: { "Content-Type": "application/json" },
519
+ body: JSON.stringify({ name: "New item", status: "active" }),
520
+ });
521
+ ```
522
+
523
+ **Key rules:**
524
+
525
+ - Always create the route handler files via `file_write` before calling `app_refresh`
526
+ - Export an optional `description` string for CLI discoverability (`assistant routes list`)
527
+ - Handlers have full Node.js API access — `fs`, `path`, `crypto`, etc.
528
+ - Handlers get a 30-second timeout per request
529
+ - Files are hot-reloaded on change (mtime-based cache)
530
+ - Use `.ts` (preferred) or `.js` extensions
531
+ - Route resolution: `routes/foo.ts` → `/v1/x/foo`, `routes/bar/index.ts` → `/v1/x/bar`
532
+
448
533
  #### Client-side state management
449
534
 
450
- `localStorage` and `sessionStorage` are available for ephemeral UI state (filters, view modes, collapsed state, preferences, form drafts). Use `window.vellum.data` for persistent app records, `localStorage` for UI preferences.
535
+ `localStorage` and `sessionStorage` are available for ephemeral UI state (filters, view modes, collapsed state, preferences, form drafts). Use custom routes for persistent app records, `localStorage` for UI preferences.
451
536
 
452
537
  <!-- feature:app-builder-multifile:alt -->
453
538
 
@@ -464,7 +549,8 @@ let allRecords = [];
464
549
 
465
550
  async function loadRecords() {
466
551
  try {
467
- allRecords = await window.vellum.data.query();
552
+ const res = await fetch("/v1/x/records");
553
+ allRecords = await res.json();
468
554
  render();
469
555
  } catch (err) {
470
556
  console.error("Failed to load:", err);
@@ -553,7 +639,7 @@ Every app must meet these baselines:
553
639
 
554
640
  ## Presentation Slide Design
555
641
 
556
- Slides are a different domain from apps. Skip app-specific patterns (contextual headers, search/filter, toast notifications, form validation, data bridge). Slides are static content — build navigation and layouts with custom HTML/CSS.
642
+ Slides are a different domain from apps. Skip app-specific patterns (contextual headers, search/filter, toast notifications, form validation, custom routes). Slides are static content — build navigation and layouts with custom HTML/CSS.
557
643
 
558
644
  **Key principles:**
559
645
 
@@ -566,7 +652,7 @@ Slides are a different domain from apps. Skip app-specific patterns (contextual
566
652
 
567
653
  ## Error Handling
568
654
 
569
- - All `window.vellum.data` calls must be wrapped in `try/catch` with user-friendly feedback.
655
+ - All `fetch()` calls to custom routes must be wrapped in `try/catch` with user-friendly feedback.
570
656
  - Never let a failed operation silently pass - always show a toast or inline error.
571
657
  - If the page loads with no data, show a designed empty state (`.v-empty-state`).
572
658
  - For forms, show validation errors inline next to the relevant field.
@@ -110,22 +110,27 @@ When a user asks to declutter, clean up, or organize their email - start scannin
110
110
 
111
111
  ### Workflow
112
112
 
113
- 1. **Scan**: Call `gmail_sender_digest`. Default query targets promotions from the last 90 days.
113
+ 1. **Scan**: Call `gmail_sender_digest`. Default query targets promotions currently in the inbox from the last 90 days (`in:inbox category:promotions newer_than:90d`). Counts shown in the table reflect only what is currently in the inbox — these are the emails that will be archived.
114
114
  2. **Present**: Show results as a `ui_show` table with `selectionMode: "multiple"`:
115
115
  - **Columns (exactly 3)**: Sender, Emails Found, Unsub?
116
116
  - **Unsub? cell values**: Use rich cell format: `{ "text": "Yes", "icon": "checkmark.circle.fill", "iconColor": "success" }` when `has_unsubscribe` is true, `{ "text": "No", "icon": "minus.circle", "iconColor": "muted" }` when false.
117
117
  - **Pre-select all rows** (`selected: true`) - users deselect what they want to keep
118
118
  - **Caption**: Include two parts separated by a newline: (1) data scope, e.g. "Newsletters, notifications, and outreach from last 90 days. Deselect anything you want to keep." (adjusted to match the query used), and (2) the Unsub? column legend: "Unsub? - \"Yes\" means these emails contain an unsubscribe link, so I can opt you out automatically. \"No\" means no unsubscribe link was found - these will be archived but you may continue receiving them."
119
119
  - **Action buttons (exactly 2)**: "Archive & Unsubscribe" (primary), "Archive Only" (secondary). **NEVER offer Delete, Trash, or any destructive action.**
120
- 3. **Wait for user action**: Stop and wait. Do NOT proceed to archiving or unsubscribing until the user clicks one of the action buttons on the table. When the user clicks an action button:
120
+ 3. **Embed scan_id in button data**: When constructing the action buttons in `ui_show`, include the `scan_id` from the `gmail_sender_digest` result in each button's `data` field. This ensures `scan_id` is forwarded automatically when the user clicks the LLM does not need to recall it from earlier context:
121
+ ```json
122
+ { "id": "archive_unsubscribe", "label": "Archive & Unsubscribe", "style": "primary", "data": { "scan_id": "<scan_id value here>" } }
123
+ ```
124
+ 4. **Wait for user action**: Stop and wait. Do NOT proceed to archiving or unsubscribing until the user clicks one of the action buttons on the table. When the user clicks an action button you will receive a surface action message containing `action data: { scan_id, selectedIds }`:
125
+ - `selectedIds` are **sender IDs** (the `id` values from the scan result rows, base64-encoded email addresses) — NOT Gmail message IDs. Always use them as `sender_ids` with `scan_id`, never as `message_ids`.
121
126
  - **Dismiss the table immediately** with `ui_dismiss` - it collapses to a completion chip
122
127
  - **Show a `task_progress` card** with steps for each phase (e.g., "Archiving 89 senders (2,400 emails)", "Unsubscribing from 72 senders"). Update each step from `in_progress` → `completed` as each phase finishes.
123
128
  - When all senders are processed, set the progress card's `status: "completed"`.
124
- 4. **Act on selection** - batch, don't loop:
125
- - **Archive all at once**: Call `gmail_archive` **once** with `scan_id` + **all** selected senders' `id` values in the `sender_ids` array. The tool resolves message IDs server-side and batches the Gmail API calls internally - never loop sender-by-sender.
129
+ 5. **Act on selection** - batch, don't loop:
130
+ - **Archive all at once**: Call `gmail_archive` **once** with `scan_id` (from action data) + `sender_ids` set to all `selectedIds` from the action data. The tool resolves message IDs server-side and batches the Gmail API calls internally - never loop sender-by-sender. **Never** pass `selectedIds` as `message_ids` — they are sender IDs, not Gmail message IDs.
126
131
  - **Unsubscribe in bulk**: If the action is "Archive & Unsubscribe", call `gmail_unsubscribe` for each sender that has `has_unsubscribe: true` - but emit **all** unsubscribe tool calls in a **single assistant response** (parallel tool use) rather than one-at-a-time across separate turns.
127
- 5. **Accurate summary**: The scan counts are exact - the `message_count` shown in the table matches the number of messages archived. Format: "Cleaned up [total_archived] emails from [sender_count] senders. Unsubscribed from [unsub_count]."
128
- 6. **Ongoing protection offer**: After reporting results, offer auto-archive filters:
132
+ 6. **Accurate summary**: The scan counts are exact - the `message_count` shown in the table matches the number of messages archived. Format: "Cleaned up [total_archived] emails from [sender_count] senders. Unsubscribed from [unsub_count]."
133
+ 7. **Ongoing protection offer**: After reporting results, offer auto-archive filters:
129
134
  - "Want me to set up auto-archive filters so future emails from these senders skip your inbox?"
130
135
  - If yes, call `gmail_filters` with `action: "create"` for each sender with `from` set to the sender's email and `remove_label_ids: ["INBOX"]`.
131
136
  - Then offer a recurring declutter schedule: "Want me to scan for new clutter monthly?" If yes, use `schedule_create` to set up a monthly declutter check.
@@ -156,9 +161,9 @@ Scan tools (`gmail_sender_digest`, `gmail_outreach_scan`) return a `scan_id` tha
156
161
 
157
162
  Before composing any email that references a date or time:
158
163
 
159
- 1. Check the `<temporal_context>` block in the current turn for today's date and timezone
164
+ 1. Check the `timestamp:` field in the `<turn_context>` block for today's date and timezone
160
165
  2. Verify that "tomorrow" means the day after today's date, "next week" means the upcoming Monday–Friday, etc.
161
- 3. If the email references a date from another message, cross-check it against the temporal context to ensure it's in the future
166
+ 3. If the email references a date from another message, cross-check it against the turn context to ensure it's in the future
162
167
 
163
168
  ## Confidence Scores
164
169
 
@@ -490,7 +490,7 @@
490
490
  "properties": {
491
491
  "query": {
492
492
  "type": "string",
493
- "description": "Gmail search query (default 'category:promotions newer_than:90d')"
493
+ "description": "Gmail search query (default 'in:inbox category:promotions newer_than:90d')"
494
494
  },
495
495
  "max_messages": {
496
496
  "type": "number",
@@ -49,7 +49,8 @@ export async function run(
49
49
  _context: ToolContext,
50
50
  ): Promise<ToolExecutionResult> {
51
51
  const account = input.account as string | undefined;
52
- const query = (input.query as string) ?? "category:promotions newer_than:90d";
52
+ const query =
53
+ (input.query as string) ?? "in:inbox category:promotions newer_than:90d";
53
54
  const maxMessages = Math.min(
54
55
  (input.max_messages as number) ?? 5000,
55
56
  MAX_MESSAGES_CAP,
@@ -148,6 +148,7 @@ Telegram is supported as a messaging provider with limited capabilities compared
148
148
  - `send_notification` is provided by the **notifications** skill (always active) -- use it when the user asks for an alert/notification (for example "send this as a desktop notification").
149
149
  - Use `messaging_send` when the user asks to send a message into a specific chat/email destination.
150
150
  - `send_notification` channel routing is LLM-driven; `preferred_channels` are hints, not hard channel forcing.
151
+ - Before using `messaging_send` or `send_notification`, look up the recipient's contact record with `contact_search` to inform tone and content (see **Recipient Context** below).
151
152
 
152
153
  ## Personalized Drafting
153
154
 
@@ -157,6 +158,12 @@ If no style items exist and the user asks you to draft a message, suggest runnin
157
158
 
158
159
  > "I can analyze your sent messages to learn your writing style so drafts sound like you. Want me to do that?"
159
160
 
161
+ ## Recipient Context
162
+
163
+ Before composing or sending a message to someone, look up their contact record with `contact_search` using their name or channel address. If the contact has notes (e.g. relationship context, communication preferences, response expectations), use that context to inform the message's tone, level of detail, and content. This ensures outbound messages are personalized to the recipient — not just the sender's style.
164
+
165
+ If no contact record exists, proceed without recipient context.
166
+
160
167
  ## Confidence Scores
161
168
 
162
169
  Medium and high risk tools require a confidence score between 0 and 1:
@@ -87,6 +87,14 @@ The `mode` parameter controls what happens when a schedule fires:
87
87
 
88
88
  Use `notify` for simple reminders ("remind me to take medicine at 9am") and `execute` for tasks that need assistant action ("check my calendar at 8am and send me a digest").
89
89
 
90
+ ## Conversation Reuse
91
+
92
+ By default, each schedule run creates a new conversation. For recurring schedules that benefit from accumulating context across runs (e.g. polling-style jobs, daily digests that reference prior results), set `reuse_conversation: true`. When enabled, subsequent runs reuse the conversation from the last successful run instead of creating a new one.
93
+
94
+ - Only applies to **recurring** schedules; ignored for one-shot schedules.
95
+ - If the prior conversation has been deleted, a new one is created automatically.
96
+ - On the first run (no prior conversation), a new conversation is created as usual.
97
+
90
98
  ## Routing (notify mode)
91
99
 
92
100
  Control how notify-mode schedules are delivered at trigger time with `routing_intent`:
@@ -101,9 +109,20 @@ Optionally pass `routing_hints` (a JSON object) to influence routing decisions (
101
109
 
102
110
  - **Default to `all_channels`** for most notifications. Users usually want to be notified wherever they are.
103
111
  - **Use `single_channel`** only when the user explicitly specifies a single channel (e.g. "remind me on Telegram").
104
- - **Check `user_message_channel` (or `channel` when all channels are the same)** from the turn context. If the user is currently active on a specific channel, include it as a routing hint:
112
+ - **Determine the originating channel** for routing hints using this priority:
113
+ 1. **`source_channel`** from `<turn_context>` — use directly if present. This is the authoritative channel name.
114
+ 2. **`interface` fallback** — if `source_channel` is absent (common for guardian/direct users), map the `interface` value to a channel name:
115
+ | `interface` value | Channel name |
116
+ | --- | --- |
117
+ | `macos`, `ios` | `vellum` |
118
+ | `telegram` | `telegram` |
119
+ | `slack` | `slack` |
120
+ | `cli` | _(omit — no routable channel)_ |
121
+ 3. If neither field is present or the interface is `cli`, omit `preferred_channels`.
122
+
123
+ When a channel is determined, include it as a routing hint:
105
124
  ```
106
- routing_hints: { preferred_channels: ["vellum"] }
125
+ routing_hints: { preferred_channels: ["<resolved channel>"] }
107
126
  routing_intent: "all_channels"
108
127
  ```
109
128
 
@@ -119,6 +138,7 @@ Use `syntax` + `expression` to specify the schedule type explicitly, or just `ex
119
138
 
120
139
  ## Tips
121
140
 
141
+ - **When the user specifies a name for the schedule, use it exactly as given.** Do not paraphrase, embellish, or generate a descriptive name.
122
142
  - Use `schedule_create` for both recurring automation ("every day at 9am") and one-time reminders ("remind me at 3pm").
123
143
  - For task tracking ("add to my tasks", "add to my queue"), use task_list_add instead.
124
144
  - `fire_at` must be a strict ISO 8601 timestamp with timezone offset or Z (e.g. `2025-03-15T09:00:00-05:00`).
@@ -56,6 +56,10 @@
56
56
  "type": "boolean",
57
57
  "description": "When true, suppress completion notifications for this schedule. The job still runs and produces output, but no notification or conversation message is sent on completion. Useful for high-frequency recurring jobs that report findings separately. Defaults to false."
58
58
  },
59
+ "reuse_conversation": {
60
+ "type": "boolean",
61
+ "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Useful for polling-style schedules that accumulate context over time. Ignored for one-shot schedules. Defaults to false."
62
+ },
59
63
  "activity": {
60
64
  "type": "string",
61
65
  "description": "Brief non-technical explanation of why this tool is being called"
@@ -147,6 +151,10 @@
147
151
  "type": "boolean",
148
152
  "description": "When true, suppress completion notifications for this schedule. Useful for high-frequency jobs that report findings separately."
149
153
  },
154
+ "reuse_conversation": {
155
+ "type": "boolean",
156
+ "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Useful for polling-style schedules that accumulate context over time. Ignored for one-shot schedules."
157
+ },
150
158
  "activity": {
151
159
  "type": "string",
152
160
  "description": "Brief non-technical explanation of why this tool is being called"
@@ -72,7 +72,7 @@
72
72
  "Sounds",
73
73
  "Permissions & Privacy",
74
74
  "Billing",
75
- "Archived Conversations",
75
+ "Archive",
76
76
  "Schedules",
77
77
  "Developer"
78
78
  ],
@@ -7,27 +7,17 @@ import type {
7
7
  ToolContext,
8
8
  ToolExecutionResult,
9
9
  } from "../../../../tools/types.js";
10
- import { getWorkspaceDir } from "../../../../util/platform.js";
10
+ import { getAvatarDir, getAvatarImagePath } from "../../../../util/platform.js";
11
11
 
12
12
  export async function run(
13
13
  _input: Record<string, unknown>,
14
14
  _context: ToolContext,
15
15
  ): Promise<ToolExecutionResult> {
16
- const avatarPath = join(
17
- getWorkspaceDir(),
18
- "data",
19
- "avatar",
20
- "avatar-image.png",
21
- );
16
+ const avatarPath = getAvatarImagePath();
22
17
 
23
18
  if (!existsSync(avatarPath)) {
24
19
  // Check for native character traits and regenerate the static PNG
25
- const traitsPath = join(
26
- getWorkspaceDir(),
27
- "data",
28
- "avatar",
29
- "character-traits.json",
30
- );
20
+ const traitsPath = join(getAvatarDir(), "character-traits.json");
31
21
  if (existsSync(traitsPath)) {
32
22
  try {
33
23
  const traits = JSON.parse(readFileSync(traitsPath, "utf-8"));
@@ -1,5 +1,4 @@
1
1
  import { existsSync, unlinkSync } from "node:fs";
2
- import { join } from "node:path";
3
2
 
4
3
  import { buildAssistantEvent } from "../../../../runtime/assistant-event.js";
5
4
  import { assistantEventHub } from "../../../../runtime/assistant-event-hub.js";
@@ -9,7 +8,7 @@ import type {
9
8
  ToolExecutionResult,
10
9
  } from "../../../../tools/types.js";
11
10
  import { getLogger } from "../../../../util/logger.js";
12
- import { getWorkspaceDir } from "../../../../util/platform.js";
11
+ import { getAvatarImagePath } from "../../../../util/platform.js";
13
12
  import { updateIdentityAvatarSection } from "./identity-avatar.js";
14
13
 
15
14
  const log = getLogger("avatar-remove");
@@ -18,8 +17,7 @@ export async function run(
18
17
  _input: Record<string, unknown>,
19
18
  _context: ToolContext,
20
19
  ): Promise<ToolExecutionResult> {
21
- const avatarDir = join(getWorkspaceDir(), "data", "avatar");
22
- const avatarPath = join(avatarDir, "avatar-image.png");
20
+ const avatarPath = getAvatarImagePath();
23
21
 
24
22
  if (!existsSync(avatarPath)) {
25
23
  return {
@@ -9,14 +9,17 @@ import type {
9
9
  ToolExecutionResult,
10
10
  } from "../../../../tools/types.js";
11
11
  import { getLogger } from "../../../../util/logger.js";
12
- import { getWorkspaceDir } from "../../../../util/platform.js";
12
+ import {
13
+ getAvatarImagePath,
14
+ getWorkspaceDir,
15
+ } from "../../../../util/platform.js";
13
16
  import { updateIdentityAvatarSection } from "./identity-avatar.js";
14
17
 
15
18
  const log = getLogger("avatar-update");
16
19
 
17
20
  /** Canonical path where the custom avatar PNG is stored. */
18
21
  function getAvatarPath(): string {
19
- return join(getWorkspaceDir(), "data", "avatar", "avatar-image.png");
22
+ return getAvatarImagePath();
20
23
  }
21
24
 
22
25
  export async function run(
@@ -10,21 +10,26 @@ const SETTINGS_TABS = [
10
10
  "Sounds",
11
11
  "Permissions & Privacy",
12
12
  "Billing",
13
- "Archived Conversations",
13
+ "Archive",
14
14
  "Schedules",
15
15
  "Developer",
16
16
  ] as const;
17
17
 
18
18
  type SettingsTab = (typeof SETTINGS_TABS)[number];
19
19
 
20
+ const LEGACY_TAB_ALIASES: Record<string, SettingsTab> = {
21
+ "Archived Conversations": "Archive",
22
+ };
23
+
20
24
  export async function run(
21
25
  input: Record<string, unknown>,
22
26
  context: ToolContext,
23
27
  ): Promise<ToolExecutionResult> {
24
- const tab = input.tab as string;
28
+ const rawTab = input.tab as string;
29
+ const tab = LEGACY_TAB_ALIASES[rawTab] ?? rawTab;
25
30
  if (!SETTINGS_TABS.includes(tab as SettingsTab)) {
26
31
  return {
27
- content: `Error: unknown tab "${tab}". Valid tabs: ${SETTINGS_TABS.join(
32
+ content: `Error: unknown tab "${rawTab}". Valid tabs: ${SETTINGS_TABS.join(
28
33
  ", ",
29
34
  )}`,
30
35
  isError: true,
@@ -68,6 +68,8 @@ When you need to send a DM or look up a Slack user by name, check contacts first
68
68
 
69
69
  1. **Before calling `users.list`**: Use `contact_search` with `query: "<name>"` and `channel_type: "slack"`. If a matching contact has `externalUserId` (Slack user ID) and `externalChatId` (DM channel ID), skip the API lookups and use those IDs directly with `chat.postMessage`.
70
70
 
71
+ When `contact_search` returns notes for the recipient, use them to inform the message's tone, formality, and content. Contact notes capture relationship context and communication preferences that should shape how you write to this person.
72
+
71
73
  2. **After resolving via API**: When you had to call `users.list` or `conversations.open` to resolve a user, save the contact with `contact_upsert` so you can find them by name next time. External Slack IDs (user ID, DM channel ID) are cached automatically by the messaging layer and should not be passed through `contact_upsert`.
72
74
 
73
75
  ## Privacy Rules
@@ -6,6 +6,9 @@ metadata:
6
6
  emoji: "🤖"
7
7
  vellum:
8
8
  display-name: "Subagent"
9
+ activation-hints:
10
+ - "Run tasks in parallel, delegate work to background agents, or do multiple things at once"
11
+ - "Spawn a researcher, coder, or planner agent for independent work"
9
12
  ---
10
13
 
11
14
  Subagent orchestration -- spawn background agents to work on tasks in parallel.
@@ -14,9 +17,43 @@ Subagent orchestration -- spawn background agents to work on tasks in parallel.
14
17
 
15
18
  Subagents follow this status flow: `pending` -> `running` -> `completed` / `failed` / `aborted`
16
19
 
17
- - **Spawn**: Use `subagent_spawn` with a label and objective. The subagent runs autonomously.
18
- - **Auto-notification**: The parent conversation is automatically notified when a subagent reaches a terminal status. Do NOT poll `subagent_status`.
19
- - **Read output**: Use `subagent_read` only after the subagent reaches a terminal status (completed/failed/aborted).
20
+ - **Spawn**: Use `subagent_spawn` with a label, objective, and role. The subagent runs autonomously.
21
+ - **Mid-run communication**: Subagents can send notifications to the parent via `notify_parent` while still running -- useful for sharing interim findings or signaling that they are blocked.
22
+ - **Auto-notification**: The parent conversation is automatically notified when a subagent reaches a terminal status (completed/failed/aborted). Do NOT poll `subagent_status`.
23
+ - **Read output**: Use `subagent_read` after the subagent reaches a terminal status to retrieve its full output.
24
+
25
+ ## Roles
26
+
27
+ Each subagent is spawned with a role that determines its tool access. Choose the most restrictive role that can accomplish the task.
28
+
29
+ | Role | Tools | When to use |
30
+ |---|---|---|
31
+ | `general` | Full tool access | Task genuinely needs unrestricted capabilities (rare -- prefer a specialized role) |
32
+ | `researcher` | `web_search`, `web_fetch`, `file_read`, `file_list`, `recall`, `notify_parent` | Information gathering, web research, codebase exploration, reading documentation |
33
+ | `coder` | `bash`, `file_read`, `file_write`, `file_edit`, `web_search`, `recall`, `notify_parent` | Code changes, file editing, running commands, build/test tasks |
34
+ | `planner` | `file_read`, `file_list`, `web_search`, `web_fetch`, `recall`, `notify_parent` | Analysis, planning, synthesizing information, reviewing approaches |
35
+
36
+ All specialized roles (`researcher`, `coder`, `planner`) include `notify_parent` for mid-run communication with the parent.
37
+
38
+ ## Parent Communication
39
+
40
+ Subagents use `notify_parent` to send messages to the parent conversation while still running. Each notification has an urgency level:
41
+
42
+ - **`info`** -- Progress updates, minor findings. The parent is informed but does not need to act.
43
+ - **`important`** -- Key findings, significant results. The parent should review when convenient.
44
+ - **`blocked`** -- The subagent needs guidance or a decision from the parent to continue.
45
+
46
+ Use notifications judiciously -- one per major finding or milestone. Do not send a notification for every small step.
47
+
48
+ ## Naming
49
+
50
+ Subagents can be referenced by label instead of UUID. The `label` parameter is accepted on `subagent_message`, `subagent_status`, `subagent_read`, and `subagent_abort` as an alternative to `subagent_id`. Label lookup is case-insensitive.
51
+
52
+ Use descriptive labels when spawning subagents (e.g., "research-auth-libraries", "implement-login-form") so they are easy to reference later.
53
+
54
+ ## Reading Output
55
+
56
+ `subagent_read` returns the subagent's assistant text output. Use the `last_n` parameter to retrieve only the most recent N assistant messages instead of the full history. This is useful for large outputs where you only need the final result.
20
57
 
21
58
  ## Ownership
22
59
 
@@ -29,5 +66,8 @@ Set `send_result_to_user: false` when spawning a subagent whose result is for in
29
66
  ## Tips
30
67
 
31
68
  - Do NOT poll `subagent_status` in a loop. You will be notified automatically when a subagent completes.
69
+ - Use roles to scope tool access and minimize blast radius. Default to the most restrictive role that works.
70
+ - Spawn a `researcher` and `coder` in parallel for research-then-implement workflows -- the researcher gathers context while the coder starts on the known parts.
71
+ - Use `notify_parent` for interim findings instead of waiting for completion. This lets the parent act on partial results early.
32
72
  - Use `subagent_message` to send follow-up instructions to a running subagent.
33
73
  - Use `subagent_abort` to cancel a subagent that is no longer needed.
@@ -25,6 +25,11 @@
25
25
  "type": "boolean",
26
26
  "description": "Whether to present the subagent's result to the user when it completes. Defaults to true. Set to false for internal/silent processing."
27
27
  },
28
+ "role": {
29
+ "type": "string",
30
+ "enum": ["general", "researcher", "coder", "planner"],
31
+ "description": "Agent specialization that controls tool access. 'researcher': read-only (web, files, memory). 'coder': file and bash access. 'planner': read-only analysis. 'general': full access (default)."
32
+ },
28
33
  "activity": {
29
34
  "type": "string",
30
35
  "description": "Brief non-technical explanation of why this tool is being called"
@@ -47,6 +52,10 @@
47
52
  "type": "string",
48
53
  "description": "Optional subagent ID to query. If omitted, returns all subagents for this conversation."
49
54
  },
55
+ "label": {
56
+ "type": "string",
57
+ "description": "The label of the subagent (alternative to subagent_id). Case-insensitive."
58
+ },
50
59
  "activity": {
51
60
  "type": "string",
52
61
  "description": "Brief non-technical explanation of why this tool is being called"
@@ -59,7 +68,7 @@
59
68
  },
60
69
  {
61
70
  "name": "subagent_abort",
62
- "description": "Abort a running subagent by ID.",
71
+ "description": "Abort a running subagent by ID or label.",
63
72
  "category": "orchestration",
64
73
  "risk": "low",
65
74
  "input_schema": {
@@ -69,12 +78,16 @@
69
78
  "type": "string",
70
79
  "description": "The ID of the subagent to abort."
71
80
  },
81
+ "label": {
82
+ "type": "string",
83
+ "description": "The label of the subagent (alternative to subagent_id). Case-insensitive."
84
+ },
72
85
  "activity": {
73
86
  "type": "string",
74
87
  "description": "Brief non-technical explanation of why this tool is being called"
75
88
  }
76
89
  },
77
- "required": ["subagent_id"]
90
+ "required": []
78
91
  },
79
92
  "executor": "tools/subagent-abort.ts",
80
93
  "execution_target": "host"
@@ -91,6 +104,10 @@
91
104
  "type": "string",
92
105
  "description": "The ID of the subagent to send a message to."
93
106
  },
107
+ "label": {
108
+ "type": "string",
109
+ "description": "The label of the subagent (alternative to subagent_id). Case-insensitive."
110
+ },
94
111
  "content": {
95
112
  "type": "string",
96
113
  "description": "The message content to send to the subagent."
@@ -100,7 +117,7 @@
100
117
  "description": "Brief non-technical explanation of why this tool is being called"
101
118
  }
102
119
  },
103
- "required": ["subagent_id", "content"]
120
+ "required": ["content"]
104
121
  },
105
122
  "executor": "tools/subagent-message.ts",
106
123
  "execution_target": "host"
@@ -117,12 +134,20 @@
117
134
  "type": "string",
118
135
  "description": "The ID of the subagent whose output to read."
119
136
  },
137
+ "label": {
138
+ "type": "string",
139
+ "description": "The label of the subagent (alternative to subagent_id). Case-insensitive."
140
+ },
141
+ "last_n": {
142
+ "type": "integer",
143
+ "description": "Number of recent assistant messages to return. Omit to return all messages (current behavior)."
144
+ },
120
145
  "activity": {
121
146
  "type": "string",
122
147
  "description": "Brief non-technical explanation of why this tool is being called"
123
148
  }
124
149
  },
125
- "required": ["subagent_id"]
150
+ "required": []
126
151
  },
127
152
  "executor": "tools/subagent-read.ts",
128
153
  "execution_target": "host"