@vellumai/assistant 0.6.0 → 0.6.1

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 (285) 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 +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/openapi.yaml +538 -3
  9. package/package.json +5 -1
  10. package/src/__tests__/anthropic-provider.test.ts +160 -95
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +47 -1
  13. package/src/__tests__/app-source-watcher.test.ts +159 -0
  14. package/src/__tests__/checker.test.ts +38 -6
  15. package/src/__tests__/config-schema.test.ts +5 -0
  16. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  17. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  18. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  19. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  20. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  21. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  22. package/src/__tests__/conversation-wipe.test.ts +2 -6
  23. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  24. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  25. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  26. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  27. package/src/__tests__/date-context.test.ts +76 -210
  28. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  29. package/src/__tests__/file-list-tool.test.ts +219 -0
  30. package/src/__tests__/first-greeting.test.ts +1 -1
  31. package/src/__tests__/heartbeat-service.test.ts +180 -3
  32. package/src/__tests__/identity-routes.test.ts +328 -0
  33. package/src/__tests__/injection-block.test.ts +24 -0
  34. package/src/__tests__/install-skill-routing.test.ts +7 -6
  35. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  36. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  37. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  38. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  39. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  40. package/src/__tests__/log-export-workspace.test.ts +72 -105
  41. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  42. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  43. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  44. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  45. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  46. package/src/__tests__/mock-fetch.ts +87 -0
  47. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  48. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  49. package/src/__tests__/parser.test.ts +32 -0
  50. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  51. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  52. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  53. package/src/__tests__/permission-mode-store.test.ts +277 -0
  54. package/src/__tests__/permission-mode.test.ts +101 -0
  55. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  56. package/src/__tests__/profiler-routes.test.ts +502 -0
  57. package/src/__tests__/profiler-run-store.test.ts +441 -0
  58. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  59. package/src/__tests__/registry.test.ts +1 -1
  60. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  61. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  62. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  63. package/src/__tests__/search-skills-unified.test.ts +4 -3
  64. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  65. package/src/__tests__/set-permission-mode.test.ts +274 -0
  66. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  67. package/src/__tests__/skill-memory.test.ts +2 -783
  68. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  69. package/src/__tests__/subagent-detail.test.ts +84 -0
  70. package/src/__tests__/subagent-disposal.test.ts +308 -0
  71. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  72. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  73. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  74. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  75. package/src/__tests__/subagent-tools.test.ts +464 -4
  76. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  77. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  78. package/src/__tests__/terminal-tools.test.ts +17 -27
  79. package/src/__tests__/test-preload.ts +4 -0
  80. package/src/__tests__/tool-executor.test.ts +4 -26
  81. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  82. package/src/__tests__/top-level-renderer.test.ts +10 -13
  83. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  84. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  85. package/src/agent/loop.ts +6 -0
  86. package/src/approvals/guardian-request-resolvers.ts +24 -0
  87. package/src/avatar/traits-png-sync.ts +3 -3
  88. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  89. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  90. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  91. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  92. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  93. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  94. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  95. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  96. package/src/cli/commands/conversations.ts +1 -8
  97. package/src/cli/commands/email.ts +584 -835
  98. package/src/cli/commands/memory.ts +1 -34
  99. package/src/cli/commands/notifications.ts +7 -2
  100. package/src/cli/commands/oauth/connect.ts +14 -5
  101. package/src/cli/commands/routes.ts +396 -0
  102. package/src/cli/commands/skills.ts +130 -20
  103. package/src/cli/program.ts +2 -0
  104. package/src/cli.ts +1 -120
  105. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  106. package/src/config/bundled-skills/gmail/SKILL.md +2 -2
  107. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  108. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  109. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  111. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  112. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  113. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  114. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  115. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  116. package/src/config/env-registry.ts +63 -0
  117. package/src/config/feature-flag-registry.json +17 -1
  118. package/src/config/schema.ts +8 -0
  119. package/src/config/schemas/filing.ts +51 -0
  120. package/src/config/schemas/heartbeat.ts +15 -12
  121. package/src/config/schemas/memory-lifecycle.ts +12 -0
  122. package/src/config/schemas/security.ts +14 -0
  123. package/src/daemon/app-source-watcher.ts +93 -0
  124. package/src/daemon/config-watcher.ts +79 -1
  125. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  126. package/src/daemon/conversation-agent-loop.ts +158 -65
  127. package/src/daemon/conversation-history.ts +4 -19
  128. package/src/daemon/conversation-lifecycle.ts +8 -14
  129. package/src/daemon/conversation-process.ts +13 -7
  130. package/src/daemon/conversation-runtime-assembly.ts +300 -306
  131. package/src/daemon/conversation-tool-setup.ts +44 -14
  132. package/src/daemon/conversation-workspace.ts +1 -2
  133. package/src/daemon/conversation.ts +18 -0
  134. package/src/daemon/date-context.ts +26 -53
  135. package/src/daemon/first-greeting.ts +1 -1
  136. package/src/daemon/handlers/conversations.ts +4 -7
  137. package/src/daemon/handlers/shared.test.ts +143 -0
  138. package/src/daemon/handlers/shared.ts +63 -5
  139. package/src/daemon/handlers/skills.ts +11 -18
  140. package/src/daemon/lifecycle.ts +199 -157
  141. package/src/daemon/message-types/conversations.ts +25 -6
  142. package/src/daemon/message-types/messages.ts +9 -1
  143. package/src/daemon/message-types/schedules.ts +1 -0
  144. package/src/daemon/message-types/settings.ts +6 -0
  145. package/src/daemon/profiler-run-store.ts +557 -0
  146. package/src/daemon/server.ts +89 -9
  147. package/src/daemon/shutdown-handlers.ts +5 -0
  148. package/src/daemon/tool-side-effects.ts +23 -3
  149. package/src/export/transcript-formatter.ts +148 -0
  150. package/src/filing/filing-service.ts +228 -0
  151. package/src/heartbeat/heartbeat-service.ts +96 -7
  152. package/src/mcp/client.ts +6 -0
  153. package/src/mcp/mcp-oauth-provider.ts +149 -27
  154. package/src/memory/admin.ts +33 -32
  155. package/src/memory/app-store.ts +69 -0
  156. package/src/memory/conversation-bootstrap.ts +1 -1
  157. package/src/memory/conversation-crud.ts +136 -107
  158. package/src/memory/conversation-group-migration.ts +1 -1
  159. package/src/memory/conversation-queries.ts +58 -12
  160. package/src/memory/conversation-title-service.ts +1 -0
  161. package/src/memory/db-init.ts +182 -376
  162. package/src/memory/graph/bootstrap.ts +75 -66
  163. package/src/memory/graph/capability-seed.ts +167 -15
  164. package/src/memory/graph/consolidation.ts +38 -4
  165. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  166. package/src/memory/graph/extraction-job.ts +9 -4
  167. package/src/memory/graph/extraction.ts +66 -23
  168. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  169. package/src/memory/graph/graph-search.ts +29 -15
  170. package/src/memory/graph/injection.ts +38 -8
  171. package/src/memory/graph/inspect.ts +12 -3
  172. package/src/memory/graph/retriever.ts +365 -262
  173. package/src/memory/graph/store.test.ts +48 -0
  174. package/src/memory/graph/store.ts +150 -11
  175. package/src/memory/graph/tool-handlers.ts +84 -209
  176. package/src/memory/graph/tools.ts +8 -52
  177. package/src/memory/graph/types.ts +24 -0
  178. package/src/memory/job-handlers/cleanup.ts +44 -1
  179. package/src/memory/jobs-store.ts +70 -60
  180. package/src/memory/jobs-worker.ts +44 -28
  181. package/src/memory/llm-request-log-store.ts +96 -12
  182. package/src/memory/memory-recall-log-store.ts +49 -5
  183. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  184. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  185. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  186. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  187. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  188. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  189. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  190. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  191. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  192. package/src/memory/migrations/index.ts +8 -0
  193. package/src/memory/migrations/registry.ts +8 -0
  194. package/src/memory/schema/conversations.ts +14 -0
  195. package/src/memory/schema/infrastructure.ts +8 -1
  196. package/src/memory/schema/memory-core.ts +0 -51
  197. package/src/memory/schema/memory-graph.ts +15 -0
  198. package/src/memory/task-memory-cleanup.ts +30 -11
  199. package/src/notifications/copy-composer.ts +86 -0
  200. package/src/notifications/decision-engine.ts +35 -0
  201. package/src/permissions/checker.ts +12 -1
  202. package/src/permissions/permission-mode-store.ts +180 -0
  203. package/src/permissions/permission-mode.ts +31 -0
  204. package/src/permissions/workspace-policy.ts +9 -0
  205. package/src/prompts/system-prompt.ts +59 -7
  206. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  207. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  208. package/src/prompts/templates/HEARTBEAT.md +3 -1
  209. package/src/prompts/templates/SOUL.md +25 -4
  210. package/src/prompts/templates/UPDATES.md +8 -0
  211. package/src/providers/anthropic/client.ts +107 -219
  212. package/src/runtime/auth/route-policy.ts +23 -0
  213. package/src/runtime/http-server.ts +32 -2
  214. package/src/runtime/http-types.ts +12 -1
  215. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  216. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  217. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  218. package/src/runtime/routes/app-management-routes.ts +1 -11
  219. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  220. package/src/runtime/routes/archive-utils.ts +29 -0
  221. package/src/runtime/routes/avatar-routes.ts +2 -9
  222. package/src/runtime/routes/btw-routes.ts +14 -1
  223. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  224. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  225. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  226. package/src/runtime/routes/conversation-routes.ts +264 -44
  227. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  228. package/src/runtime/routes/identity-routes.ts +53 -18
  229. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  230. package/src/runtime/routes/log-export-routes.ts +23 -275
  231. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  232. package/src/runtime/routes/migration-routes.ts +18 -7
  233. package/src/runtime/routes/profiler-routes.ts +350 -0
  234. package/src/runtime/routes/schedule-routes.ts +27 -12
  235. package/src/runtime/routes/settings-routes.ts +95 -8
  236. package/src/runtime/routes/subagents-routes.ts +28 -7
  237. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  238. package/src/runtime/routes/user-routes.ts +41 -0
  239. package/src/runtime/routes/workspace-routes.ts +0 -1
  240. package/src/schedule/schedule-store.ts +30 -0
  241. package/src/schedule/scheduler.ts +45 -18
  242. package/src/skills/catalog-install.ts +10 -2
  243. package/src/skills/managed-store.ts +2 -2
  244. package/src/skills/skill-memory.ts +1 -293
  245. package/src/subagent/index.ts +13 -3
  246. package/src/subagent/manager.ts +308 -29
  247. package/src/subagent/types.ts +68 -0
  248. package/src/tasks/task-runner.ts +4 -4
  249. package/src/tools/apps/executors.ts +29 -4
  250. package/src/tools/filesystem/list.ts +93 -0
  251. package/src/tools/permission-checker.ts +78 -0
  252. package/src/tools/registry.ts +4 -0
  253. package/src/tools/schedule/create.ts +3 -0
  254. package/src/tools/schedule/list.ts +1 -0
  255. package/src/tools/schedule/update.ts +6 -0
  256. package/src/tools/shared/filesystem/errors.ts +5 -0
  257. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  258. package/src/tools/shared/filesystem/types.ts +17 -0
  259. package/src/tools/shared/shell-output.ts +31 -2
  260. package/src/tools/subagent/abort.ts +12 -2
  261. package/src/tools/subagent/message.ts +9 -2
  262. package/src/tools/subagent/notify-parent.ts +79 -0
  263. package/src/tools/subagent/read.ts +29 -8
  264. package/src/tools/subagent/resolve.ts +21 -0
  265. package/src/tools/subagent/spawn.ts +2 -0
  266. package/src/tools/subagent/status.ts +11 -1
  267. package/src/tools/system/avatar-generator.ts +3 -3
  268. package/src/tools/system/register.ts +23 -0
  269. package/src/tools/system/set-permission-mode.ts +103 -0
  270. package/src/tools/terminal/parser.ts +30 -5
  271. package/src/tools/terminal/safe-env.ts +16 -1
  272. package/src/tools/tool-manifest.ts +6 -0
  273. package/src/tools/types.ts +2 -0
  274. package/src/util/logger.ts +1 -1
  275. package/src/util/platform.ts +50 -17
  276. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  277. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  278. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  279. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  280. package/src/workspace/migrations/registry.ts +4 -0
  281. package/src/workspace/top-level-renderer.ts +5 -9
  282. package/src/__tests__/cli-memory.test.ts +0 -377
  283. package/src/__tests__/clipboard.test.ts +0 -88
  284. package/src/cli/cli-memory.ts +0 -179
  285. package/src/util/clipboard.ts +0 -34
