@vellumai/assistant 0.5.16 → 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 (592) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +69 -16
  3. package/Dockerfile +2 -5
  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/knip.json +2 -1
  9. package/openapi.yaml +1198 -83
  10. package/package.json +5 -1
  11. package/src/__tests__/actor-token-service.test.ts +68 -0
  12. package/src/__tests__/agent-loop.test.ts +0 -32
  13. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  14. package/src/__tests__/anthropic-provider.test.ts +217 -98
  15. package/src/__tests__/app-compiler.test.ts +120 -0
  16. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  17. package/src/__tests__/app-executors.test.ts +47 -1
  18. package/src/__tests__/app-source-watcher.test.ts +159 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  20. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  21. package/src/__tests__/call-domain.test.ts +2 -6
  22. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  23. package/src/__tests__/call-recovery.test.ts +2 -6
  24. package/src/__tests__/call-routes-http.test.ts +2 -6
  25. package/src/__tests__/call-store.test.ts +2 -6
  26. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  27. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  28. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  29. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  30. package/src/__tests__/checker.test.ts +63 -9
  31. package/src/__tests__/clawhub.test.ts +54 -24
  32. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  33. package/src/__tests__/config-schema.test.ts +6 -1
  34. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  35. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  36. package/src/__tests__/contacts-tools.test.ts +31 -0
  37. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  38. package/src/__tests__/context-token-estimator.test.ts +175 -10
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +13 -6
  40. package/src/__tests__/conversation-agent-loop.test.ts +13 -51
  41. package/src/__tests__/conversation-attachments.test.ts +2 -6
  42. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  43. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  44. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  45. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  46. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  47. package/src/__tests__/conversation-error.test.ts +33 -2
  48. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  49. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  50. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  53. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  54. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  55. package/src/__tests__/conversation-store.test.ts +2 -6
  56. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  57. package/src/__tests__/conversation-usage.test.ts +2 -6
  58. package/src/__tests__/conversation-wipe.test.ts +13 -414
  59. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  60. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  61. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  62. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  63. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  64. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  65. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  66. package/src/__tests__/date-context.test.ts +76 -210
  67. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  68. package/src/__tests__/file-list-tool.test.ts +219 -0
  69. package/src/__tests__/first-greeting.test.ts +1 -1
  70. package/src/__tests__/followup-tools.test.ts +2 -6
  71. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  72. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  73. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  74. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  75. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  76. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  77. package/src/__tests__/guardian-action-store.test.ts +2 -6
  78. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  79. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  80. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  81. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  82. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  83. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  84. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  85. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  86. package/src/__tests__/heartbeat-service.test.ts +180 -3
  87. package/src/__tests__/identity-routes.test.ts +328 -0
  88. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  89. package/src/__tests__/injection-block.test.ts +178 -0
  90. package/src/__tests__/install-meta.test.ts +506 -0
  91. package/src/__tests__/install-skill-routing.test.ts +293 -0
  92. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  93. package/src/__tests__/invite-routes-http.test.ts +2 -6
  94. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +17 -28
  95. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  96. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  97. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  98. package/src/__tests__/llm-context-route-provider.test.ts +103 -6
  99. package/src/__tests__/llm-request-log-turn-query.test.ts +164 -6
  100. package/src/__tests__/llm-usage-store.test.ts +2 -6
  101. package/src/__tests__/log-export-workspace.test.ts +74 -111
  102. package/src/__tests__/managed-store.test.ts +38 -11
  103. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  104. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  105. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  106. package/src/__tests__/memory-recall-log-store.test.ts +134 -6
  107. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  108. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  109. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  110. package/src/__tests__/mock-fetch.ts +87 -0
  111. package/src/__tests__/non-member-access-request.test.ts +2 -6
  112. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  113. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  114. package/src/__tests__/oauth-cli.test.ts +364 -2
  115. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  116. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  117. package/src/__tests__/outlook-attachments.test.ts +301 -0
  118. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  119. package/src/__tests__/outlook-categories.test.ts +212 -0
  120. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  121. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  122. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  123. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  124. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  125. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  126. package/src/__tests__/outlook-trash.test.ts +77 -0
  127. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  128. package/src/__tests__/parser.test.ts +32 -0
  129. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  130. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  131. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  132. package/src/__tests__/permission-mode-store.test.ts +277 -0
  133. package/src/__tests__/permission-mode.test.ts +101 -0
  134. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  135. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  136. package/src/__tests__/playbook-execution.test.ts +76 -80
  137. package/src/__tests__/playbook-tools.test.ts +5 -7
  138. package/src/__tests__/profiler-routes.test.ts +502 -0
  139. package/src/__tests__/profiler-run-store.test.ts +441 -0
  140. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  141. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  142. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  143. package/src/__tests__/registry.test.ts +3 -3
  144. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  145. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  146. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  147. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  148. package/src/__tests__/schedule-store.test.ts +2 -6
  149. package/src/__tests__/schedule-tools.test.ts +2 -6
  150. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  151. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  152. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  153. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  154. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  155. package/src/__tests__/search-skills-unified.test.ts +422 -0
  156. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  157. package/src/__tests__/send-endpoint-busy.test.ts +44 -9
  158. package/src/__tests__/sequence-store.test.ts +2 -6
  159. package/src/__tests__/server-history-render.test.ts +2 -6
  160. package/src/__tests__/set-permission-mode.test.ts +274 -0
  161. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  162. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  163. package/src/__tests__/skill-load-feature-flag.test.ts +23 -11
  164. package/src/__tests__/skill-memory.test.ts +2 -741
  165. package/src/__tests__/skills-uninstall.test.ts +2 -2
  166. package/src/__tests__/skills.test.ts +1 -1
  167. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  168. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  169. package/src/__tests__/subagent-detail.test.ts +84 -0
  170. package/src/__tests__/subagent-disposal.test.ts +308 -0
  171. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  172. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  173. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  174. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  175. package/src/__tests__/subagent-tools.test.ts +464 -4
  176. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  177. package/src/__tests__/task-compiler.test.ts +2 -6
  178. package/src/__tests__/task-management-tools.test.ts +2 -6
  179. package/src/__tests__/task-memory-cleanup.test.ts +185 -241
  180. package/src/__tests__/task-runner.test.ts +2 -6
  181. package/src/__tests__/task-scheduler.test.ts +2 -6
  182. package/src/__tests__/terminal-tools.test.ts +17 -27
  183. package/src/__tests__/test-preload.ts +7 -0
  184. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  185. package/src/__tests__/tool-executor.test.ts +4 -26
  186. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  187. package/src/__tests__/tool-side-effects-slack-dm.test.ts +277 -0
  188. package/src/__tests__/top-level-renderer.test.ts +10 -13
  189. package/src/__tests__/trust-store.test.ts +1 -1
  190. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  191. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +118 -8
  192. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  193. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  194. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  195. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  196. package/src/__tests__/usage-routes.test.ts +2 -6
  197. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  198. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  199. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  200. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  201. package/src/__tests__/volume-security-guard.test.ts +2 -0
  202. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  203. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  204. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  205. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  206. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  207. package/src/__tests__/workspace-policy.test.ts +1 -1
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +7 -15
  211. package/src/approvals/guardian-request-resolvers.ts +24 -0
  212. package/src/avatar/traits-png-sync.ts +3 -3
  213. package/src/bundler/app-compiler.ts +179 -2
  214. package/src/bundler/package-resolver.ts +3 -5
  215. package/src/cli/__tests__/notifications.test.ts +1 -2
  216. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  217. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  218. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  219. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  220. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  221. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  222. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  223. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  224. package/src/cli/commands/avatar.ts +3 -3
  225. package/src/cli/commands/config.ts +26 -13
  226. package/src/cli/commands/conversations.ts +1 -8
  227. package/src/cli/commands/doctor.ts +2 -2
  228. package/src/cli/commands/email.ts +584 -835
  229. package/src/cli/commands/memory.ts +37 -84
  230. package/src/cli/commands/notifications.ts +7 -2
  231. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  232. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  233. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  234. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  235. package/src/cli/commands/oauth/connect.ts +25 -11
  236. package/src/cli/commands/oauth/mode.ts +7 -0
  237. package/src/cli/commands/oauth/shared.ts +39 -3
  238. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  239. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  240. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  241. package/src/cli/commands/platform/index.ts +16 -16
  242. package/src/cli/commands/routes.ts +396 -0
  243. package/src/cli/commands/skills.ts +218 -36
  244. package/src/cli/commands/trust.ts +2 -2
  245. package/src/cli/lib/daemon-credential-client.ts +2 -3
  246. package/src/cli/program.ts +2 -0
  247. package/src/cli.ts +1 -120
  248. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  249. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  250. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  251. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  252. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  253. package/src/config/bundled-skills/gmail/SKILL.md +4 -12
  254. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  255. package/src/config/bundled-skills/messaging/SKILL.md +17 -18
  256. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  257. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  258. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  259. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  260. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  261. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  262. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  263. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  264. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  265. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  266. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  267. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  268. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  269. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  270. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  271. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  272. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  273. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  274. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  275. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  276. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  277. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  278. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  279. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  280. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  281. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  282. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  283. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  284. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  285. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  286. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  287. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  288. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  289. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  290. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  291. package/src/config/bundled-skills/slack/SKILL.md +3 -7
  292. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  293. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  294. package/src/config/bundled-tool-registry.ts +56 -4
  295. package/src/config/env-registry.ts +78 -8
  296. package/src/config/feature-flag-registry.json +38 -125
  297. package/src/config/schema.ts +8 -0
  298. package/src/config/schemas/filing.ts +51 -0
  299. package/src/config/schemas/heartbeat.ts +15 -12
  300. package/src/config/schemas/memory-lifecycle.ts +12 -0
  301. package/src/config/schemas/platform.ts +8 -0
  302. package/src/config/schemas/security.ts +14 -0
  303. package/src/config/schemas/timeouts.ts +1 -1
  304. package/src/config/skills.ts +18 -7
  305. package/src/context/token-estimator.ts +25 -18
  306. package/src/context/window-manager.ts +6 -2
  307. package/src/credential-execution/process-manager.ts +3 -1
  308. package/src/daemon/app-source-watcher.ts +93 -0
  309. package/src/daemon/config-watcher.ts +79 -1
  310. package/src/daemon/context-overflow-reducer.ts +46 -2
  311. package/src/daemon/conversation-agent-loop-handlers.ts +143 -82
  312. package/src/daemon/conversation-agent-loop.ts +236 -108
  313. package/src/daemon/conversation-error.ts +31 -8
  314. package/src/daemon/conversation-history.ts +4 -19
  315. package/src/daemon/conversation-lifecycle.ts +36 -9
  316. package/src/daemon/conversation-media-retry.ts +85 -7
  317. package/src/daemon/conversation-notifiers.ts +4 -1
  318. package/src/daemon/conversation-process.ts +13 -7
  319. package/src/daemon/conversation-runtime-assembly.ts +305 -306
  320. package/src/daemon/conversation-tool-setup.ts +44 -14
  321. package/src/daemon/conversation-workspace.ts +1 -2
  322. package/src/daemon/conversation.ts +59 -2
  323. package/src/daemon/daemon-control.ts +8 -2
  324. package/src/daemon/date-context.ts +26 -53
  325. package/src/daemon/first-greeting.ts +1 -1
  326. package/src/daemon/handlers/conversations.ts +4 -7
  327. package/src/daemon/handlers/shared.test.ts +143 -0
  328. package/src/daemon/handlers/shared.ts +85 -17
  329. package/src/daemon/handlers/skills.ts +416 -209
  330. package/src/daemon/lifecycle.ts +212 -131
  331. package/src/daemon/main.ts +5 -1
  332. package/src/daemon/message-types/conversations.ts +29 -7
  333. package/src/daemon/message-types/messages.ts +12 -2
  334. package/src/daemon/message-types/schedules.ts +1 -0
  335. package/src/daemon/message-types/settings.ts +6 -0
  336. package/src/daemon/message-types/skills.ts +97 -36
  337. package/src/daemon/profiler-run-store.ts +557 -0
  338. package/src/daemon/providers-setup.ts +5 -0
  339. package/src/daemon/server.ts +100 -11
  340. package/src/daemon/shutdown-handlers.ts +5 -0
  341. package/src/daemon/tool-side-effects.ts +50 -8
  342. package/src/export/transcript-formatter.ts +148 -0
  343. package/src/filing/filing-service.ts +228 -0
  344. package/src/heartbeat/heartbeat-service.ts +97 -7
  345. package/src/hooks/cli.ts +2 -2
  346. package/src/hooks/runner.ts +15 -38
  347. package/src/inbound/platform-callback-registration.ts +14 -14
  348. package/src/mcp/client.ts +6 -0
  349. package/src/mcp/mcp-oauth-provider.ts +149 -27
  350. package/src/memory/admin.ts +42 -75
  351. package/src/memory/app-store.ts +69 -0
  352. package/src/memory/conversation-bootstrap.ts +3 -1
  353. package/src/memory/conversation-crud.ts +211 -288
  354. package/src/memory/conversation-group-migration.ts +157 -0
  355. package/src/memory/conversation-queries.ts +61 -13
  356. package/src/memory/conversation-title-service.ts +1 -0
  357. package/src/memory/db-init.ts +194 -361
  358. package/src/memory/embed.ts +73 -0
  359. package/src/memory/embedding-backend.ts +8 -14
  360. package/src/memory/embedding-runtime-manager.ts +12 -114
  361. package/src/memory/fingerprint.ts +2 -2
  362. package/src/memory/graph/bootstrap.ts +521 -0
  363. package/src/memory/graph/capability-seed.ts +449 -0
  364. package/src/memory/graph/consolidation.ts +725 -0
  365. package/src/memory/graph/conversation-graph-memory.ts +659 -0
  366. package/src/memory/graph/decay.test.ts +208 -0
  367. package/src/memory/graph/decay.ts +195 -0
  368. package/src/memory/graph/extraction-job.ts +74 -0
  369. package/src/memory/graph/extraction.test.ts +936 -0
  370. package/src/memory/graph/extraction.ts +1297 -0
  371. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  372. package/src/memory/graph/graph-search.ts +280 -0
  373. package/src/memory/graph/image-ref-utils.ts +29 -0
  374. package/src/memory/graph/injection.test.ts +513 -0
  375. package/src/memory/graph/injection.ts +469 -0
  376. package/src/memory/graph/inspect.ts +543 -0
  377. package/src/memory/graph/narrative.ts +267 -0
  378. package/src/memory/graph/pattern-scan.ts +269 -0
  379. package/src/memory/graph/retriever.ts +1111 -0
  380. package/src/memory/graph/scoring.test.ts +548 -0
  381. package/src/memory/graph/scoring.ts +232 -0
  382. package/src/memory/graph/serendipity.ts +65 -0
  383. package/src/memory/graph/store.test.ts +1098 -0
  384. package/src/memory/graph/store.ts +838 -0
  385. package/src/memory/graph/tool-handlers.ts +301 -0
  386. package/src/memory/graph/tools.ts +97 -0
  387. package/src/memory/graph/triggers.test.ts +487 -0
  388. package/src/memory/graph/triggers.ts +223 -0
  389. package/src/memory/graph/types.ts +295 -0
  390. package/src/memory/group-crud.ts +191 -0
  391. package/src/memory/indexer.ts +37 -19
  392. package/src/memory/job-handlers/cleanup.ts +32 -42
  393. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  394. package/src/memory/job-handlers/embedding.ts +5 -31
  395. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  396. package/src/memory/job-handlers/summarization.ts +32 -17
  397. package/src/memory/job-utils.ts +1 -1
  398. package/src/memory/jobs-store.ts +21 -31
  399. package/src/memory/jobs-worker.ts +180 -129
  400. package/src/memory/llm-request-log-store.ts +96 -12
  401. package/src/memory/memory-recall-log-store.ts +49 -5
  402. package/src/memory/message-content.ts +1 -0
  403. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  404. package/src/memory/migrations/203-drop-memory-items-tables.ts +55 -0
  405. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  406. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  407. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  408. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  409. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  410. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  411. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  412. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  413. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  414. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  415. package/src/memory/migrations/index.ts +12 -0
  416. package/src/memory/migrations/registry.ts +16 -0
  417. package/src/memory/qdrant-client.ts +44 -17
  418. package/src/memory/schema/conversations.ts +14 -0
  419. package/src/memory/schema/index.ts +1 -0
  420. package/src/memory/schema/infrastructure.ts +8 -1
  421. package/src/memory/schema/memory-core.ts +0 -51
  422. package/src/memory/schema/memory-graph.ts +154 -0
  423. package/src/memory/search/semantic.ts +47 -91
  424. package/src/memory/task-memory-cleanup.ts +58 -61
  425. package/src/messaging/providers/outlook/adapter.ts +8 -1
  426. package/src/messaging/providers/outlook/client.ts +299 -0
  427. package/src/messaging/providers/outlook/types.ts +118 -0
  428. package/src/notifications/adapters/macos.ts +1 -0
  429. package/src/notifications/copy-composer.ts +95 -0
  430. package/src/notifications/decision-engine.ts +35 -0
  431. package/src/notifications/signal.ts +16 -0
  432. package/src/oauth/seed-providers.ts +2 -1
  433. package/src/permissions/checker.ts +36 -4
  434. package/src/permissions/defaults.ts +4 -4
  435. package/src/permissions/permission-mode-store.ts +180 -0
  436. package/src/permissions/permission-mode.ts +31 -0
  437. package/src/permissions/workspace-policy.ts +10 -1
  438. package/src/playbooks/playbook-compiler.ts +19 -18
  439. package/src/playbooks/types.ts +4 -3
  440. package/src/prompts/system-prompt.ts +62 -36
  441. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  442. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  443. package/src/prompts/templates/HEARTBEAT.md +3 -1
  444. package/src/prompts/templates/SOUL.md +25 -4
  445. package/src/prompts/templates/UPDATES.md +8 -0
  446. package/src/providers/anthropic/client.ts +136 -220
  447. package/src/providers/gemini/client.ts +1 -1
  448. package/src/providers/openai/client.ts +1 -1
  449. package/src/providers/registry.ts +1 -1
  450. package/src/providers/retry.ts +19 -3
  451. package/src/runtime/actor-trust-resolver.ts +5 -1
  452. package/src/runtime/auth/route-policy.ts +30 -0
  453. package/src/runtime/guardian-reply-router.ts +5 -1
  454. package/src/runtime/http-server.ts +55 -5
  455. package/src/runtime/http-types.ts +12 -1
  456. package/src/runtime/middleware/auth.ts +20 -0
  457. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  458. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  459. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  460. package/src/runtime/routes/app-management-routes.ts +1 -11
  461. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  462. package/src/runtime/routes/archive-utils.ts +29 -0
  463. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  464. package/src/runtime/routes/attachment-routes.ts +106 -16
  465. package/src/runtime/routes/avatar-routes.ts +2 -9
  466. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  467. package/src/runtime/routes/btw-routes.ts +22 -1
  468. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  469. package/src/runtime/routes/conversation-management-routes.ts +3 -14
  470. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  471. package/src/runtime/routes/conversation-routes.ts +264 -44
  472. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  473. package/src/runtime/routes/debug-routes.ts +1 -1
  474. package/src/runtime/routes/global-search-routes.ts +21 -19
  475. package/src/runtime/routes/group-routes.ts +207 -0
  476. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  477. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  478. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  479. package/src/runtime/routes/identity-routes.ts +53 -18
  480. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  481. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  482. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  483. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  484. package/src/runtime/routes/log-export-routes.ts +23 -275
  485. package/src/runtime/routes/memory-item-routes.test.ts +170 -247
  486. package/src/runtime/routes/memory-item-routes.ts +341 -388
  487. package/src/runtime/routes/migration-routes.ts +18 -7
  488. package/src/runtime/routes/profiler-routes.ts +350 -0
  489. package/src/runtime/routes/schedule-routes.ts +28 -11
  490. package/src/runtime/routes/settings-routes.ts +95 -8
  491. package/src/runtime/routes/skills-routes.ts +103 -37
  492. package/src/runtime/routes/subagents-routes.ts +28 -7
  493. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  494. package/src/runtime/routes/user-routes.ts +41 -0
  495. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  496. package/src/runtime/routes/workspace-routes.ts +0 -1
  497. package/src/schedule/schedule-store.ts +30 -0
  498. package/src/schedule/scheduler.ts +52 -18
  499. package/src/security/oauth2.ts +1 -1
  500. package/src/security/secure-keys.ts +4 -8
  501. package/src/shared/provider-env-vars.ts +19 -0
  502. package/src/skills/catalog-cache.ts +5 -0
  503. package/src/skills/catalog-install.ts +25 -16
  504. package/src/skills/clawhub.ts +134 -154
  505. package/src/skills/install-meta.ts +208 -0
  506. package/src/skills/managed-store.ts +29 -18
  507. package/src/skills/skill-memory.ts +12 -229
  508. package/src/skills/skillssh-registry.ts +19 -17
  509. package/src/subagent/index.ts +13 -3
  510. package/src/subagent/manager.ts +308 -29
  511. package/src/subagent/types.ts +68 -0
  512. package/src/tasks/task-runner.ts +7 -5
  513. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  514. package/src/tools/apps/executors.ts +29 -4
  515. package/src/tools/browser/runtime-check.ts +3 -1
  516. package/src/tools/filesystem/list.ts +93 -0
  517. package/src/tools/memory/register.ts +63 -46
  518. package/src/tools/permission-checker.ts +85 -1
  519. package/src/tools/registry.ts +4 -0
  520. package/src/tools/schedule/create.ts +3 -0
  521. package/src/tools/schedule/list.ts +1 -0
  522. package/src/tools/schedule/update.ts +6 -0
  523. package/src/tools/shared/filesystem/errors.ts +5 -0
  524. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  525. package/src/tools/shared/filesystem/image-read.ts +22 -85
  526. package/src/tools/shared/filesystem/types.ts +17 -0
  527. package/src/tools/shared/shell-output.ts +31 -2
  528. package/src/tools/subagent/abort.ts +12 -2
  529. package/src/tools/subagent/message.ts +9 -2
  530. package/src/tools/subagent/notify-parent.ts +79 -0
  531. package/src/tools/subagent/read.ts +29 -8
  532. package/src/tools/subagent/resolve.ts +21 -0
  533. package/src/tools/subagent/spawn.ts +2 -0
  534. package/src/tools/subagent/status.ts +11 -1
  535. package/src/tools/system/avatar-generator.ts +3 -3
  536. package/src/tools/system/register.ts +23 -0
  537. package/src/tools/system/set-permission-mode.ts +103 -0
  538. package/src/tools/terminal/parser.ts +30 -5
  539. package/src/tools/terminal/safe-env.ts +17 -1
  540. package/src/tools/tool-manifest.ts +9 -3
  541. package/src/tools/types.ts +2 -0
  542. package/src/util/browser.ts +25 -10
  543. package/src/util/bun-runtime.ts +172 -0
  544. package/src/util/logger.ts +1 -1
  545. package/src/util/platform.ts +50 -17
  546. package/src/watcher/providers/outlook-calendar.ts +343 -0
  547. package/src/watcher/providers/outlook.ts +198 -0
  548. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  549. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  550. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  551. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  552. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  553. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  554. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  555. package/src/workspace/migrations/registry.ts +10 -0
  556. package/src/workspace/top-level-renderer.ts +5 -9
  557. package/src/__tests__/cli-memory.test.ts +0 -372
  558. package/src/__tests__/clipboard.test.ts +0 -88
  559. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  560. package/src/__tests__/journal-context.test.ts +0 -268
  561. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  562. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  563. package/src/__tests__/memory-query-builder.test.ts +0 -59
  564. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  565. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  566. package/src/__tests__/memory-regressions.test.ts +0 -3696
  567. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  568. package/src/cli/cli-memory.ts +0 -176
  569. package/src/daemon/conversation-memory.ts +0 -207
  570. package/src/memory/conversation-starters-cadence.ts +0 -74
  571. package/src/memory/items-extractor.ts +0 -860
  572. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  573. package/src/memory/job-handlers/extraction.ts +0 -40
  574. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  575. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  576. package/src/memory/journal-memory.ts +0 -224
  577. package/src/memory/query-builder.ts +0 -47
  578. package/src/memory/query-expansion.ts +0 -83
  579. package/src/memory/retriever.test.ts +0 -1592
  580. package/src/memory/retriever.ts +0 -1331
  581. package/src/memory/search/formatting.test.ts +0 -140
  582. package/src/memory/search/formatting.ts +0 -262
  583. package/src/memory/search/mmr.ts +0 -139
  584. package/src/memory/search/ranking.ts +0 -15
  585. package/src/memory/search/staleness.ts +0 -40
  586. package/src/memory/search/tier-classifier.ts +0 -18
  587. package/src/memory/search/types.ts +0 -121
  588. package/src/prompts/journal-context.ts +0 -154
  589. package/src/tools/memory/definitions.ts +0 -69
  590. package/src/tools/memory/handlers.test.ts +0 -562
  591. package/src/tools/memory/handlers.ts +0 -434
  592. package/src/util/clipboard.ts +0 -34
