@vellumai/assistant 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -0,0 +1,378 @@
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Mocks
7
+ // ---------------------------------------------------------------------------
8
+
9
+ mock.module("../../../util/logger.js", () => ({
10
+ getLogger: () =>
11
+ new Proxy({} as Record<string, unknown>, {
12
+ get: () => () => {},
13
+ }),
14
+ }));
15
+
16
+ import { getWorkspaceRoutesDir } from "../../../util/platform.js";
17
+ import { UserRouteDispatcher } from "../user-route-dispatcher.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function makeRequest(
24
+ method: string,
25
+ path = "http://localhost/v1/x/test",
26
+ ): Request {
27
+ return new Request(path, { method });
28
+ }
29
+
30
+ function writeHandler(relativePath: string, content: string): string {
31
+ const routesDir = getWorkspaceRoutesDir();
32
+ const fullPath = join(routesDir, relativePath);
33
+ const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
34
+ mkdirSync(dir, { recursive: true });
35
+ writeFileSync(fullPath, content);
36
+ return fullPath;
37
+ }
38
+
39
+ async function readErrorBody(
40
+ response: Response,
41
+ ): Promise<{ error: { code: string; message: string } }> {
42
+ return response.json();
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Setup / teardown
47
+ // ---------------------------------------------------------------------------
48
+
49
+ beforeEach(() => {
50
+ mkdirSync(getWorkspaceRoutesDir(), { recursive: true });
51
+ });
52
+
53
+ afterEach(() => {
54
+ rmSync(getWorkspaceRoutesDir(), { recursive: true, force: true });
55
+ });
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Path traversal
59
+ // ---------------------------------------------------------------------------
60
+
61
+ describe("path traversal", () => {
62
+ test("rejects paths containing '..'", async () => {
63
+ const dispatcher = new UserRouteDispatcher();
64
+ const res = await dispatcher.dispatch("../etc/passwd", makeRequest("GET"));
65
+ expect(res.status).toBe(400);
66
+ const body = await readErrorBody(res);
67
+ expect(body.error.code).toBe("BAD_REQUEST");
68
+ expect(body.error.message).toContain("Path traversal");
69
+ });
70
+
71
+ test("rejects embedded '..' segments", async () => {
72
+ const dispatcher = new UserRouteDispatcher();
73
+ const res = await dispatcher.dispatch(
74
+ "foo/../../etc/passwd",
75
+ makeRequest("GET"),
76
+ );
77
+ expect(res.status).toBe(400);
78
+ });
79
+ });
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // 404 — missing handler
83
+ // ---------------------------------------------------------------------------
84
+
85
+ describe("missing handler", () => {
86
+ test("returns 404 when no handler file exists", async () => {
87
+ const dispatcher = new UserRouteDispatcher();
88
+ const res = await dispatcher.dispatch("nonexistent", makeRequest("GET"));
89
+ expect(res.status).toBe(404);
90
+ const body = await readErrorBody(res);
91
+ expect(body.error.code).toBe("NOT_FOUND");
92
+ expect(body.error.message).toContain("/x/nonexistent");
93
+ });
94
+ });
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Successful dispatch
98
+ // ---------------------------------------------------------------------------
99
+
100
+ describe("successful dispatch", () => {
101
+ test("dispatches GET to handler exporting GET function", async () => {
102
+ writeHandler(
103
+ "hello.ts",
104
+ `export function GET(request) {
105
+ return Response.json({ greeting: "hello" });
106
+ }`,
107
+ );
108
+
109
+ const dispatcher = new UserRouteDispatcher();
110
+ const res = await dispatcher.dispatch("hello", makeRequest("GET"));
111
+ expect(res.status).toBe(200);
112
+ const body = await res.json();
113
+ expect(body.greeting).toBe("hello");
114
+ });
115
+
116
+ test("dispatches POST to handler exporting POST function", async () => {
117
+ writeHandler(
118
+ "submit.ts",
119
+ `export async function POST(request) {
120
+ return Response.json({ received: true }, { status: 201 });
121
+ }`,
122
+ );
123
+
124
+ const dispatcher = new UserRouteDispatcher();
125
+ const res = await dispatcher.dispatch("submit", makeRequest("POST"));
126
+ expect(res.status).toBe(201);
127
+ const body = await res.json();
128
+ expect(body.received).toBe(true);
129
+ });
130
+
131
+ test("dispatches to .js handler files", async () => {
132
+ writeHandler(
133
+ "legacy.js",
134
+ `export function GET(request) {
135
+ return Response.json({ format: "js" });
136
+ }`,
137
+ );
138
+
139
+ const dispatcher = new UserRouteDispatcher();
140
+ const res = await dispatcher.dispatch("legacy", makeRequest("GET"));
141
+ expect(res.status).toBe(200);
142
+ const body = await res.json();
143
+ expect(body.format).toBe("js");
144
+ });
145
+ });
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Index file convention
149
+ // ---------------------------------------------------------------------------
150
+
151
+ describe("index file convention", () => {
152
+ test("resolves directory to index.ts", async () => {
153
+ writeHandler(
154
+ "my-app/index.ts",
155
+ `export function GET(request) {
156
+ return Response.json({ index: true });
157
+ }`,
158
+ );
159
+
160
+ const dispatcher = new UserRouteDispatcher();
161
+ const res = await dispatcher.dispatch("my-app", makeRequest("GET"));
162
+ expect(res.status).toBe(200);
163
+ const body = await res.json();
164
+ expect(body.index).toBe(true);
165
+ });
166
+
167
+ test("resolves directory to index.js when no index.ts", async () => {
168
+ writeHandler(
169
+ "fallback-app/index.js",
170
+ `export function GET(request) {
171
+ return Response.json({ index: "js" });
172
+ }`,
173
+ );
174
+
175
+ const dispatcher = new UserRouteDispatcher();
176
+ const res = await dispatcher.dispatch("fallback-app", makeRequest("GET"));
177
+ expect(res.status).toBe(200);
178
+ const body = await res.json();
179
+ expect(body.index).toBe("js");
180
+ });
181
+
182
+ test("prefers direct file over index file", async () => {
183
+ writeHandler(
184
+ "dual.ts",
185
+ `export function GET(request) {
186
+ return Response.json({ source: "direct" });
187
+ }`,
188
+ );
189
+ writeHandler(
190
+ "dual/index.ts",
191
+ `export function GET(request) {
192
+ return Response.json({ source: "index" });
193
+ }`,
194
+ );
195
+
196
+ const dispatcher = new UserRouteDispatcher();
197
+ const res = await dispatcher.dispatch("dual", makeRequest("GET"));
198
+ expect(res.status).toBe(200);
199
+ const body = await res.json();
200
+ expect(body.source).toBe("direct");
201
+ });
202
+ });
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // 405 — method not allowed
206
+ // ---------------------------------------------------------------------------
207
+
208
+ describe("method not allowed", () => {
209
+ test("returns 405 with Allow header when method not exported", async () => {
210
+ writeHandler(
211
+ "get-only.ts",
212
+ `export function GET(request) {
213
+ return Response.json({ ok: true });
214
+ }`,
215
+ );
216
+
217
+ const dispatcher = new UserRouteDispatcher();
218
+ const res = await dispatcher.dispatch("get-only", makeRequest("POST"));
219
+ expect(res.status).toBe(405);
220
+ expect(res.headers.get("Allow")).toBe("GET");
221
+ });
222
+
223
+ test("lists multiple allowed methods in Allow header", async () => {
224
+ writeHandler(
225
+ "multi.ts",
226
+ `export function GET(request) { return new Response("ok"); }
227
+ export function POST(request) { return new Response("ok"); }
228
+ export function DELETE(request) { return new Response("ok"); }`,
229
+ );
230
+
231
+ const dispatcher = new UserRouteDispatcher();
232
+ const res = await dispatcher.dispatch("multi", makeRequest("PUT"));
233
+ expect(res.status).toBe(405);
234
+ const allow = res.headers.get("Allow");
235
+ expect(allow).toContain("GET");
236
+ expect(allow).toContain("POST");
237
+ expect(allow).toContain("DELETE");
238
+ });
239
+ });
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // Handler timeout
243
+ // ---------------------------------------------------------------------------
244
+
245
+ describe("handler timeout", () => {
246
+ test("returns 504 when handler exceeds timeout", async () => {
247
+ writeHandler(
248
+ "slow.ts",
249
+ `export function GET(request) {
250
+ return new Promise(() => {});
251
+ }`,
252
+ );
253
+
254
+ // Use a very short timeout for testing
255
+ const dispatcher = new UserRouteDispatcher({ handlerTimeoutMs: 50 });
256
+ const res = await dispatcher.dispatch("slow", makeRequest("GET"));
257
+ expect(res.status).toBe(504);
258
+ const body = await readErrorBody(res);
259
+ expect(body.error.code).toBe("SERVICE_UNAVAILABLE");
260
+ expect(body.error.message).toContain("timed out");
261
+ });
262
+ });
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Handler errors
266
+ // ---------------------------------------------------------------------------
267
+
268
+ describe("handler errors", () => {
269
+ test("returns 500 when handler throws synchronously", async () => {
270
+ writeHandler(
271
+ "throws.ts",
272
+ `export function GET(request) {
273
+ throw new Error("boom");
274
+ }`,
275
+ );
276
+
277
+ const dispatcher = new UserRouteDispatcher();
278
+ const res = await dispatcher.dispatch("throws", makeRequest("GET"));
279
+ expect(res.status).toBe(500);
280
+ const body = await readErrorBody(res);
281
+ expect(body.error.code).toBe("INTERNAL_ERROR");
282
+ expect(body.error.message).toBe("boom");
283
+ });
284
+
285
+ test("returns 500 when handler rejects", async () => {
286
+ writeHandler(
287
+ "rejects.ts",
288
+ `export async function GET(request) {
289
+ throw new Error("async boom");
290
+ }`,
291
+ );
292
+
293
+ const dispatcher = new UserRouteDispatcher();
294
+ const res = await dispatcher.dispatch("rejects", makeRequest("GET"));
295
+ expect(res.status).toBe(500);
296
+ const body = await readErrorBody(res);
297
+ expect(body.error.message).toBe("async boom");
298
+ });
299
+ });
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Mtime-based cache invalidation
303
+ // ---------------------------------------------------------------------------
304
+
305
+ describe("mtime cache", () => {
306
+ test("serves updated content after file modification", async () => {
307
+ const filePath = writeHandler(
308
+ "mutable.ts",
309
+ `export function GET(request) {
310
+ return Response.json({ version: 1 });
311
+ }`,
312
+ );
313
+
314
+ const dispatcher = new UserRouteDispatcher();
315
+
316
+ // First request — version 1
317
+ const res1 = await dispatcher.dispatch("mutable", makeRequest("GET"));
318
+ expect(res1.status).toBe(200);
319
+ const body1 = await res1.json();
320
+ expect(body1.version).toBe(1);
321
+
322
+ // Wait briefly to ensure mtime changes, then rewrite
323
+ await new Promise((resolve) => setTimeout(resolve, 50));
324
+ writeFileSync(
325
+ filePath,
326
+ `export function GET(request) {
327
+ return Response.json({ version: 2 });
328
+ }`,
329
+ );
330
+
331
+ // Second request — should pick up version 2
332
+ const res2 = await dispatcher.dispatch("mutable", makeRequest("GET"));
333
+ expect(res2.status).toBe(200);
334
+ const body2 = await res2.json();
335
+ expect(body2.version).toBe(2);
336
+ });
337
+ });
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Subdirectory routing
341
+ // ---------------------------------------------------------------------------
342
+
343
+ describe("subdirectory routing", () => {
344
+ test("dispatches to nested handler files", async () => {
345
+ writeHandler(
346
+ "api/v1/status.ts",
347
+ `export function GET(request) {
348
+ return Response.json({ nested: true });
349
+ }`,
350
+ );
351
+
352
+ const dispatcher = new UserRouteDispatcher();
353
+ const res = await dispatcher.dispatch("api/v1/status", makeRequest("GET"));
354
+ expect(res.status).toBe(200);
355
+ const body = await res.json();
356
+ expect(body.nested).toBe(true);
357
+ });
358
+ });
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // Description metadata
362
+ // ---------------------------------------------------------------------------
363
+
364
+ describe("description metadata", () => {
365
+ test("ignores non-handler exports without affecting dispatch", async () => {
366
+ writeHandler(
367
+ "with-meta.ts",
368
+ `export const description = "A test handler";
369
+ export function GET(request) {
370
+ return Response.json({ ok: true });
371
+ }`,
372
+ );
373
+
374
+ const dispatcher = new UserRouteDispatcher();
375
+ const res = await dispatcher.dispatch("with-meta", makeRequest("GET"));
376
+ expect(res.status).toBe(200);
377
+ });
378
+ });
@@ -24,6 +24,7 @@ import { packageApp } from "../../bundler/app-bundler.js";
24
24
  import { compileApp } from "../../bundler/app-compiler.js";
25
25
  import { scanBundle } from "../../bundler/bundle-scanner.js";
26
26
  import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
27
+ import { compareSemver } from "../../daemon/handlers/shared.js";
27
28
  import { defaultGallery } from "../../gallery/default-gallery.js";
28
29
  import {
29
30
  getAppDiff,
@@ -68,17 +69,6 @@ function getSharedAppsDir(): string {
68
69
  );
69
70
  }
70
71
 
71
- /** Compare two semver strings. Returns negative if a < b, 0 if equal, positive if a > b. */
72
- function compareSemver(a: string, b: string): number {
73
- const pa = a.split(".").map(Number);
74
- const pb = b.split(".").map(Number);
75
- for (let i = 0; i < 3; i++) {
76
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
77
- if (diff !== 0) return diff;
78
- }
79
- return 0;
80
- }
81
-
82
72
  // ---------------------------------------------------------------------------
83
73
  // Extracted business logic
84
74
  // ---------------------------------------------------------------------------
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { applyGuardianDecision } from "../../../approvals/guardian-decision-primitive.js";
7
7
  import type { ChannelId } from "../../../channels/types.js";
8
+ import { findContactChannel } from "../../../contacts/contact-store.js";
8
9
  import {
9
10
  getAllPendingApprovalsByGuardianChat,
10
11
  getApprovalRequestById,
@@ -804,6 +805,25 @@ async function handleAccessRequestApproval(
804
805
  return { handled: true, type: "stale_ignored" };
805
806
  }
806
807
 
808
+ // Resolve display names from the contacts database for enriched payloads
809
+ const requesterContactResult = approval.requesterExternalUserId
810
+ ? findContactChannel({
811
+ channelType: approval.channel,
812
+ externalUserId: approval.requesterExternalUserId,
813
+ })
814
+ : null;
815
+ const requesterDisplayName =
816
+ requesterContactResult?.contact.displayName ?? null;
817
+
818
+ const decidedByContactResult = decidedByExternalUserId
819
+ ? findContactChannel({
820
+ channelType: approval.channel,
821
+ externalUserId: decidedByExternalUserId,
822
+ })
823
+ : null;
824
+ const decidedByDisplayName =
825
+ decidedByContactResult?.contact.displayName ?? null;
826
+
807
827
  if (decisionResult.type === "denied") {
808
828
  await notifyRequesterOfDenial({
809
829
  replyCallbackUrl,
@@ -821,6 +841,8 @@ async function handleAccessRequestApproval(
821
841
  requesterExternalUserId: approval.requesterExternalUserId,
822
842
  requesterChatId: approval.requesterChatId,
823
843
  decidedByExternalUserId,
844
+ requesterDisplayName,
845
+ decidedByDisplayName,
824
846
  decision: "denied" as const,
825
847
  };
826
848
 
@@ -952,6 +974,8 @@ async function handleAccessRequestApproval(
952
974
  requesterExternalUserId: approval.requesterExternalUserId,
953
975
  requesterChatId: approval.requesterChatId,
954
976
  decidedByExternalUserId,
977
+ requesterDisplayName,
978
+ decidedByDisplayName,
955
979
  decision: "approved",
956
980
  },
957
981
  dedupeKey: `trusted-contact:guardian-decision:${approval.id}`,
@@ -977,6 +1001,8 @@ async function handleAccessRequestApproval(
977
1001
  sourceChannel: approval.channel as NotificationSourceChannel,
978
1002
  requesterExternalUserId: approval.requesterExternalUserId,
979
1003
  requesterChatId: approval.requesterChatId,
1004
+ requesterDisplayName,
1005
+ decidedByDisplayName,
980
1006
  verificationSessionId: decisionResult.verificationSessionId,
981
1007
  },
982
1008
  dedupeKey: `trusted-contact:verification-sent:${decisionResult.verificationSessionId}`,
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared tar.gz archive creation utilities used by
3
+ * log export and profiler export routes.
4
+ */
5
+
6
+ import { spawnSync } from "node:child_process";
7
+
8
+ /** Maximum compressed archive size (50 MB). */
9
+ export const MAX_ARCHIVE_BYTES = 50 * 1024 * 1024;
10
+
11
+ /**
12
+ * Attempts to create a tar.gz archive of `staging` into a Buffer.
13
+ * Returns the Buffer on success, or `undefined` if the archive exceeds
14
+ * the size limit or tar otherwise fails.
15
+ */
16
+ export function createTarGz(
17
+ staging: string,
18
+ maxBytes: number = MAX_ARCHIVE_BYTES,
19
+ ): ArrayBuffer | undefined {
20
+ const proc = spawnSync("tar", ["czf", "-", "-C", staging, "."], {
21
+ maxBuffer: maxBytes,
22
+ timeout: 30_000,
23
+ });
24
+ if (proc.status !== 0) return undefined;
25
+ const buf = Buffer.isBuffer(proc.stdout)
26
+ ? proc.stdout
27
+ : Buffer.from(proc.stdout);
28
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
29
+ }
@@ -1,5 +1,3 @@
1
- import { join } from "node:path";
2
-
3
1
  import { z } from "zod";
4
2
 
5
3
  import { getCharacterComponents } from "../../avatar/character-components.js";
@@ -8,7 +6,7 @@ import {
8
6
  writeTraitsAndRenderAvatar,
9
7
  } from "../../avatar/traits-png-sync.js";
10
8
  import { getLogger } from "../../util/logger.js";
11
- import { getWorkspaceDir } from "../../util/platform.js";
9
+ import { getAvatarImagePath } from "../../util/platform.js";
12
10
  import { buildAssistantEvent } from "../assistant-event.js";
13
11
  import { assistantEventHub } from "../assistant-event-hub.js";
14
12
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
@@ -18,12 +16,7 @@ import type { RouteDefinition } from "../http-router.js";
18
16
  const log = getLogger("avatar-routes");
19
17
 
20
18
  function publishAvatarUpdated(): void {
21
- const avatarPath = join(
22
- getWorkspaceDir(),
23
- "data",
24
- "avatar",
25
- "avatar-image.png",
26
- );
19
+ const avatarPath = getAvatarImagePath();
27
20
  assistantEventHub
28
21
  .publish(
29
22
  buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, {
@@ -17,6 +17,7 @@ import { existsSync, readFileSync } from "node:fs";
17
17
  import { z } from "zod";
18
18
 
19
19
  import { getConfig } from "../../config/loader.js";
20
+ import { readNowScratchpad } from "../../daemon/conversation-runtime-assembly.js";
20
21
  import { getConversationByKey } from "../../memory/conversation-key-store.js";
21
22
  import {
22
23
  resolveChannelPersona,
@@ -136,6 +137,18 @@ async function handleBtw(
136
137
  }
137
138
  }
138
139
 
140
+ // ----- Greeting context enrichment -----
141
+ // Inject NOW.md scratchpad so the model has contextual awareness (mood,
142
+ // current activity) and produces varied, relevant greetings instead of
143
+ // the same deterministic output each time.
144
+ let effectiveContent = trimmedContent;
145
+ if (conversationKey === GREETING_KEY) {
146
+ const now = readNowScratchpad();
147
+ if (now) {
148
+ effectiveContent = `${trimmedContent}\n\n<context>\n${now}\n</context>`;
149
+ }
150
+ }
151
+
139
152
  // Look up an existing conversation — never create one. BTW is ephemeral
140
153
  // (the file header promises "No messages are persisted"), so we must not
141
154
  // call getOrCreateConversation which would insert a DB row. When no
@@ -158,7 +171,7 @@ async function handleBtw(
158
171
  const userPersona = resolveGuardianPersona();
159
172
  const channelPersona = resolveChannelPersona(undefined);
160
173
  const result = await runBtwSidechain({
161
- content: trimmedContent,
174
+ content: effectiveContent,
162
175
  conversation,
163
176
  signal: req.signal,
164
177
  userPersona,