@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a

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 (624) hide show
  1. package/ARCHITECTURE.md +19 -35
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +1128 -119
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  28. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  29. package/src/__tests__/app-compiler.test.ts +15 -1
  30. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  31. package/src/__tests__/approval-cascade.test.ts +6 -0
  32. package/src/__tests__/approval-routes-http.test.ts +128 -0
  33. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  34. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  35. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  36. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  37. package/src/__tests__/btw-routes.test.ts +6 -8
  38. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  39. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  40. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  41. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  42. package/src/__tests__/checker.test.ts +0 -3
  43. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  44. package/src/__tests__/compaction-events.test.ts +6 -0
  45. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  46. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  47. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  48. package/src/__tests__/config-schema.test.ts +35 -0
  49. package/src/__tests__/config-watcher.test.ts +0 -18
  50. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  51. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  52. package/src/__tests__/contacts-tools.test.ts +29 -0
  53. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  57. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  59. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  60. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  61. package/src/__tests__/conversation-process-callsite.test.ts +6 -0
  62. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  63. package/src/__tests__/conversation-queue.test.ts +6 -0
  64. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  65. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  66. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  68. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  69. package/src/__tests__/conversation-title-service.test.ts +62 -0
  70. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  71. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  72. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  73. package/src/__tests__/credential-broker.test.ts +449 -1
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  75. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  76. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  77. package/src/__tests__/credential-routes.test.ts +360 -0
  78. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  79. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  80. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  81. package/src/__tests__/disk-usage.test.ts +65 -0
  82. package/src/__tests__/document-tool-security.test.ts +9 -6
  83. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  84. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  85. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  86. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  87. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  88. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  89. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  90. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  91. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  92. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  93. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  94. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  95. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  96. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  97. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  98. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  99. package/src/__tests__/identity-routes.test.ts +0 -189
  100. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  101. package/src/__tests__/injector-disk-pressure.test.ts +10 -5
  102. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  103. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  104. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  105. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  106. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  107. package/src/__tests__/llm-resolver.test.ts +70 -24
  108. package/src/__tests__/llm-schema.test.ts +1 -0
  109. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  110. package/src/__tests__/mcp-health-check.test.ts +6 -7
  111. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  112. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  113. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  114. package/src/__tests__/path-policy.test.ts +34 -0
  115. package/src/__tests__/persona-resolver.test.ts +49 -14
  116. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  118. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  119. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  120. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  121. package/src/__tests__/reaction-persistence.test.ts +150 -29
  122. package/src/__tests__/registry.test.ts +2 -7
  123. package/src/__tests__/relay-server.test.ts +285 -0
  124. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  125. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  126. package/src/__tests__/schedule-routes.test.ts +0 -30
  127. package/src/__tests__/schedule-tools.test.ts +2 -18
  128. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  129. package/src/__tests__/secret-response-routing.test.ts +35 -0
  130. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  131. package/src/__tests__/skill-execute-input.test.ts +177 -1
  132. package/src/__tests__/skill-load-tool.test.ts +72 -0
  133. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  134. package/src/__tests__/skills.test.ts +51 -0
  135. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  136. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  137. package/src/__tests__/subagent-tools.test.ts +266 -0
  138. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  139. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  140. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  141. package/src/__tests__/title-generate-hook.test.ts +100 -3
  142. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  143. package/src/__tests__/token-manager.test.ts +519 -0
  144. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  145. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  146. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  147. package/src/__tests__/tool-executor.test.ts +0 -79
  148. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  149. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  150. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  151. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  152. package/src/__tests__/twilio-routes.test.ts +81 -1
  153. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  154. package/src/__tests__/weak-open-model.test.ts +30 -0
  155. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  156. package/src/__tests__/workspace-greetings.test.ts +152 -0
  157. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  158. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  159. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  160. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  161. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  162. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  163. package/src/agent/loop.ts +75 -29
  164. package/src/api/README.md +6 -6
  165. package/src/api/events/tool-result.ts +6 -0
  166. package/src/api/events/workflow-completed.ts +53 -0
  167. package/src/api/events/workflow-leaf-finished.ts +38 -0
  168. package/src/api/events/workflow-leaf-started.ts +35 -0
  169. package/src/api/events/workflow-progress.ts +32 -0
  170. package/src/api/events/workflow-started.ts +31 -0
  171. package/src/api/index.ts +40 -0
  172. package/src/api/responses/conversation-message.ts +28 -4
  173. package/src/api/responses/home.ts +26 -4
  174. package/src/api/responses/workflow-journal.ts +53 -0
  175. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  176. package/src/approvals/guardian-decision-primitive.ts +26 -3
  177. package/src/approvals/guardian-request-resolvers.ts +183 -80
  178. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  179. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  180. package/src/calls/call-pointer-messages.ts +10 -4
  181. package/src/calls/channel-admission-reader.ts +104 -0
  182. package/src/calls/guardian-dispatch.ts +17 -45
  183. package/src/calls/media-stream-server.ts +84 -2
  184. package/src/calls/relay-access-wait.ts +1 -1
  185. package/src/calls/relay-server.ts +66 -0
  186. package/src/calls/relay-setup-router.ts +82 -1
  187. package/src/calls/twilio-routes.ts +17 -8
  188. package/src/calls/voice-session-bridge.ts +2 -2
  189. package/src/cli/commands/clients.ts +3 -0
  190. package/src/cli/commands/db/status.ts +37 -3
  191. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  192. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  193. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  194. package/src/cli/commands/memory/index.ts +30 -0
  195. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  196. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  197. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  198. package/src/cli/commands/oauth/status.test.ts +36 -0
  199. package/src/cli/commands/oauth/status.ts +23 -3
  200. package/src/cli/commands/plugins.ts +400 -14
  201. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  202. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  203. package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
  204. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  205. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  206. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  207. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  208. package/src/cli/lib/diff-plugin.ts +346 -0
  209. package/src/cli/lib/inspect-plugin.ts +12 -1
  210. package/src/cli/lib/install-from-github.ts +128 -19
  211. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  212. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  213. package/src/cli/lib/plugin-marketplace.ts +11 -0
  214. package/src/cli/lib/plugin-pin-history.ts +257 -0
  215. package/src/cli/lib/plugin-surfaces.ts +104 -0
  216. package/src/cli/lib/upgrade-plugin.ts +298 -10
  217. package/src/cli/program.ts +2 -6
  218. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  219. package/src/config/assistant-feature-flags.ts +22 -7
  220. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  221. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  222. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  223. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  224. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  225. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  226. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  227. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  228. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  229. package/src/config/bundled-tool-registry.ts +2 -7
  230. package/src/config/call-site-defaults.ts +15 -2
  231. package/src/config/feature-flag-registry.json +46 -31
  232. package/src/config/inference-profile-validation.ts +26 -0
  233. package/src/config/llm-resolver.ts +3 -0
  234. package/src/config/loader.ts +4 -0
  235. package/src/config/memory-v3-gate.ts +11 -0
  236. package/src/config/profile-order.ts +28 -0
  237. package/src/config/schema.ts +8 -6
  238. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  239. package/src/config/schemas/call-site-catalog.ts +7 -0
  240. package/src/config/schemas/channels.ts +11 -0
  241. package/src/config/schemas/elevenlabs.ts +0 -1
  242. package/src/config/schemas/llm.ts +31 -0
  243. package/src/config/schemas/memory-lifecycle.ts +3 -7
  244. package/src/config/schemas/memory-v3.ts +6 -0
  245. package/src/config/schemas/platform.ts +0 -8
  246. package/src/config/schemas/services.ts +18 -0
  247. package/src/config/seed-inference-profiles.ts +109 -44
  248. package/src/config/skills.ts +21 -0
  249. package/src/config/sync-gated-profiles.ts +220 -0
  250. package/src/contacts/contact-store.ts +89 -106
  251. package/src/contacts/contacts-write.ts +5 -22
  252. package/src/contacts/types.ts +0 -1
  253. package/src/context/compactor.ts +88 -54
  254. package/src/context/strip-injections.ts +58 -10
  255. package/src/context/token-estimator.ts +1 -1
  256. package/src/credential-execution/process-manager.ts +55 -14
  257. package/src/credential-execution/prompted-credential.ts +2 -3
  258. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  259. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  260. package/src/daemon/config-watcher.ts +0 -4
  261. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  262. package/src/daemon/conversation-agent-loop.ts +114 -22
  263. package/src/daemon/conversation-history.ts +1 -1
  264. package/src/daemon/conversation-lifecycle.ts +3 -5
  265. package/src/daemon/conversation-process.ts +13 -5
  266. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  267. package/src/daemon/conversation-slash.ts +2 -23
  268. package/src/daemon/conversation-surfaces.ts +26 -0
  269. package/src/daemon/conversation-tool-setup.ts +53 -19
  270. package/src/daemon/conversation.ts +131 -20
  271. package/src/daemon/disk-pressure-policy.ts +5 -3
  272. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  273. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  274. package/src/daemon/handlers/config-a2a.ts +0 -2
  275. package/src/daemon/handlers/config-channels.ts +15 -16
  276. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  277. package/src/daemon/handlers/conversations.ts +107 -0
  278. package/src/daemon/host-browser-proxy.ts +41 -0
  279. package/src/daemon/lifecycle.ts +55 -27
  280. package/src/daemon/memory-v2-startup.test.ts +131 -0
  281. package/src/daemon/memory-v2-startup.ts +100 -1
  282. package/src/daemon/message-provenance.ts +2 -0
  283. package/src/daemon/message-types/contacts.ts +0 -1
  284. package/src/daemon/message-types/conversations.ts +3 -3
  285. package/src/daemon/message-types/sync.ts +0 -1
  286. package/src/daemon/message-types/web-activity.ts +7 -1
  287. package/src/daemon/message-types/workflows.ts +83 -1
  288. package/src/daemon/orphan-reaper.test.ts +0 -19
  289. package/src/daemon/orphan-reaper.ts +2 -24
  290. package/src/daemon/server.ts +0 -10
  291. package/src/daemon/tool-setup-types.ts +4 -0
  292. package/src/daemon/trust-context.ts +1 -1
  293. package/src/daemon/wake-conversation-ops.ts +1 -0
  294. package/src/events/tool-audit-listener.ts +2 -2
  295. package/src/home/feed-source-enrichment.test.ts +151 -0
  296. package/src/home/feed-source-enrichment.ts +176 -0
  297. package/src/home/relationship-state.ts +2 -4
  298. package/src/instrument.ts +18 -6
  299. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  300. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  301. package/src/ipc/assistant-server.ts +37 -4
  302. package/src/ipc/gateway-flag-listener.ts +18 -2
  303. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  304. package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
  305. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  306. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  307. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  308. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  309. package/src/memory/auth-fallback-events-store.ts +2 -2
  310. package/src/memory/auto-analysis-enqueue.ts +3 -5
  311. package/src/memory/bookmark-crud.ts +1 -2
  312. package/src/memory/canonical-guardian-store.ts +39 -1
  313. package/src/memory/conversation-crud.ts +9 -4
  314. package/src/memory/conversation-key-store.ts +17 -2
  315. package/src/memory/conversation-title-service.ts +64 -7
  316. package/src/memory/db-async-query.ts +20 -2
  317. package/src/memory/db-connection.ts +49 -5
  318. package/src/memory/db-init.ts +17 -17
  319. package/src/memory/embedding-backend.ts +38 -1
  320. package/src/memory/embedding-billing-breaker.ts +96 -0
  321. package/src/memory/jobs-store.ts +25 -13
  322. package/src/memory/jobs-worker.ts +54 -1
  323. package/src/memory/lifecycle-events-store.ts +2 -2
  324. package/src/memory/memory-retrospective-constants.ts +4 -4
  325. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  326. package/src/memory/memory-retrospective-job.ts +28 -227
  327. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  328. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  329. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  330. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  331. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  332. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  333. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  334. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  335. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  336. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  337. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  338. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  339. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  340. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  341. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  342. package/src/memory/migrations/index.ts +6 -0
  343. package/src/memory/migrations/run-migrations.ts +41 -0
  344. package/src/memory/migrations/validate-migration-state.ts +1 -1
  345. package/src/memory/onboarding-events-store.ts +3 -3
  346. package/src/memory/schema/contacts.ts +0 -5
  347. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  348. package/src/memory/skill-loaded-events-store.ts +2 -2
  349. package/src/memory/tool-executed-events-store.test.ts +7 -7
  350. package/src/memory/turn-trace-store.test.ts +736 -0
  351. package/src/memory/turn-trace-store.ts +364 -0
  352. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  353. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  354. package/src/memory/v2/consolidation-job.ts +2 -2
  355. package/src/memory/v2/skill-content.ts +25 -7
  356. package/src/memory/v2/skill-store.ts +7 -1
  357. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  358. package/src/memory/v3-eval/eval-packets.ts +546 -0
  359. package/src/messaging/providers/slack/adapter.ts +1 -1
  360. package/src/messaging/providers/slack/api.ts +31 -0
  361. package/src/messaging/providers/slack/send.test.ts +114 -2
  362. package/src/messaging/providers/slack/send.ts +30 -7
  363. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  364. package/src/messaging/providers/slack/withdraw.ts +161 -0
  365. package/src/notifications/AGENTS.md +2 -0
  366. package/src/notifications/access-request-copy.ts +72 -59
  367. package/src/notifications/adapters/shared.ts +29 -0
  368. package/src/notifications/adapters/slack.ts +58 -103
  369. package/src/notifications/adapters/telegram.ts +2 -20
  370. package/src/notifications/approval-card-data.ts +333 -0
  371. package/src/notifications/broadcaster.ts +16 -3
  372. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  373. package/src/notifications/copy-composer.ts +3 -3
  374. package/src/notifications/decision-engine.ts +4 -2
  375. package/src/notifications/destination-resolver.ts +4 -6
  376. package/src/notifications/guardian-question-mode.ts +10 -0
  377. package/src/notifications/home-feed-side-effect.ts +7 -16
  378. package/src/notifications/notification-utils.ts +19 -20
  379. package/src/notifications/signal.ts +79 -43
  380. package/src/notifications/types.ts +98 -121
  381. package/src/oauth/AGENTS.md +5 -24
  382. package/src/permissions/checker.test.ts +51 -0
  383. package/src/permissions/checker.ts +185 -26
  384. package/src/permissions/ipc-risk-types.ts +24 -0
  385. package/src/permissions/question-prompter.test.ts +27 -0
  386. package/src/permissions/question-prompter.ts +4 -0
  387. package/src/permissions/secret-prompter.ts +15 -1
  388. package/src/platform/client.test.ts +119 -0
  389. package/src/platform/client.ts +66 -0
  390. package/src/platform/consent-cache.test.ts +267 -0
  391. package/src/platform/consent-cache.ts +174 -0
  392. package/src/plugin-api/constants.ts +1 -1
  393. package/src/plugin-api/index.ts +33 -1
  394. package/src/plugin-api/model-profiles.ts +33 -0
  395. package/src/plugin-api/types.ts +50 -2
  396. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  397. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  398. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  399. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  400. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  401. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  402. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  403. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  404. package/src/plugins/defaults/advisor/config.ts +21 -0
  405. package/src/plugins/defaults/advisor/consult.ts +93 -0
  406. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  407. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  408. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  409. package/src/plugins/defaults/advisor/package.json +14 -0
  410. package/src/plugins/defaults/advisor/steering.ts +67 -0
  411. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  412. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  413. package/src/plugins/defaults/compaction/window-manager.ts +18 -0
  414. package/src/plugins/defaults/index.ts +60 -0
  415. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  416. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  417. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  418. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  419. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  420. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  421. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  422. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  423. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  424. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  425. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  426. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  427. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  428. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  429. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  430. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  431. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  432. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  433. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  434. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  435. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  436. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  437. package/src/prompts/persona-resolver.ts +14 -4
  438. package/src/prompts/templates/system-sections.ts +7 -2
  439. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  440. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  441. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  442. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  443. package/src/providers/anthropic/client.ts +230 -175
  444. package/src/providers/atlascloud/client.ts +85 -0
  445. package/src/providers/call-site-routing.ts +10 -0
  446. package/src/providers/fetch-provider-catalog.ts +85 -0
  447. package/src/providers/gemini/client.ts +43 -0
  448. package/src/providers/inference/adapter-factory.ts +3 -0
  449. package/src/providers/model-catalog.ts +58 -0
  450. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  451. package/src/providers/openai/chat-completions-provider.ts +7 -0
  452. package/src/providers/openai/responses-provider.ts +10 -0
  453. package/src/providers/provider-send-message.ts +11 -3
  454. package/src/providers/ratelimit.ts +9 -0
  455. package/src/providers/retry.ts +62 -12
  456. package/src/providers/search-provider-catalog.ts +10 -0
  457. package/src/providers/types.ts +16 -0
  458. package/src/providers/usage-tracking.ts +7 -0
  459. package/src/providers/weak-open-model.ts +22 -0
  460. package/src/runtime/AGENTS.md +0 -1
  461. package/src/runtime/__tests__/agent-wake.test.ts +227 -0
  462. package/src/runtime/__tests__/client-health.test.ts +44 -0
  463. package/src/runtime/access-request-helper.ts +21 -53
  464. package/src/runtime/actor-trust-resolver.ts +59 -63
  465. package/src/runtime/agent-wake.ts +71 -0
  466. package/src/runtime/assistant-event-hub.ts +18 -4
  467. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  468. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  469. package/src/runtime/btw-sidechain.ts +3 -6
  470. package/src/runtime/capabilities.test.ts +120 -0
  471. package/src/runtime/capabilities.ts +197 -0
  472. package/src/runtime/channel-approval-types.ts +22 -45
  473. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  474. package/src/runtime/channel-retry-sweep.ts +1 -0
  475. package/src/runtime/channel-verification-service.ts +3 -3
  476. package/src/runtime/client-health.ts +26 -0
  477. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  478. package/src/runtime/effective-capabilities.test.ts +128 -0
  479. package/src/runtime/effective-capabilities.ts +84 -0
  480. package/src/runtime/guardian-reply-router.ts +106 -21
  481. package/src/runtime/invite-redemption-service.ts +9 -25
  482. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
  483. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  484. package/src/runtime/pending-interactions.ts +35 -0
  485. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  486. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  487. package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
  488. package/src/runtime/routes/app-routes.ts +1 -1
  489. package/src/runtime/routes/approval-routes.ts +40 -9
  490. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  491. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  492. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  493. package/src/runtime/routes/btw-routes.ts +1 -27
  494. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  495. package/src/runtime/routes/client-routes.ts +10 -0
  496. package/src/runtime/routes/contact-routes.ts +31 -8
  497. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  498. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  499. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  500. package/src/runtime/routes/conversation-routes.ts +39 -14
  501. package/src/runtime/routes/credential-routes.ts +40 -16
  502. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  503. package/src/runtime/routes/events-routes.ts +1 -3
  504. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  505. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  506. package/src/runtime/routes/home-feed-routes.ts +8 -3
  507. package/src/runtime/routes/identity-routes.ts +1 -296
  508. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  509. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  510. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  511. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  512. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  513. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  514. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  515. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  516. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  517. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  518. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  519. package/src/runtime/routes/index.ts +2 -0
  520. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  521. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  522. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  523. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  524. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  525. package/src/runtime/routes/notification-routes.ts +122 -133
  526. package/src/runtime/routes/platform-routes.ts +2 -2
  527. package/src/runtime/routes/plugins-routes.ts +366 -11
  528. package/src/runtime/routes/schedule-routes.ts +0 -22
  529. package/src/runtime/routes/secret-routes.ts +15 -0
  530. package/src/runtime/routes/surface-action-routes.ts +2 -1
  531. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  532. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  533. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  534. package/src/runtime/routes/workflow-routes.ts +131 -29
  535. package/src/runtime/routes/workspace-greetings.ts +55 -0
  536. package/src/runtime/sync/resource-sync-events.ts +1 -11
  537. package/src/runtime/tool-grant-request-helper.ts +18 -16
  538. package/src/runtime/trust-context-resolver.ts +8 -5
  539. package/src/schedule/inference-profile.ts +2 -14
  540. package/src/schedule/schedule-store.ts +1 -1
  541. package/src/schedule/scheduler-types.ts +5 -1
  542. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  543. package/src/security/secret-patterns.ts +3 -0
  544. package/src/subagent/manager.ts +17 -4
  545. package/src/subagent/types.ts +6 -0
  546. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  547. package/src/telemetry/trace-collection-policy.ts +30 -0
  548. package/src/telemetry/types.ts +89 -0
  549. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  550. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  551. package/src/tools/AGENTS.md +3 -3
  552. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  553. package/src/tools/browser/browser-execution.ts +30 -19
  554. package/src/tools/document/document-tool.ts +30 -6
  555. package/src/tools/executor.ts +5 -3
  556. package/src/tools/host-terminal/host-shell.ts +5 -4
  557. package/src/tools/memory/register.ts +2 -2
  558. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  559. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  560. package/src/tools/network/web-fetch.ts +372 -1
  561. package/src/tools/network/web-search-error.ts +1 -1
  562. package/src/tools/network/web-search.ts +213 -10
  563. package/src/tools/permission-checker.ts +4 -3
  564. package/src/tools/registry.ts +20 -0
  565. package/src/tools/schedule/create.ts +7 -12
  566. package/src/tools/schedule/update.ts +4 -11
  567. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  568. package/src/tools/side-effects.ts +2 -17
  569. package/src/tools/skills/execute.ts +99 -2
  570. package/src/tools/skills/load.ts +43 -2
  571. package/src/tools/subagent/spawn.ts +64 -15
  572. package/src/tools/terminal/shell.ts +10 -4
  573. package/src/tools/tool-approval-handler.ts +18 -13
  574. package/src/tools/tool-manifest.ts +0 -2
  575. package/src/tools/types.ts +10 -0
  576. package/src/tools/ui-surface/definitions.ts +64 -3
  577. package/src/tools/verification-control-plane-policy.ts +3 -1
  578. package/src/tools/workflows/run-workflow.test.ts +8 -18
  579. package/src/tools/workflows/run-workflow.ts +1 -0
  580. package/src/util/disk-usage.ts +78 -23
  581. package/src/util/logs-db-path.ts +22 -0
  582. package/src/util/platform.ts +10 -3
  583. package/src/watcher/telemetry.ts +2 -2
  584. package/src/workflows/capabilities.ts +2 -3
  585. package/src/workflows/engine.test.ts +175 -1
  586. package/src/workflows/engine.ts +82 -0
  587. package/src/workflows/journal-store.test.ts +70 -0
  588. package/src/workflows/journal-store.ts +18 -3
  589. package/src/workflows/run-manager.test.ts +171 -28
  590. package/src/workflows/run-manager.ts +66 -24
  591. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  592. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  593. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  594. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  595. package/src/workspace/migrations/registry.ts +8 -0
  596. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  597. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  598. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  599. package/src/__tests__/credential-vault.test.ts +0 -1706
  600. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  601. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  602. package/src/cli/commands/__tests__/task.test.ts +0 -914
  603. package/src/cli/commands/task.ts +0 -771
  604. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  605. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  606. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  607. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  608. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  609. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  610. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  611. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  612. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  613. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  614. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  615. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  616. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  617. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  618. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  619. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  620. package/src/memory/preloaded-apps.ts +0 -116
  621. package/src/notifications/tool-approval-copy.ts +0 -142
  622. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  623. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  624. package/src/tools/credentials/vault.ts +0 -712
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Tests for {@link mergePluginTree}.
3
+ *
4
+ * The merge is exercised against three real temp directories (base/ours/theirs)
5
+ * and the merged tree is read back from a fourth (dest). `git merge-file` runs
6
+ * for real — these tests assert the line-level merge behavior (non-conflicting
7
+ * hunks from both sides survive; conflicts resolve toward `ours`/`theirs` or
8
+ * get conflict markers under `assistant`), the file-level add/delete
9
+ * resolution, and binary whole-file handling.
10
+ */
11
+
12
+ import {
13
+ mkdirSync,
14
+ mkdtempSync,
15
+ readFileSync,
16
+ rmSync,
17
+ writeFileSync,
18
+ } from "node:fs";
19
+ import { tmpdir } from "node:os";
20
+ import { dirname, join } from "node:path";
21
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
22
+
23
+ import { mergePluginTree } from "../merge-plugin-tree.js";
24
+
25
+ /** A file tree keyed by POSIX-relative path; Buffer values are binary files. */
26
+ type Tree = Record<string, string | Buffer>;
27
+
28
+ let scratch: string;
29
+ let baseDir: string;
30
+ let oursDir: string;
31
+ let theirsDir: string;
32
+ let destDir: string;
33
+
34
+ function writeTree(root: string, tree: Tree): void {
35
+ for (const [rel, content] of Object.entries(tree)) {
36
+ const abs = join(root, rel);
37
+ mkdirSync(dirname(abs), { recursive: true });
38
+ writeFileSync(abs, content);
39
+ }
40
+ }
41
+
42
+ /** Read a merged file as UTF-8, or null when absent from the dest tree. */
43
+ function read(rel: string): string | null {
44
+ const abs = join(destDir, rel);
45
+ try {
46
+ return readFileSync(abs, "utf-8");
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ beforeEach(() => {
53
+ scratch = mkdtempSync(join(tmpdir(), "merge-plugin-tree-"));
54
+ baseDir = join(scratch, "base");
55
+ oursDir = join(scratch, "ours");
56
+ theirsDir = join(scratch, "theirs");
57
+ destDir = join(scratch, "dest");
58
+ for (const d of [baseDir, oursDir, theirsDir, destDir]) {
59
+ mkdirSync(d, { recursive: true });
60
+ }
61
+ });
62
+
63
+ afterEach(() => {
64
+ rmSync(scratch, { recursive: true, force: true });
65
+ });
66
+
67
+ describe("mergePluginTree", () => {
68
+ test("keeps non-conflicting edits from both sides and resolves conflicts toward ours", async () => {
69
+ // GIVEN a file edited on disjoint lines by each side, a true conflict, and
70
+ // a one-sided addition on each side
71
+ writeTree(baseDir, {
72
+ "common.txt": "a\nb\nc\n",
73
+ "conflict.txt": "base\n",
74
+ });
75
+ writeTree(oursDir, {
76
+ "common.txt": "A\nb\nc\n",
77
+ "conflict.txt": "ours\n",
78
+ "local-only.txt": "local\n",
79
+ });
80
+ writeTree(theirsDir, {
81
+ "common.txt": "a\nb\nC\n",
82
+ "conflict.txt": "theirs\n",
83
+ "remote-only.txt": "remote\n",
84
+ });
85
+
86
+ // WHEN merged with the `ours` strategy
87
+ const count = await mergePluginTree({
88
+ baseDir,
89
+ oursDir,
90
+ theirsDir,
91
+ destDir,
92
+ strategy: "ours",
93
+ });
94
+
95
+ // THEN both disjoint edits survive, both additions land, and the conflict
96
+ // resolves toward the local edit
97
+ expect(read("common.txt")).toBe("A\nb\nC\n");
98
+ expect(read("local-only.txt")).toBe("local\n");
99
+ expect(read("remote-only.txt")).toBe("remote\n");
100
+ expect(read("conflict.txt")).toBe("ours\n");
101
+ expect(count.fileCount).toBe(4);
102
+ expect(count.conflicts).toEqual([]);
103
+ expect(count.binaryConflicts).toEqual([]);
104
+ });
105
+
106
+ test("resolves conflicts toward the pin under the theirs strategy", async () => {
107
+ // GIVEN a file edited differently on both sides
108
+ writeTree(baseDir, { "conflict.txt": "base\n" });
109
+ writeTree(oursDir, { "conflict.txt": "ours\n" });
110
+ writeTree(theirsDir, { "conflict.txt": "theirs\n" });
111
+
112
+ // WHEN merged with the `theirs` strategy
113
+ await mergePluginTree({
114
+ baseDir,
115
+ oursDir,
116
+ theirsDir,
117
+ destDir,
118
+ strategy: "theirs",
119
+ });
120
+
121
+ // THEN the conflicting hunk resolves toward the pin
122
+ expect(read("conflict.txt")).toBe("theirs\n");
123
+ });
124
+
125
+ test("never writes conflict markers", async () => {
126
+ // GIVEN a hard conflict on every line
127
+ writeTree(baseDir, { "f.txt": "1\n2\n3\n" });
128
+ writeTree(oursDir, { "f.txt": "1\nOURS\n3\n" });
129
+ writeTree(theirsDir, { "f.txt": "1\nTHEIRS\n3\n" });
130
+
131
+ // WHEN merged
132
+ await mergePluginTree({
133
+ baseDir,
134
+ oursDir,
135
+ theirsDir,
136
+ destDir,
137
+ strategy: "ours",
138
+ });
139
+
140
+ // THEN the result carries no git conflict markers
141
+ const merged = read("f.txt") ?? "";
142
+ expect(merged).not.toContain("<<<<<<<");
143
+ expect(merged).not.toContain("=======");
144
+ expect(merged).not.toContain(">>>>>>>");
145
+ });
146
+
147
+ test("drops a file deleted upstream when it is unchanged locally", async () => {
148
+ // GIVEN a file present at base and locally but removed at the pin
149
+ writeTree(baseDir, { "gone.txt": "keep\n", "stay.txt": "x\n" });
150
+ writeTree(oursDir, { "gone.txt": "keep\n", "stay.txt": "x\n" });
151
+ writeTree(theirsDir, { "stay.txt": "x\n" });
152
+
153
+ // WHEN merged
154
+ await mergePluginTree({
155
+ baseDir,
156
+ oursDir,
157
+ theirsDir,
158
+ destDir,
159
+ strategy: "theirs",
160
+ });
161
+
162
+ // THEN the upstream deletion is honored
163
+ expect(read("gone.txt")).toBeNull();
164
+ expect(read("stay.txt")).toBe("x\n");
165
+ });
166
+
167
+ test("keeps a locally-edited file the pin deleted under the ours strategy", async () => {
168
+ // GIVEN a modify/delete conflict: edited locally, deleted at the pin
169
+ writeTree(baseDir, { "f.txt": "base\n" });
170
+ writeTree(oursDir, { "f.txt": "local edit\n" });
171
+ writeTree(theirsDir, {});
172
+
173
+ // WHEN merged with `ours`
174
+ await mergePluginTree({
175
+ baseDir,
176
+ oursDir,
177
+ theirsDir,
178
+ destDir,
179
+ strategy: "ours",
180
+ });
181
+
182
+ // THEN the local edit wins over the deletion
183
+ expect(read("f.txt")).toBe("local edit\n");
184
+ });
185
+
186
+ test("honors the pin's deletion of a locally-edited file under the theirs strategy", async () => {
187
+ // GIVEN the same modify/delete conflict
188
+ writeTree(baseDir, { "f.txt": "base\n" });
189
+ writeTree(oursDir, { "f.txt": "local edit\n" });
190
+ writeTree(theirsDir, {});
191
+
192
+ // WHEN merged with `theirs`
193
+ await mergePluginTree({
194
+ baseDir,
195
+ oursDir,
196
+ theirsDir,
197
+ destDir,
198
+ strategy: "theirs",
199
+ });
200
+
201
+ // THEN the deletion wins over the local edit
202
+ expect(read("f.txt")).toBeNull();
203
+ });
204
+
205
+ test("resolves a binary conflict whole-file by strategy rather than line-merging", async () => {
206
+ // GIVEN a binary file (NUL bytes) diverged on both sides
207
+ const base = Buffer.from([0x00, 0x01, 0x02]);
208
+ const ours = Buffer.from([0x00, 0xaa, 0xbb]);
209
+ const theirs = Buffer.from([0x00, 0xcc, 0xdd]);
210
+ writeTree(baseDir, { "img.bin": base });
211
+ writeTree(oursDir, { "img.bin": ours });
212
+ writeTree(theirsDir, { "img.bin": theirs });
213
+
214
+ // WHEN merged with `theirs`
215
+ await mergePluginTree({
216
+ baseDir,
217
+ oursDir,
218
+ theirsDir,
219
+ destDir,
220
+ strategy: "theirs",
221
+ });
222
+
223
+ // THEN the whole pin blob is taken, byte-for-byte (no markers, no corruption)
224
+ expect(readFileSync(join(destDir, "img.bin")).equals(theirs)).toBe(true);
225
+ });
226
+
227
+ test("keeps a one-sided binary change rather than resolving by strategy", async () => {
228
+ // GIVEN a binary file the pin updated but the local install left untouched
229
+ const base = Buffer.from([0x00, 0x01, 0x02]);
230
+ const theirs = Buffer.from([0x00, 0xcc, 0xdd]);
231
+ writeTree(baseDir, { "img.bin": base });
232
+ writeTree(oursDir, { "img.bin": base });
233
+ writeTree(theirsDir, { "img.bin": theirs });
234
+
235
+ // WHEN merged with `ours` (which only governs true conflicts)
236
+ await mergePluginTree({
237
+ baseDir,
238
+ oursDir,
239
+ theirsDir,
240
+ destDir,
241
+ strategy: "ours",
242
+ });
243
+
244
+ // THEN the upstream-only update survives instead of being dropped
245
+ expect(readFileSync(join(destDir, "img.bin")).equals(theirs)).toBe(true);
246
+ });
247
+
248
+ test("keeps a one-sided local binary edit under the theirs strategy", async () => {
249
+ // GIVEN a binary file edited locally but unchanged in the pin
250
+ const base = Buffer.from([0x00, 0x01, 0x02]);
251
+ const ours = Buffer.from([0x00, 0xaa, 0xbb]);
252
+ writeTree(baseDir, { "img.bin": base });
253
+ writeTree(oursDir, { "img.bin": ours });
254
+ writeTree(theirsDir, { "img.bin": base });
255
+
256
+ // WHEN merged with `theirs` (which only governs true conflicts)
257
+ await mergePluginTree({
258
+ baseDir,
259
+ oursDir,
260
+ theirsDir,
261
+ destDir,
262
+ strategy: "theirs",
263
+ });
264
+
265
+ // THEN the local-only edit survives instead of being reverted to the pin
266
+ expect(readFileSync(join(destDir, "img.bin")).equals(ours)).toBe(true);
267
+ });
268
+
269
+ test("excludes the provenance sidecar from the merge", async () => {
270
+ // GIVEN an install-meta sidecar present on every side with differing content
271
+ writeTree(baseDir, {
272
+ "install-meta.json": '{"commit":"base"}',
273
+ "f.txt": "x\n",
274
+ });
275
+ writeTree(oursDir, {
276
+ "install-meta.json": '{"commit":"ours"}',
277
+ "f.txt": "x\n",
278
+ });
279
+ writeTree(theirsDir, {
280
+ "install-meta.json": '{"commit":"theirs"}',
281
+ "f.txt": "x\n",
282
+ });
283
+
284
+ // WHEN merged
285
+ const count = await mergePluginTree({
286
+ baseDir,
287
+ oursDir,
288
+ theirsDir,
289
+ destDir,
290
+ strategy: "ours",
291
+ });
292
+
293
+ // THEN the sidecar is never carried through (the caller rewrites it)
294
+ expect(read("install-meta.json")).toBeNull();
295
+ expect(count.fileCount).toBe(1);
296
+ });
297
+
298
+ test("merges files in nested directories", async () => {
299
+ // GIVEN a conflict on a deeply-nested file
300
+ writeTree(baseDir, { "src/a/b.txt": "base\n" });
301
+ writeTree(oursDir, { "src/a/b.txt": "ours\n" });
302
+ writeTree(theirsDir, { "src/a/b.txt": "theirs\n" });
303
+
304
+ // WHEN merged
305
+ await mergePluginTree({
306
+ baseDir,
307
+ oursDir,
308
+ theirsDir,
309
+ destDir,
310
+ strategy: "ours",
311
+ });
312
+
313
+ // THEN the nested path is preserved and resolved
314
+ expect(read("src/a/b.txt")).toBe("ours\n");
315
+ });
316
+
317
+ test("writes git conflict markers and reports the path under the assistant strategy", async () => {
318
+ // GIVEN a file edited on disjoint lines by each side plus one true conflict
319
+ writeTree(baseDir, { "common.txt": "a\nb\nc\n", "conflict.txt": "base\n" });
320
+ writeTree(oursDir, { "common.txt": "A\nb\nc\n", "conflict.txt": "ours\n" });
321
+ writeTree(theirsDir, {
322
+ "common.txt": "a\nb\nC\n",
323
+ "conflict.txt": "theirs\n",
324
+ });
325
+
326
+ // WHEN merged with the `assistant` strategy
327
+ const result = await mergePluginTree({
328
+ baseDir,
329
+ oursDir,
330
+ theirsDir,
331
+ destDir,
332
+ strategy: "assistant",
333
+ });
334
+
335
+ // THEN the non-conflicting edits still auto-merge and only the true
336
+ // conflict carries markers naming both sides, and its path is reported
337
+ expect(read("common.txt")).toBe("A\nb\nC\n");
338
+ const conflict = read("conflict.txt") ?? "";
339
+ expect(conflict).toContain("<<<<<<<");
340
+ expect(conflict).toContain("=======");
341
+ expect(conflict).toContain(">>>>>>>");
342
+ expect(conflict).toContain("ours\n");
343
+ expect(conflict).toContain("theirs\n");
344
+ expect(result.conflicts).toEqual(["conflict.txt"]);
345
+ expect(result.binaryConflicts).toEqual([]);
346
+ });
347
+
348
+ test("uses the supplied conflict labels in the markers", async () => {
349
+ // GIVEN a true conflict and custom marker labels
350
+ writeTree(baseDir, { "f.txt": "base\n" });
351
+ writeTree(oursDir, { "f.txt": "ours\n" });
352
+ writeTree(theirsDir, { "f.txt": "theirs\n" });
353
+
354
+ // WHEN merged with the `assistant` strategy and explicit labels
355
+ await mergePluginTree({
356
+ baseDir,
357
+ oursDir,
358
+ theirsDir,
359
+ destDir,
360
+ strategy: "assistant",
361
+ conflictLabels: {
362
+ ours: "local edits (was abc1234)",
363
+ base: "install baseline abc1234",
364
+ theirs: "upgrade pin def5678",
365
+ },
366
+ });
367
+
368
+ // THEN the marker lines carry those labels
369
+ const merged = read("f.txt") ?? "";
370
+ expect(merged).toContain("<<<<<<< local edits (was abc1234)");
371
+ expect(merged).toContain(">>>>>>> upgrade pin def5678");
372
+ });
373
+
374
+ test("auto-merges with no markers when the assistant strategy hits no true conflict", async () => {
375
+ // GIVEN edits on disjoint lines of the same file (no overlapping hunk)
376
+ writeTree(baseDir, { "f.txt": "a\nb\nc\n" });
377
+ writeTree(oursDir, { "f.txt": "A\nb\nc\n" });
378
+ writeTree(theirsDir, { "f.txt": "a\nb\nC\n" });
379
+
380
+ // WHEN merged with the `assistant` strategy
381
+ const result = await mergePluginTree({
382
+ baseDir,
383
+ oursDir,
384
+ theirsDir,
385
+ destDir,
386
+ strategy: "assistant",
387
+ });
388
+
389
+ // THEN both edits merge cleanly with no markers and nothing is reported
390
+ const merged = read("f.txt") ?? "";
391
+ expect(merged).toBe("A\nb\nC\n");
392
+ expect(merged).not.toContain("<<<<<<<");
393
+ expect(result.conflicts).toEqual([]);
394
+ expect(result.binaryConflicts).toEqual([]);
395
+ });
396
+
397
+ test("keeps the local copy and reports a binary conflict under the assistant strategy", async () => {
398
+ // GIVEN a binary file (NUL bytes) diverged on both sides
399
+ const base = Buffer.from([0x00, 0x01, 0x02]);
400
+ const ours = Buffer.from([0x00, 0xaa, 0xbb]);
401
+ const theirs = Buffer.from([0x00, 0xcc, 0xdd]);
402
+ writeTree(baseDir, { "img.bin": base });
403
+ writeTree(oursDir, { "img.bin": ours });
404
+ writeTree(theirsDir, { "img.bin": theirs });
405
+
406
+ // WHEN merged with the `assistant` strategy
407
+ const result = await mergePluginTree({
408
+ baseDir,
409
+ oursDir,
410
+ theirsDir,
411
+ destDir,
412
+ strategy: "assistant",
413
+ });
414
+
415
+ // THEN the local copy is kept byte-for-byte (markers are impossible) and
416
+ // the path is surfaced as a binary conflict, not a marker conflict
417
+ expect(readFileSync(join(destDir, "img.bin")).equals(ours)).toBe(true);
418
+ expect(result.binaryConflicts).toEqual(["img.bin"]);
419
+ expect(result.conflicts).toEqual([]);
420
+ });
421
+
422
+ test("keeps the surviving content and reports a modify/delete conflict under the assistant strategy", async () => {
423
+ // GIVEN a file edited locally but deleted at the pin
424
+ writeTree(baseDir, { "f.txt": "base\n" });
425
+ writeTree(oursDir, { "f.txt": "local edit\n" });
426
+ writeTree(theirsDir, {});
427
+
428
+ // WHEN merged with the `assistant` strategy
429
+ const result = await mergePluginTree({
430
+ baseDir,
431
+ oursDir,
432
+ theirsDir,
433
+ destDir,
434
+ strategy: "assistant",
435
+ });
436
+
437
+ // THEN the local edit is preserved (never silently dropped) and the
438
+ // modify/delete divergence is reported for resolution
439
+ expect(read("f.txt")).toBe("local edit\n");
440
+ expect(result.conflicts).toEqual(["f.txt"]);
441
+ expect(result.binaryConflicts).toEqual([]);
442
+ });
443
+ });
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Tests for {@link listPinHistory} and {@link resolvePinToMarketplaceCommit}.
3
+ *
4
+ * The marketplace manifest stores only the current pin, so pin history is
5
+ * reconstructed by walking the manifest's commit history and reading the
6
+ * plugin's pin at each commit. The fixtures model that timeline with an
7
+ * in-memory `fetch`: a commits-list endpoint plus a manifest-at-ref endpoint
8
+ * keyed by commit SHA.
9
+ */
10
+
11
+ import { describe, expect, test } from "bun:test";
12
+
13
+ import type { FetchLike } from "../install-from-github.js";
14
+ import {
15
+ listPinHistory,
16
+ resolvePinToMarketplaceCommit,
17
+ } from "../plugin-pin-history.js";
18
+
19
+ const PIN_A = "a".repeat(40);
20
+ const PIN_B = "b".repeat(40);
21
+ const PIN_C = "c".repeat(40);
22
+
23
+ // Marketplace-manifest commits, newest → oldest. `C2B` touches the manifest
24
+ // without changing this plugin's pin (a bump for some other plugin), so it must
25
+ // collapse into the same distinct pin as `C3`.
26
+ const C3 = "3".repeat(40);
27
+ const C2B = "d".repeat(40);
28
+ const C2 = "2".repeat(40);
29
+ const C1 = "1".repeat(40);
30
+
31
+ const COMMIT_DATES: Record<string, string> = {
32
+ [C3]: "2026-06-10T00:00:00.000Z",
33
+ [C2B]: "2026-06-08T00:00:00.000Z",
34
+ [C2]: "2026-06-05T00:00:00.000Z",
35
+ [C1]: "2026-06-01T00:00:00.000Z",
36
+ };
37
+
38
+ /** Plugin pin resolved from the manifest at each ref (`main` = current tip). */
39
+ const PIN_BY_REF: Record<string, string> = {
40
+ main: PIN_C,
41
+ [C3]: PIN_C,
42
+ [C2B]: PIN_C,
43
+ [C2]: PIN_B,
44
+ [C1]: PIN_A,
45
+ };
46
+
47
+ function manifest(pin: string): string {
48
+ return JSON.stringify({
49
+ name: "vellum",
50
+ plugins: [
51
+ {
52
+ name: "level-up",
53
+ source: { source: "github", repo: "example-org/level-up", ref: pin },
54
+ },
55
+ ],
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Build a `fetch` serving the commits list and the manifest at each ref.
61
+ * `commits` is the newest-first commit list the API returns.
62
+ */
63
+ function makeFetch(commits: string[] = [C3, C2B, C2, C1]): FetchLike {
64
+ return (async (input: RequestInfo | URL) => {
65
+ const url = typeof input === "string" ? input : input.toString();
66
+ if (url.includes("/commits?")) {
67
+ const body = commits.map((sha) => ({
68
+ sha,
69
+ commit: { committer: { date: COMMIT_DATES[sha] } },
70
+ }));
71
+ return new Response(JSON.stringify(body), { status: 200 });
72
+ }
73
+ const m = url.match(/contents\/plugins\/marketplace\.json\?ref=([^&]+)/);
74
+ if (m) {
75
+ const ref = decodeURIComponent(m[1]!);
76
+ const pin = PIN_BY_REF[ref];
77
+ if (!pin) return new Response("not found", { status: 404 });
78
+ return new Response(manifest(pin), { status: 200 });
79
+ }
80
+ return new Response("unexpected url: " + url, { status: 500 });
81
+ }) as FetchLike;
82
+ }
83
+
84
+ describe("listPinHistory", () => {
85
+ test("lists distinct pins newest-first, collapsing unchanged revisions", async () => {
86
+ // GIVEN a manifest history with three distinct pins (one commit left the
87
+ // plugin's pin unchanged)
88
+ const history = await listPinHistory("level-up", { fetch: makeFetch() });
89
+
90
+ // THEN one entry per distinct pin, newest first, each tagged with the
91
+ // newest marketplace commit that carries it
92
+ expect(history).toEqual([
93
+ {
94
+ pin: PIN_C,
95
+ marketplaceCommit: C3,
96
+ promotedAt: COMMIT_DATES[C3]!,
97
+ current: true,
98
+ },
99
+ {
100
+ pin: PIN_B,
101
+ marketplaceCommit: C2,
102
+ promotedAt: COMMIT_DATES[C2]!,
103
+ current: false,
104
+ },
105
+ {
106
+ pin: PIN_A,
107
+ marketplaceCommit: C1,
108
+ promotedAt: COMMIT_DATES[C1]!,
109
+ current: false,
110
+ },
111
+ ]);
112
+ });
113
+
114
+ test("honors the limit (newest pins win)", async () => {
115
+ const history = await listPinHistory(
116
+ "level-up",
117
+ { fetch: makeFetch() },
118
+ { limit: 2 },
119
+ );
120
+ expect(history.map((e) => e.pin)).toEqual([PIN_C, PIN_B]);
121
+ });
122
+
123
+ test("returns an empty list for a plugin absent from every revision", async () => {
124
+ const history = await listPinHistory("ghost", { fetch: makeFetch() });
125
+ expect(history).toEqual([]);
126
+ });
127
+
128
+ test("returns an empty list when the manifest has no commit history", async () => {
129
+ const history = await listPinHistory("level-up", {
130
+ fetch: makeFetch([]),
131
+ });
132
+ expect(history).toEqual([]);
133
+ });
134
+ });
135
+
136
+ describe("resolvePinToMarketplaceCommit", () => {
137
+ test("maps a reviewed pin to the marketplace commit that introduced it", async () => {
138
+ const entry = await resolvePinToMarketplaceCommit("level-up", PIN_B, {
139
+ fetch: makeFetch(),
140
+ });
141
+ expect(entry?.marketplaceCommit).toBe(C2);
142
+ expect(entry?.pin).toBe(PIN_B);
143
+ });
144
+
145
+ test("is case-insensitive on the pin SHA", async () => {
146
+ const entry = await resolvePinToMarketplaceCommit(
147
+ "level-up",
148
+ PIN_B.toUpperCase(),
149
+ { fetch: makeFetch() },
150
+ );
151
+ expect(entry?.marketplaceCommit).toBe(C2);
152
+ });
153
+
154
+ test("returns null for a pin not in the reviewed history", async () => {
155
+ const entry = await resolvePinToMarketplaceCommit(
156
+ "level-up",
157
+ "f".repeat(40),
158
+ { fetch: makeFetch() },
159
+ );
160
+ expect(entry).toBeNull();
161
+ });
162
+ });