@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 diffPlugin}.
3
+ *
4
+ * A diff re-materializes the recorded install commit through the same pipeline
5
+ * install uses, then compares that baseline tree against the on-disk install.
6
+ * The clone is replaced with a fake {@link GitRunner} that writes a known
7
+ * baseline tree, the adapter-stub lookup is answered 404 by an in-memory
8
+ * `fetch` (so the clone is treated as a raw external tree), and the install
9
+ * target is a real temp directory passed via `workspacePluginsDir` — no globals
10
+ * are patched. Fixtures vary the on-disk tree to exercise modified/added/
11
+ * removed drift, binary content, and the clean case.
12
+ */
13
+
14
+ import { createHash } from "node:crypto";
15
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { dirname, join } from "node:path";
18
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
19
+
20
+ import { diffPlugin, PluginDiffUnavailableError } from "../diff-plugin.js";
21
+ import {
22
+ type FetchLike,
23
+ type GitRunner,
24
+ PluginNotFoundError,
25
+ } from "../install-from-github.js";
26
+ import { PluginNotInstalledError } from "../uninstall-plugin.js";
27
+
28
+ const SHA_A = "a".repeat(40);
29
+
30
+ /** Bytes for each baseline / on-disk file, keyed by repo-relative POSIX path. */
31
+ type Tree = Record<string, string | Buffer>;
32
+
33
+ /**
34
+ * Build a `fetch` that answers the GitHub Contents API listing (the adapter
35
+ * stub lookup) with a 404, so the clone is materialized as a raw external tree
36
+ * with no overlay. Anything else surfaces a 500 so test bugs are loud.
37
+ */
38
+ function makeFetch(): FetchLike {
39
+ return (async (input: RequestInfo | URL) => {
40
+ const url = typeof input === "string" ? input : input.toString();
41
+ if (url.includes("api.github.com")) {
42
+ return new Response("not found", { status: 404 });
43
+ }
44
+ return new Response(`unexpected url: ${url}`, { status: 500 });
45
+ }) as FetchLike;
46
+ }
47
+
48
+ /**
49
+ * A fake clone that materializes `files` into the scratch clone dir at `fetch`
50
+ * and reports `commit` at HEAD, mirroring how the real git pipeline stages a
51
+ * tree before it is copied into the destination.
52
+ */
53
+ function fakeGitRunner(commit: string, files: Tree): GitRunner {
54
+ return async (args, { cwd }) => {
55
+ switch (args[0]) {
56
+ case "fetch": {
57
+ mkdirSync(join(cwd, ".git"), { recursive: true });
58
+ writeFileSync(join(cwd, ".git", "config"), "[core]\n");
59
+ for (const [rel, content] of Object.entries(files)) {
60
+ const abs = join(cwd, rel);
61
+ mkdirSync(dirname(abs), { recursive: true });
62
+ writeFileSync(abs, content);
63
+ }
64
+ return { stdout: "" };
65
+ }
66
+ case "rev-parse":
67
+ return { stdout: `${commit}\n` };
68
+ default:
69
+ return { stdout: "" };
70
+ }
71
+ };
72
+ }
73
+
74
+ /** A git runner that fails the test if any git command runs. */
75
+ const unusedGitRunner: GitRunner = async (args) => {
76
+ throw new Error(`git should not run for this diff: ${args.join(" ")}`);
77
+ };
78
+
79
+ /**
80
+ * Per-file SHA-256 digest of a tree, in the {@link Fingerprint} shape install
81
+ * records in `install-meta.json`. Drift is classified against this recorded
82
+ * baseline (as `inspect` does), so each fixture records the digest of its
83
+ * install-time tree.
84
+ */
85
+ function fingerprintOf(tree: Tree): {
86
+ algorithm: "sha256";
87
+ files: Record<string, string>;
88
+ } {
89
+ const files: Record<string, string> = {};
90
+ for (const [rel, content] of Object.entries(tree)) {
91
+ const buf = typeof content === "string" ? Buffer.from(content) : content;
92
+ files[rel] = createHash("sha256").update(buf).digest("hex");
93
+ }
94
+ return { algorithm: "sha256", files };
95
+ }
96
+
97
+ /**
98
+ * Materialize an installed plugin copy with `files` on disk and an optional
99
+ * provenance sidecar. `sidecar: null` writes no `install-meta.json`; a sidecar
100
+ * with `commit: null` records an install that captured no commit. `baseline`
101
+ * is the install-time tree whose fingerprint is recorded — drift is classified
102
+ * against it (as `inspect` does); omit it to record an install with no
103
+ * fingerprint (an older or manually-copied install).
104
+ */
105
+ function installCopy(
106
+ pluginsDir: string,
107
+ name: string,
108
+ files: Tree,
109
+ sidecar: {
110
+ commit: string | null;
111
+ committedAt?: string;
112
+ baseline?: Tree;
113
+ } | null,
114
+ ): void {
115
+ const dir = join(pluginsDir, name);
116
+ mkdirSync(dir, { recursive: true });
117
+ for (const [rel, content] of Object.entries(files)) {
118
+ const abs = join(dir, rel);
119
+ mkdirSync(dirname(abs), { recursive: true });
120
+ writeFileSync(abs, content);
121
+ }
122
+ if (sidecar !== null) {
123
+ writeFileSync(
124
+ join(dir, "install-meta.json"),
125
+ JSON.stringify({
126
+ origin: "vellum",
127
+ name,
128
+ source: {
129
+ kind: "github",
130
+ owner: "example-org",
131
+ repo: name,
132
+ ref: SHA_A,
133
+ },
134
+ commit: sidecar.commit,
135
+ committedAt: sidecar.committedAt,
136
+ installedAt: "2026-06-10T12:00:00.000Z",
137
+ fingerprint:
138
+ sidecar.baseline !== undefined
139
+ ? fingerprintOf(sidecar.baseline)
140
+ : null,
141
+ }),
142
+ );
143
+ }
144
+ }
145
+
146
+ let ws: string;
147
+ let pluginsDir: string;
148
+
149
+ beforeEach(() => {
150
+ ws = mkdtempSync(join(tmpdir(), "diff-plugin-"));
151
+ pluginsDir = join(ws, "plugins");
152
+ mkdirSync(pluginsDir, { recursive: true });
153
+ });
154
+
155
+ afterEach(() => {
156
+ rmSync(ws, { recursive: true, force: true });
157
+ });
158
+
159
+ describe("diffPlugin", () => {
160
+ test("reports a unified diff for a file edited since install", async () => {
161
+ // GIVEN a plugin whose install baseline declares a two-line file
162
+ const baseline: Tree = {
163
+ "package.json": '{"name":"level-up"}',
164
+ "src/skill.ts": "export const a = 1;\nexport const b = 2;\n",
165
+ };
166
+ // AND the on-disk copy edited the second line of that file
167
+ installCopy(
168
+ pluginsDir,
169
+ "level-up",
170
+ {
171
+ "package.json": '{"name":"level-up"}',
172
+ "src/skill.ts": "export const a = 1;\nexport const b = 99;\n",
173
+ },
174
+ { commit: SHA_A, baseline },
175
+ );
176
+
177
+ // WHEN the plugin is diffed against its install commit
178
+ const result = await diffPlugin(
179
+ { name: "level-up" },
180
+ {
181
+ fetch: makeFetch(),
182
+ runGit: fakeGitRunner(SHA_A, baseline),
183
+ workspacePluginsDir: pluginsDir,
184
+ },
185
+ );
186
+
187
+ // THEN the recorded baseline commit is reported as drifted
188
+ expect(result.commit).toBe(SHA_A);
189
+ expect(result.clean).toBe(false);
190
+ // AND exactly the edited file is surfaced as modified
191
+ expect(result.files).toHaveLength(1);
192
+ const [file] = result.files;
193
+ expect(file.path).toBe("src/skill.ts");
194
+ expect(file.status).toBe("modified");
195
+ expect(file.binary).toBe(false);
196
+ expect(file.reconstructed).toBe(true);
197
+ // AND the unified diff shows the old and new line
198
+ expect(file.diff).toContain("-export const b = 2;");
199
+ expect(file.diff).toContain("+export const b = 99;");
200
+ expect(file.diff).toContain("a/src/skill.ts");
201
+ expect(file.diff).toContain("b/src/skill.ts");
202
+ });
203
+
204
+ test("classifies added and removed files against the baseline", async () => {
205
+ // GIVEN a baseline with a single source file besides the manifest
206
+ const baseline: Tree = {
207
+ "package.json": '{"name":"level-up"}',
208
+ "src/old.ts": "export const gone = true;\n",
209
+ };
210
+ // AND an on-disk copy that dropped that file and added a new one
211
+ installCopy(
212
+ pluginsDir,
213
+ "level-up",
214
+ {
215
+ "package.json": '{"name":"level-up"}',
216
+ "src/new.ts": "export const added = true;\n",
217
+ },
218
+ { commit: SHA_A, baseline },
219
+ );
220
+
221
+ // WHEN the plugin is diffed
222
+ const result = await diffPlugin(
223
+ { name: "level-up" },
224
+ {
225
+ fetch: makeFetch(),
226
+ runGit: fakeGitRunner(SHA_A, baseline),
227
+ workspacePluginsDir: pluginsDir,
228
+ },
229
+ );
230
+
231
+ // THEN both the addition and the removal are reported, sorted by path
232
+ expect(result.clean).toBe(false);
233
+ expect(result.files.map((f) => [f.path, f.status])).toEqual([
234
+ ["src/new.ts", "added"],
235
+ ["src/old.ts", "removed"],
236
+ ]);
237
+ // AND each side diffs against /dev/null like git
238
+ const added = result.files[0];
239
+ const removed = result.files[1];
240
+ expect(added.diff).toContain("+export const added = true;");
241
+ expect(added.diff).toContain("/dev/null");
242
+ expect(removed.diff).toContain("-export const gone = true;");
243
+ expect(removed.diff).toContain("/dev/null");
244
+ });
245
+
246
+ test("reports clean when the on-disk tree matches the baseline", async () => {
247
+ // GIVEN a baseline and an identical on-disk copy
248
+ const tree: Tree = {
249
+ "package.json": '{"name":"level-up"}',
250
+ "src/skill.ts": "export const a = 1;\n",
251
+ };
252
+ installCopy(pluginsDir, "level-up", tree, {
253
+ commit: SHA_A,
254
+ baseline: tree,
255
+ });
256
+
257
+ // WHEN the plugin is diffed
258
+ const result = await diffPlugin(
259
+ { name: "level-up" },
260
+ {
261
+ fetch: makeFetch(),
262
+ runGit: fakeGitRunner(SHA_A, tree),
263
+ workspacePluginsDir: pluginsDir,
264
+ },
265
+ );
266
+
267
+ // THEN no drift is reported and the file list is empty
268
+ expect(result.clean).toBe(true);
269
+ expect(result.files).toHaveLength(0);
270
+ });
271
+
272
+ test("excludes the provenance sidecar from the diff", async () => {
273
+ // GIVEN a baseline (which never contains install-meta.json) and an on-disk
274
+ // copy identical to it apart from the sidecar install always writes
275
+ const tree: Tree = { "package.json": '{"name":"level-up"}' };
276
+ installCopy(pluginsDir, "level-up", tree, {
277
+ commit: SHA_A,
278
+ baseline: tree,
279
+ });
280
+
281
+ // WHEN the plugin is diffed
282
+ const result = await diffPlugin(
283
+ { name: "level-up" },
284
+ {
285
+ fetch: makeFetch(),
286
+ runGit: fakeGitRunner(SHA_A, tree),
287
+ workspacePluginsDir: pluginsDir,
288
+ },
289
+ );
290
+
291
+ // THEN the sidecar is not surfaced as a local addition
292
+ expect(result.clean).toBe(true);
293
+ expect(result.files.map((f) => f.path)).not.toContain("install-meta.json");
294
+ });
295
+
296
+ test("marks a drifted binary file instead of emitting a line diff", async () => {
297
+ // GIVEN a baseline binary blob and an on-disk copy with different bytes
298
+ const baseline: Tree = {
299
+ "package.json": '{"name":"level-up"}',
300
+ "assets/icon.bin": Buffer.from([0x00, 0x01, 0x02, 0x03]),
301
+ };
302
+ installCopy(
303
+ pluginsDir,
304
+ "level-up",
305
+ {
306
+ "package.json": '{"name":"level-up"}',
307
+ "assets/icon.bin": Buffer.from([0x00, 0xff, 0xfe, 0xfd]),
308
+ },
309
+ { commit: SHA_A, baseline },
310
+ );
311
+
312
+ // WHEN the plugin is diffed
313
+ const result = await diffPlugin(
314
+ { name: "level-up" },
315
+ {
316
+ fetch: makeFetch(),
317
+ runGit: fakeGitRunner(SHA_A, baseline),
318
+ workspacePluginsDir: pluginsDir,
319
+ },
320
+ );
321
+
322
+ // THEN the binary file is flagged rather than line-diffed
323
+ expect(result.files).toHaveLength(1);
324
+ const [file] = result.files;
325
+ expect(file.path).toBe("assets/icon.bin");
326
+ expect(file.binary).toBe(true);
327
+ expect(file.reconstructed).toBe(true);
328
+ expect(file.diff).toContain("Binary files differ");
329
+ });
330
+
331
+ test("flags a file whose install-time baseline cannot be reconstructed", async () => {
332
+ // GIVEN an install whose recorded fingerprint captured one version of a file
333
+ const recorded: Tree = {
334
+ "package.json": '{"name":"level-up"}',
335
+ "src/skill.ts": "export const v = 1;\n",
336
+ };
337
+ // AND an on-disk copy the user edited (drift vs the recorded baseline)
338
+ installCopy(
339
+ pluginsDir,
340
+ "level-up",
341
+ {
342
+ "package.json": '{"name":"level-up"}',
343
+ "src/skill.ts": "export const v = 2;\n",
344
+ },
345
+ { commit: SHA_A, baseline: recorded },
346
+ );
347
+ // AND a re-materialization that yields DIFFERENT bytes than were recorded
348
+ // (e.g. the curated adapter overlay moved since install), so the baseline
349
+ // bytes cannot be faithfully reconstructed for this file
350
+ const driftedClone: Tree = {
351
+ "package.json": '{"name":"level-up"}',
352
+ "src/skill.ts": "export const v = 3;\n",
353
+ };
354
+
355
+ // WHEN the plugin is diffed
356
+ const result = await diffPlugin(
357
+ { name: "level-up" },
358
+ {
359
+ fetch: makeFetch(),
360
+ runGit: fakeGitRunner(SHA_A, driftedClone),
361
+ workspacePluginsDir: pluginsDir,
362
+ },
363
+ );
364
+
365
+ // THEN the file is still reported as drifted (classified vs the recorded
366
+ // fingerprint, like inspect), but flagged rather than diffed against the
367
+ // fabricated baseline bytes
368
+ expect(result.clean).toBe(false);
369
+ expect(result.files).toHaveLength(1);
370
+ const [file] = result.files;
371
+ expect(file.path).toBe("src/skill.ts");
372
+ expect(file.status).toBe("modified");
373
+ expect(file.reconstructed).toBe(false);
374
+ expect(file.diff).toContain("Baseline unavailable");
375
+ // AND the re-materialized (wrong) bytes are never presented as the baseline
376
+ expect(file.diff).not.toContain("export const v = 3;");
377
+ });
378
+
379
+ test("throws PluginNotInstalledError when no copy is installed", async () => {
380
+ // GIVEN an empty plugins directory
381
+ // WHEN a plugin that was never installed is diffed
382
+ // THEN it reports the install is missing (git is never reached)
383
+ await expect(
384
+ diffPlugin(
385
+ { name: "level-up" },
386
+ {
387
+ fetch: makeFetch(),
388
+ runGit: unusedGitRunner,
389
+ workspacePluginsDir: pluginsDir,
390
+ },
391
+ ),
392
+ ).rejects.toBeInstanceOf(PluginNotInstalledError);
393
+ });
394
+
395
+ test("throws PluginDiffUnavailableError when the install recorded no commit", async () => {
396
+ // GIVEN an installed copy whose sidecar captured no commit
397
+ installCopy(
398
+ pluginsDir,
399
+ "level-up",
400
+ { "package.json": '{"name":"level-up"}' },
401
+ { commit: null },
402
+ );
403
+
404
+ // WHEN the plugin is diffed
405
+ // THEN there is no immutable baseline to re-materialize (git is never run)
406
+ await expect(
407
+ diffPlugin(
408
+ { name: "level-up" },
409
+ {
410
+ fetch: makeFetch(),
411
+ runGit: unusedGitRunner,
412
+ workspacePluginsDir: pluginsDir,
413
+ },
414
+ ),
415
+ ).rejects.toBeInstanceOf(PluginDiffUnavailableError);
416
+ });
417
+
418
+ test("throws PluginNotFoundError when the recorded commit yields no tree", async () => {
419
+ // GIVEN an installed copy with a recorded commit and fingerprint (so the
420
+ // baseline-classification guard passes and execution reaches the clone)
421
+ installCopy(
422
+ pluginsDir,
423
+ "level-up",
424
+ { "package.json": '{"name":"level-up"}' },
425
+ { commit: SHA_A, baseline: { "package.json": '{"name":"level-up"}' } },
426
+ );
427
+ // AND a clone that materializes no files (the commit/sub-path is gone)
428
+ const emptyClone = fakeGitRunner(SHA_A, {});
429
+
430
+ // WHEN the plugin is diffed
431
+ // THEN the missing baseline is surfaced as not-found
432
+ await expect(
433
+ diffPlugin(
434
+ { name: "level-up" },
435
+ {
436
+ fetch: makeFetch(),
437
+ runGit: emptyClone,
438
+ workspacePluginsDir: pluginsDir,
439
+ },
440
+ ),
441
+ ).rejects.toBeInstanceOf(PluginNotFoundError);
442
+ });
443
+ });
@@ -90,6 +90,8 @@ function installPlugin(
90
90
  } | null;
