@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
@@ -14,7 +14,7 @@ const CANONICAL_TABS = [
14
14
  "Sounds",
15
15
  "Permissions & Privacy",
16
16
  "Billing",
17
- "Archived Conversations",
17
+ "Archive",
18
18
  "Schedules",
19
19
  "Developer",
20
20
  ];
@@ -85,4 +85,17 @@ describe("navigate-settings-tab", () => {
85
85
  expect(result.isError).toBe(false);
86
86
  expect(result.content).toContain("Developer");
87
87
  });
88
+
89
+ test("normalizes legacy 'Archived Conversations' alias to 'Archive'", async () => {
90
+ const messages: unknown[] = [];
91
+ const result = await run(
92
+ { tab: "Archived Conversations" },
93
+ makeContext((msg) => messages.push(msg)),
94
+ );
95
+
96
+ expect(result.isError).toBe(false);
97
+ expect(result.content).toContain("Archive");
98
+ expect(messages).toHaveLength(1);
99
+ expect(messages[0]).toEqual({ type: "navigate_settings", tab: "Archive" });
100
+ });
88
101
  });
@@ -583,4 +583,69 @@ describe("notification broadcaster", () => {
583
583
  expect(vellumCall).toBeDefined();
584
584
  expect(vellumCall!.options?.bindingContext).toBeUndefined();
585
585
  });
