@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
@@ -0,0 +1,93 @@
1
+ import { RiskLevel } from "../../permissions/types.js";
2
+ import type { ToolDefinition } from "../../providers/types.js";
3
+ import { registerTool } from "../registry.js";
4
+ import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
5
+ import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
6
+ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
7
+
8
+ class FileListTool implements Tool {
9
+ name = "file_list";
10
+ description =
11
+ "List the contents of a directory. Returns file and subdirectory names with type indicators and sizes.";
12
+ category = "filesystem";
13
+ defaultRiskLevel = RiskLevel.Low;
14
+
15
+ getDefinition(): ToolDefinition {
16
+ return {
17
+ name: this.name,
18
+ description: this.description,
19
+ input_schema: {
20
+ type: "object",
21
+ properties: {
22
+ path: {
23
+ type: "string",
24
+ description: "The directory path to list",
25
+ },
26
+ glob: {
27
+ type: "string",
28
+ description: "Filter entries by glob pattern, e.g. '*.md'",
29
+ },
30
+ activity: {
31
+ type: "string",
32
+ description:
33
+ "Brief non-technical explanation of what you are doing and why, shown to the user as a status update.",
34
+ },
35
+ },
36
+ required: ["path", "activity"],
37
+ },
38
+ };
39
+ }
40
+
41
+ async execute(
42
+ input: Record<string, unknown>,
43
+ context: ToolContext,
44
+ ): Promise<ToolExecutionResult> {
45
+ const rawPath = input.path as string;
46
+ if (!rawPath || typeof rawPath !== "string") {
47
+ return {
48
+ content: "Error: path is required and must be a string",
49
+ isError: true,
50
+ };
51
+ }
52
+
53
+ const ops = new FileSystemOps((path, opts) =>
54
+ sandboxPolicy(path, context.workingDir, opts),
55
+ );
56
+
57
+ const result = ops.listDirSafe({
58
+ path: rawPath,
59
+ glob: typeof input.glob === "string" ? input.glob : undefined,
60
+ });
61
+
62
+ if (!result.ok) {
63
+ const { error } = result;
64
+ switch (error.code) {
65
+ case "NOT_A_DIRECTORY":
66
+ return {
67
+ content: `Error: ${error.path} is not a directory`,
68
+ isError: true,
69
+ };
70
+ case "NOT_FOUND":
71
+ return {
72
+ content: `Error: directory not found: ${error.path}`,
73
+ isError: true,
74
+ };
75
+ default: {
76
+ const hint =
77
+ error.code === "PATH_OUT_OF_BOUNDS"
78
+ ? ". To list files outside the workspace, use the host_bash tool instead."
79
+ : "";
80
+ return {
81
+ content: `Error: ${error.message}${hint}`,
82
+ isError: true,
83
+ };
84
+ }
85
+ }
86
+ }
87
+
88
+ return { content: result.value.listing, isError: false };
89
+ }
90
+ }
91
+
92
+ export const fileListTool = new FileListTool();
93
+ registerTool(fileListTool);
@@ -1,3 +1,4 @@
1
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
1
2
  import { getConfig } from "../config/loader.js";
2
3
  import { getHookManager } from "../hooks/manager.js";