@@ -75,6 +75,7 @@ function injectSubagent(
75
75
  }
76
76
  >;
77
77
  parentToChildren: Map<string, Set<string>>;
78
+ labelIndex: Map<string, string>;
78
79
  };
79
80
  const state: SubagentState = {
80
81
  config: {
@@ -108,6 +109,14 @@ function injectSubagent(
108
109
  internals.parentToChildren.set(parentConversationId, new Set());
109
110
  }
110
111
  internals.parentToChildren.get(parentConversationId)!.add(subagentId);
112
+
113
+ // Populate label index so label-based lookups work in tests.
114
+ const label = state.config.label;
115
+ internals.labelIndex.set(
116
+ `${parentConversationId}:${label.toLowerCase().trim()}`,
117
+ subagentId,
118
+ );
119
+
111
120
  return state;
112
121
  }
113
122
 
@@ -135,25 +144,29 @@ describe("Subagent tool definitions", () => {
135
144
  test("abort tool has correct definition", () => {
136
145
  const def = findTool("subagent_abort");
137
146
  expect(def).toBeDefined();
138
- expect(def.input_schema.required).toEqual(["subagent_id"]);
147
+ expect(def.input_schema.required).toEqual([]);
148
+ expect(def.input_schema.properties.label).toBeDefined();
139
149
  });
140
150
 
141
151
  test("message tool has correct definition", () => {
142
152
  const def = findTool("subagent_message");
143
153
  expect(def).toBeDefined();
144
- expect(def.input_schema.required).toEqual(["subagent_id", "content"]);
154
+ expect(def.input_schema.required).toEqual(["content"]);
155
+ expect(def.input_schema.properties.label).toBeDefined();
145
156
  });
146
157
 
147
158
  test("read tool has correct definition", () => {
148
159
  const def = findTool("subagent_read");
149
160
  expect(def).toBeDefined();
150
- expect(def.input_schema.required).toEqual(["subagent_id"]);
161
+ expect(def.input_schema.required).toEqual([]);
162
+ expect(def.input_schema.properties.label).toBeDefined();
151
163
  });
152
164
 
153
165
  test("status tool has correct definition", () => {
154
166
  const def = findTool("subagent_status");
155
167
  expect(def).toBeDefined();
156
168
  expect(def.input_schema.required).toEqual([]);
169
+ expect(def.input_schema.properties.label).toBeDefined();
157
170
  });
158
171
  });
159
172
 
@@ -247,7 +260,7 @@ describe("Subagent tool execute validation", () => {
247
260
  expect(result.content).toContain("required");
248
261
  });
249
262
 
250
- test("message returns error when missing subagent_id", async () => {
263
+ test("message returns error when missing subagent_id and label", async () => {
251
264
  const result = await executeSubagentMessage(
252
265
  { content: "hello" },
253
266
  makeContext("sess-1"),
@@ -790,6 +803,111 @@ describe("Subagent read tool", () => {
790
803
  }
791
804
  });
792
805
 
806
+ test("read with last_n: 1 returns only the last message", async () => {
807
+ const manager = getSubagentManager();
808
+ const subagentId = "read-last-n-1";
809
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
810
+
811
+ mockGetMessages = (convId: string) => {
812
+ if (convId !== `conv-${subagentId}`) return null;
813
+ return [
814
+ { role: "assistant", content: "First message" },
815
+ { role: "assistant", content: "Second message" },
816
+ { role: "assistant", content: "Third message" },
817
+ ];
818
+ };
819
+
820
+ try {
821
+ const result = await executeSubagentRead(
822
+ { subagent_id: subagentId, last_n: 1 },
823
+ makeContext(ownerConversation),
824
+ );
825
+ expect(result.isError).toBe(false);
826
+ expect(result.content).toBe("Third message");
827
+ } finally {
828
+ mockGetMessages = () => null;
829
+ }
830
+ });
831
+
832
+ test("read with last_n: 2 returns last 2 messages joined", async () => {
833
+ const manager = getSubagentManager();
834
+ const subagentId = "read-last-n-2";
835
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
836
+
837
+ mockGetMessages = (convId: string) => {
838
+ if (convId !== `conv-${subagentId}`) return null;
839
+ return [
840
+ { role: "assistant", content: "First message" },
841
+ { role: "assistant", content: "Second message" },
842
+ { role: "assistant", content: "Third message" },
843
+ ];
844
+ };
845
+
846
+ try {
847
+ const result = await executeSubagentRead(
848
+ { subagent_id: subagentId, last_n: 2 },
849
+ makeContext(ownerConversation),
850
+ );
851
+ expect(result.isError).toBe(false);
852
+ expect(result.content).toBe("Second message\n\nThird message");
853
+ } finally {
854
+ mockGetMessages = () => null;
855
+ }
856
+ });
857
+
858
+ test("read with last_n omitted returns all messages", async () => {
859
+ const manager = getSubagentManager();
860
+ const subagentId = "read-last-n-omit";
861
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
862
+
863
+ mockGetMessages = (convId: string) => {
864
+ if (convId !== `conv-${subagentId}`) return null;
865
+ return [
866
+ { role: "assistant", content: "First message" },
867
+ { role: "assistant", content: "Second message" },
868
+ { role: "assistant", content: "Third message" },
869
+ ];
870
+ };
871
+
872
+ try {
873
+ const result = await executeSubagentRead(
874
+ { subagent_id: subagentId },
875
+ makeContext(ownerConversation),
876
+ );
877
+ expect(result.isError).toBe(false);
878
+ expect(result.content).toBe(
879
+ "First message\n\nSecond message\n\nThird message",
880
+ );
881
+ } finally {
882
+ mockGetMessages = () => null;
883
+ }
884
+ });
885
+
886
+ test("read with last_n larger than available returns all messages", async () => {
887
+ const manager = getSubagentManager();
888
+ const subagentId = "read-last-n-large";
889
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
890
+
891
+ mockGetMessages = (convId: string) => {
892
+ if (convId !== `conv-${subagentId}`) return null;
893
+ return [
894
+ { role: "assistant", content: "First message" },
895
+ { role: "assistant", content: "Second message" },
896
+ ];
897
+ };
898
+
899
+ try {
900
+ const result = await executeSubagentRead(
901
+ { subagent_id: subagentId, last_n: 100 },
902
+ makeContext(ownerConversation),
903
+ );
904
+ expect(result.isError).toBe(false);
905
+ expect(result.content).toBe("First message\n\nSecond message");
906
+ } finally {
907
+ mockGetMessages = () => null;
908
+ }
909
+ });
910
+
793
911
  test("read concatenates multiple assistant messages", async () => {
794
912
  const manager = getSubagentManager();
795
913
  const subagentId = "read-multi-1";
@@ -822,6 +940,113 @@ describe("Subagent read tool", () => {
822
940
  mockGetMessages = () => null;
823
941
  }
824
942
  });
943
+
944
+ test("read with last_n: 1 returns only the last message", async () => {
945
+ const manager = getSubagentManager();
946
+ const subagentId = "read-last-n-1";
947
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
948
+
949
+ mockGetMessages = (convId: string) => {
950
+ if (convId !== `conv-${subagentId}`) return null;
951
+ return [
952
+ { role: "assistant", content: "First response" },
953
+ { role: "user", content: "Follow up" },
954
+ { role: "assistant", content: "Second response" },
955
+ { role: "assistant", content: "Third response" },
956
+ ];
957
+ };
958
+
959
+ try {
960
+ const result = await executeSubagentRead(
961
+ { subagent_id: subagentId, last_n: 1 },
962
+ makeContext(ownerConversation),
963
+ );
964
+ expect(result.isError).toBe(false);
965
+ expect(result.content).toBe("Third response");
966
+ } finally {
967
+ mockGetMessages = () => null;
968
+ }
969
+ });
970
+
971
+ test("read with last_n: 2 returns last two messages", async () => {
972
+ const manager = getSubagentManager();
973
+ const subagentId = "read-last-n-2";
974
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
975
+
976
+ mockGetMessages = (convId: string) => {
977
+ if (convId !== `conv-${subagentId}`) return null;
978
+ return [
979
+ { role: "assistant", content: "First response" },
980
+ { role: "user", content: "Follow up" },
981
+ { role: "assistant", content: "Second response" },
982
+ { role: "assistant", content: "Third response" },
983
+ ];
984
+ };
985
+
986
+ try {
987
+ const result = await executeSubagentRead(
988
+ { subagent_id: subagentId, last_n: 2 },
989
+ makeContext(ownerConversation),
990
+ );
991
+ expect(result.isError).toBe(false);
992
+ expect(result.content).toBe("Second response\n\nThird response");
993
+ } finally {
994
+ mockGetMessages = () => null;
995
+ }
996
+ });
997
+
998
+ test("read without last_n returns all messages", async () => {
999
+ const manager = getSubagentManager();
1000
+ const subagentId = "read-no-last-n-1";
1001
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
1002
+
1003
+ mockGetMessages = (convId: string) => {
1004
+ if (convId !== `conv-${subagentId}`) return null;
1005
+ return [
1006
+ { role: "assistant", content: "First response" },
1007
+ { role: "assistant", content: "Second response" },
1008
+ { role: "assistant", content: "Third response" },
1009
+ ];
1010
+ };
1011
+
1012
+ try {
1013
+ const result = await executeSubagentRead(
1014
+ { subagent_id: subagentId },
1015
+ makeContext(ownerConversation),
1016
+ );
1017
+ expect(result.isError).toBe(false);
1018
+ expect(result.content).toBe(
1019
+ "First response\n\nSecond response\n\nThird response",
1020
+ );
1021
+ } finally {
1022
+ mockGetMessages = () => null;
1023
+ }
1024
+ });
1025
+
1026
+ test("read with last_n larger than available messages returns all", async () => {
1027
+ const manager = getSubagentManager();
1028
+ const subagentId = "read-last-n-big-1";
1029
+ injectSubagent(manager, subagentId, ownerConversation, "completed");
1030
+
1031
+ mockGetMessages = (convId: string) => {
1032
+ if (convId !== `conv-${subagentId}`) return null;
1033
+ return [
1034
+ { role: "assistant", content: "First response" },
1035
+ { role: "assistant", content: "Second response" },
1036
+ ];
1037
+ };
1038
+
1039
+ try {
1040
+ const result = await executeSubagentRead(
1041
+ { subagent_id: subagentId, last_n: 100 },
1042
+ makeContext(ownerConversation),
1043
+ );
1044
+ expect(result.isError).toBe(false);
1045
+ expect(result.content).toBe("First response\n\nSecond response");
1046
+ } finally {
1047
+ mockGetMessages = () => null;
1048
+ }
1049
+ });
825
1050
  });
826
1051
 
827
1052
  // ── Abort success path details ──────────────────────────────────────
@@ -869,3 +1094,238 @@ describe("Subagent abort success responses", () => {
869
1094
  expect(result.content).toContain("Could not abort");
870
1095
  });
871
1096
  });
1097
+
1098
+ // ── Label-based subagent lookup ────────────────────────────────────
1099
+
1100
+ describe("Label-based subagent lookup", () => {
1101
+ const parentConversation = "label-test-sess";
1102
+ const subagentId = "label-sub-1";
1103
+
1104
+ // Inject a subagent with a specific label for the test suite.
1105
+ const manager = getSubagentManager();
1106
+ injectSubagent(manager, subagentId, parentConversation, "running", {
1107
+ config: {
1108
+ id: subagentId,
1109
+ parentConversationId: parentConversation,
1110
+ label: "Research task",
1111
+ objective: "research something",
1112
+ },
1113
+ });
1114
+
1115
+ test("subagent_status with label returns status", async () => {
1116
+ const result = await executeSubagentStatus(
1117
+ { label: "Research task" },
1118
+ makeContext(parentConversation),
1119
+ );
1120
+ expect(result.isError).toBe(false);
1121
+ const parsed = JSON.parse(result.content);
1122
+ expect(parsed.subagentId).toBe(subagentId);
1123
+ expect(parsed.label).toBe("Research task");
1124
+ expect(parsed.status).toBe("running");
1125
+ });
1126
+
1127
+ test("subagent_status with lowercase label (case-insensitive)", async () => {
1128
+ const result = await executeSubagentStatus(
1129
+ { label: "research task" },
1130
+ makeContext(parentConversation),
1131
+ );
1132
+ expect(result.isError).toBe(false);
1133
+ const parsed = JSON.parse(result.content);
1134
+ expect(parsed.subagentId).toBe(subagentId);
1135
+ });
1136
+
1137
+ test("subagent_status with nonexistent label returns error", async () => {
1138
+ const result = await executeSubagentStatus(
1139
+ { label: "nonexistent" },
1140
+ makeContext(parentConversation),
1141
+ );
1142
+ expect(result.isError).toBe(true);
1143
+ expect(result.content).toContain("No subagent found");
1144
+ });
1145
+
1146
+ test("subagent_message with label succeeds", async () => {
1147
+ const result = await executeSubagentMessage(
1148
+ { label: "Research task", content: "hello" },
1149
+ makeContext(parentConversation),
1150
+ );
1151
+ expect(result.isError).toBe(false);
1152
+ const parsed = JSON.parse(result.content);
1153
+ expect(parsed.subagentId).toBe(subagentId);
1154
+ expect(parsed.message).toContain("Message sent");
1155
+ });
1156
+
1157
+ test("subagent_read with label on completed subagent returns output", async () => {
1158
+ // Inject a completed subagent for the read test.
1159
+ const readSubId = "label-read-sub-1";
1160
+ injectSubagent(manager, readSubId, parentConversation, "completed", {
1161
+ config: {
1162
+ id: readSubId,
1163
+ parentConversationId: parentConversation,
1164
+ label: "Read task",
1165
+ objective: "read something",
1166
+ },
1167
+ });
1168
+
1169
+ mockGetMessages = (convId: string) => {
1170
+ if (convId !== `conv-${readSubId}`) return null;
1171
+ return [
1172
+ {
1173
+ role: "assistant",
1174
+ content: JSON.stringify([
1175
+ { type: "text", text: "Research findings here" },
1176
+ ]),
1177
+ },
1178
+ ];
1179
+ };
1180
+
1181
+ try {
1182
+ const result = await executeSubagentRead(
1183
+ { label: "Read task" },
1184
+ makeContext(parentConversation),
1185
+ );
1186
+ expect(result.isError).toBe(false);
1187
+ expect(result.content).toContain("Research findings here");
1188
+ } finally {
1189
+ mockGetMessages = () => null;
1190
+ }
1191
+ });
1192
+ });
1193
+
1194
+ // ── Label collision & dispose guard ─────────────────────────────────
1195
+
1196
+ describe("Label collision and dispose guard", () => {
1197
+ test("disposing second subagent with same label keeps first reachable by label", () => {
1198
+ const manager = getSubagentManager();
1199
+ const parentConversation = "label-collision-sess";
1200
+ const firstId = "collision-sub-1";
1201
+ const secondId = "collision-sub-2";
1202
+ const sharedLabel = "Shared Worker";
1203
+
1204
+ // Inject two subagents with the same label — second overwrites label index.
1205
+ injectSubagent(manager, firstId, parentConversation, "running", {
1206
+ config: {
1207
+ id: firstId,
1208
+ parentConversationId: parentConversation,
1209
+ label: sharedLabel,
1210
+ objective: "first task",
1211
+ },
1212
+ });
1213
+ injectSubagent(manager, secondId, parentConversation, "completed", {
1214
+ config: {
1215
+ id: secondId,
1216
+ parentConversationId: parentConversation,
1217
+ label: sharedLabel,
1218
+ objective: "second task",
1219
+ },
1220
+ });
1221
+
1222
+ // Label should currently resolve to the second subagent.
1223
+ expect(manager.getByLabel(sharedLabel, parentConversation)?.config.id).toBe(
1224
+ secondId,
1225
+ );
1226
+
1227
+ // Dispose the FIRST subagent — its label was already overwritten,
1228
+ // so the label index entry (pointing to second) must survive.
1229
+ manager.dispose(firstId);
1230
+
1231
+ const afterDispose = manager.getByLabel(sharedLabel, parentConversation);
1232
+ expect(afterDispose).toBeDefined();
1233
+ expect(afterDispose!.config.id).toBe(secondId);
1234
+
1235
+ // The second subagent should still be directly accessible too.
1236
+ expect(manager.getState(secondId)).toBeDefined();
1237
+ // And the first should be gone.
1238
+ expect(manager.getState(firstId)).toBeUndefined();
1239
+ });
1240
+ });
1241
+
1242
+ // ── Role-based spawn ──────────────────────────────────────────────
1243
+
1244
+ describe("Subagent role-based spawn", () => {
1245
+ test("spawn with role 'researcher' passes role to manager", async () => {
1246
+ const manager = getSubagentManager();
1247
+ const originalSpawn = manager.spawn.bind(manager);
1248
+ let capturedConfig: Record<string, unknown> | undefined;
1249
+
1250
+ manager.spawn = async (config: Record<string, unknown>) => {
1251
+ capturedConfig = config;
1252
+ return "role-researcher-id";
1253
+ };
1254
+
1255
+ try {
1256
+ const result = await executeSubagentSpawn(
1257
+ {
1258
+ label: "Research task",
1259
+ objective: "Find pricing data",
1260
+ role: "researcher",
1261
+ },
1262
+ makeContext("sess-role-1", { sendToClient: () => {} }),
1263
+ );
1264
+ expect(result.isError).toBe(false);
1265
+ const parsed = JSON.parse(result.content);
1266
+ expect(parsed.subagentId).toBe("role-researcher-id");
1267
+ expect(capturedConfig).toBeDefined();
1268
+ expect(capturedConfig!.role).toBe("researcher");
1269
+ } finally {
1270
+ manager.spawn = originalSpawn;
1271
+ }
1272
+ });
1273
+
1274
+ test("spawn without role defaults to general (backwards compat)", async () => {
1275
+ const manager = getSubagentManager();
1276
+ const originalSpawn = manager.spawn.bind(manager);
1277
+ let capturedConfig: Record<string, unknown> | undefined;
1278
+
1279
+ manager.spawn = async (config: Record<string, unknown>) => {
1280
+ capturedConfig = config;
1281
+ return "role-default-id";
1282
+ };
1283
+
1284
+ try {
1285
+ const result = await executeSubagentSpawn(
1286
+ { label: "General task", objective: "Do something" },
1287
+ makeContext("sess-role-2", { sendToClient: () => {} }),
1288
+ );
1289
+ expect(result.isError).toBe(false);
1290
+ const parsed = JSON.parse(result.content);
1291
+ expect(parsed.subagentId).toBe("role-default-id");
1292
+ expect(capturedConfig).toBeDefined();
1293
+ // When role is not specified, it should not be present in config
1294
+ expect(capturedConfig!.role).toBeUndefined();
1295
+ } finally {
1296
+ manager.spawn = originalSpawn;
1297
+ }
1298
+ });
1299
+
1300
+ test("spawn with invalid role returns clear error message", async () => {
1301
+ const result = await executeSubagentSpawn(
1302
+ {
1303
+ label: "Bad role task",
1304
+ objective: "Should fail",
1305
+ role: "nonexistent-role",
1306
+ },
1307
+ makeContext("sess-role-invalid", { sendToClient: () => {} }),
1308
+ );
1309
+ expect(result.isError).toBe(true);
1310
+ expect(result.content).toContain("Invalid subagent role");
1311
+ expect(result.content).toContain("nonexistent-role");
1312
+ expect(result.content).toContain("Must be one of");
1313
+ expect(result.content).toContain("general");
1314
+ expect(result.content).toContain("researcher");
1315
+ });
1316
+
1317
+ test("spawn tool definition includes role property", () => {
1318
+ const def = findTool("subagent_spawn");
1319
+ expect(def).toBeDefined();
1320
+ expect(def.input_schema.properties.role).toBeDefined();
1321
+ expect(def.input_schema.properties.role.type).toBe("string");
1322
+ expect(def.input_schema.properties.role.enum).toEqual([
1323
+ "general",
1324
+ "researcher",
1325
+ "coder",
1326
+ "planner",
1327
+ ]);
1328
+ // role is not required
1329
+ expect(def.input_schema.required).not.toContain("role");
1330
+ });
1331
+ });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Tests for the "Action Confirmation Mode" system prompt injection.
3
+ *
4
+ * Verifies:
5
+ * - Prompt includes the section when `permission-controls-v2` flag is enabled
6
+ * AND `askBeforeActing` is `true`.
7
+ * - Prompt excludes the section when the flag is disabled.
8
+ * - Prompt excludes the section when `askBeforeActing` is `false`.
9
+ */
10
+ import { mkdirSync } from "node:fs";
11
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ // Mock platform to use a temp directory
14
+ const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
15
+
16
+ const noopLogger: Record<string, unknown> = new Proxy(
17
+ {} as Record<string, unknown>,
18
+ {
19
+ get: (_target, prop) => (prop === "child" ? () => noopLogger : () => {}),
20
+ },
21
+ );
22
+
23
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
24
+ const realLogger = require("../util/logger.js");
25
+ mock.module("../util/logger.js", () => ({
26
+ ...realLogger,
27
+ getLogger: () => noopLogger,
28
+ getCliLogger: () => noopLogger,
29
+ truncateForLog: (v: string) => v,
30
+ initLogger: () => {},
31
+ pruneOldLogFiles: () => 0,
32
+ }));
33
+
34
+ mock.module("../config/loader.js", () => ({
35
+ getConfig: () => ({
36
+ ui: {},
37
+ services: {
38
+ inference: {
39
+ mode: "your-own",
40
+ provider: "anthropic",
41
+ model: "claude-opus-4-6",
42
+ },
43
+ "image-generation": {
44
+ mode: "your-own",
45
+ provider: "gemini",
46
+ model: "gemini-3.1-flash-image-preview",
47
+ },
48
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
49
+ },
50
+ }),
51
+ loadConfig: () => ({}),
52
+ loadRawConfig: () => ({}),
53
+ saveConfig: () => {},
54
+ saveRawConfig: () => {},
55
+ invalidateConfigCache: () => {},
56
+ getNestedValue: () => undefined,
57
+ setNestedValue: () => {},
58
+ }));
59
+
60
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
61
+ const realUserReference = require("../prompts/user-reference.js");
62
+ mock.module("../prompts/user-reference.js", () => ({
63
+ ...realUserReference,
64
+ resolveUserReference: () => "John",
65
+ resolveUserPronouns: () => null,
66
+ }));
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Controllable mocks for feature flags and permission mode
70
+ // ---------------------------------------------------------------------------
71
+
72
+ let flagEnabled = false;
73
+ let askBeforeActing = true;
74
+
75
+ mock.module("../config/assistant-feature-flags.js", () => ({
76
+ isAssistantFeatureFlagEnabled: (key: string) => {
77
+ if (key === "permission-controls-v2") return flagEnabled;
78
+ return true;
79
+ },
80
+ _setOverridesForTesting: () => {},
81
+ clearFeatureFlagOverridesCache: () => {},
82
+ getAssistantFeatureFlagDefaults: () => ({}),
83
+ }));
84
+
85
+ mock.module("../permissions/permission-mode-store.js", () => ({
86
+ getMode: () => ({ askBeforeActing, hostAccess: false }),
87
+ initPermissionModeStore: () => {},
88
+ setAskBeforeActing: () => {},
89
+ setHostAccess: () => {},
90
+ onModeChanged: () => () => {},
91
+ resetForTesting: () => {},
92
+ }));
93
+
94
+ // Import after mocks
95
+ const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
96
+
97
+ const ACTION_CONFIRMATION_HEADING = "## Action Confirmation Mode";
98
+
99
+ describe("Action Confirmation Mode system prompt injection", () => {
100
+ beforeEach(() => {
101
+ mkdirSync(TEST_DIR, { recursive: true });
102
+ flagEnabled = false;
103
+ askBeforeActing = true;
104
+ });
105
+
106
+ afterEach(() => {
107
+ flagEnabled = false;
108
+ askBeforeActing = true;
109
+ });
110
+
111
+ test("includes section when flag enabled and askBeforeActing is true", () => {
112
+ flagEnabled = true;
113
+ askBeforeActing = true;
114
+ const result = buildSystemPrompt();
115
+ expect(result).toContain(ACTION_CONFIRMATION_HEADING);
116
+ expect(result).toContain('"Ask before acting" mode');
117
+ });
118
+
119
+ test("excludes section when flag is disabled", () => {
120
+ flagEnabled = false;
121
+ askBeforeActing = true;
122
+ const result = buildSystemPrompt();
123
+ expect(result).not.toContain(ACTION_CONFIRMATION_HEADING);
124
+ });
125
+
126
+ test("excludes section when askBeforeActing is false", () => {
127
+ flagEnabled = true;
128
+ askBeforeActing = false;
129
+ const result = buildSystemPrompt();
130
+ expect(result).not.toContain(ACTION_CONFIRMATION_HEADING);
131
+ });
132
+
133
+ test("excludes section when both flag disabled and askBeforeActing false", () => {
134
+ flagEnabled = false;
135
+ askBeforeActing = false;
136
+ const result = buildSystemPrompt();
137
+ expect(result).not.toContain(ACTION_CONFIRMATION_HEADING);
138
+ });
139
+ });
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeEach, describe, expect, test } from "bun:test";
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
2
  import { mock } from "bun:test";
3
3
 
4
4
  mock.module("../util/logger.js", () => ({
@@ -21,7 +21,7 @@ mock.module("./indexer.js", () => ({
21
21
 
22
22
  import type { Database } from "bun:sqlite";
23
23
 
24
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
24
+ import { getDb, initializeDb } from "../memory/db.js";
25
25
  import {
26
26
  compileTaskFromConversation,
27
27
  saveCompiledTask,
@@ -31,10 +31,6 @@ import { getTask } from "../tasks/task-store.js";
31
31
 
32
32
  initializeDb();
33
33
 
34
- afterAll(() => {
35
- resetDb();
36
- });
37
-
38
34
  // ── Helpers ──────────────────────────────────────────────────────────
39
35
 
40
36
  function getRawDb(): Database {