586
+
587
+ // ── conversationMetadata propagation ──────────────────────────────
588
+
589
+ test("onConversationCreated includes groupId and source from conversationMetadata", async () => {
590
+ const vellumAdapter = new MockAdapter("vellum");
591
+ const broadcaster = new NotificationBroadcaster([vellumAdapter]);
592
+ const createdCalls: ConversationCreatedInfo[] = [];
593
+ broadcaster.setOnConversationCreated((info) => createdCalls.push(info));
594
+
595
+ const signal = makeSignal({
596
+ sourceEventName: "schedule.complete",
597
+ conversationMetadata: {
598
+ groupId: "system:scheduled",
599
+ source: "schedule",
600
+ scheduleJobId: "job-abc-123",
601
+ },
602
+ });
603
+ const decision = makeDecision();
604
+
605
+ await broadcaster.broadcastDecision(signal, decision);
606
+
607
+ expect(createdCalls).toHaveLength(1);
608
+ expect(createdCalls[0].groupId).toBe("system:scheduled");
609
+ expect(createdCalls[0].source).toBe("schedule");
610
+ expect(createdCalls[0].sourceEventName).toBe("schedule.complete");
611
+ });
612
+
613
+ test("onConversationCreated omits groupId and source when conversationMetadata is absent", async () => {
614
+ const vellumAdapter = new MockAdapter("vellum");
615
+ const broadcaster = new NotificationBroadcaster([vellumAdapter]);
616
+ const createdCalls: ConversationCreatedInfo[] = [];
617
+ broadcaster.setOnConversationCreated((info) => createdCalls.push(info));
618
+
619
+ const signal = makeSignal(); // no conversationMetadata
620
+ const decision = makeDecision();
621
+
622
+ await broadcaster.broadcastDecision(signal, decision);
623
+
624
+ expect(createdCalls).toHaveLength(1);
625
+ expect(createdCalls[0].groupId).toBeUndefined();
626
+ expect(createdCalls[0].source).toBeUndefined();
627
+ });
628
+
629
+ test("per-dispatch callback receives conversationMetadata fields", async () => {
630
+ const vellumAdapter = new MockAdapter("vellum");
631
+ const broadcaster = new NotificationBroadcaster([vellumAdapter]);
632
+ const dispatchCalls: ConversationCreatedInfo[] = [];
633
+
634
+ const signal = makeSignal({
635
+ sourceEventName: "schedule.complete",
636
+ conversationMetadata: {
637
+ groupId: "system:scheduled",
638
+ source: "schedule",
639
+ },
640
+ });
641
+ const decision = makeDecision();
642
+
643
+ await broadcaster.broadcastDecision(signal, decision, {
644
+ onConversationCreated: (info) => dispatchCalls.push(info),
645
+ });
646
+
647
+ expect(dispatchCalls).toHaveLength(1);
648
+ expect(dispatchCalls[0].groupId).toBe("system:scheduled");
649
+ expect(dispatchCalls[0].source).toBe("schedule");
650
+ });
586
651
  });
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Tests for recipient context (guardian contact notes) injection in the
3
+ * notification decision engine.
4
+ *
5
+ * Validates that guardian contact notes appear in the LLM system prompt as
6
+ * a <recipient-context> block when available, are omitted when absent or
7
+ * empty, and are truncated when large.
8
+ */
9
+
10
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // ── Mocks (must precede imports from mocked modules) ──────────────────
13
+
14
+ mock.module("../channels/config.js", () => ({
15
+ getDeliverableChannels: () => ["vellum"],
16
+ }));
17
+
18
+ mock.module("../config/loader.js", () => ({
19
+ getConfig: () => ({
20
+ ui: {},
21
+ notifications: {
22
+ decisionModelIntent: "latency-optimized",
23
+ },
24
+ }),
25
+ }));
26
+
27
+ mock.module("../notifications/decisions-store.js", () => ({
28
+ createDecision: () => {},
29
+ }));
30
+
31
+ mock.module("../notifications/preference-summary.js", () => ({
32
+ getPreferenceSummary: () => undefined,
33
+ }));
34
+
35
+ mock.module("../notifications/conversation-candidates.js", () => ({
36
+ buildConversationCandidates: () => undefined,
37
+ serializeCandidatesForPrompt: () => undefined,
38
+ }));
39
+
40
+ mock.module("../prompts/persona-resolver.js", () => ({
41
+ resolveGuardianPersona: () => null,
42
+ }));
43
+
44
+ mock.module("../prompts/system-prompt.js", () => ({
45
+ buildCoreIdentityContext: () => null,
46
+ }));
47
+
48
+ // ── Guardian contact mock ────────────────────────────────────────────
49
+
50
+ let mockGuardianResult: {
51
+ contact: { notes: string | null };
52
+ channels: Record<string, unknown>[];
53
+ } | null = null;
54
+
55
+ mock.module("../contacts/contact-store.js", () => ({
56
+ listGuardianChannels: () => mockGuardianResult,
57
+ }));
58
+
59
+ // ── Provider mock with system prompt capture ──────────────────────────
60
+
61
+ let configuredProvider: {
62
+ sendMessage: (...args: unknown[]) => Promise<unknown>;
63
+ } | null = null;
64
+ let extractedToolUse: unknown = null;
65
+ let capturedSystemPrompt: string | undefined;
66
+
67
+ mock.module("../providers/provider-send-message.js", () => ({
68
+ getConfiguredProvider: async () => configuredProvider,
69
+ createTimeout: () => ({
70
+ signal: new AbortController().signal,
71
+ cleanup: () => {},
72
+ }),
73
+ extractToolUse: () => extractedToolUse,
74
+ userMessage: (text: string) => ({ role: "user", content: text }),
75
+ }));
76
+
77
+ mock.module("../util/logger.js", () => ({
78
+ getLogger: () =>
79
+ new Proxy({} as Record<string, unknown>, {
80
+ get: () => () => {},
81
+ }),
82
+ }));
83
+
84
+ // ── Imports (after all mocks) ─────────────────────────────────────────
85
+
86
+ import { evaluateSignal } from "../notifications/decision-engine.js";
87
+ import type { NotificationSignal } from "../notifications/signal.js";
88
+ import type { NotificationChannel } from "../notifications/types.js";
89
+
90
+ // ── Helpers ───────────────────────────────────────────────────────────
91
+
92
+ function makeSignal(
93
+ overrides?: Partial<NotificationSignal>,
94
+ ): NotificationSignal {
95
+ return {
96
+ signalId: "sig-recipient-ctx-test-1",
97
+ createdAt: Date.now(),
98
+ sourceChannel: "phone",
99
+ sourceContextId: "call-session-1",
100
+ sourceEventName: "guardian.question",
101
+ contextPayload: {
102
+ questionText: "What is the gate code?",
103
+ },
104
+ attentionHints: {
105
+ requiresAction: true,
106
+ urgency: "high",
107
+ isAsyncBackground: false,
108
+ visibleInSourceNow: false,
109
+ },
110
+ ...overrides,
111
+ };
112
+ }
113
+
114
+ function setupLLMProvider() {
115
+ configuredProvider = {
116
+ sendMessage: async (
117
+ _messages: unknown,
118
+ _tools: unknown,
119
+ systemPrompt: unknown,
120
+ ) => {
121
+ capturedSystemPrompt = systemPrompt as string;
122
+ return { content: [] };
123
+ },
124
+ };
125
+ extractedToolUse = {
126
+ name: "record_notification_decision",
127
+ input: {
128
+ shouldNotify: true,
129
+ selectedChannels: ["vellum"],
130
+ reasoningSummary: "LLM decision with recipient context",
131
+ renderedCopy: {
132
+ vellum: {
133
+ title: "Guardian Question",
134
+ body: "What is the gate code?",
135
+ },
136
+ },
137
+ dedupeKey: "recipient-ctx-test",
138
+ confidence: 0.9,
139
+ },
140
+ };
141
+ }
142
+
143
+ // ── Tests ─────────────────────────────────────────────────────────────
144
+
145
+ describe("recipient context in notification decision engine", () => {
146
+ beforeEach(() => {
147
+ configuredProvider = null;
148
+ extractedToolUse = null;
149
+ mockGuardianResult = null;
150
+ capturedSystemPrompt = undefined;
151
+ });
152
+
153
+ test("guardian contact notes appear in system prompt as <recipient-context>", async () => {
154
+ mockGuardianResult = {
155
+ contact: { notes: "Prefers formal tone. Address as Dr. Smith." },
156
+ channels: [{ type: "vellum" }],
157
+ };
158
+ setupLLMProvider();
159
+
160
+ const signal = makeSignal();
161
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
162
+
163
+ expect(capturedSystemPrompt).toBeDefined();
164
+ expect(capturedSystemPrompt).toContain("<recipient-context>");
165
+ expect(capturedSystemPrompt).toContain(
166
+ "Prefers formal tone. Address as Dr. Smith.",
167
+ );
168
+ expect(capturedSystemPrompt).toContain("</recipient-context>");
169
+ });
170
+
171
+ test("recipient-context is omitted when no guardian exists", async () => {
172
+ mockGuardianResult = null;
173
+ setupLLMProvider();
174
+
175
+ const signal = makeSignal();
176
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
177
+
178
+ expect(capturedSystemPrompt).toBeDefined();
179
+ expect(capturedSystemPrompt).not.toContain("<recipient-context>");
180
+ expect(capturedSystemPrompt).not.toContain("</recipient-context>");
181
+ });
182
+
183
+ test("recipient-context is omitted when guardian notes are null", async () => {
184
+ mockGuardianResult = {
185
+ contact: { notes: null },
186
+ channels: [{ type: "vellum" }],
187
+ };
188
+ setupLLMProvider();
189
+
190
+ const signal = makeSignal();
191
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
192
+
193
+ expect(capturedSystemPrompt).toBeDefined();
194
+ expect(capturedSystemPrompt).not.toContain("<recipient-context>");
195
+ expect(capturedSystemPrompt).not.toContain("</recipient-context>");
196
+ });
197
+
198
+ test("recipient-context is omitted when guardian notes are empty string", async () => {
199
+ mockGuardianResult = {
200
+ contact: { notes: "" },
201
+ channels: [{ type: "vellum" }],
202
+ };
203
+ setupLLMProvider();
204
+
205
+ const signal = makeSignal();
206
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
207
+
208
+ expect(capturedSystemPrompt).toBeDefined();
209
+ expect(capturedSystemPrompt).not.toContain("<recipient-context>");
210
+ expect(capturedSystemPrompt).not.toContain("</recipient-context>");
211
+ });
212
+
213
+ test("large guardian notes are truncated to prevent oversized prompts", async () => {
214
+ mockGuardianResult = {
215
+ contact: { notes: "N".repeat(3000) },
216
+ channels: [{ type: "vellum" }],
217
+ };
218
+ setupLLMProvider();
219
+
220
+ const signal = makeSignal();
221
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
222
+
223
+ expect(capturedSystemPrompt).toBeDefined();
224
+ expect(capturedSystemPrompt).toContain("<recipient-context>");
225
+ // Full 3000-char string should NOT appear
226
+ expect(capturedSystemPrompt).not.toContain("N".repeat(3000));
227
+ // Truncation marker should be present
228
+ expect(capturedSystemPrompt).toContain("\u2026[truncated]");
229
+
230
+ const match = capturedSystemPrompt!.match(
231
+ /<recipient-context>([\s\S]*?)<\/recipient-context>/,
232
+ );
233
+ expect(match).toBeTruthy();
234
+ const block = match![1];
235
+ expect(block).toContain("\u2026[truncated]");
236
+ // The notes portion within the block should not exceed 2000 chars
237
+ expect(block).not.toContain("N".repeat(2001));
238
+ });
239
+
240
+ test("fallback path works correctly without recipient context", async () => {
241
+ mockGuardianResult = {
242
+ contact: { notes: "Prefers formal tone." },
243
+ channels: [{ type: "vellum" }],
244
+ };
245
+ // null provider forces fallback path
246
+ configuredProvider = null;
247
+
248
+ const signal = makeSignal();
249
+ const decision = await evaluateSignal(signal, [
250
+ "vellum",
251
+ ] as NotificationChannel[]);
252
+
253
+ expect(decision.fallbackUsed).toBe(true);
254
+ expect(decision.shouldNotify).toBe(true);
255
+ expect(decision.renderedCopy.vellum?.title).toBeDefined();
256
+ expect(decision.renderedCopy.vellum?.body).toBeDefined();
257
+ // No LLM call, so no system prompt captured
258
+ expect(capturedSystemPrompt).toBeUndefined();
259
+ });
260
+
261
+ test("recipient-context appears after user-preferences in prompt", async () => {
262
+ mockGuardianResult = {
263
+ contact: { notes: "Prefers brief updates." },
264
+ channels: [{ type: "vellum" }],
265
+ };
266
+ setupLLMProvider();
267
+
268
+ const signal = makeSignal();
269
+ await evaluateSignal(
270
+ signal,
271
+ ["vellum"] as NotificationChannel[],
272
+ "Notify only for urgent items",
273
+ );
274
+
275
+ expect(capturedSystemPrompt).toBeDefined();
276
+ const prefsIdx = capturedSystemPrompt!.indexOf("</user-preferences>");
277
+ const recipientIdx = capturedSystemPrompt!.indexOf("<recipient-context>");
278
+ expect(prefsIdx).toBeGreaterThan(-1);
279
+ expect(recipientIdx).toBeGreaterThan(-1);
280
+ expect(recipientIdx).toBeGreaterThan(prefsIdx);
281
+ });
282
+ });
@@ -4,6 +4,10 @@ import { describe, expect, test } from "bun:test";
4
4
 