@@ -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(
@@ -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"
@@ -24,6 +24,13 @@ function flag(name: string): boolean {
24
24
  return raw === "true" || raw === "1";
25
25
  }
26
26
 
27
+ function int(name: string): number | undefined {
28
+ const raw = str(name);
29
+ if (raw === undefined) return undefined;
30
+ const n = parseInt(raw, 10);
31
+ return Number.isFinite(n) ? n : undefined;
32
+ }
33
+
27
34
  // ── Registry ─────────────────────────────────────────────────────────────────
28
35
  // Each entry documents the env var name, type, default, and purpose.
29
36
 
@@ -74,6 +81,57 @@ export function getWorkspaceDirOverride(): string | undefined {
74
81
  return str("VELLUM_WORKSPACE_DIR");
75
82
  }
76
83
 
84
+ // ── Profiler env vars ───────────────────────────────────────────────────
85
+ // These are injected by the platform when running a managed assistant in
86
+ // profiler mode. The runtime uses them to locate, scope, and budget-limit
87
+ // profiler output on the workspace volume.
88
+
89
+ /**
90
+ * VELLUM_PROFILER_RUN_ID — string, default: undefined
91
+ * Unique identifier for the current profiler run. When set, the profiler
92
+ * run store treats this run as "active" and will never prune its directory.
93
+ */
94
+ export function getProfilerRunId(): string | undefined {
95
+ return str("VELLUM_PROFILER_RUN_ID");
96
+ }
97
+
98
+ /**
99
+ * VELLUM_PROFILER_MODE — string, default: undefined
100
+ * The profiling mode to activate (e.g. "cpu", "heap", "cpu+heap").
101
+ * When unset, profiling is disabled.
102
+ */
103
+ export function getProfilerMode(): string | undefined {
104
+ return str("VELLUM_PROFILER_MODE");
105
+ }
106
+
107
+ /**
108
+ * VELLUM_PROFILER_MAX_BYTES — integer, default: undefined
109
+ * Maximum total bytes retained across all profiler runs (including active).
110
+ * The startup sweep prunes oldest completed runs to stay within budget.
111
+ */
112
+ export function getProfilerMaxBytes(): number | undefined {
113
+ return int("VELLUM_PROFILER_MAX_BYTES");
114
+ }
115
+
116
+ /**
117
+ * VELLUM_PROFILER_MAX_RUNS — integer, default: undefined
118
+ * Maximum number of completed profiler runs retained on disk.
119
+ * The startup sweep prunes oldest completed runs to stay within budget.
120
+ */
121
+ export function getProfilerMaxRuns(): number | undefined {
122
+ return int("VELLUM_PROFILER_MAX_RUNS");
123
+ }
124
+
125
+ /**
126
+ * VELLUM_PROFILER_MIN_FREE_MB — integer, default: undefined
127
+ * Minimum free disk space (in megabytes) that must remain after profiler
128
+ * runs are accounted for. The startup sweep prunes oldest completed runs
129
+ * until at least this much free space is available.
130
+ */
131
+ export function getProfilerMinFreeMb(): number | undefined {
132
+ return int("VELLUM_PROFILER_MIN_FREE_MB");
133
+ }
134
+
77
135
  // ── Known env var names ──────────────────────────────────────────────────────
78
136
 
79
137
  /**
@@ -97,6 +155,11 @@ const KNOWN_VELLUM_VARS = new Set([
97
155
  "VELLUM_HOOK_SETTINGS",
98
156
  "VELLUM_LOCKFILE_DIR",
99
157
  "VELLUM_PLATFORM_URL",
158
+ "VELLUM_PROFILER_MAX_BYTES",
159
+ "VELLUM_PROFILER_MAX_RUNS",
160
+ "VELLUM_PROFILER_MIN_FREE_MB",
161
+ "VELLUM_PROFILER_MODE",
162
+ "VELLUM_PROFILER_RUN_ID",
100
163
  "VELLUM_ROOT_DIR",
101
164
  "VELLUM_SSH_USER",
102
165
  "VELLUM_UNSAFE_AUTH_BYPASS",
@@ -121,6 +121,14 @@
121
121
  "description": "Show the Billing tab in Settings when signed in, displaying credits balance and top-up",
122
122
  "defaultEnabled": true
123
123
  },
124
+ {
125
+ "id": "referral-codes",
126
+ "scope": "macos",
127
+ "key": "referral-codes",
128
+ "label": "Referral Codes",
129
+ "description": "Show the referral invite link and stats panel on the Billing tab in Settings",
130
+ "defaultEnabled": false
131
+ },
124
132
  {
125
133
  "id": "managed-sign-in",
126
134
  "scope": "macos",
@@ -199,7 +207,7 @@
199
207
  "key": "show-thinking-blocks",
200
208
  "label": "Show Thinking Blocks",
201
209
  "description": "Display the assistant's thinking/reasoning inline in chat messages as collapsible blocks",
202
- "defaultEnabled": false
210
+ "defaultEnabled": true
203
211
  },
204
212
  {
205
213
  "id": "inline-skill-commands",
@@ -272,6 +280,14 @@
272
280
  "label": "Teleport",
273
281
  "description": "Enable teleport UI in General settings for moving assistants between hosting environments",
274
282
  "defaultEnabled": false
283
+ },
284
+ {
285
+ "id": "permission-controls-v2",
286
+ "scope": "assistant",
287
+ "key": "permission-controls-v2",
288
+ "label": "Permission Controls V2",
289
+ "description": "Replace risk-level permission system with two independent controls: 'Ask before acting' (LLM behavior toggle) and 'Host access' (system-enforced gate)",
290
+ "defaultEnabled": false
275
291
  }
276
292
  ]
277
293
  }
@@ -3,6 +3,11 @@ import { z } from "zod";
3
3
  import { getDataDir } from "../util/platform.js";
4
4
 
5
5
  // Re-export all domain schemas
6
+ export type { PermissionMode } from "../permissions/permission-mode.js";
7
+ export {
8
+ DEFAULT_PERMISSION_MODE,
9
+ PermissionModeSchema,
10
+ } from "../permissions/permission-mode.js";
6
11
  export type { AcpAgentConfig, AcpConfig } from "./acp-schema.js";
7
12
  export { AcpAgentConfigSchema, AcpConfigSchema } from "./acp-schema.js";
8
13
  export type {
@@ -143,6 +148,7 @@ export type {
143
148
  export {
144
149
  PermissionsConfigSchema,
145
150
  SecretDetectionConfigSchema,
151
+ VALID_PERMISSIONS_MODES,
146
152
  } from "./schemas/security.js";
147
153
  export type {
148
154
  ImageGenerationService,
@@ -197,6 +203,7 @@ import {
197
203
  WhatsAppConfigSchema,
198
204
  } from "./schemas/channels.js";
199
205
  import { ElevenLabsConfigSchema } from "./schemas/elevenlabs.js";
206
+ import { FilingConfigSchema } from "./schemas/filing.js";
200
207
  import { FishAudioConfigSchema } from "./schemas/fish-audio.js";
201
208
  import { HeartbeatConfigSchema } from "./schemas/heartbeat.js";
202
209
  import {
@@ -272,6 +279,7 @@ export const AssistantConfigSchema = z
272
279
  .describe(
273
280
  "Custom pricing overrides for specific provider/model combinations",
274
281
  ),
282
+ filing: FilingConfigSchema.default(FilingConfigSchema.parse({})),
275
283
  heartbeat: HeartbeatConfigSchema.default(HeartbeatConfigSchema.parse({})),
276
284
  journal: JournalConfigSchema.default(JournalConfigSchema.parse({})),
277
285
  mcp: McpConfigSchema.default(McpConfigSchema.parse({})),
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+
3
+ import { SpeedSchema } from "./inference.js";
4
+
5
+ export const FilingConfigSchema = z
6
+ .object({
7
+ enabled: z
8
+ .boolean({ error: "filing.enabled must be a boolean" })
9
+ .default(true)
10
+ .describe(
11
+ "Whether periodic PKB filing is enabled — processes buffer.md into topic files and reviews knowledge base organization",
12
+ ),
13
+ intervalMs: z
14
+ .number({ error: "filing.intervalMs must be a number" })
15
+ .int("filing.intervalMs must be an integer")
16
+ .positive("filing.intervalMs must be a positive integer")
17
+ .default(4 * 3_600_000)
18
+ .describe("Time between filing runs in milliseconds"),
19
+ speed: SpeedSchema.default("standard").describe(
20
+ "Inference speed mode for filing conversations",
21
+ ),
22
+ activeHoursStart: z
23
+ .number({ error: "filing.activeHoursStart must be a number" })
24
+ .int("filing.activeHoursStart must be an integer")
25
+ .min(0, "filing.activeHoursStart must be >= 0")
26
+ .max(23, "filing.activeHoursStart must be <= 23")
27
+ .default(8)
28
+ .describe("Hour of the day (0-23) when filing runs begin"),
29
+ activeHoursEnd: z
30
+ .number({ error: "filing.activeHoursEnd must be a number" })
31
+ .int("filing.activeHoursEnd must be an integer")
32
+ .min(0, "filing.activeHoursEnd must be >= 0")
33
+ .max(23, "filing.activeHoursEnd must be <= 23")
34
+ .default(22)
35
+ .describe("Hour of the day (0-23) when filing runs stop"),
36
+ })
37
+ .describe(
38
+ "Periodic PKB (personal knowledge base) filing — processes the buffer into topic files and maintains knowledge organization",
39
+ )
40
+ .superRefine((config, ctx) => {
41
+ if (config.activeHoursStart === config.activeHoursEnd) {
42
+ ctx.addIssue({
43
+ code: z.ZodIssueCode.custom,
44
+ path: ["activeHoursEnd"],
45
+ message:
46
+ "filing.activeHoursStart and filing.activeHoursEnd must not be equal (would create an empty window)",
47
+ });
48
+ }
49
+ });
50
+
51
+ export type FilingConfig = z.infer<typeof FilingConfigSchema>;
@@ -12,7 +12,7 @@ export const HeartbeatConfigSchema = z
12
12
  .number({ error: "heartbeat.intervalMs must be a number" })
13
13
  .int("heartbeat.intervalMs must be an integer")
14
14
  .positive("heartbeat.intervalMs must be a positive integer")
15
- .default(3_600_000)
15
+ .default(6 * 3_600_000)
16
16
  .describe("Time between heartbeat checks in milliseconds"),
17
17
  speed: SpeedSchema.default("standard").describe(
18
18
  "Inference speed mode for heartbeat conversations — defaults to standard to avoid inheriting the global fast mode multiplier",
@@ -22,35 +22,38 @@ export const HeartbeatConfigSchema = z
22
22
  .int("heartbeat.activeHoursStart must be an integer")
23
23
  .min(0, "heartbeat.activeHoursStart must be >= 0")
24
24
  .max(23, "heartbeat.activeHoursStart must be <= 23")
25
- .optional()
25
+ .nullable()
26
+ .default(8)
26
27
  .describe(
27
- "Hour of the day (0-23) when heartbeat checks begin must be set together with activeHoursEnd",
28
+ "Hour of the day (0-23) when heartbeat checks begin, or null to disable active hours restriction",
28
29
  ),
29
30
  activeHoursEnd: z
30
31
  .number({ error: "heartbeat.activeHoursEnd must be a number" })
31
32
  .int("heartbeat.activeHoursEnd must be an integer")
32
33
  .min(0, "heartbeat.activeHoursEnd must be >= 0")
33
34
  .max(23, "heartbeat.activeHoursEnd must be <= 23")
34
- .optional()
35
+ .nullable()
36
+ .default(22)
35
37
  .describe(
36
- "Hour of the day (0-23) when heartbeat checks stop must be set together with activeHoursStart",
38
+ "Hour of the day (0-23) when heartbeat checks stop, or null to disable active hours restriction",
37
39
  ),
38
40
  })
39
41
  .describe("Periodic heartbeat configuration for health monitoring")
40
42
  .superRefine((config, ctx) => {
41
- const hasStart = config.activeHoursStart != null;
42
- const hasEnd = config.activeHoursEnd != null;
43
- if (hasStart !== hasEnd) {
43
+ const startNull = config.activeHoursStart == null;
44
+ const endNull = config.activeHoursEnd == null;
45
+ if (startNull !== endNull) {
44
46
  ctx.addIssue({
45
47
  code: z.ZodIssueCode.custom,
46
- path: [hasStart ? "activeHoursEnd" : "activeHoursStart"],
48
+ path: [startNull ? "activeHoursStart" : "activeHoursEnd"],
47
49
  message:
48
- "heartbeat.activeHoursStart and heartbeat.activeHoursEnd must both be set or both be omitted",
50
+ "heartbeat.activeHoursStart and heartbeat.activeHoursEnd must both be set or both be null",
49
51
  });
52
+ return;
50
53
  }
51
54
  if (
52
- hasStart &&
53
- hasEnd &&
55
+ config.activeHoursStart != null &&
56
+ config.activeHoursEnd != null &&
54
57
  config.activeHoursStart === config.activeHoursEnd
55
58
  ) {
56
59
  ctx.addIssue({
@@ -72,6 +72,18 @@ export const MemoryCleanupConfigSchema = z
72
72
  .describe(
73
73
  "Number of days to retain conversation data before cleanup (0 disables pruning)",
74
74
  ),
75
+ llmRequestLogRetentionMs: z
76
+ .number({
77
+ error: "memory.cleanup.llmRequestLogRetentionMs must be a number",
78
+ })
79
+ .int("memory.cleanup.llmRequestLogRetentionMs must be an integer")
80
+ .nonnegative(
81
+ "memory.cleanup.llmRequestLogRetentionMs must be non-negative",
82
+ )
83
+ .default(7 * 24 * 60 * 60 * 1000)
84
+ .describe(
85
+ "Retention period for LLM request/response logs in milliseconds (0 disables pruning)",
86
+ ),
75
87
  })
76
88
  .describe("Automatic memory cleanup and garbage collection settings");
77
89
 
@@ -79,6 +79,20 @@ export const PermissionsConfigSchema = z
79
79
  .describe(
80
80
  "Permission mode — 'strict' requires explicit approval for all operations, 'workspace' allows operations within the workspace",
81
81
  ),
82
+ askBeforeActing: z
83
+ .boolean({
84
+ error: "permissions.askBeforeActing must be a boolean",
85
+ })
86
+ .default(true)
87
+ .describe("Whether the assistant should check in before taking actions"),
88
+ hostAccess: z
89
+ .boolean({
90
+ error: "permissions.hostAccess must be a boolean",
91
+ })
92
+ .default(false)
93
+ .describe(
94
+ "Whether the assistant can execute commands on the host machine without prompting",
95
+ ),
82
96
  })
83
97
  .describe("Permission enforcement mode for tool operations");
84
98
 
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Filesystem watcher for app source directories.
3
+ *
4
+ * Watches the apps root directory recursively using fs.watch (FSEvents on
5
+ * macOS). When a source file changes, debounces per app ID and calls the
6
+ * provided callback so the server can recompile + refresh surfaces.
7
+ *
8
+ * This catches ALL modification sources (file_edit, file_write, bash, etc.)
9
+ * without relying on individual tool hooks.
10
+ */
11
+
12
+ import { existsSync, type FSWatcher, watch } from "node:fs";
13
+
14
+ import {
15
+ getAppsDir,
16
+ resolveAppIdByDirName,
17
+ } from "../memory/app-store.js";
18
+ import { DebouncerMap } from "../util/debounce.js";
19
+ import { getLogger } from "../util/logger.js";
20
+
21
+ const log = getLogger("app-source-watcher");
22
+
23
+ const APP_REFRESH_DEBOUNCE_MS = 500;
24
+
25
+ export type AppSourceChangeCallback = (appId: string) => void;
26
+
27
+ /**
28
+ * Resolve app ID from a relative path within the apps directory.
29
+ * Returns null if the path is not an app source file (e.g. dist/, records/).
30
+ */
31
+ function resolveAppIdFromRelPath(relPath: string): string | null {
32
+ const slashIdx = relPath.indexOf("/");
33
+ if (slashIdx === -1) return null; // file directly in apps/ (e.g. .json definition)
34
+
35
+ const dirName = relPath.slice(0, slashIdx);
36
+ const innerPath = relPath.slice(slashIdx + 1);
37
+
38
+ // Skip non-source directories (include bare directory names for fs.watch events)
39
+ if (
40
+ innerPath === "records" || innerPath.startsWith("records/") ||
41
+ innerPath === "dist" || innerPath.startsWith("dist/")
42
+ ) {
43
+ return null;
44
+ }
45
+
46
+ return resolveAppIdByDirName(dirName);
47
+ }
48
+
49
+ export class AppSourceWatcher {
50
+ private watcher: FSWatcher | null = null;
51
+ private debouncer = new DebouncerMap({
52
+ defaultDelayMs: APP_REFRESH_DEBOUNCE_MS,
53
+ maxEntries: 50,
54
+ });
55
+
56
+ start(onChange: AppSourceChangeCallback): void {
57
+ let appsDir: string;
58
+ try {
59
+ appsDir = getAppsDir();
60
+ } catch {
61
+ log.warn("Could not resolve apps directory; app source watching disabled");
62
+ return;
63
+ }
64
+
65
+ if (!existsSync(appsDir)) {
66
+ log.info("Apps directory does not exist yet; skipping source watcher");
67
+ return;
68
+ }
69
+
70
+ try {
71
+ this.watcher = watch(appsDir, { recursive: true }, (_eventType, filename) => {
72
+ if (!filename) return;
73
+
74
+ const appId = resolveAppIdFromRelPath(filename);
75
+ if (!appId) return;
76
+
77
+ this.debouncer.schedule(`app:${appId}`, () => {
78
+ onChange(appId);
79
+ });
80
+ });
81
+ } catch (err) {
82
+ log.warn({ err }, "Failed to watch apps directory; source watching disabled");
83
+ }
84
+ }
85
+
86
+ stop(): void {
87
+ this.debouncer.cancelAll();
88
+ if (this.watcher) {
89
+ this.watcher.close();
90
+ this.watcher = null;
91
+ }
92
+ }
93
+ }