91
91
  /** Embed a content fingerprint of the materialized tree in the sidecar. */
92
92
  fingerprint?: boolean;
93
+ /** Materialize surface directories (`hooks/<name>.ts`, `skills/<id>/SKILL.md`). */
94
+ surfaces?: { hooks?: string[]; tools?: string[]; skills?: string[] };
93
95
  } = {},
94
96
  ): void {
95
97
  const dir = join(workspace, name);
@@ -102,6 +104,18 @@ function installPlugin(
102
104
  description: "Installed copy.",
103
105
  }),
104
106
  );
107
+ for (const hook of opts.surfaces?.hooks ?? []) {
108
+ mkdirSync(join(dir, "hooks"), { recursive: true });
109
+ writeFileSync(join(dir, "hooks", `${hook}.ts`), "export default () => {};");
110
+ }
111
+ for (const tool of opts.surfaces?.tools ?? []) {
112
+ mkdirSync(join(dir, "tools"), { recursive: true });
113
+ writeFileSync(join(dir, "tools", `${tool}.ts`), "export default {};");
114
+ }
115
+ for (const skill of opts.surfaces?.skills ?? []) {
116
+ mkdirSync(join(dir, "skills", skill), { recursive: true });
117
+ writeFileSync(join(dir, "skills", skill, "SKILL.md"), `# ${skill}`);
118
+ }
105
119
  if (opts.sidecar !== null) {
106
120
  const sidecar = opts.sidecar ?? { commit: SHA_A };
107
121
  // Mirror install: fingerprint the tree before the sidecar is written so it
@@ -367,6 +381,46 @@ describe("inspectPlugin", () => {
367
381
  expect(result.local?.localChanges).toBeNull();
368
382
  });
369
383
 
384
+ test("reports the surfaces an installed copy contributes", async () => {
385
+ // GIVEN an installed plugin shipping hooks and skills but no tools
386
+ installPlugin(workspace, "level-up", {
387
+ sidecar: { commit: SHA_A },
388
+ surfaces: {
389
+ hooks: ["post-model-call", "init"],
390
+ skills: ["second-skill", "first-skill"],
391
+ },
392
+ });
393
+ const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
394
+
395
+ // WHEN it is inspected
396
+ const result = await inspectPlugin(
397
+ { name: "level-up" },
398
+ { fetch, workspacePluginsDir: workspace },
399
+ );
400
+
401
+ // THEN the contributed surfaces are listed (sorted) and tools is empty
402
+ expect(result.surfaces).toEqual({
403
+ skills: ["first-skill", "second-skill"],
404
+ hooks: ["init", "post-model-call"],
405
+ tools: [],
406
+ });
407
+ });
408
+
409
+ test("leaves surfaces null when the plugin is not installed", async () => {
410
+ // GIVEN a marketplace entry with no local copy to walk
411
+ const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_B) });
412
+
413
+ // WHEN it is inspected
414
+ const result = await inspectPlugin(
415
+ { name: "level-up" },
416
+ { fetch, workspacePluginsDir: workspace },
417
+ );
418
+
419
+ // THEN there is no tree to read surfaces from
420
+ expect(result.installed).toBe(false);
421
+ expect(result.surfaces).toBeNull();
422
+ });
423
+
370
424
  test("throws when the plugin is neither installed nor in the marketplace", async () => {
371
425
  // GIVEN no local copy and an empty catalog
372
426
  const fetch = makeFetch({ marketplace: undefined });