@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -0,0 +1,503 @@
1
+ /**
2
+ * Route handlers for memory item CRUD endpoints.
3
+ *
4
+ * GET /v1/memory-items — list memory items (with filtering, search, sort, pagination)
5
+ * GET /v1/memory-items/:id — get a single memory item
6
+ * POST /v1/memory-items — create a new memory item
7
+ * PATCH /v1/memory-items/:id — update an existing memory item
8
+ * DELETE /v1/memory-items/:id — delete a memory item and its embeddings
9
+ */
10
+
11
+ import { and, asc, count, desc, eq, like, ne, or } from "drizzle-orm";
12
+ import { v4 as uuid } from "uuid";
13
+
14
+ import { getDb } from "../../memory/db.js";
15
+ import { computeMemoryFingerprint } from "../../memory/fingerprint.js";
16
+ import { enqueueMemoryJob } from "../../memory/jobs-store.js";
17
+ import { memoryEmbeddings, memoryItems } from "../../memory/schema.js";
18
+ import { truncate } from "../../util/truncate.js";
19
+ import { httpError } from "../http-errors.js";
20
+ import type { RouteContext, RouteDefinition } from "../http-router.js";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Constants
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const VALID_KINDS = [
27
+ "identity",
28
+ "preference",
29
+ "project",
30
+ "decision",
31
+ "constraint",
32
+ "event",
33
+ ] as const;
34
+
35
+ type MemoryItemKind = (typeof VALID_KINDS)[number];
36
+
37
+ const VALID_SORT_FIELDS = [
38
+ "lastSeenAt",
39
+ "importance",
40
+ "kind",
41
+ "firstSeenAt",
42
+ ] as const;
43
+
44
+ type SortField = (typeof VALID_SORT_FIELDS)[number];
45
+
46
+ const SORT_COLUMN_MAP = {
47
+ lastSeenAt: memoryItems.lastSeenAt,
48
+ importance: memoryItems.importance,
49
+ kind: memoryItems.kind,
50
+ firstSeenAt: memoryItems.firstSeenAt,
51
+ } as const;
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Helpers
55
+ // ---------------------------------------------------------------------------
56
+
57
+ function isValidKind(value: string): value is MemoryItemKind {
58
+ return (VALID_KINDS as readonly string[]).includes(value);
59
+ }
60
+
61
+ function isValidSortField(value: string): value is SortField {
62
+ return (VALID_SORT_FIELDS as readonly string[]).includes(value);
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // GET /v1/memory-items
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export function handleListMemoryItems(url: URL): Response {
70
+ const kindParam = url.searchParams.get("kind");
71
+ const statusParam = url.searchParams.get("status") ?? "active";
72
+ const searchParam = url.searchParams.get("search");
73
+ const sortParam = url.searchParams.get("sort") ?? "lastSeenAt";
74
+ const orderParam = url.searchParams.get("order") ?? "desc";
75
+ const limitParam = Number(url.searchParams.get("limit") ?? 100);
76
+ const offsetParam = Number(url.searchParams.get("offset") ?? 0);
77
+
78
+ if (kindParam && !isValidKind(kindParam)) {
79
+ return httpError(
80
+ "BAD_REQUEST",
81
+ `Invalid kind "${kindParam}". Must be one of: ${VALID_KINDS.join(", ")}`,
82
+ 400,
83
+ );
84
+ }
85
+
86
+ if (!isValidSortField(sortParam)) {
87
+ return httpError(
88
+ "BAD_REQUEST",
89
+ `Invalid sort "${sortParam}". Must be one of: ${VALID_SORT_FIELDS.join(", ")}`,
90
+ 400,
91
+ );
92
+ }
93
+
94
+ if (orderParam !== "asc" && orderParam !== "desc") {
95
+ return httpError(
96
+ "BAD_REQUEST",
97
+ `Invalid order "${orderParam}". Must be "asc" or "desc"`,
98
+ 400,
99
+ );
100
+ }
101
+
102
+ const db = getDb();
103
+
104
+ // Build WHERE conditions
105
+ const conditions = [];
106
+ if (statusParam && statusParam !== "all") {
107
+ conditions.push(eq(memoryItems.status, statusParam));
108
+ }
109
+ if (kindParam) {
110
+ conditions.push(eq(memoryItems.kind, kindParam));
111
+ }
112
+ if (searchParam) {
113
+ conditions.push(
114
+ or(
115
+ like(memoryItems.subject, `%${searchParam}%`),
116
+ like(memoryItems.statement, `%${searchParam}%`),
117
+ )!,
118
+ );
119
+ }
120
+
121
+ const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
122
+
123
+ // Count query
124
+ const countResult = db
125
+ .select({ count: count() })
126
+ .from(memoryItems)
127
+ .where(whereClause)
128
+ .get();
129
+ const total = countResult?.count ?? 0;
130
+
131
+ // Data query
132
+ const sortColumn = SORT_COLUMN_MAP[sortParam];
133
+ const orderFn = orderParam === "asc" ? asc : desc;
134
+
135
+ const items = db
136
+ .select()
137
+ .from(memoryItems)
138
+ .where(whereClause)
139
+ .orderBy(orderFn(sortColumn))
140
+ .limit(limitParam)
141
+ .offset(offsetParam)
142
+ .all();
143
+
144
+ return Response.json({ items, total });
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // GET /v1/memory-items/:id
149
+ // ---------------------------------------------------------------------------
150
+
151
+ export function handleGetMemoryItem(ctx: RouteContext): Response {
152
+ const { id } = ctx.params;
153
+ const db = getDb();
154
+
155
+ const item = db
156
+ .select()
157
+ .from(memoryItems)
158
+ .where(eq(memoryItems.id, id))
159
+ .get();
160
+
161
+ if (!item) {
162
+ return httpError("NOT_FOUND", "Memory item not found", 404);
163
+ }
164
+
165
+ let supersedesSubject: string | undefined;
166
+ let supersededBySubject: string | undefined;
167
+
168
+ if (item.supersedes) {
169
+ const superseded = db
170
+ .select({ subject: memoryItems.subject })
171
+ .from(memoryItems)
172
+ .where(eq(memoryItems.id, item.supersedes))
173
+ .get();
174
+ supersedesSubject = superseded?.subject;
175
+ }
176
+
177
+ if (item.supersededBy) {
178
+ const superseding = db
179
+ .select({ subject: memoryItems.subject })
180
+ .from(memoryItems)
181
+ .where(eq(memoryItems.id, item.supersededBy))
182
+ .get();
183
+ supersededBySubject = superseding?.subject;
184
+ }
185
+
186
+ return Response.json({
187
+ item: {
188
+ ...item,
189
+ ...(supersedesSubject !== undefined ? { supersedesSubject } : {}),
190
+ ...(supersededBySubject !== undefined ? { supersededBySubject } : {}),
191
+ },
192
+ });
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // POST /v1/memory-items
197
+ // ---------------------------------------------------------------------------
198
+
199
+ export async function handleCreateMemoryItem(
200
+ ctx: RouteContext,
201
+ ): Promise<Response> {
202
+ const body = (await ctx.req.json()) as {
203
+ kind?: string;
204
+ subject?: string;
205
+ statement?: string;
206
+ importance?: number;
207
+ };
208
+
209
+ const { kind, subject, statement, importance } = body;
210
+
211
+ // Validate kind
212
+ if (typeof kind !== "string" || !isValidKind(kind)) {
213
+ return httpError(
214
+ "BAD_REQUEST",
215
+ `kind is required and must be one of: ${VALID_KINDS.join(", ")}`,
216
+ 400,
217
+ );
218
+ }
219
+
220
+ // Validate subject
221
+ if (typeof subject !== "string" || subject.trim().length === 0) {
222
+ return httpError(
223
+ "BAD_REQUEST",
224
+ "subject is required and must be a non-empty string",
225
+ 400,
226
+ );
227
+ }
228
+
229
+ // Validate statement
230
+ if (typeof statement !== "string" || statement.trim().length === 0) {
231
+ return httpError(
232
+ "BAD_REQUEST",
233
+ "statement is required and must be a non-empty string",
234
+ 400,
235
+ );
236
+ }
237
+
238
+ const trimmedSubject = truncate(subject.trim(), 80, "");
239
+ const trimmedStatement = truncate(statement.trim(), 500, "");
240
+
241
+ const scopeId = "default";
242
+ const fingerprint = computeMemoryFingerprint(
243
+ scopeId,
244
+ kind,
245
+ trimmedSubject,
246
+ trimmedStatement,
247
+ );
248
+
249
+ const db = getDb();
250
+
251
+ // Check for existing item with same fingerprint + scopeId
252
+ const existing = db
253
+ .select()
254
+ .from(memoryItems)
255
+ .where(
256
+ and(
257
+ eq(memoryItems.fingerprint, fingerprint),
258
+ eq(memoryItems.scopeId, scopeId),
259
+ ),
260
+ )
261
+ .get();
262
+
263
+ if (existing) {
264
+ return httpError(
265
+ "CONFLICT",
266
+ "A memory with this content already exists",
267
+ 409,
268
+ );
269
+ }
270
+
271
+ const id = uuid();
272
+ const now = Date.now();
273
+
274
+ db.insert(memoryItems)
275
+ .values({
276
+ id,
277
+ kind,
278
+ subject: trimmedSubject,
279
+ statement: trimmedStatement,
280
+ status: "active",
281
+ confidence: 0.95,
282
+ importance: importance ?? 0.8,
283
+ fingerprint,
284
+ verificationState: "user_confirmed",
285
+ scopeId,
286
+ firstSeenAt: now,
287
+ lastSeenAt: now,
288
+ lastUsedAt: null,
289
+ overrideConfidence: "explicit",
290
+ })
291
+ .run();
292
+
293
+ enqueueMemoryJob("embed_item", { itemId: id });
294
+
295
+ // Fetch the inserted row to return it
296
+ const insertedRow = db
297
+ .select()
298
+ .from(memoryItems)
299
+ .where(eq(memoryItems.id, id))
300
+ .get();
301
+
302
+ return Response.json({ item: insertedRow }, { status: 201 });
303
+ }
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // PATCH /v1/memory-items/:id
307
+ // ---------------------------------------------------------------------------
308
+
309
+ export async function handleUpdateMemoryItem(
310
+ ctx: RouteContext,
311
+ ): Promise<Response> {
312
+ const { id } = ctx.params;
313
+ const body = (await ctx.req.json()) as {
314
+ subject?: string;
315
+ statement?: string;
316
+ kind?: string;
317
+ status?: string;
318
+ importance?: number;
319
+ verificationState?: string;
320
+ };
321
+
322
+ const db = getDb();
323
+
324
+ const existing = db
325
+ .select()
326
+ .from(memoryItems)
327
+ .where(eq(memoryItems.id, id))
328
+ .get();
329
+
330
+ if (!existing) {
331
+ return httpError("NOT_FOUND", "Memory item not found", 404);
332
+ }
333
+
334
+ // Build the update set with only provided fields
335
+ const set: Record<string, unknown> = {
336
+ lastSeenAt: Date.now(),
337
+ };
338
+
339
+ if (body.subject !== undefined) {
340
+ if (typeof body.subject !== "string") {
341
+ return httpError("BAD_REQUEST", "subject must be a string", 400);
342
+ }
343
+ set.subject = truncate(body.subject.trim(), 80, "");
344
+ }
345
+ if (body.statement !== undefined) {
346
+ if (typeof body.statement !== "string") {
347
+ return httpError("BAD_REQUEST", "statement must be a string", 400);
348
+ }
349
+ set.statement = truncate(body.statement.trim(), 500, "");
350
+ }
351
+ if (body.kind !== undefined) {
352
+ if (!isValidKind(body.kind)) {
353
+ return httpError(
354
+ "BAD_REQUEST",
355
+ `Invalid kind "${body.kind}". Must be one of: ${VALID_KINDS.join(", ")}`,
356
+ 400,
357
+ );
358
+ }
359
+ set.kind = body.kind;
360
+ }
361
+ if (body.status !== undefined) {
362
+ set.status = body.status;
363
+ }
364
+ if (body.importance !== undefined) {
365
+ set.importance = body.importance;
366
+ }
367
+ if (body.verificationState !== undefined) {
368
+ set.verificationState = body.verificationState;
369
+ }
370
+
371
+ // If subject, statement, or kind changed, recompute fingerprint
372
+ const contentChanged =
373
+ body.subject !== undefined ||
374
+ body.statement !== undefined ||
375
+ body.kind !== undefined;
376
+
377
+ if (contentChanged) {
378
+ const newSubject = (set.subject as string | undefined) ?? existing.subject;
379
+ const newStatement =
380
+ (set.statement as string | undefined) ?? existing.statement;
381
+ const newKind = (set.kind as string | undefined) ?? existing.kind;
382
+ const scopeId = existing.scopeId;
383
+
384
+ const fingerprint = computeMemoryFingerprint(
385
+ scopeId,
386
+ newKind,
387
+ newSubject,
388
+ newStatement,
389
+ );
390
+
391
+ // Check for collision (exclude self)
392
+ const collision = db
393
+ .select({ id: memoryItems.id })
394
+ .from(memoryItems)
395
+ .where(
396
+ and(
397
+ eq(memoryItems.fingerprint, fingerprint),
398
+ eq(memoryItems.scopeId, scopeId),
399
+ ne(memoryItems.id, id),
400
+ ),
401
+ )
402
+ .get();
403
+
404
+ if (collision) {
405
+ return httpError(
406
+ "CONFLICT",
407
+ "Another memory item with this content already exists",
408
+ 409,
409
+ );
410
+ }
411
+
412
+ set.fingerprint = fingerprint;
413
+ }
414
+
415
+ db.update(memoryItems).set(set).where(eq(memoryItems.id, id)).run();
416
+
417
+ // If statement changed, enqueue embed job
418
+ if (body.statement !== undefined) {
419
+ enqueueMemoryJob("embed_item", { itemId: id });
420
+ }
421
+
422
+ // Fetch and return the updated row
423
+ const updatedRow = db
424
+ .select()
425
+ .from(memoryItems)
426
+ .where(eq(memoryItems.id, id))
427
+ .get();
428
+
429
+ return Response.json({ item: updatedRow });
430
+ }
431
+
432
+ // ---------------------------------------------------------------------------
433
+ // DELETE /v1/memory-items/:id
434
+ // ---------------------------------------------------------------------------
435
+
436
+ export async function handleDeleteMemoryItem(
437
+ ctx: RouteContext,
438
+ ): Promise<Response> {
439
+ const { id } = ctx.params;
440
+ const db = getDb();
441
+
442
+ const existing = db
443
+ .select()
444
+ .from(memoryItems)
445
+ .where(eq(memoryItems.id, id))
446
+ .get();
447
+
448
+ if (!existing) {
449
+ return httpError("NOT_FOUND", "Memory item not found", 404);
450
+ }
451
+
452
+ // Delete embeddings for this item
453
+ db.delete(memoryEmbeddings)
454
+ .where(
455
+ and(
456
+ eq(memoryEmbeddings.targetType, "item"),
457
+ eq(memoryEmbeddings.targetId, id),
458
+ ),
459
+ )
460
+ .run();
461
+
462
+ // Delete the item (cascades memoryItemSources)
463
+ db.delete(memoryItems).where(eq(memoryItems.id, id)).run();
464
+
465
+ return new Response(null, { status: 204 });
466
+ }
467
+
468
+ // ---------------------------------------------------------------------------
469
+ // Route definitions
470
+ // ---------------------------------------------------------------------------
471
+
472
+ export function memoryItemRouteDefinitions(): RouteDefinition[] {
473
+ return [
474
+ {
475
+ endpoint: "memory-items",
476
+ method: "GET",
477
+ handler: (ctx) => handleListMemoryItems(ctx.url),
478
+ },
479
+ {
480
+ endpoint: "memory-items/:id",
481
+ method: "GET",
482
+ policyKey: "memory-items",
483
+ handler: (ctx) => handleGetMemoryItem(ctx),
484
+ },
485
+ {
486
+ endpoint: "memory-items",
487
+ method: "POST",
488
+ handler: (ctx) => handleCreateMemoryItem(ctx),
489
+ },
490
+ {
491
+ endpoint: "memory-items/:id",
492
+ method: "PATCH",
493
+ policyKey: "memory-items",
494
+ handler: (ctx) => handleUpdateMemoryItem(ctx),
495
+ },
496
+ {
497
+ endpoint: "memory-items/:id",
498
+ method: "DELETE",
499
+ policyKey: "memory-items",
500
+ handler: (ctx) => handleDeleteMemoryItem(ctx),
501
+ },
502
+ ];
503
+ }
@@ -20,6 +20,17 @@ import { httpError } from "../http-errors.js";
20
20
  import type { RouteDefinition } from "../http-router.js";
21
21
 
22
22
  const log = getLogger("runtime-http");
23
+ const MANAGED_PROXY_CREDENTIAL = {
24
+ service: "vellum",
25
+ field: "assistant_api_key",
26
+ };
27
+
28
+ function isManagedProxyCredential(service: string, field: string): boolean {
29
+ return (
30
+ service === MANAGED_PROXY_CREDENTIAL.service &&
31
+ field === MANAGED_PROXY_CREDENTIAL.field
32
+ );
33
+ }
23
34
 
24
35
  export async function handleAddSecret(req: Request): Promise<Response> {
25
36
  const body = (await req.json()) as {
@@ -89,6 +100,9 @@ export async function handleAddSecret(req: Request): Promise<Response> {
89
100
  );
90
101
  }
91
102
  upsertCredentialMetadata(service, field, {});
103
+ if (isManagedProxyCredential(service, field)) {
104
+ initializeProviders(getConfig());
105
+ }
92
106
  log.info({ service, field }, "Credential added via HTTP");
93
107
  return Response.json({ success: true, type, name }, { status: 201 });
94
108
  }
@@ -181,6 +195,9 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
181
195
  );
182
196
  }
183
197
  deleteCredentialMetadata(service, field);
198
+ if (isManagedProxyCredential(service, field)) {
199
+ initializeProviders(getConfig());
200
+ }
184
201
  log.info({ service, field }, "Credential deleted via HTTP");
185
202
  return Response.json({ success: true, type, name });
186
203
  }
