@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Tests for the `assistant memory worker` CLI subgroup.
3
+ *
4
+ * Validates:
5
+ * - Subcommand registration (start, stop, status) under `memory worker`.
6
+ * - `status` reports running/not_running via PID-file liveness.
7
+ * - `stop` sends SIGTERM to a live worker and errors when none is running.
8
+ * - `start` refuses to spawn when a worker is already running, and reports
9
+ * the PID once the spawned process writes its PID file.
10
+ */
11
+
12
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
16
+
17
+ import { Command } from "commander";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Mock state
21
+ // ---------------------------------------------------------------------------
22
+
23
+ let tmpDir: string;
24
+ let pidPath: string;
25
+ let logOutput: string[] = [];
26
+
27
+ /** Records (pid, signal) pairs passed to the mocked process.kill. */
28
+ let killCalls: Array<{ pid: number; signal: string | number }> = [];
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Mocks
32
+ // ---------------------------------------------------------------------------
33
+
34
+ mock.module("../../../../util/platform.js", () => ({
35
+ getMemoryWorkerPidPath: () => pidPath,
36
+ }));
37
+
38
+ const capture = (...args: unknown[]) => {
39
+ logOutput.push(args.map(String).join(" "));
40
+ };
41
+ const fakeLogger = {
42
+ info: capture,
43
+ warn: capture,
44
+ error: capture,
45
+ debug: () => {},
46
+ };
47
+ mock.module("../../../../util/logger.js", () => ({
48
+ getLogger: () => fakeLogger,
49
+ getCliLogger: () => fakeLogger,
50
+ }));
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Import module under test (after mocks)
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const { registerMemoryWorkerCommand } = await import("../worker.js");
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Test helpers
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function buildProgram(): Command {
63
+ const program = new Command();
64
+ program.exitOverride();
65
+ program.configureOutput({
66
+ writeErr: () => {},
67
+ writeOut: () => {},
68
+ });
69
+ const memory = program.command("memory");
70
+ registerMemoryWorkerCommand(memory);
71
+ return program;
72
+ }
73
+
74
+ async function runCommand(
75
+ args: string[],
76
+ ): Promise<{ stdout: string; exitCode: number }> {
77
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
78
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
79
+ const stdoutChunks: string[] = [];
80
+
81
+ process.stdout.write = ((chunk: unknown) => {
82
+ stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
83
+ return true;
84
+ }) as typeof process.stdout.write;
85
+
86
+ process.stderr.write = (() => true) as typeof process.stderr.write;
87
+
88
+ process.exitCode = 0;
89
+
90
+ try {
91
+ const program = buildProgram();
92
+ await program.parseAsync(["node", "assistant", ...args]);
93
+ } catch {
94
+ if (process.exitCode === 0) process.exitCode = 1;
95
+ } finally {
96
+ process.stdout.write = originalStdoutWrite;
97
+ process.stderr.write = originalStderrWrite;
98
+ }
99
+
100
+ const exitCode = process.exitCode ?? 0;
101
+ process.exitCode = 0;
102
+
103
+ return { exitCode, stdout: stdoutChunks.join("") };
104
+ }
105
+
106
+ /**
107
+ * Replace process.kill with a recording stub. `signal 0` is the liveness
108
+ * probe: it resolves for `livePids` and throws ESRCH otherwise. Other signals
109
+ * are recorded and no-oped so the test runner is never actually signalled.
110
+ */
111
+ function stubProcessKill(livePids: Set<number>): () => void {
112
+ const original = process.kill.bind(process);
113
+ killCalls = [];
114
+ process.kill = ((pid: number, signal?: string | number) => {
115
+ const sig = signal ?? 0;
116
+ if (sig === 0) {
117
+ if (livePids.has(pid)) return true;
118
+ const err = new Error("kill ESRCH") as NodeJS.ErrnoException;
119
+ err.code = "ESRCH";
120
+ throw err;
121
+ }
122
+ killCalls.push({ pid, signal: sig });
123
+ return true;
124
+ }) as typeof process.kill;
125
+ return () => {
126
+ process.kill = original;
127
+ };
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Setup
132
+ // ---------------------------------------------------------------------------
133
+
134
+ beforeEach(() => {
135
+ tmpDir = mkdtempSync(join(tmpdir(), "memory-worker-test-"));
136
+ pidPath = join(tmpDir, "memory-worker.pid");
137
+ logOutput = [];
138
+ killCalls = [];
139
+ process.exitCode = 0;
140
+ });
141
+
142
+ afterEach(() => {
143
+ rmSync(tmpDir, { recursive: true, force: true });
144
+ });
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // Subcommand registration
148
+ // ---------------------------------------------------------------------------
149
+
150
+ describe("subcommand registration", () => {
151
+ test("registers worker under memory with start/stop/status", () => {
152
+ const program = buildProgram();
153
+ const memory = program.commands.find((c) => c.name() === "memory");
154
+ expect(memory).toBeDefined();
155
+ const worker = memory!.commands.find((c) => c.name() === "worker");
156
+ expect(worker).toBeDefined();
157
+ const subcommandNames = worker!.commands.map((c) => c.name()).sort();
158
+ expect(subcommandNames).toEqual(["start", "status", "stop"]);
159
+ });
160
+ });
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // status
164
+ // ---------------------------------------------------------------------------
165
+
166
+ describe("memory worker status", () => {
167
+ test("reports not_running when no PID file exists", async () => {
168
+ const { exitCode, stdout } = await runCommand([
169
+ "memory",
170
+ "worker",
171
+ "status",
172
+ "--json",
173
+ ]);
174
+ expect(exitCode).toBe(0);
175
+ expect(JSON.parse(stdout)).toEqual({ status: "not_running" });
176
+ });
177
+
178
+ test("reports running when PID file points at a live process", async () => {
179
+ writeFileSync(pidPath, String(process.pid));
180
+ const restore = stubProcessKill(new Set([process.pid]));
181
+ try {
182
+ const { exitCode, stdout } = await runCommand([
183
+ "memory",
184
+ "worker",
185
+ "status",
186
+ "--json",
187
+ ]);
188
+ expect(exitCode).toBe(0);
189
+ expect(JSON.parse(stdout)).toEqual({
190
+ status: "running",
191
+ pid: process.pid,
192
+ });
193
+ } finally {
194
+ restore();
195
+ }
196
+ });
197
+
198
+ test("treats a stale PID file as not_running and cleans it up", async () => {
199
+ writeFileSync(pidPath, "999999");
200
+ const restore = stubProcessKill(new Set());
201
+ try {
202
+ const { exitCode, stdout } = await runCommand([
203
+ "memory",
204
+ "worker",
205
+ "status",
206
+ "--json",
207
+ ]);
208
+ expect(exitCode).toBe(0);
209
+ expect(JSON.parse(stdout)).toEqual({ status: "not_running" });
210
+ expect(existsSync(pidPath)).toBe(false);
211
+ } finally {
212
+ restore();
213
+ }
214
+ });
215
+ });
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // stop
219
+ // ---------------------------------------------------------------------------
220
+
221
+ describe("memory worker stop", () => {
222
+ test("errors with exit 1 when no worker is running", async () => {
223
+ const { exitCode, stdout } = await runCommand([
224
+ "memory",
225
+ "worker",
226
+ "stop",
227
+ "--json",
228
+ ]);
229
+ expect(exitCode).toBe(1);
230
+ expect(JSON.parse(stdout)).toMatchObject({ ok: false });
231
+ });
232
+
233
+ test("sends SIGTERM to a running worker", async () => {
234
+ writeFileSync(pidPath, String(process.pid));
235
+ const restore = stubProcessKill(new Set([process.pid]));
236
+ try {
237
+ const { exitCode, stdout } = await runCommand([
238
+ "memory",
239
+ "worker",
240
+ "stop",
241
+ "--json",
242
+ ]);
243
+ expect(exitCode).toBe(0);
244
+ expect(JSON.parse(stdout)).toEqual({ ok: true, pid: process.pid });
245
+ expect(killCalls).toContainEqual({
246
+ pid: process.pid,
247
+ signal: "SIGTERM",
248
+ });
249
+ } finally {
250
+ restore();
251
+ }
252
+ });
253
+ });
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // start
257
+ // ---------------------------------------------------------------------------
258
+
259
+ describe("memory worker start", () => {
260
+ test("refuses to start when a worker is already running", async () => {
261
+ writeFileSync(pidPath, String(process.pid));
262
+ const restore = stubProcessKill(new Set([process.pid]));
263
+ try {
264
+ const { exitCode, stdout } = await runCommand([
265
+ "memory",
266
+ "worker",
267
+ "start",
268
+ "--json",
269
+ ]);
270
+ expect(exitCode).toBe(1);
271
+ expect(JSON.parse(stdout)).toMatchObject({
272
+ ok: false,
273
+ pid: process.pid,
274
+ });
275
+ } finally {
276
+ restore();
277
+ }
278
+ });
279
+
280
+ test("spawns the worker and reports the PID it writes", async () => {
281
+ const restore = stubProcessKill(new Set());
282
+ const originalSpawn = Bun.spawn;
283
+ // Simulate the spawned worker writing its PID file on startup.
284
+ (Bun as { spawn: typeof Bun.spawn }).spawn = (() => {
285
+ writeFileSync(pidPath, "424242");
286
+ return { unref: () => {}, pid: 424242 };
287
+ }) as unknown as typeof Bun.spawn;
288
+ try {
289
+ const { exitCode, stdout } = await runCommand([
290
+ "memory",
291
+ "worker",
292
+ "start",
293
+ "--json",
294
+ ]);
295
+ expect(exitCode).toBe(0);
296
+ expect(JSON.parse(stdout)).toMatchObject({ ok: true, pid: 424242 });
297
+ } finally {
298
+ (Bun as { spawn: typeof Bun.spawn }).spawn = originalSpawn;
299
+ restore();
300
+ }
301
+ });
302
+ });
@@ -4,6 +4,7 @@ import { registerCommand } from "../../lib/register-command.js";
4
4
  import { registerMemoryRetrospectiveCommand } from "./memory-retrospective.js";
5
5
  import { registerMemoryV2Command } from "./memory-v2.js";
6
6
  import { registerMemoryV3Command } from "./memory-v3.js";
7
+ import { registerMemoryWorkerCommand } from "./worker.js";
7
8
 
8
9
  export function registerMemoryCommand(program: Command): void {
9
10
  registerCommand(program, {
@@ -27,6 +28,7 @@ Examples:
27
28
  registerMemoryV2Command(memory);
28
29
  registerMemoryV3Command(memory);
29
30
  registerMemoryRetrospectiveCommand(memory);
31
+ registerMemoryWorkerCommand(memory);
30
32
  },
31
33
  });
32
34
  }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * `assistant memory worker` CLI subgroup.
