@vellumai/assistant 0.9.0 → 0.10.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  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 +976 -63
  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__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. 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,111 @@
1
+ /**
2
+ * Tests for {@link detectPluginSurfaces}.
3
+ *
4
+ * Surface detection is a pure walk of an installed plugin's on-disk tree, so
5
+ * the fixtures materialize the `hooks/`, `tools/`, and `skills/` directory
6
+ * conventions in a real temp dir and assert the derived listing matches what
7
+ * the runtime loader / skills catalog would discover.
8
+ */
9
+
10
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14
+
15
+ import { detectPluginSurfaces } from "../plugin-surfaces.js";
16
+
17
+ let pluginDir: string;
18
+
19
+ beforeEach(() => {
20
+ pluginDir = mkdtempSync(join(tmpdir(), "plugin-surfaces-"));
21
+ });
22
+
23
+ afterEach(() => {
24
+ rmSync(pluginDir, { recursive: true, force: true });
25
+ });
26
+
27
+ /** Create `<pluginDir>/<rel>` with empty contents, making parents as needed. */
28
+ function touch(rel: string): void {
29
+ const path = join(pluginDir, rel);
30
+ mkdirSync(join(path, ".."), { recursive: true });
31
+ writeFileSync(path, "");
32
+ }
33
+
34
+ describe("detectPluginSurfaces", () => {
35
+ test("lists hooks, tools, and skills from the directory conventions", () => {
36
+ // GIVEN a plugin shipping two hooks, one tool, and two skills
37
+ touch("hooks/post-model-call.ts");
38
+ touch("hooks/init.ts");
39
+ touch("tools/summarize.ts");
40
+ touch("skills/first-skill/SKILL.md");
41
+ touch("skills/second-skill/SKILL.md");
42
+
43
+ // WHEN its surfaces are detected
44
+ const surfaces = detectPluginSurfaces(pluginDir);
45
+
46
+ // THEN each surface type lists its items, sorted
47
+ expect(surfaces.hooks).toEqual(["init", "post-model-call"]);
48
+ expect(surfaces.tools).toEqual(["summarize"]);
49
+ expect(surfaces.skills).toEqual(["first-skill", "second-skill"]);
50
+ });
51
+
52
+ test("reports tools under their registered name, not the raw filename", () => {
53
+ // GIVEN a tool whose filename is not already a valid tool-name segment
54
+ touch("tools/create-issue.ts");
55
+
56
+ // WHEN its surfaces are detected
57
+ const surfaces = detectPluginSurfaces(pluginDir);
58
+
59
+ // THEN the derived (registered) name is reported, matching the loader
60
+ expect(surfaces.tools).toEqual(["create_issue"]);
61
+ });
62
+
63
+ test("omits surface types the plugin does not contribute", () => {
64
+ // GIVEN a plugin that ships only a hook
65
+ touch("hooks/post-model-call.ts");
66
+
67
+ // WHEN its surfaces are detected
68
+ const surfaces = detectPluginSurfaces(pluginDir);
69
+
70
+ // THEN the contributed type is listed and the others are empty
71
+ expect(surfaces.hooks).toEqual(["post-model-call"]);
72
+ expect(surfaces.tools).toEqual([]);
73
+ expect(surfaces.skills).toEqual([]);
74
+ });
75
+
76
+ test("prefers .js over .ts for the same basename and skips .d.ts declarations", () => {
77
+ // GIVEN a compiled hook shipping both .ts and .js plus a .d.ts declaration
78
+ touch("hooks/post-model-call.ts");
79
+ touch("hooks/post-model-call.js");
80
+ touch("hooks/post-model-call.d.ts");
81
+
82
+ // WHEN its surfaces are detected
83
+ const surfaces = detectPluginSurfaces(pluginDir);
84
+
85
+ // THEN the basename appears once and the declaration file is ignored
86
+ expect(surfaces.hooks).toEqual(["post-model-call"]);
87
+ });
88
+
89
+ test("ignores skill directories without a SKILL.md", () => {
90
+ // GIVEN a skills dir with one real skill and one stray subdirectory
91
+ touch("skills/real-skill/SKILL.md");
92
+ touch("skills/not-a-skill/README.md");
93
+
94
+ // WHEN its surfaces are detected
95
+ const surfaces = detectPluginSurfaces(pluginDir);
96
+
97
+ // THEN only the directory carrying a SKILL.md is reported
98
+ expect(surfaces.skills).toEqual(["real-skill"]);
99
+ });
100
+
101
+ test("returns empty surfaces for a plugin with no surface directories", () => {
102
+ // GIVEN a plugin tree with only a package.json
103
+ touch("package.json");
104
+
105
+ // WHEN its surfaces are detected
106
+ const surfaces = detectPluginSurfaces(pluginDir);
107
+
108
+ // THEN every surface type is empty
109
+ expect(surfaces).toEqual({ skills: [], hooks: [], tools: [] });
110
+ });
111
+ });