@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
@@ -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
+ }
@@ -0,0 +1,106 @@
1
+ import {
2
+ mkdirSync,
3
+ mkdtempSync,
4
+ rmSync,
5
+ symlinkSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
11
+
12
+ const testWorkspaceDir = mkdtempSync(
13
+ join(tmpdir(), "attachment-routes-workspace-"),
14
+ );
15
+ const testHomeDir = mkdtempSync(join(tmpdir(), "attachment-routes-home-"));
16
+
17
+ const attachmentsDir = join(testWorkspaceDir, "data", "attachments");
18
+ const conversationsDir = join(testWorkspaceDir, "conversations");
19
+ const recordingsDir = join(
20
+ testHomeDir,
21
+ "Library/Application Support/vellum-assistant/recordings",
22
+ );
23
+ const outsideDir = mkdtempSync(join(tmpdir(), "attachment-routes-outside-"));
24
+
25
+ const originalHome = process.env.HOME;
26
+ process.env.HOME = testHomeDir;
27
+
28
+ mock.module("../../util/platform.js", () => ({
29
+ getWorkspaceDir: () => testWorkspaceDir,
30
+ }));
31
+
32
+ import { resolveAllowedFileBackedAttachmentPath } from "./attachment-routes.js";
33
+
34
+ beforeAll(() => {
35
+ mkdirSync(attachmentsDir, { recursive: true });
36
+ mkdirSync(conversationsDir, { recursive: true });
37
+ mkdirSync(recordingsDir, { recursive: true });
38
+ });
39
+
40
+ afterAll(() => {
41
+ process.env.HOME = originalHome;
42
+ rmSync(testWorkspaceDir, { recursive: true, force: true });
43
+ rmSync(testHomeDir, { recursive: true, force: true });
44
+ rmSync(outsideDir, { recursive: true, force: true });
45
+ });
46
+
47
+ describe("resolveAllowedFileBackedAttachmentPath", () => {
48
+ test("allows files in workspace attachments directory", () => {
49
+ const attachmentFile = join(attachmentsDir, "sample.txt");
50
+ writeFileSync(attachmentFile, "ok");
51
+
52
+ expect(resolveAllowedFileBackedAttachmentPath(attachmentFile)).toBe(
53
+ attachmentFile,
54
+ );
55
+ });
56
+
57
+ test("allows files in recordings directory", () => {
58
+ const recordingFile = join(recordingsDir, "recording.mov");
59
+ writeFileSync(recordingFile, "ok");
60
+
61
+ expect(resolveAllowedFileBackedAttachmentPath(recordingFile)).toBe(
62
+ recordingFile,
63
+ );
64
+ });
65
+
66
+ test("allows files in conversation attachments directory", () => {
67
+ const convAttachDir = join(conversationsDir, "conv-123", "attachments");
68
+ mkdirSync(convAttachDir, { recursive: true });
69
+ const convFile = join(convAttachDir, "photo.jpg");
70
+ writeFileSync(convFile, "ok");
71
+
72
+ expect(resolveAllowedFileBackedAttachmentPath(convFile)).toBe(convFile);
73
+ });
74
+
75
+ test("rejects files in conversation dir outside attachments subdir", () => {
76
+ const convDir = join(conversationsDir, "conv-456");
77
+ mkdirSync(convDir, { recursive: true });
78
+ const metaFile = join(convDir, "meta.json");
79
+ writeFileSync(metaFile, "{}");
80
+
81
+ expect(resolveAllowedFileBackedAttachmentPath(metaFile)).toBeNull();
82
+ });
83
+
84
+ test("rejects files outside allowed directories", () => {
85
+ const outsideFile = join(outsideDir, "secret.txt");
86
+ writeFileSync(outsideFile, "secret");
87
+
88
+ expect(resolveAllowedFileBackedAttachmentPath(outsideFile)).toBeNull();
89
+ });
90
+
91
+ test("rejects path traversal via '..' segments", () => {
92
+ const traversalPath = join(attachmentsDir, "..", "..", "secret.txt");
93
+
94
+ expect(resolveAllowedFileBackedAttachmentPath(traversalPath)).toBeNull();
95
+ });
96
+
97
+ test("rejects symlinks inside allowed dir pointing outside", () => {
98
+ const outsideFile = join(outsideDir, "linked-secret.txt");
99
+ writeFileSync(outsideFile, "secret");
100
+
101
+ const symlinkPath = join(attachmentsDir, "sneaky-link.txt");
102
+ symlinkSync(outsideFile, symlinkPath);
103
+
104
+ expect(resolveAllowedFileBackedAttachmentPath(symlinkPath)).toBeNull();
105
+ });
106
+ });