3
4
  import {
@@ -6,9 +7,11 @@ import {
6
7
  generateAllowlistOptions,
7
8
  generateScopeOptions,
8
9
  } from "../permissions/checker.js";
10
+ import { getMode } from "../permissions/permission-mode-store.js";
9
11
  import type { PermissionPrompter } from "../permissions/prompter.js";
10
12
  import { addRule } from "../permissions/trust-store.js";
11
13
  import { RiskLevel } from "../permissions/types.js";
14
+ import { isHostTool } from "../permissions/workspace-policy.js";
12
15
  import {
13
16
  getEffectiveMode,
14
17
  setConversationMode,
@@ -17,7 +20,6 @@ import {
17
20
  import { getLogger } from "../util/logger.js";
18
21
  import { buildPolicyContext } from "./policy-context.js";
19
22
  import { isSideEffectTool } from "./side-effects.js";
20
- import { wrapCommand } from "./terminal/sandbox.js";
21
23
  import type { ExecutionTarget } from "./types.js";
22
24
  import type { Tool, ToolContext, ToolLifecycleEvent } from "./types.js";
23
25
 
@@ -65,6 +67,52 @@ export class PermissionChecker {
65
67
  }
66
68
  | undefined,
67
69
  ): Promise<PermissionDecision> {
70
+ // ── permission-controls-v2 early gate ──────────────────────────────
71
+ // When the v2 flag is enabled, replace the entire risk-classification
72
+ // path with a simple binary check: is it a host tool + is host access
73
+ // enabled? Certain security gates (requireFreshApproval,
74
+ // forcePromptSideEffects, hostAccess=false) fall through to the v1
75
+ // prompt flow so the interactive prompter is engaged.
76
+ const cfg = getConfig();
77
+ let v2ForcePrompt = false;
78
+ if (isAssistantFeatureFlagEnabled("permission-controls-v2", cfg)) {
79
+ // requireFreshApproval demands an interactive prompt every time —
80
+ // fall through to v1 so the prompter is engaged.
81
+ const needsFreshApproval = !!context.requireFreshApproval;
82
+
83
+ // forcePromptSideEffects (private conversations, untrusted actors)
84
+ // requires explicit approval for side-effect tools.
85
+ const needsSideEffectPrompt =
86
+ !!context.forcePromptSideEffects && isSideEffectTool(name, input);
87
+
88
+ if (!needsFreshApproval && !needsSideEffectPrompt) {
89
+ if (isHostTool(name)) {
90
+ const mode = getMode();
91
+ if (mode.hostAccess) {
92
+ return {
93
+ allowed: true,
94
+ decision: "allow",
95
+ riskLevel: RiskLevel.Low,
96
+ };
97
+ }
98
+ // Host tool with hostAccess disabled — fall through to v1 so the
99
+ // interactive prompter is engaged (returning allowed:false here
100
+ // would surface an error string instead of a permission dialog).
101
+ // The v2ForcePrompt flag ensures check()'s allow decision is
102
+ // promoted to prompt so the user sees a permission dialog.
103
+ v2ForcePrompt = true;
104
+ } else {
105
+ // Non-host tools are auto-allowed when v2 is on
106
+ return {
107
+ allowed: true,
108
+ decision: "allow",
109
+ riskLevel: RiskLevel.Low,
110
+ };
111
+ }
112
+ }
113
+ // Falls through to the v1 risk-classification + prompter path
114
+ }
115
+
68
116
  const risk = await classifyRisk(
69
117
  name,
70
118
  input,
@@ -114,6 +162,14 @@ export class PermissionChecker {
114
162
  "Fresh approval required: per-invocation human review enforced";
115
163
  }
116
164
 
165
+ // v2 host-access-disabled: the v2 gate fell through because
166
+ // hostAccess is off. Promote allow → prompt so the user sees an
167
+ // interactive permission dialog instead of an error string.
168
+ if (v2ForcePrompt && result.decision === "allow") {
169
+ result.decision = "prompt";
170
+ result.reason = "Host access disabled: requires explicit approval";
171
+ }
172
+
117
173
  if (result.decision === "deny") {
118
174
  const durationMs = Date.now() - startTime;
119
175
  emitLifecycleEvent({
@@ -137,6 +193,26 @@ export class PermissionChecker {
137
193
  };
138
194
  }
139
195
 
196
+ // Platform-hosted mode: auto-approve sandboxed bash for guardians.
197
+ // The sandbox provides the security boundary — prompting is unnecessary
198
+ // friction. host_bash is excluded because it runs unsandboxed on the
199
+ // user's machine and warrants explicit approval.
200
+ // Deny rules are still respected (checked above). requireFreshApproval
201
+ // is preserved as a belt-and-suspenders guard.
202
+ if (
203
+ result.decision === "prompt" &&
204
+ context.isPlatformHosted &&
205
+ name === "bash" &&
206
+ context.trustClass === "guardian" &&
207
+ !context.requireFreshApproval
208
+ ) {
209
+ log.info(
210
+ { toolName: name, riskLevel },
211
+ "Auto-approving bash tool for platform-hosted guardian session",
212
+ );
213
+ return { allowed: true, decision: "platform_auto_approve", riskLevel };
214
+ }
215
+
140
216
  if (result.decision === "prompt") {
141
217
  // Guardian-trust sessions (e.g. scheduled jobs, reminders) should be
142
218
  // able to use bundled tools without interactive approval. The guardian
@@ -158,6 +234,7 @@ export class PermissionChecker {
158
234
  context.trustClass === "guardian" &&
159
235
  !context.requireFreshApproval &&
160
236
  !isDynamicSkillLoad &&
237
+ !v2ForcePrompt &&
161
238
  riskLevel !== RiskLevel.High
162
239
  ) {
163
240
  log.info(
@@ -232,21 +309,6 @@ export class PermissionChecker {
232
309
  const scopeOptions = generateScopeOptions(context.workingDir, name);
233
310
  const previewDiff = computePreviewDiff(name, input, context.workingDir);
234
311
 
235
- let sandboxed: boolean | undefined;
236
- if (name === "bash" && typeof input.command === "string") {
237
- const cfg = getConfig();
238
- const sandboxConfig =
239
- context.sandboxOverride != null
240
- ? { ...cfg.sandbox, enabled: context.sandboxOverride }
241
- : cfg.sandbox;
242
- const wrapped = wrapCommand(
243
- input.command,
244
- context.workingDir,
245
- sandboxConfig,
246
- );
247
- sandboxed = wrapped.sandboxed;
248
- }
249
-
250
312
  const persistentDecisionsAllowed = !context.requireFreshApproval;
251
313
 
252
314
  // Offer temporary approval options to guardians. Suppressed when
@@ -272,7 +334,6 @@ export class PermissionChecker {
272
334
  allowlistOptions,
273
335
  scopeOptions,
274
336
  diff: previewDiff,
275
- sandboxed,
276
337
  persistentDecisionsAllowed,
277
338
  });
278
339
 
@@ -290,7 +351,6 @@ export class PermissionChecker {
290
351
  allowlistOptions,
291
352
  scopeOptions,
292
353
  previewDiff,
293
- sandboxed,
294
354
  context.conversationId,
295
355
  executionTarget,
296
356
  persistentDecisionsAllowed,
@@ -7,6 +7,8 @@ import { hostFileEditTool } from "./host-filesystem/edit.js";
7
7
  import { hostFileReadTool } from "./host-filesystem/read.js";
8
8
  import { hostFileWriteTool } from "./host-filesystem/write.js";
9
9
  import { hostShellTool } from "./host-terminal/host-shell.js";
10
+ import { registerSystemTools } from "./system/register.js";
11
+ import { setPermissionModeTool } from "./system/set-permission-mode.js";
10
12
  import type { Tool } from "./types.js";
11
13
  import { allUiSurfaceTools } from "./ui-surface/definitions.js";
12
14
  import { registerUiSurfaceTools } from "./ui-surface/registry.js";
@@ -264,6 +266,7 @@ export async function initializeTools(): Promise<void> {
264
266
 
265
267
  registerUiSurfaceTools();
266
268
  registerAppTools();
269
+ registerSystemTools();
267
270
 
268
271
  // Snapshot core tools for __resetRegistryForTesting(). We include every
269
272
  // non-skill tool that was registered by the manifest, while excluding
@@ -282,6 +285,7 @@ export async function initializeTools(): Promise<void> {
282
285
  ...allComputerUseTools.map((t: Tool) => t.name),
283
286
  ...allUiSurfaceTools.map((t: Tool) => t.name),
284
287
  ...coreAppProxyTools.map((t: Tool) => t.name),
288
+ setPermissionModeTool.name,
285
289
  ]);
286
290
 
287
291
  coreToolsSnapshot = new Map<string, Tool>();
@@ -42,6 +42,7 @@ export async function executeScheduleCreate(
42
42
  | Record<string, unknown>
43
43
  | undefined;
44
44
  const quiet = (input.quiet as boolean) ?? false;
45
+ const reuseConversation = (input.reuse_conversation as boolean) ?? false;
45
46
 
46
47
  if (!name || typeof name !== "string") {
47
48
  return {
@@ -114,6 +115,7 @@ export async function executeScheduleCreate(
114
115
  routingIntent: routingIntent as RoutingIntent | undefined,
115
116
  routingHints,
116
117
  quiet,
118
+ reuseConversation,
117
119
  });
118
120
 
119
121
  const fireDate = formatLocalDate(job.nextRunAt);
@@ -190,6 +192,7 @@ export async function executeScheduleCreate(
190
192
  routingIntent: routingIntent as RoutingIntent | undefined,
191
193
  routingHints,
192
194
  quiet,
195
+ reuseConversation,
193
196
  });
194
197
 
195
198
  const scheduleDescription =
@@ -65,6 +65,7 @@ export async function executeScheduleList(
65
65
  lines.push(
66
66
  ` Enabled: ${job.enabled}`,
67
67
  ` Quiet: ${job.quiet}`,
68
+ ` Reuse conversation: ${job.reuseConversation}`,
68
69
  ` Message: ${job.message}`,
69
70
  );
70
71
 
@@ -102,6 +102,11 @@ export async function executeScheduleUpdate(
102
102
  updates.quiet = input.quiet;
103
103
  }
104
104
 
105
+ // Conversation reuse
106
+ if (input.reuse_conversation !== undefined) {
107
+ updates.reuseConversation = input.reuse_conversation;
108
+ }
109
+
105
110
  // Auto-detect syntax when expression changes without explicit syntax
106
111
  if (input.expression !== undefined || input.syntax !== undefined) {
107
112
  const resolved = normalizeScheduleSyntax({
@@ -165,6 +170,7 @@ export async function executeScheduleUpdate(
165
170
  routingIntent?: RoutingIntent;
166
171
  routingHints?: Record<string, unknown>;
167
172
  quiet?: boolean;
173
+ reuseConversation?: boolean;
168
174
  },
169
175
  );
170
176
 
@@ -331,7 +331,6 @@ export class SecretDetectionHandler {
331
331
  [], // no allowlist options
332
332
  [], // no scope options
333
333
  undefined, // no diff
334
- undefined, // not sandboxed
335
334
  context.conversationId,
336
335
  executionTarget,
337
336
  false, // no persistent decisions
@@ -8,6 +8,7 @@ export type FsErrorCode =
8
8
  | "PATH_NOT_ABSOLUTE"
9
9
  | "NOT_FOUND"
10
10
  | "NOT_A_FILE"
11
+ | "NOT_A_DIRECTORY"
11
12
  | "SIZE_LIMIT_EXCEEDED"
12
13
  | "MATCH_NOT_FOUND"
13
14
  | "MATCH_AMBIGUOUS"
@@ -56,6 +57,10 @@ export function notAFile(path: string): FsError {
56
57
  return { code: "NOT_A_FILE", message: `Not a regular file: ${path}`, path };
57
58
  }
58
59
 
60
+ export function notADirectory(path: string): FsError {
61
+ return { code: "NOT_A_DIRECTORY", message: `Not a directory: ${path}`, path };
62
+ }
63
+
59
64
  export function sizeLimitExceeded(path: string, detail: string): FsError {
60
65
  return {
61
66
  code: "SIZE_LIMIT_EXCEEDED",
@@ -1,5 +1,13 @@
1
- import { readFileSync, statSync, writeFileSync } from "node:fs";
2
- import { dirname } from "node:path";
1
+ import {
2
+ lstatSync,
3
+ readdirSync,
4
+ readFileSync,
5
+ statSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+
10
+ import { minimatch } from "minimatch";
3
11
 
4
12
  import { ensureDir, pathExists } from "../../../util/fs.js";
5
13
  import { applyEdit } from "./edit-engine.js";
@@ -9,6 +17,8 @@ import { checkContentSize, checkFileSizeOnDisk } from "./size-guard.js";
9
17
  import type {
10
18
  EditInput,
11
19
  EditResult,
20
+ ListInput,
21
+ ListResult,
12
22
  ReadInput,
13
23
  ReadResult,
14
24
  WriteInput,
@@ -237,4 +247,82 @@ export class FileSystemOps {
237
247
  },
238
248
  };
239
249
  }
250
+
251
+ // -------------------------------------------------------------------------
252
+ // List
253
+ // -------------------------------------------------------------------------
254
+
255
+ listDirSafe(input: ListInput): ListResult {
256
+ const pathCheck = this.policy(input.path, { mustExist: true });
257
+ if (!pathCheck.ok) {
258
+ return {
259
+ ok: false,
260
+ error: pathError(input.path, pathCheck.reason, pathCheck.error),
261
+ };
262
+ }
263
+ const resolved = pathCheck.resolved;
264
+
265
+ if (!pathExists(resolved)) {
266
+ return { ok: false, error: Err.notFound(resolved) };
267
+ }
268
+
269
+ const stat = statSync(resolved);
270
+ if (!stat.isDirectory()) {
271
+ return { ok: false, error: Err.notADirectory(resolved) };
272
+ }
273
+
274
+ try {
275
+ let entries = readdirSync(resolved, { withFileTypes: true });
276
+
277
+ if (input.glob) {
278
+ const pattern = input.glob;
279
+ entries = entries.filter((e) => minimatch(e.name, pattern));
280
+ }
281
+
282
+ // Sort: directories first (alphabetical), then files (alphabetical)
283
+ const dirs = entries
284
+ .filter((e) => e.isDirectory())
285
+ .sort((a, b) => a.name.localeCompare(b.name));
286
+ const files = entries
287
+ .filter((e) => !e.isDirectory())
288
+ .sort((a, b) => a.name.localeCompare(b.name));
289
+ const sorted = [...dirs, ...files];
290
+
291
+ const MAX_ENTRIES = 500;
292
+ const truncated = sorted.length > MAX_ENTRIES;
293
+ const visible = sorted.slice(0, MAX_ENTRIES);
294
+
295
+ const lines = visible.map((entry) => {
296
+ if (entry.isDirectory()) {
297
+ return `${entry.name}/`;
298
+ }
299
+ if (entry.isSymbolicLink()) {
300
+ return `${entry.name}@`;
301
+ }
302
+ const fileStat = lstatSync(join(resolved, entry.name));
303
+ return `${entry.name} ${formatSize(fileStat.size)}`;
304
+ });
305
+
306
+ if (truncated) {
307
+ lines.push(
308
+ `\n... and ${sorted.length - MAX_ENTRIES} more entries (use glob to filter)`,
309
+ );
310
+ }
311
+
312
+ return { ok: true, value: { listing: lines.join("\n") } };
313
+ } catch (err) {
314
+ const msg = err instanceof Error ? err.message : String(err);
315
+ return { ok: false, error: Err.ioError(resolved, msg) };
316
+ }
317
+ }
318
+ }
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // Helpers
322
+ // ---------------------------------------------------------------------------
323
+
324
+ function formatSize(bytes: number): string {
325
+ if (bytes < 1024) return `${bytes} B`;
326
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
327
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
240
328
  }
@@ -78,3 +78,20 @@ export interface EditOutput {
78
78
  export type EditResult =
79
79
  | { ok: true; value: EditOutput }
80
80
  | { ok: false; error: FsError };
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // List
84
+ // ---------------------------------------------------------------------------
85
+
86
+ export interface ListInput {
87
+ path: string;
88
+ glob?: string;
89
+ }
90
+
91
+ export interface ListOutput {
92
+ listing: string;
93
+ }
94
+
95
+ export type ListResult =
96
+ | { ok: true; value: ListOutput }
97
+ | { ok: false; error: FsError };
@@ -1,4 +1,24 @@
1
- export const MAX_OUTPUT_LENGTH = 50_000;
1
+ import { randomUUID } from "node:crypto";
2
+ import { unlinkSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ export const MAX_OUTPUT_LENGTH = 20_000;
7
+
8
+ /** Tracks temp files created for truncated shell output so they can be cleaned up on shutdown. */
9
+ const trackedTempFiles = new Set<string>();
10
+
11
+ /** Remove all tracked truncated-output temp files. Safe to call multiple times. */
12
+ export function cleanupShellOutputTempFiles(): void {
13
+ for (const filePath of trackedTempFiles) {
14
+ try {
15
+ unlinkSync(filePath);
16
+ } catch {
17
+ // File may already be gone — ignore.
18
+ }
19
+ }
20
+ trackedTempFiles.clear();
21
+ }
2
22
 
3
23
  export interface ShellOutputResult {
4
24
  content: string;
@@ -33,7 +53,16 @@ export function formatShellOutput(
33
53
  }
34
54
 
35
55
  if (output.length > MAX_OUTPUT_LENGTH) {
36
- const msg = '<output_truncated limit="50K" />';
56
+ let fullOutputPath: string | undefined;
57
+ try {
58
+ fullOutputPath = join(tmpdir(), `vellum-shell-output-${randomUUID()}.txt`);
59
+ writeFileSync(fullOutputPath, output, { encoding: "utf-8", mode: 0o600 });
60
+ trackedTempFiles.add(fullOutputPath);
61
+ } catch {
62
+ fullOutputPath = undefined;
63
+ }
64
+ const fileAttr = fullOutputPath ? ` file="${fullOutputPath}"` : "";
65
+ const msg = `<output_truncated limit="20K"${fileAttr} />`;
37
66
  output = output.slice(0, MAX_OUTPUT_LENGTH) + `\n${msg}`;
38
67
  statusParts.push(msg);
39
68
  }
@@ -3,7 +3,6 @@ import { randomUUID } from "node:crypto";
3
3
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
 
6
- import { getConfig } from "../../config/loader.js";
7
6
  import { computeSkillVersionHash } from "../../skills/version-hash.js";
8
7
  import { buildSanitizedEnv } from "../terminal/safe-env.js";
9
8
  import { wrapCommand } from "../terminal/sandbox.js";
@@ -138,11 +137,9 @@ function spawnRunner(
138
137
  const stderrChunks: Buffer[] = [];
139
138
  let timedOut = false;
140
139
 
141
- const config = getConfig();
142
- const sandboxConfig =
143
- context.sandboxOverride != null
144
- ? { ...config.sandbox, enabled: context.sandboxOverride }
145
- : config.sandbox;
140
+ // The assistant runs exclusively in Docker or platform-managed
141
+ // environments where the container provides isolation.
142
+ const sandboxConfig = { enabled: false } as const;
146
143
 
147
144
  const bunRunCmd = "bun run __skill_runner.ts";
148
145
  const wrapped = wrapCommand(bunRunCmd, runDir, sandboxConfig);
@@ -1,13 +1,23 @@
1
1
  import { getSubagentManager } from "../../subagent/index.js";
2
2
  import type { ToolContext, ToolExecutionResult } from "../types.js";
3
+ import { resolveSubagentId } from "./resolve.js";
3
4
 
4
5
  export async function executeSubagentAbort(
5
6
  input: Record<string, unknown>,
6
7
  context: ToolContext,
7
8
  ): Promise<ToolExecutionResult> {
8
- const subagentId = input.subagent_id as string;
9
+ const subagentId = resolveSubagentId(input, context);
10
+ if (!subagentId && input.label) {
11
+ return {
12
+ content: `No subagent found with label "${input.label as string}".`,
13
+ isError: true,
14
+ };
15
+ }
9
16
  if (!subagentId) {
10
- return { content: '"subagent_id" is required.', isError: true };
17
+ return {
18
+ content: '"subagent_id" or "label" is required.',
19
+ isError: true,
20
+ };
11
21
  }
12
22
 
13
23
  const manager = getSubagentManager();
@@ -1,16 +1,23 @@
1
1
  import { getSubagentManager } from "../../subagent/index.js";
2
2
  import type { ToolContext, ToolExecutionResult } from "../types.js";
3
+ import { resolveSubagentId } from "./resolve.js";
3
4
 
4
5
  export async function executeSubagentMessage(
5
6
  input: Record<string, unknown>,
6
7
  context: ToolContext,
7
8
  ): Promise<ToolExecutionResult> {
8
- const subagentId = input.subagent_id as string;
9
+ const subagentId = resolveSubagentId(input, context);
9
10
  const content = input.content as string;
10
11
 
12
+ if (!subagentId && input.label) {
13
+ return {
14
+ content: `No subagent found with label "${input.label as string}".`,
15
+ isError: true,
16
+ };
17
+ }
11
18
  if (!subagentId || !content) {
12
19
  return {
13
- content: 'Both "subagent_id" and "content" are required.',
20
+ content: '"subagent_id" or "label", and "content" are required.',
14
21
  isError: true,
15
22
  };
16
23
  }