5
5
  const templatesDir = join(import.meta.dirname, "..", "prompts", "templates");
6
6
  const bootstrap = readFileSync(join(templatesDir, "BOOTSTRAP.md"), "utf-8");
7
+ const bootstrapRef = readFileSync(
8
+ join(templatesDir, "BOOTSTRAP-REFERENCE.md"),
9
+ "utf-8",
10
+ );
7
11
  const identity = readFileSync(join(templatesDir, "IDENTITY.md"), "utf-8");
8
12
  const user = readFileSync(join(templatesDir, "USER.md"), "utf-8");
9
13
 
@@ -17,28 +21,32 @@ describe("onboarding template contracts", () => {
17
21
  const lower = bootstrap.toLowerCase();
18
22
  expect(lower).toContain("your name");
19
23
  expect(lower).toContain("personality");
20
- expect(lower).toContain("avatar");
21
24
  });
22
25
 
23
- test("infers personality organically instead of asking directly", () => {
26
+ test("leads with personality-first emotional arc", () => {
24
27
  const lower = bootstrap.toLowerCase();
25
28
  expect(lower).toContain("personality");
26
- expect(lower).toContain("emerge");
27
29
  expect(lower).toContain("vibe");
30
+ // Personality arc should come before usefulness arc
31
+ const personalityIdx = lower.indexOf("oh, this has personality");
32
+ const usefulIdx = lower.indexOf("oh, this is useful");
33
+ expect(personalityIdx).toBeGreaterThan(-1);
34
+ expect(usefulIdx).toBeGreaterThan(-1);
35
+ expect(personalityIdx).toBeLessThan(usefulIdx);
28
36
  });
29
37
 
30
38
  test("contains name selection with change-later instruction", () => {
31
39
  const lower = bootstrap.toLowerCase();
32
- expect(lower).toContain("what do you want to call me");
40
+ expect(lower).toContain("what they want to call you");
33
41
  expect(lower).toContain("change it later");
34
42
  });
35
43
 
36
- test("asks about user after assistant identity", () => {
37
- const nameIdx = bootstrap.indexOf("Your name");
38
- const theirNameIdx = bootstrap.indexOf("Their name");
44
+ test("name exchange happens before personality quiz", () => {
45
+ const nameIdx = bootstrap.indexOf("Step 1: Name Exchange");
46
+ const quizIdx = bootstrap.indexOf("Step 2: Personality Quiz");
39
47
  expect(nameIdx).toBeGreaterThan(-1);
40
- expect(theirNameIdx).toBeGreaterThan(-1);
41
- expect(nameIdx).toBeLessThan(theirNameIdx);
48
+ expect(quizIdx).toBeGreaterThan(-1);
49
+ expect(nameIdx).toBeLessThan(quizIdx);
42
50
  });
43
51
 
44
52
  test("gathers user context: work role, hobbies, daily tools", () => {
@@ -48,18 +56,16 @@ describe("onboarding template contracts", () => {
48
56
  expect(lower).toContain("tools");
49
57
  });
50
58
 
51
- test("shows exactly 2 suggestions via ui_show card with relay_prompt actions", () => {
59
+ test("references ui_show payloads from BOOTSTRAP-REFERENCE.md", () => {
52
60
  expect(bootstrap).toContain("ui_show");
53
- expect(bootstrap).toContain("exactly 2");
54
- expect(bootstrap).toContain("relay_prompt");
61
+ expect(bootstrap).toContain("BOOTSTRAP-REFERENCE.md");
55
62
  });
56
63
 
57
- test("contains wrapping-up criteria with required conditions", () => {
64
+ test("contains wrapping-up criteria with deletion instructions", () => {
58
65
  const lower = bootstrap.toLowerCase();
59
66
  expect(lower).toContain("wrapping up");
60
67
  expect(lower).toContain("delete");
61
68
  expect(lower).toContain("bootstrap.md");
62
- expect(lower).toContain("two suggestions");
63
69
  });
64
70
 
65
71
  test("contains refusal policy", () => {
@@ -83,6 +89,49 @@ describe("onboarding template contracts", () => {
83
89
  expect(bootstrap).toContain("SOUL.md");
84
90
  expect(bootstrap).toContain("file_edit");
85
91
  });
92
+
93
+ test("includes budget constraint", () => {
94
+ expect(bootstrap).toContain("$5");
95
+ });
96
+
97
+ test("includes new colleague framing", () => {
98
+ expect(bootstrap).toContain("new colleague");
99
+ });
100
+
101
+ test("instructs checking Connected Services for email task variant", () => {
102
+ expect(bootstrap).toContain("Connected Services");
103
+ expect(bootstrap).toContain("Connect my email");
104
+ expect(bootstrap).toContain("Check my email");
105
+ });
106
+
107
+ test("keeps momentum by chaining off the first task", () => {
108
+ const lower = bootstrap.toLowerCase();
109
+ expect(lower).toContain("keep the momentum");
110
+ expect(lower).toContain("don't pivot to setup");
111
+ expect(lower).toContain("chain off the task");
112
+ expect(lower).toContain("while we're at it");
113
+ });
114
+ });
115
+
116
+ describe("BOOTSTRAP-REFERENCE.md", () => {
117
+ test("contains personality form with 4 dropdowns", () => {
118
+ expect(bootstrapRef).toContain('surface_type: "form"');
119
+ expect(bootstrapRef).toContain("communication_style");
120
+ expect(bootstrapRef).toContain("task_style");
121
+ expect(bootstrapRef).toContain("humor");
122
+ expect(bootstrapRef).toContain("depth");
123
+ });
124
+
125
+ test("contains email-not-connected task card variant", () => {
126
+ expect(bootstrapRef).toContain("Email Not Connected");
127
+ expect(bootstrapRef).toContain("Connect my email");
128
+ expect(bootstrapRef).toContain("relay_prompt");
129
+ });
130
+
131
+ test("contains email-already-connected task card variant", () => {
132
+ expect(bootstrapRef).toContain("Email Already Connected");
133
+ expect(bootstrapRef).toContain("Check my email");
134
+ });
86
135
  });
87
136
 
88
137
  describe("IDENTITY.md", () => {
@@ -177,6 +177,38 @@ describe("Shell Parser", () => {
177
177
  result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
178
178
  ).toBe(false);
179
179
  });
180
+
181
+ test("pipe to python3 -c is not flagged (inline code, not stdin exec)", async () => {
182
+ const result = await parse(
183
+ 'cat data.json | python3 -c "import sys; print(sys.stdin.read())"',
184
+ );
185
+ expect(
186
+ result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
187
+ ).toBe(false);
188
+ });
189
+
190
+ test("pipe to node -e is not flagged (inline code)", async () => {
191
+ const result = await parse(
192
+ "cat data.json | node -e \"process.stdin.resume()\"",
193
+ );
194
+ expect(
195
+ result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
196
+ ).toBe(false);
197
+ });
198
+
199
+ test("pipe to python3 without flags is flagged (stdin exec)", async () => {
200
+ const result = await parse("cat exploit.py | python3");
201
+ expect(
202
+ result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
203
+ ).toBe(true);
204
+ });
205
+
206
+ test("pipe to python3 - is flagged (explicit stdin exec)", async () => {
207
+ const result = await parse("cat exploit.py | python3 -");
208
+ expect(
209
+ result.dangerousPatterns.some((p) => p.type === "pipe_to_shell"),
210
+ ).toBe(true);
211
+ });
180
212
  });
181
213
 
182
214
  // base64_execute