@@ -29,7 +29,7 @@ export interface SessionManagementDeps {
29
29
  renameSession: (sessionId: string, name: string) => boolean;
30
30
  clearAllSessions: () => number;
31
31
  cancelGeneration: (sessionId: string) => boolean;
32
- undoLastMessage: (sessionId: string) => { removedCount: number } | null;
32
+ undoLastMessage: (sessionId: string) => Promise<{ removedCount: number } | null>;
33
33
  regenerateResponse: (
34
34
  sessionId: string,
35
35
  ) => Promise<{ requestId: string } | null>;
@@ -115,8 +115,8 @@ export function sessionManagementRouteDefinitions(
115
115
  endpoint: "conversations/:id/undo",
116
116
  method: "POST",
117
117
  policyKey: "conversations/undo",
118
- handler: ({ params }) => {
119
- const result = deps.undoLastMessage(params.id);
118
+ handler: async ({ params }) => {
119
+ const result = await deps.undoLastMessage(params.id);
120
120
  if (!result) {
121
121
  return httpError(
122
122
  "NOT_FOUND",
@@ -29,7 +29,7 @@ import {
29
29
  generateAllowlistOptions,
30
30
  generateScopeOptions,
31
31
  } from "../../permissions/checker.js";
32
- import { getSecureKey } from "../../security/secure-keys.js";
32
+ import { getSecureKeyAsync } from "../../security/secure-keys.js";
33
33
  import { parseToolManifestFile } from "../../skills/tool-manifest.js";
34
34
  import {
35
35
  type ManifestOverride,
@@ -160,7 +160,7 @@ async function handleOAuthConnectStart(body: {
160
160
  const app = getApp(conn.oauthAppId);
161
161
  if (app) {
162
162
  clientId = app.clientId;
163
- clientSecret = getSecureKey(`oauth_app/${app.id}/client_secret`);
163
+ clientSecret = await getSecureKeyAsync(app.clientSecretCredentialPath);
164
164
  }
165
165
  }
166
166
 
@@ -170,7 +170,7 @@ async function handleOAuthConnectStart(body: {
170
170
  if (dbApp) {
171
171
  clientId = dbApp.clientId;
172
172
  if (!clientSecret) {
173
- clientSecret = getSecureKey(`oauth_app/${dbApp.id}/client_secret`);
173
+ clientSecret = await getSecureKeyAsync(dbApp.clientSecretCredentialPath);
174
174
  }
175
175
  }
176
176
  }
@@ -48,6 +48,13 @@ export async function handleAddTrustRuleManage(
48
48
  if (!toolName || typeof toolName !== "string") {
49
49
  return httpError("BAD_REQUEST", "toolName is required", 400);
50
50
  }
51
+ if (toolName.startsWith("__internal:")) {
52
+ return httpError(
53
+ "BAD_REQUEST",
54
+ "toolName must not start with __internal:",
55
+ 400,
56
+ );
57
+ }
51
58
  if (!pattern || typeof pattern !== "string") {
52
59
  return httpError("BAD_REQUEST", "pattern is required", 400);
53
60
  }
@@ -124,6 +131,13 @@ export async function handleUpdateTrustRuleManage(
124
131
  priority?: number;
125
132
  };
126
133
 
134
+ if (typeof body.tool === "string" && body.tool.startsWith("__internal:")) {
135
+ return httpError(
136
+ "BAD_REQUEST",
137
+ "tool must not start with __internal:",
138
+ 400,
139
+ );
140
+ }
127
141
  if (body.decision !== undefined) {
128
142
  const validDecisions = ["allow", "deny", "ask"] as const;
129
143
  if (
@@ -33,7 +33,10 @@ interface TreeEntry {
33
33
 
34
34
  function handleWorkspaceTree(ctx: RouteContext): Response {
35
35
  const requestedPath = ctx.url.searchParams.get("path") ?? "";
36
- const resolved = resolveWorkspacePath(requestedPath);
36
+ const showHidden = ctx.url.searchParams.get("showHidden") === "true";
37
+ const resolved = resolveWorkspacePath(requestedPath, {
38
+ allowHidden: showHidden,
39
+ });
37
40
  if (resolved === undefined) {
38
41
  return httpError("BAD_REQUEST", "Invalid path", 400);
39
42
  }
@@ -45,7 +48,7 @@ function handleWorkspaceTree(ctx: RouteContext): Response {
45
48
  const entries: TreeEntry[] = [];
46
49
  for (const entry of dirents) {
47
50
  // Filter out dotfiles/directories (.env, .git, .private, etc.)
48
- if (entry.name.startsWith(".")) continue;
51
+ if (!showHidden && entry.name.startsWith(".")) continue;
49
52
 
50
53
  const fullPath = join(resolved, entry.name);
51
54
 
@@ -95,7 +98,8 @@ function handleWorkspaceFile(ctx: RouteContext): Response {
95
98
  return httpError("BAD_REQUEST", "path query parameter is required", 400);
96
99
  }
97
100
 
98
- const resolved = resolveWorkspacePath(path);
101
+ const showHidden = ctx.url.searchParams.get("showHidden") === "true";
102
+ const resolved = resolveWorkspacePath(path, { allowHidden: showHidden });
99
103
  if (resolved === undefined) {
100
104
  return httpError("BAD_REQUEST", "Invalid path", 400);
101
105
  }
@@ -145,7 +149,8 @@ function handleWorkspaceFileContent(ctx: RouteContext): Response {
145
149
  );
146
150
  }
147
151
 
148
- const resolved = resolveWorkspacePath(path);
152
+ const showHidden = ctx.url.searchParams.get("showHidden") === "true";
153
+ const resolved = resolveWorkspacePath(path, { allowHidden: showHidden });
149
154
  if (resolved === undefined) {
150
155
  return httpError("BAD_REQUEST", "Invalid path", 400);
151
156
  }
@@ -7,10 +7,16 @@ import { getWorkspaceDir } from "../../util/platform.js";
7
7
  * Resolves a user-provided relative path to an absolute path within the workspace.
8
8
  * Returns the resolved absolute path, or undefined if the path escapes the workspace root.
9
9
  */
10
- export function resolveWorkspacePath(relativePath: string): string | undefined {
10
+ export function resolveWorkspacePath(
11
+ relativePath: string,
12
+ options?: { allowHidden?: boolean },
13
+ ): string | undefined {
11
14
  // Reject paths containing hidden (dot-prefixed) segments like .env, .git, .hidden/foo
12
15
  const segments = relativePath.split(/[/\\]/);
13
- if (segments.some((s) => s.startsWith(".") && s !== "." && s !== "..")) {
16
+ if (
17
+ !options?.allowHidden &&
18
+ segments.some((s) => s.startsWith(".") && s !== "." && s !== "..")
19
+ ) {
14
20
  return undefined;
15
21
  }
16
22