3
+ *
4
+ * Manages the memory jobs worker as its own OS process — separate from the
5
+ * daemon's main event loop. This prevents long-running embedding jobs from
6
+ * blocking user-facing HTTP traffic.
7
+ *
8
+ * Subcommands:
9
+ *
10
+ * - `start` — spawn the worker process (detached, background).
11
+ * - `stop` — send SIGTERM to the running worker process.
12
+ * - `status` — report whether the worker process is running.
13
+ *
14
+ * All three run directly in the CLI process (transport: "local") — no IPC
15
+ * round-trip to the daemon.
16
+ */
17
+
18
+ import type { Command } from "commander";
19
+
20
+ import {
21
+ probeMemoryWorker,
22
+ spawnMemoryWorkerProcess,
23
+ } from "../../../memory/worker-control.js";
24
+ import { getMemoryWorkerPidPath } from "../../../util/platform.js";
25
+ import { registerCommand } from "../../lib/register-command.js";
26
+ import { log } from "../../logger.js";
27
+ import { shouldOutputJson, writeOutput } from "../../output.js";
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // `start`
31
+ // ---------------------------------------------------------------------------
32
+
33
+ async function startWorker(
34
+ opts: { json?: boolean },
35
+ cmd: Command,
36
+ ): Promise<void> {
37
+ let result: { pid: number; alreadyRunning: boolean };
38
+ try {
39
+ result = await spawnMemoryWorkerProcess();
40
+ } catch (err) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ if (shouldOutputJson(cmd)) {
43
+ writeOutput(cmd, { ok: false, error: msg });
44
+ } else {
45
+ log.error(msg);
46
+ }
47
+ process.exitCode = 1;
48
+ return;
49
+ }
50
+
51
+ if (result.alreadyRunning) {
52
+ const msg = `Memory worker is already running (PID ${result.pid})`;
53
+ if (shouldOutputJson(cmd)) {
54
+ writeOutput(cmd, { ok: false, error: msg, pid: result.pid });
55
+ } else {
56
+ log.error(msg);
57
+ }
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+
62
+ if (shouldOutputJson(cmd)) {
63
+ writeOutput(cmd, {
64
+ ok: true,
65
+ pid: result.pid,
66
+ pidPath: getMemoryWorkerPidPath(),
67
+ });
68
+ } else {
69
+ log.info(`Memory worker started (PID ${result.pid})`);
70
+ }
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // `stop`
75
+ // ---------------------------------------------------------------------------
76
+
77
+ function stopWorker(opts: { json?: boolean }, cmd: Command): void {
78
+ const current = probeMemoryWorker();
79
+ if (current.status !== "running" || current.pid == null) {
80
+ const msg = "Memory worker is not running";
81
+ if (shouldOutputJson(cmd)) {
82
+ writeOutput(cmd, { ok: false, error: msg });
83
+ } else {
84
+ log.error(msg);
85
+ }
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+
90
+ const pid = current.pid;
91
+ try {
92
+ process.kill(pid, "SIGTERM");
93
+ } catch (err) {
94
+ const msg = err instanceof Error ? err.message : String(err);
95
+ if (shouldOutputJson(cmd)) {
96
+ writeOutput(cmd, { ok: false, error: msg, pid });
97
+ } else {
98
+ log.error(`Failed to stop memory worker (PID ${pid}): ${msg}`);
99
+ }
100
+ process.exitCode = 1;
101
+ return;
102
+ }
103
+
104
+ if (shouldOutputJson(cmd)) {
105
+ writeOutput(cmd, { ok: true, pid });
106
+ } else {
107
+ log.info(`Memory worker stop signal sent (PID ${pid})`);
108
+ }
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // `status`
113
+ // ---------------------------------------------------------------------------
114
+
115
+ function statusWorker(opts: { json?: boolean }, cmd: Command): void {
116
+ const result = probeMemoryWorker();
117
+ if (shouldOutputJson(cmd)) {
118
+ writeOutput(cmd, result);
119
+ } else {
120
+ if (result.status === "running") {
121
+ log.info(`Memory worker is running (PID ${result.pid})`);
122
+ } else {
123
+ log.info("Memory worker is not running");
124
+ }
125
+ }
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Registration
130
+ // ---------------------------------------------------------------------------
131
+
132
+ export function registerMemoryWorkerCommand(memory: Command): void {
133
+ registerCommand(memory, {
134
+ name: "worker",
135
+ transport: "local",
136
+ description: "Manage the memory jobs worker process (start/stop/status)",
137
+ build: (worker) => {
138
+ worker.addHelpText(
139
+ "after",
140
+ `
141
+ The memory worker processes embedding, consolidation, and cleanup jobs in a
142
+ separate OS process so they do not block the daemon's main event loop.
143
+
144
+ Examples:
145
+ $ assistant memory worker start
146
+ $ assistant memory worker status
147
+ $ assistant memory worker stop`,
148
+ );
149
+
150
+ worker
151
+ .command("start")
152
+ .description("Start the memory worker as a background process")
153
+ .option("--json", "Emit raw JSON instead of a formatted summary")
154
+ .action(async (opts: { json?: boolean }, cmd: Command) => {
155
+ await startWorker(opts, cmd);
156
+ });
157
+
158
+ worker
159
+ .command("stop")
160
+ .description("Stop the running memory worker process")
161
+ .option("--json", "Emit raw JSON instead of a formatted summary")
162
+ .action((opts: { json?: boolean }, cmd: Command) => {
163
+ stopWorker(opts, cmd);
164
+ });
165
+
166
+ worker
167
+ .command("status")
168
+ .description("Check whether the memory worker process is running")
169
+ .option("--json", "Emit raw JSON instead of a formatted summary")
170
+ .action((opts: { json?: boolean }, cmd: Command) => {
171
+ statusWorker(opts, cmd);
172
+ });
173
+ },
174
+ });
175
+ }
@@ -29,7 +29,11 @@ import {
29
29
  PluginAlreadyInstalledError,
30
30
  PluginNotFoundError,
31
31
  } from "../lib/install-from-github.js";
32
- import { listInstalledPlugins } from "../lib/list-installed-plugins.js";
32
+ import {
33
+ type AllPluginInfo,
34
+ listAllPlugins,
35
+ listInstalledPlugins,
36
+ } from "../lib/list-installed-plugins.js";
33
37
  import type { FingerprintComparison } from "../lib/plugin-fingerprint.js";
34
38
  import {
35
39
  DEFAULT_PIN_HISTORY_LIMIT,
@@ -85,6 +89,8 @@ Examples:
85
89
  $ assistant plugins install example --pin <sha> --force
86
90
  $ assistant plugins list
87
91
  $ assistant plugins list --json
92
+ $ assistant plugins list --all
93
+ $ assistant plugins list --all --json
88
94
  $ assistant plugins inspect example
89
95
  $ assistant plugins inspect example --json
90
96
  $ assistant plugins diff example
@@ -239,9 +245,63 @@ Examples:
239
245
 
240
246
  plugins
241
247
  .command("list")
242
- .description("List plugins installed under <workspaceDir>/plugins/")
248
+ .description(
249
+ "List plugins installed in your workspace.",
250
+ )
243
251
  .option("--json", "Emit machine-readable JSON instead of a table")
244
- .action((opts: { json?: boolean }) => {
252
+ .option(
253
+ "--all",
254
+ "Include first-party default plugins and disabled plugins in the listing",
255
+ )
256
+ .action((opts: { json?: boolean; all?: boolean }) => {
257
+ if (opts.all) {
258
+ const all = listAllPlugins();
259
+
260
+ if (opts.json) {
261
+ process.stdout.write(JSON.stringify(all, null, 2) + "\n");
262
+ return;
263
+ }
264
+
265
+ if (all.length === 0) {
266
+ console.log("No plugins found.");
267
+ return;
268
+ }
269
+
270
+ const rows = all.map((p) => ({
271
+ name: p.name,
272
+ version: p.packageJson?.version ?? "—",
273
+ source: p.source,
274
+ status: formatAllPluginStatus(p),
275
+ }));
276
+ const nameW = Math.max(4, ...rows.map((r) => r.name.length));
277
+ const versionW = Math.max(7, ...rows.map((r) => r.version.length));
278
+ const sourceW = Math.max(6, ...rows.map((r) => r.source.length));
279
+ const pad = (s: string, w: number) =>
280
+ s + " ".repeat(w - s.length);
281
+ console.log(
282
+ `${pad("NAME", nameW)} ${pad("VERSION", versionW)} ${pad("SOURCE", sourceW)} STATUS`,
283
+ );
284
+ for (const r of rows) {
285
+ console.log(
286
+ `${pad(r.name, nameW)} ${pad(r.version, versionW)} ${pad(r.source, sourceW)} ${r.status}`,
287
+ );
288
+ }
289
+
290
+ const userCount = all.filter((p) => p.source === "user").length;
291
+ const defaultCount = all.length - userCount;
292
+ const disabledCount = all.filter((p) => p.disabled).length;
293
+ console.log("");
294
+ console.log(
295
+ `${all.length} plugin${all.length === 1 ? "" : "s"} ` +
296
+ `(${userCount} user, ${defaultCount} default` +
297
+ (disabledCount > 0
298
+ ? `, ${disabledCount} disabled`
299
+ : "") +
300
+ `).`,
301
+ );
302
+ return;
303
+ }
304
+
245
305
  const installed = listInstalledPlugins();
246
306
 
247
307
  if (opts.json) {
@@ -757,6 +817,18 @@ function formatTimestamp(iso: string | null): string {
757
817
  return new Date(ms).toISOString().slice(0, 19);
758
818
  }
759
819
 
820
+ /**
821
+ * Build a human-readable status string for a plugin in the `--all` listing.
822
+ * Combines disabled state with any structural issues.
823
+ */
824
+ function formatAllPluginStatus(p: AllPluginInfo): string {
825
+ const parts: string[] = [];
826
+ if (p.disabled) parts.push("disabled");
827
+ if (p.issues.length > 0) parts.push(p.issues.join("; "));
828
+ if (parts.length === 0) parts.push("enabled");
829
+ return parts.join(", ");
830
+ }
831
+
760
832
  /** Human-readable status line for an inspection result. The from/to revisions
761
833
  * now live in the installed/remote blocks, so the status itself is just words. */
762
834
  function statusLine(status: PluginInspection["status"]): string {