@vellumai/assistant 0.5.10 → 0.5.12

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 (395) hide show
  1. package/AGENTS.md +8 -0
  2. package/ARCHITECTURE.md +43 -43
  3. package/Dockerfile +3 -0
  4. package/docs/architecture/integrations.md +37 -42
  5. package/docs/architecture/memory.md +7 -12
  6. package/docs/credential-execution-service.md +9 -9
  7. package/docs/skills.md +1 -1
  8. package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
  9. package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
  10. package/node_modules/@vellumai/credential-storage/src/index.ts +3 -3
  11. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
  12. package/openapi.yaml +7208 -0
  13. package/package.json +2 -1
  14. package/scripts/generate-openapi.ts +562 -0
  15. package/src/__tests__/acp-session.test.ts +239 -44
  16. package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
  17. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
  19. package/src/__tests__/browser-skill-endstate.test.ts +1 -1
  20. package/src/__tests__/btw-routes.test.ts +8 -0
  21. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
  22. package/src/__tests__/catalog-cache.test.ts +164 -0
  23. package/src/__tests__/catalog-search.test.ts +61 -0
  24. package/src/__tests__/channel-approvals.test.ts +7 -7
  25. package/src/__tests__/channel-readiness-service.test.ts +41 -0
  26. package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
  27. package/src/__tests__/config-schema.test.ts +10 -2
  28. package/src/__tests__/context-memory-e2e.test.ts +2 -6
  29. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
  30. package/src/__tests__/conversation-error.test.ts +3 -2
  31. package/src/__tests__/conversation-skill-tools.test.ts +1 -3
  32. package/src/__tests__/conversation-title-service.test.ts +2 -15
  33. package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
  34. package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
  35. package/src/__tests__/credential-security-e2e.test.ts +4 -4
  36. package/src/__tests__/credential-security-invariants.test.ts +12 -18
  37. package/src/__tests__/credential-vault-unit.test.ts +32 -34
  38. package/src/__tests__/credential-vault.test.ts +25 -33
  39. package/src/__tests__/credentials-cli.test.ts +3 -3
  40. package/src/__tests__/daemon-credential-client.test.ts +2 -2
  41. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
  42. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  43. package/src/__tests__/heartbeat-service.test.ts +35 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +79 -0
  45. package/src/__tests__/host-cu-proxy.test.ts +90 -0
  46. package/src/__tests__/host-file-proxy.test.ts +89 -0
  47. package/src/__tests__/host-shell-tool.test.ts +1 -1
  48. package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
  49. package/src/__tests__/integration-status.test.ts +5 -5
  50. package/src/__tests__/list-messages-attachments.test.ts +171 -0
  51. package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
  52. package/src/__tests__/log-export-workspace.test.ts +1 -1
  53. package/src/__tests__/mcp-abort-signal.test.ts +205 -0
  54. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  55. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  56. package/src/__tests__/memory-recall-log-store.test.ts +182 -0
  57. package/src/__tests__/memory-recall-quality.test.ts +6 -8
  58. package/src/__tests__/memory-regressions.test.ts +53 -42
  59. package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
  60. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  61. package/src/__tests__/messaging-skill-split.test.ts +2 -17
  62. package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
  63. package/src/__tests__/oauth-cli.test.ts +203 -649
  64. package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
  65. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  66. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  67. package/src/__tests__/platform-callback-registration.test.ts +119 -0
  68. package/src/__tests__/secret-ingress-channel.test.ts +261 -0
  69. package/src/__tests__/secret-ingress-cli.test.ts +201 -0
  70. package/src/__tests__/secret-ingress-http.test.ts +312 -0
  71. package/src/__tests__/secret-ingress.test.ts +283 -0
  72. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  73. package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
  74. package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
  75. package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
  76. package/src/__tests__/skill-feature-flags.test.ts +11 -19
  77. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  78. package/src/__tests__/skill-load-inline-command.test.ts +3 -3
  79. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  80. package/src/__tests__/skill-memory.test.ts +2 -4
  81. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
  82. package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
  83. package/src/__tests__/skills-uninstall.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +16 -2
  85. package/src/__tests__/slack-channel-config.test.ts +1 -1
  86. package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
  87. package/src/__tests__/slack-share-routes.test.ts +5 -5
  88. package/src/__tests__/slack-skill.test.ts +5 -69
  89. package/src/__tests__/system-prompt.test.ts +39 -0
  90. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
  91. package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
  92. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
  93. package/src/acp/client-handler.ts +113 -31
  94. package/src/acp/session-manager.ts +29 -27
  95. package/src/approvals/guardian-request-resolvers.ts +1 -1
  96. package/src/cli/AGENTS.md +113 -0
  97. package/src/cli/commands/autonomy.ts +3 -5
  98. package/src/cli/commands/browser-relay.ts +2 -17
  99. package/src/cli/commands/contacts.ts +6 -4
  100. package/src/cli/commands/conversations.ts +13 -1
  101. package/src/cli/commands/credential-execution.ts +17 -3
  102. package/src/cli/commands/credentials.ts +2 -8
  103. package/src/cli/commands/memory.ts +2 -3
  104. package/src/cli/commands/oauth/__tests__/connect.test.ts +706 -0
  105. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +686 -0
  106. package/src/cli/commands/oauth/__tests__/mode.test.ts +625 -0
  107. package/src/cli/commands/oauth/__tests__/ping.test.ts +631 -0
  108. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
  109. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
  110. package/src/cli/commands/oauth/__tests__/status.test.ts +551 -0
  111. package/src/cli/commands/oauth/__tests__/token.test.ts +420 -0
  112. package/src/cli/commands/oauth/apps.ts +87 -50
  113. package/src/cli/commands/oauth/connect.ts +405 -0
  114. package/src/cli/commands/oauth/disconnect.ts +285 -0
  115. package/src/cli/commands/oauth/index.ts +62 -20
  116. package/src/cli/commands/oauth/mode.ts +251 -0
  117. package/src/cli/commands/oauth/ping.ts +196 -0
  118. package/src/cli/commands/oauth/providers.ts +589 -55
  119. package/src/cli/commands/oauth/request.ts +564 -0
  120. package/src/cli/commands/oauth/shared.ts +114 -0
  121. package/src/cli/commands/oauth/status.ts +191 -0
  122. package/src/cli/commands/oauth/token.ts +150 -0
  123. package/src/cli/commands/platform/connect.ts +104 -0
  124. package/src/cli/commands/platform/disconnect.ts +118 -0
  125. package/src/cli/commands/platform/index.ts +252 -0
  126. package/src/cli/commands/sequence.ts +5 -4
  127. package/src/cli/commands/shotgun.ts +16 -0
  128. package/src/cli/commands/skills.ts +173 -41
  129. package/src/cli/commands/usage.ts +5 -11
  130. package/src/cli/lib/daemon-credential-client.ts +22 -38
  131. package/src/cli/program.ts +1 -1
  132. package/src/cli.ts +82 -17
  133. package/src/config/assistant-feature-flags.ts +77 -18
  134. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  135. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
  136. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  137. package/src/config/bundled-skills/conversations/SKILL.md +20 -0
  138. package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
  139. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
  140. package/src/config/bundled-skills/gmail/SKILL.md +13 -13
  141. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
  142. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
  143. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
  144. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
  145. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
  146. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
  147. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
  148. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
  149. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
  150. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  151. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
  152. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
  153. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
  154. package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
  155. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  156. package/src/config/bundled-skills/messaging/SKILL.md +19 -42
  157. package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
  158. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  159. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
  160. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
  161. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  162. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  163. package/src/config/bundled-skills/settings/SKILL.md +5 -3
  164. package/src/config/bundled-skills/settings/TOOLS.json +17 -0
  165. package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
  166. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
  167. package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
  168. package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
  169. package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
  170. package/src/config/bundled-skills/slack/SKILL.md +58 -44
  171. package/src/config/bundled-tool-registry.ts +7 -19
  172. package/src/config/env.ts +5 -1
  173. package/src/config/feature-flag-registry.json +58 -42
  174. package/src/config/loader.ts +4 -0
  175. package/src/config/schemas/platform.ts +0 -8
  176. package/src/config/schemas/security.ts +9 -1
  177. package/src/config/schemas/services.ts +1 -1
  178. package/src/config/skill-state.ts +1 -3
  179. package/src/config/skills.ts +2 -4
  180. package/src/credential-execution/client.ts +1 -1
  181. package/src/credential-execution/feature-gates.ts +9 -16
  182. package/src/credential-execution/process-manager.ts +12 -0
  183. package/src/daemon/config-watcher.ts +4 -0
  184. package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
  185. package/src/daemon/conversation-agent-loop.ts +51 -2
  186. package/src/daemon/conversation-error.ts +36 -6
  187. package/src/daemon/conversation-memory.ts +0 -1
  188. package/src/daemon/conversation-messaging.ts +9 -0
  189. package/src/daemon/conversation-runtime-assembly.ts +33 -0
  190. package/src/daemon/conversation-surfaces.ts +120 -14
  191. package/src/daemon/conversation.ts +5 -0
  192. package/src/daemon/handlers/config-slack-channel.ts +43 -1
  193. package/src/daemon/handlers/conversations.ts +41 -33
  194. package/src/daemon/handlers/skills.ts +148 -3
  195. package/src/daemon/host-bash-proxy.ts +16 -0
  196. package/src/daemon/host-cu-proxy.ts +16 -0
  197. package/src/daemon/host-file-proxy.ts +16 -0
  198. package/src/daemon/lifecycle.ts +73 -3
  199. package/src/daemon/message-types/acp.ts +0 -15
  200. package/src/daemon/message-types/conversations.ts +1 -0
  201. package/src/daemon/message-types/guardian-actions.ts +2 -0
  202. package/src/daemon/message-types/host-bash.ts +6 -1
  203. package/src/daemon/message-types/host-cu.ts +6 -1
  204. package/src/daemon/message-types/host-file.ts +6 -1
  205. package/src/daemon/message-types/integrations.ts +0 -1
  206. package/src/daemon/message-types/memory.ts +0 -1
  207. package/src/daemon/message-types/messages.ts +9 -1
  208. package/src/daemon/message-types/schedules.ts +9 -0
  209. package/src/daemon/server.ts +48 -9
  210. package/src/email/feature-gate.ts +3 -3
  211. package/src/heartbeat/heartbeat-service.ts +48 -0
  212. package/src/hooks/cli.ts +74 -0
  213. package/src/inbound/platform-callback-registration.ts +68 -19
  214. package/src/mcp/client.ts +6 -1
  215. package/src/mcp/manager.ts +2 -1
  216. package/src/mcp/mcp-oauth-provider.ts +3 -3
  217. package/src/memory/app-store.ts +3 -3
  218. package/src/memory/conversation-crud.ts +213 -0
  219. package/src/memory/conversation-key-store.ts +26 -0
  220. package/src/memory/conversation-title-service.ts +7 -17
  221. package/src/memory/db-init.ts +24 -0
  222. package/src/memory/embedding-local.ts +47 -2
  223. package/src/memory/indexer.ts +13 -10
  224. package/src/memory/items-extractor.ts +12 -4
  225. package/src/memory/job-utils.ts +5 -0
  226. package/src/memory/jobs-store.ts +10 -2
  227. package/src/memory/journal-memory.ts +6 -2
  228. package/src/memory/llm-request-log-store.ts +88 -21
  229. package/src/memory/memory-recall-log-store.ts +128 -0
  230. package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
  231. package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
  232. package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
  233. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
  234. package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
  235. package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
  236. package/src/memory/migrations/index.ts +6 -0
  237. package/src/memory/migrations/registry.ts +8 -0
  238. package/src/memory/retriever.test.ts +4 -5
  239. package/src/memory/schema/infrastructure.ts +31 -0
  240. package/src/memory/schema/oauth.ts +14 -0
  241. package/src/messaging/provider.ts +13 -12
  242. package/src/messaging/providers/gmail/adapter.ts +44 -35
  243. package/src/messaging/providers/slack/adapter.ts +63 -33
  244. package/src/messaging/providers/telegram-bot/adapter.ts +7 -9
  245. package/src/messaging/providers/whatsapp/adapter.ts +6 -8
  246. package/src/notifications/adapters/telegram.ts +78 -2
  247. package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
  248. package/src/oauth/byo-connection.test.ts +22 -24
  249. package/src/oauth/connect-orchestrator.ts +79 -64
  250. package/src/oauth/connect-types.ts +7 -65
  251. package/src/oauth/connection-resolver.test.ts +13 -13
  252. package/src/oauth/connection-resolver.ts +3 -4
  253. package/src/oauth/identity-verifier.ts +177 -0
  254. package/src/oauth/manual-token-connection.ts +5 -5
  255. package/src/oauth/oauth-store.ts +251 -5
  256. package/src/oauth/platform-connection.test.ts +56 -6
  257. package/src/oauth/platform-connection.ts +8 -1
  258. package/src/oauth/seed-providers.ts +256 -34
  259. package/src/permissions/checker.ts +129 -3
  260. package/src/permissions/trust-client.ts +2 -2
  261. package/src/platform/client.ts +2 -2
  262. package/src/prompts/journal-context.ts +6 -1
  263. package/src/prompts/system-prompt.ts +43 -9
  264. package/src/prompts/templates/BOOTSTRAP.md +16 -5
  265. package/src/providers/anthropic/client.ts +139 -28
  266. package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
  267. package/src/runtime/auth/route-policy.ts +0 -1
  268. package/src/runtime/btw-sidechain.ts +7 -1
  269. package/src/runtime/channel-approvals.ts +2 -2
  270. package/src/runtime/channel-readiness-service.ts +30 -7
  271. package/src/runtime/guardian-action-service.ts +7 -2
  272. package/src/runtime/http-router.ts +31 -0
  273. package/src/runtime/http-server.ts +26 -7
  274. package/src/runtime/http-types.ts +9 -0
  275. package/src/runtime/pending-interactions.ts +21 -3
  276. package/src/runtime/routes/acp-routes.ts +46 -28
  277. package/src/runtime/routes/app-management-routes.ts +123 -0
  278. package/src/runtime/routes/app-routes.ts +31 -0
  279. package/src/runtime/routes/approval-routes.ts +108 -3
  280. package/src/runtime/routes/attachment-routes.ts +45 -0
  281. package/src/runtime/routes/avatar-routes.ts +16 -0
  282. package/src/runtime/routes/brain-graph-routes.ts +18 -0
  283. package/src/runtime/routes/btw-routes.ts +20 -0
  284. package/src/runtime/routes/call-routes.ts +81 -0
  285. package/src/runtime/routes/channel-readiness-routes.ts +48 -7
  286. package/src/runtime/routes/channel-routes.ts +18 -0
  287. package/src/runtime/routes/channel-verification-routes.ts +49 -1
  288. package/src/runtime/routes/contact-routes.ts +77 -0
  289. package/src/runtime/routes/conversation-attention-routes.ts +37 -0
  290. package/src/runtime/routes/conversation-management-routes.ts +125 -0
  291. package/src/runtime/routes/conversation-query-routes.ts +78 -0
  292. package/src/runtime/routes/conversation-routes.ts +191 -39
  293. package/src/runtime/routes/conversation-starter-routes.ts +29 -0
  294. package/src/runtime/routes/debug-routes.ts +23 -0
  295. package/src/runtime/routes/diagnostics-routes.ts +30 -0
  296. package/src/runtime/routes/documents-routes.ts +42 -0
  297. package/src/runtime/routes/events-routes.ts +10 -0
  298. package/src/runtime/routes/global-search-routes.ts +35 -0
  299. package/src/runtime/routes/guardian-action-routes.ts +61 -3
  300. package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
  301. package/src/runtime/routes/heartbeat-routes.ts +278 -0
  302. package/src/runtime/routes/host-bash-routes.ts +16 -1
  303. package/src/runtime/routes/host-cu-routes.ts +23 -1
  304. package/src/runtime/routes/host-file-routes.ts +18 -1
  305. package/src/runtime/routes/identity-routes.ts +35 -0
  306. package/src/runtime/routes/inbound-message-handler.ts +46 -25
  307. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
  308. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
  309. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
  310. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  311. package/src/runtime/routes/integrations/twilio.ts +32 -22
  312. package/src/runtime/routes/invite-routes.ts +83 -0
  313. package/src/runtime/routes/log-export-routes.ts +14 -0
  314. package/src/runtime/routes/memory-item-routes.ts +99 -1
  315. package/src/runtime/routes/migration-rollback-routes.ts +25 -0
  316. package/src/runtime/routes/migration-routes.ts +40 -0
  317. package/src/runtime/routes/notification-routes.ts +20 -0
  318. package/src/runtime/routes/oauth-apps.ts +13 -4
  319. package/src/runtime/routes/pairing-routes.ts +15 -0
  320. package/src/runtime/routes/recording-routes.ts +72 -0
  321. package/src/runtime/routes/schedule-routes.ts +77 -5
  322. package/src/runtime/routes/secret-routes.ts +99 -14
  323. package/src/runtime/routes/settings-routes.ts +102 -19
  324. package/src/runtime/routes/skills-routes.ts +141 -18
  325. package/src/runtime/routes/subagents-routes.ts +38 -3
  326. package/src/runtime/routes/surface-action-routes.ts +66 -24
  327. package/src/runtime/routes/surface-content-routes.ts +20 -0
  328. package/src/runtime/routes/telemetry-routes.ts +12 -0
  329. package/src/runtime/routes/trace-event-routes.ts +25 -0
  330. package/src/runtime/routes/trust-rules-routes.ts +46 -0
  331. package/src/runtime/routes/tts-routes.ts +15 -4
  332. package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
  333. package/src/runtime/routes/usage-routes.ts +59 -0
  334. package/src/runtime/routes/watch-routes.ts +28 -0
  335. package/src/runtime/routes/work-items-routes.ts +59 -0
  336. package/src/runtime/routes/workspace-commit-routes.ts +12 -0
  337. package/src/runtime/routes/workspace-routes.ts +102 -0
  338. package/src/schedule/integration-status.ts +2 -2
  339. package/src/schedule/scheduler.ts +7 -1
  340. package/src/security/AGENTS.md +7 -0
  341. package/src/security/ces-rpc-credential-backend.ts +19 -16
  342. package/src/security/credential-backend.ts +1 -1
  343. package/src/security/encrypted-store.ts +3 -3
  344. package/src/security/oauth-completion-page.ts +153 -0
  345. package/src/security/oauth2.ts +58 -17
  346. package/src/security/secret-ingress.ts +174 -0
  347. package/src/security/secret-patterns.ts +133 -0
  348. package/src/security/secret-scanner.ts +28 -117
  349. package/src/security/secure-keys.ts +207 -7
  350. package/src/security/token-manager.ts +3 -6
  351. package/src/signals/bash.ts +6 -1
  352. package/src/signals/confirm.ts +12 -8
  353. package/src/signals/user-message.ts +18 -3
  354. package/src/skills/catalog-cache.ts +44 -0
  355. package/src/skills/catalog-search.ts +18 -0
  356. package/src/skills/skill-memory.ts +1 -2
  357. package/src/tasks/task-runner.ts +7 -1
  358. package/src/tools/credentials/broker.ts +1 -1
  359. package/src/tools/credentials/metadata-store.ts +1 -1
  360. package/src/tools/credentials/post-connect-hooks.ts +1 -1
  361. package/src/tools/credentials/vault.ts +36 -48
  362. package/src/tools/host-terminal/host-shell.ts +16 -3
  363. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  364. package/src/tools/memory/definitions.ts +1 -1
  365. package/src/tools/memory/handlers.test.ts +2 -4
  366. package/src/tools/skills/load.ts +1 -1
  367. package/src/tools/skills/sandbox-runner.ts +16 -3
  368. package/src/tools/terminal/safe-env.ts +7 -0
  369. package/src/tools/terminal/shell.ts +16 -3
  370. package/src/tools/tool-manifest.ts +1 -1
  371. package/src/util/log-redact.ts +9 -34
  372. package/src/util/logger.ts +11 -1
  373. package/src/util/sentry-log-stream.ts +51 -0
  374. package/src/watcher/providers/github.ts +2 -2
  375. package/src/watcher/providers/gmail.ts +1 -1
  376. package/src/watcher/providers/google-calendar.ts +1 -1
  377. package/src/watcher/providers/linear.ts +2 -2
  378. package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
  379. package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
  380. package/src/workspace/migrations/registry.ts +2 -0
  381. package/docs/architecture/keychain-broker.md +0 -68
  382. package/src/cli/commands/oauth/connections.ts +0 -734
  383. package/src/cli/commands/oauth/platform.ts +0 -525
  384. package/src/cli/commands/platform.ts +0 -176
  385. package/src/config/bundled-skills/slack/TOOLS.json +0 -272
  386. package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
  387. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
  388. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
  389. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
  390. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
  391. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
  392. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
  393. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
  394. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
  395. package/src/oauth/provider-behaviors.ts +0 -634
@@ -112,11 +112,11 @@ const GATEWAY_RETRIEVAL_BANLIST: Array<{
112
112
  },
113
113
  ];
114
114
 
115
- const KEYCHAIN_ALLOWLIST = new Set<string>([
116
- // Keep empty unless a keychain lookup instruction is intentionally required.
115
+ const CREDENTIAL_LOOKUP_ALLOWLIST = new Set<string>([
116
+ // Keep empty unless a credential lookup instruction is intentionally required.
117
117
  ]);
118
118
 
119
- const KEYCHAIN_PATTERNS = [
119
+ const CREDENTIAL_LOOKUP_PATTERNS = [
120
120
  "security find-generic-password",
121
121
  "secret-tool lookup service vellum-assistant account credential:",
122
122
  "secret-tool lookup service vellum-assistant account credential/",
@@ -134,7 +134,7 @@ const RETRIEVAL_MARKERS = [
134
134
  ];
135
135
 
136
136
  describe("bundled skill retrieval guard", () => {
137
- test("migrated skills do not reintroduce direct gateway/keychain retrieval snippets", () => {
137
+ test("migrated skills do not reintroduce direct gateway/credential lookup retrieval snippets", () => {
138
138
  const violations: string[] = [];
139
139
 
140
140
  for (const rule of GATEWAY_RETRIEVAL_BANLIST) {
@@ -152,7 +152,7 @@ describe("bundled skill retrieval guard", () => {
152
152
  if (violations.length > 0) {
153
153
  const message = [
154
154
  "Skill retrieval contract regression detected.",
155
- "Migrated skills must not reintroduce direct gateway/keychain retrieval snippets.",
155
+ "Migrated skills must not reintroduce direct gateway/credential lookup retrieval snippets.",
156
156
  "",
157
157
  "Violations:",
158
158
  ...violations.map((v) => ` - ${v}`),
@@ -162,14 +162,14 @@ describe("bundled skill retrieval guard", () => {
162
162
  }
163
163
  });
164
164
 
165
- test("skills do not contain direct keychain lookup instructions", () => {
165
+ test("skills do not contain direct credential lookup instructions", () => {
166
166
  const violations: string[] = [];
167
167
 
168
168
  for (const skillFile of ALL_SKILL_FILES) {
169
169
  const rel = relative(REPO_ROOT, skillFile).replaceAll("\\", "/");
170
- if (KEYCHAIN_ALLOWLIST.has(rel)) continue;
170
+ if (CREDENTIAL_LOOKUP_ALLOWLIST.has(rel)) continue;
171
171
  const content = readFileSync(skillFile, "utf-8");
172
- for (const pattern of KEYCHAIN_PATTERNS) {
172
+ for (const pattern of CREDENTIAL_LOOKUP_PATTERNS) {
173
173
  if (content.includes(pattern)) {
174
174
  violations.push(`${rel}: contains "${pattern}"`);
175
175
  }
@@ -178,13 +178,13 @@ describe("bundled skill retrieval guard", () => {
178
178
 
179
179
  if (violations.length > 0) {
180
180
  const message = [
181
- "Direct keychain lookup instructions were found in skills.",
181
+ "Direct credential lookup instructions were found in skills.",
182
182
  "Use credential_store and CLI/proxied flows instead.",
183
183
  "",
184
184
  "Violations:",
185
185
  ...violations.map((v) => ` - ${v}`),
186
186
  "",
187
- "Add intentional exceptions to KEYCHAIN_ALLOWLIST only when required.",
187
+ "Add intentional exceptions to CREDENTIAL_LOOKUP_ALLOWLIST only when required.",
188
188
  ].join("\n");
189
189
 
190
190
  expect(violations, message).toEqual([]);
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Unit tests for the catalog cache (catalog-cache.ts).
3
+ *
4
+ * Validates TTL-based caching, re-fetch after expiry, stale-cache fallback
5
+ * on fetch failure, and explicit cache invalidation.
6
+ */
7
+
8
+ import { afterEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ import type { CatalogSkill } from "../skills/catalog-install.js";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mocks — must be defined before importing the module under test
14
+ // ---------------------------------------------------------------------------
15
+
16
+ // Suppress logger output
17
+ mock.module("../util/logger.js", () => ({
18
+ getLogger: () =>
19
+ new Proxy({} as Record<string, unknown>, {
20
+ get: () => () => {},
21
+ }),
22
+ }));
23
+
24
+ let mockRepoSkillsDir: string | undefined = undefined;
25
+ let mockFetchCatalogResult: CatalogSkill[] = [];
26
+ let mockFetchCatalogError: Error | null = null;
27
+ let fetchCatalogCallCount = 0;
28
+ let readLocalCatalogCallCount = 0;
29
+
30
+ mock.module("../skills/catalog-install.js", () => ({
31
+ getRepoSkillsDir: () => mockRepoSkillsDir,
32
+ readLocalCatalog: (_dir: string) => {
33
+ readLocalCatalogCallCount++;
34
+ return mockFetchCatalogResult;
35
+ },
36
+ fetchCatalog: async () => {
37
+ fetchCatalogCallCount++;
38
+ if (mockFetchCatalogError) {
39
+ throw mockFetchCatalogError;
40
+ }
41
+ return mockFetchCatalogResult;
42
+ },
43
+ }));
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Imports (after mocks)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ import { getCatalog, invalidateCatalogCache } from "../skills/catalog-cache.js";
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Helpers
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const sampleCatalog: CatalogSkill[] = [
56
+ { id: "web-search", name: "Web Search", description: "Search the web" },
57
+ { id: "browser", name: "Browser", description: "Browse the web" },
58
+ ];
59
+
60
+ const updatedCatalog: CatalogSkill[] = [
61
+ { id: "web-search", name: "Web Search v2", description: "Updated search" },
62
+ ];
63
+
64
+ function resetState(): void {
65
+ invalidateCatalogCache();
66
+ mockRepoSkillsDir = undefined;
67
+ mockFetchCatalogResult = [];
68
+ mockFetchCatalogError = null;
69
+ fetchCatalogCallCount = 0;
70
+ readLocalCatalogCallCount = 0;
71
+ }
72
+
73
+ afterEach(resetState);
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Tests
77
+ // ---------------------------------------------------------------------------
78
+
79
+ describe("getCatalog", () => {
80
+ test("returns cached value within TTL without re-fetching", async () => {
81
+ mockFetchCatalogResult = sampleCatalog;
82
+
83
+ const first = await getCatalog();
84
+ expect(first).toEqual(sampleCatalog);
85
+ expect(fetchCatalogCallCount).toBe(1);
86
+
87
+ // Second call should use cache
88
+ const second = await getCatalog();
89
+ expect(second).toEqual(sampleCatalog);
90
+ expect(fetchCatalogCallCount).toBe(1); // no additional fetch
91
+ });
92
+
93
+ test("re-fetches after TTL expires", async () => {
94
+ mockFetchCatalogResult = sampleCatalog;
95
+
96
+ const first = await getCatalog();
97
+ expect(first).toEqual(sampleCatalog);
98
+ expect(fetchCatalogCallCount).toBe(1);
99
+
100
+ // Simulate TTL expiry by manipulating Date.now
101
+ const originalNow = Date.now;
102
+ Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
103
+
104
+ try {
105
+ mockFetchCatalogResult = updatedCatalog;
106
+ const second = await getCatalog();
107
+ expect(second).toEqual(updatedCatalog);
108
+ expect(fetchCatalogCallCount).toBe(2);
109
+ } finally {
110
+ Date.now = originalNow;
111
+ }
112
+ });
113
+
114
+ test("falls back to stale cache on fetch failure", async () => {
115
+ mockFetchCatalogResult = sampleCatalog;
116
+
117
+ // Populate cache
118
+ const first = await getCatalog();
119
+ expect(first).toEqual(sampleCatalog);
120
+
121
+ // Expire cache and make fetch fail
122
+ const originalNow = Date.now;
123
+ Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
124
+
125
+ try {
126
+ mockFetchCatalogError = new Error("Network timeout");
127
+ const fallback = await getCatalog();
128
+ expect(fallback).toEqual(sampleCatalog); // stale cache
129
+ } finally {
130
+ Date.now = originalNow;
131
+ }
132
+ });
133
+
134
+ test("returns empty array on fetch failure with no stale cache", async () => {
135
+ mockFetchCatalogError = new Error("Network timeout");
136
+
137
+ const result = await getCatalog();
138
+ expect(result).toEqual([]);
139
+ });
140
+
141
+ test("invalidateCatalogCache forces re-fetch", async () => {
142
+ mockFetchCatalogResult = sampleCatalog;
143
+
144
+ await getCatalog();
145
+ expect(fetchCatalogCallCount).toBe(1);
146
+
147
+ invalidateCatalogCache();
148
+
149
+ mockFetchCatalogResult = updatedCatalog;
150
+ const refreshed = await getCatalog();
151
+ expect(refreshed).toEqual(updatedCatalog);
152
+ expect(fetchCatalogCallCount).toBe(2);
153
+ });
154
+
155
+ test("uses local catalog when repoSkillsDir is set", async () => {
156
+ mockRepoSkillsDir = "/mock/repo/skills";
157
+ mockFetchCatalogResult = sampleCatalog;
158
+
159
+ const result = await getCatalog();
160
+ expect(result).toEqual(sampleCatalog);
161
+ expect(readLocalCatalogCallCount).toBe(1);
162
+ expect(fetchCatalogCallCount).toBe(0); // no remote fetch
163
+ });
164
+ });
@@ -0,0 +1,61 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { filterByQuery } from "../skills/catalog-search.js";
4
+
5
+ interface FakeSkill {
6
+ id: string;
7
+ name: string;
8
+ description: string;
9
+ }
10
+
11
+ const skills: FakeSkill[] = [
12
+ {
13
+ id: "weather",
14
+ name: "Weather Lookup",
15
+ description: "Get current weather for a city",
16
+ },
17
+ {
18
+ id: "search",
19
+ name: "Web Search",
20
+ description: "Search the web for information",
21
+ },
22
+ {
23
+ id: "deploy",
24
+ name: "Deploy Helper",
25
+ description: "Deploy apps to production",
26
+ },
27
+ ];
28
+
29
+ const fields: ((s: FakeSkill) => string)[] = [
30
+ (s) => s.id,
31
+ (s) => s.name,
32
+ (s) => s.description,
33
+ ];
34
+
35
+ describe("filterByQuery", () => {
36
+ test("case-insensitive matching", () => {
37
+ const result = filterByQuery(skills, "WEATHER", fields);
38
+ expect(result).toEqual([skills[0]]);
39
+ });
40
+
41
+ test("matches on any supplied field accessor", () => {
42
+ // Match on id
43
+ expect(filterByQuery(skills, "deploy", fields)).toEqual([skills[2]]);
44
+ // Match on name
45
+ expect(filterByQuery(skills, "Web Search", fields)).toEqual([skills[1]]);
46
+ // Match on description
47
+ expect(filterByQuery(skills, "production", fields)).toEqual([skills[2]]);
48
+ });
49
+
50
+ test("returns empty array for no matches", () => {
51
+ const result = filterByQuery(skills, "nonexistent", fields);
52
+ expect(result).toEqual([]);
53
+ });
54
+
55
+ test("returns all items for broad query", () => {
56
+ // All skills have "e" somewhere in their fields
57
+ const result = filterByQuery(skills, "e", fields);
58
+ expect(result).toHaveLength(3);
59
+ expect(result).toEqual(skills);
60
+ });
61
+ });
@@ -304,7 +304,7 @@ describe("handleChannelDecision", () => {
304
304
  expect(result.applied).toBe(true);
305
305
  expect(result.requestId).toBe("req-1");
306
306
  expect(
307
- interaction!.conversation.handleConfirmationResponse,
307
+ interaction!.conversation!.handleConfirmationResponse,
308
308
  ).toHaveBeenCalledWith("req-1", "allow");
309
309
  });
310
310
 
@@ -320,7 +320,7 @@ describe("handleChannelDecision", () => {
320
320
  expect(result.applied).toBe(true);
321
321
  expect(result.requestId).toBe("req-1");
322
322
  expect(
323
- interaction!.conversation.handleConfirmationResponse,
323
+ interaction!.conversation!.handleConfirmationResponse,
324
324
  ).toHaveBeenCalledWith("req-1", "deny");
325
325
  });
326
326
 
@@ -339,10 +339,10 @@ describe("handleChannelDecision", () => {
339
339
  expect(result.applied).toBe(true);
340
340
  expect(result.requestId).toBe("req-newer");
341
341
  expect(
342
- newerInteraction!.conversation.handleConfirmationResponse,
342
+ newerInteraction!.conversation!.handleConfirmationResponse,
343
343
  ).toHaveBeenCalledWith("req-newer", "allow");
344
344
  expect(
345
- olderInteraction!.conversation.handleConfirmationResponse,
345
+ olderInteraction!.conversation!.handleConfirmationResponse,
346
346
  ).not.toHaveBeenCalled();
347
347
  });
348
348
 
@@ -393,7 +393,7 @@ describe("handleChannelDecision", () => {
393
393
 
394
394
  // The session is approved with "allow"
395
395
  expect(
396
- interaction!.conversation.handleConfirmationResponse,
396
+ interaction!.conversation!.handleConfirmationResponse,
397
397
  ).toHaveBeenCalledWith("req-1", "allow");
398
398
 
399
399
  addRuleSpy.mockRestore();
@@ -420,7 +420,7 @@ describe("handleChannelDecision", () => {
420
420
  // The decision should still be applied as a one-time approval
421
421
  expect(result.applied).toBe(true);
422
422
  expect(
423
- interaction!.conversation.handleConfirmationResponse,
423
+ interaction!.conversation!.handleConfirmationResponse,
424
424
  ).toHaveBeenCalledWith("req-1", "allow");
425
425
 
426
426
  addRuleSpy.mockRestore();
@@ -446,7 +446,7 @@ describe("handleChannelDecision", () => {
446
446
  // The current invocation should still be approved (one-time allow)
447
447
  expect(result.applied).toBe(true);
448
448
  expect(
449
- interaction!.conversation.handleConfirmationResponse,
449
+ interaction!.conversation!.handleConfirmationResponse,
450
450
  ).toHaveBeenCalledWith("req-1", "allow");
451
451
 
452
452
  addRuleSpy.mockRestore();
@@ -1,9 +1,12 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import { credentialKey } from "../security/credential-key.js";
4
+
3
5
  let mockTwilioPhoneNumber: string | undefined;
4
6
  let mockRawConfig: Record<string, unknown> | undefined;
5
7
  let mockSecureKeys: Record<string, string>;
6
8
  let mockHasTwilioCredentials: boolean;
9
+ let mockShouldUsePlatformCallbacks: boolean;
7
10
 
8
11
  mock.module("../calls/twilio-rest.js", () => ({
9
12
  getPhoneNumberSid: async () => null,
@@ -40,6 +43,10 @@ mock.module("../email/service.js", () => ({
40
43
  }),
41
44
  }));
42
45
 
46
+ mock.module("../inbound/platform-callback-registration.js", () => ({
47
+ shouldUsePlatformCallbacks: () => mockShouldUsePlatformCallbacks,
48
+ }));
49
+
43
50
  mock.module("../security/secure-keys.js", () => ({
44
51
  getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
45
52
  }));
@@ -51,6 +58,7 @@ mock.module("../runtime/channel-invite-transports/whatsapp.js", () => ({
51
58
  import type { ChannelId } from "../channels/types.js";
52
59
  import {
53
60
  ChannelReadinessService,
61
+ createReadinessService,
54
62
  REMOTE_TTL_MS,
55
63
  } from "../runtime/channel-readiness-service.js";
56
64
  import type {
@@ -96,6 +104,7 @@ describe("ChannelReadinessService", () => {
96
104
  mockRawConfig = undefined;
97
105
  mockSecureKeys = {};
98
106
  mockHasTwilioCredentials = false;
107
+ mockShouldUsePlatformCallbacks = false;
99
108
  });
100
109
 
101
110
  test("local checks run on every call (no caching of local results)", async () => {
@@ -257,6 +266,38 @@ describe("ChannelReadinessService", () => {
257
266
  expect(snapshot.reasons).toHaveLength(0);
258
267
  });
259
268
 
269
+ test("telegram readiness accepts managed callback routing when ingress is absent", async () => {
270
+ mockShouldUsePlatformCallbacks = true;
271
+ mockSecureKeys[credentialKey("telegram", "bot_token")] = "123:abc";
272
+ mockSecureKeys[credentialKey("telegram", "webhook_secret")] = "secret";
273
+
274
+ const readiness = createReadinessService();
275
+ const [snapshot] = await readiness.getReadiness("telegram");
276
+
277
+ expect(snapshot.ready).toBe(true);
278
+ expect(snapshot.localChecks).toContainEqual({
279
+ name: "ingress",
280
+ passed: true,
281
+ message: "Managed platform callback routing is configured",
282
+ });
283
+ });
284
+
285
+ test("phone readiness accepts managed callback routing when ingress is absent", async () => {
286
+ mockShouldUsePlatformCallbacks = true;
287
+ mockHasTwilioCredentials = true;
288
+ mockTwilioPhoneNumber = "+15555550123";
289
+
290
+ const readiness = createReadinessService();
291
+ const [snapshot] = await readiness.getReadiness("phone");
292
+
293
+ expect(snapshot.ready).toBe(true);
294
+ expect(snapshot.localChecks).toContainEqual({
295
+ name: "ingress",
296
+ passed: true,
297
+ message: "Managed platform callback routing is configured",
298
+ });
299
+ });
300
+
260
301
  test("getReadiness with no channel returns all registered channels", async () => {
261
302
  service.registerProbe(
262
303
  makeProbe("phone", [{ name: "a", passed: true, message: "ok" }]),
@@ -1,7 +1,8 @@
1
- // Guard test: assistant CLI commands must always classify as Low risk.
1
+ // Guard test: assistant CLI commands must classify at the expected risk level.
2
2
  //
3
- // The assistant uses its own CLI tools during normal operation. If these
4
- // commands require user approval, it blocks autonomous assistant workflows.
3
+ // The assistant uses its own CLI tools during normal operation. Most commands
4
+ // should be Low risk so they don't block autonomous workflows. Certain
5
+ // sensitive subcommands are intentionally elevated to Medium or High.
5
6
  // See #18982 / #18998 for the regression that motivated this guard.
6
7
 
7
8
  import { mkdtempSync } from "node:fs";
@@ -60,10 +61,10 @@ function expectLowRisk(command: string, actual: RiskLevel): void {
60
61
  if (actual !== RiskLevel.Low) {
61
62
  throw new Error(
62
63
  `"${command}" classified as ${actual} instead of Low. ` +
63
- `assistant CLI commands must always be Low risk — the assistant ` +
64
+ `assistant CLI commands must be Low risk by default — the assistant ` +
64
65
  `uses its own CLI during normal operation. If you need risk ` +
65
- `escalation for specific subcommands, add them to an allowlist ` +
66
- `in this guard test with justification.`,
66
+ `escalation for specific subcommands, add them to the elevated ` +
67
+ `risk tests in this guard test with justification.`,
67
68
  );
68
69
  }
69
70
  expect(actual).toBe(RiskLevel.Low);
@@ -86,6 +87,9 @@ describe("CLI command risk guard: assistant commands", () => {
86
87
 
87
88
  test("all assistant CLI subcommands classify as Low risk", async () => {
88
89
  for (const subcommand of ASSISTANT_SUBCOMMANDS) {
90
+ // Subcommands with elevated children are tested separately below.
91
+ // The bare top-level subcommand (e.g. `assistant oauth`) is still
92
+ // expected to be Low.
89
93
  const command = `assistant ${subcommand}`;
90
94
  const risk = await classifyRisk("bash", { command });
91
95
  expectLowRisk(command, risk);
@@ -110,3 +114,174 @@ describe("CLI command risk guard: assistant commands", () => {
110
114
  }
111
115
  });
112
116
  });
117
+
118
+ // Sensitive subcommands that are intentionally elevated above Low risk.
119
+ // Each entry documents why the elevation is necessary.
120
+
121
+ describe("CLI command risk guard: elevated assistant subcommands", () => {
122
+ test("assistant oauth token is High risk (exposes raw tokens)", async () => {
123
+ const risk = await classifyRisk("bash", {
124
+ command: "assistant oauth token",
125
+ });
126
+ expect(risk).toBe(RiskLevel.High);
127
+ });
128
+
129
+ test("assistant oauth mode --set is High risk (changes auth mode)", async () => {
130
+ const risk = await classifyRisk("bash", {
131
+ command: "assistant oauth mode --set managed",
132
+ });
133
+ expect(risk).toBe(RiskLevel.High);
134
+ });
135
+
136
+ test("assistant oauth mode --set=value is High risk (equals syntax)", async () => {
137
+ const risk = await classifyRisk("bash", {
138
+ command: "assistant oauth mode google --set=managed",
139
+ });
140
+ expect(risk).toBe(RiskLevel.High);
141
+ });
142
+
143
+ test("assistant oauth mode without --set is Low risk (read-only)", async () => {
144
+ const risk = await classifyRisk("bash", {
145
+ command: "assistant oauth mode",
146
+ });
147
+ expect(risk).toBe(RiskLevel.Low);
148
+ });
149
+
150
+ test("assistant credentials reveal is High risk (exposes secrets)", async () => {
151
+ const risk = await classifyRisk("bash", {
152
+ command: "assistant credentials reveal",
153
+ });
154
+ expect(risk).toBe(RiskLevel.High);
155
+ });
156
+
157
+ test("assistant oauth request is Medium risk (initiates OAuth flow)", async () => {
158
+ const risk = await classifyRisk("bash", {
159
+ command: "assistant oauth request",
160
+ });
161
+ expect(risk).toBe(RiskLevel.Medium);
162
+ });
163
+
164
+ test("assistant oauth connect is Medium risk (modifies OAuth connections)", async () => {
165
+ const risk = await classifyRisk("bash", {
166
+ command: "assistant oauth connect",
167
+ });
168
+ expect(risk).toBe(RiskLevel.Medium);
169
+ });
170
+
171
+ test("assistant oauth disconnect is Medium risk (removes OAuth connections)", async () => {
172
+ const risk = await classifyRisk("bash", {
173
+ command: "assistant oauth disconnect",
174
+ });
175
+ expect(risk).toBe(RiskLevel.Medium);
176
+ });
177
+
178
+ test("--help on elevated subcommands is Low risk (read-only)", async () => {
179
+ const helpCommands = [
180
+ "assistant oauth token --help",
181
+ "assistant oauth mode --set --help",
182
+ "assistant credentials reveal --help",
183
+ "assistant oauth request --help",
184
+ "assistant oauth connect --help",
185
+ "assistant oauth disconnect -h",
186
+ ];
187
+
188
+ for (const command of helpCommands) {
189
+ const risk = await classifyRisk("bash", { command });
190
+ expectLowRisk(command, risk);
191
+ }
192
+ });
193
+
194
+ test("--help after -- option terminator does not downgrade risk", async () => {
195
+ const risk = await classifyRisk("bash", {
196
+ command: "assistant oauth token -- --help",
197
+ });
198
+ expect(risk).toBe(RiskLevel.High);
199
+ });
200
+
201
+ test("non-sensitive oauth subcommands remain Low risk", async () => {
202
+ const lowRiskOauthCommands = [
203
+ "assistant oauth apps",
204
+ "assistant oauth apps list",
205
+ "assistant oauth providers",
206
+ "assistant oauth status",
207
+ ];
208
+
209
+ for (const command of lowRiskOauthCommands) {
210
+ const risk = await classifyRisk("bash", { command });
211
+ expectLowRisk(command, risk);
212
+ }
213
+ });
214
+
215
+ test("non-sensitive credentials subcommands remain Low risk", async () => {
216
+ const lowRiskCredCommands = [
217
+ "assistant credentials",
218
+ "assistant credentials list",
219
+ ];
220
+
221
+ for (const command of lowRiskCredCommands) {
222
+ const risk = await classifyRisk("bash", { command });
223
+ expectLowRisk(command, risk);
224
+ }
225
+ });
226
+ });
227
+
228
+ describe("CLI command risk guard: wrapper program propagation", () => {
229
+ test("env assistant oauth token is High risk", async () => {
230
+ const risk = await classifyRisk("bash", {
231
+ command: "env assistant oauth token",
232
+ });
233
+ expect(risk).toBe(RiskLevel.High);
234
+ });
235
+
236
+ test("nice assistant credentials reveal is High risk", async () => {
237
+ const risk = await classifyRisk("bash", {
238
+ command: "nice assistant credentials reveal",
239
+ });
240
+ expect(risk).toBe(RiskLevel.High);
241
+ });
242
+
243
+ test("timeout 30 assistant oauth request is Medium risk", async () => {
244
+ const risk = await classifyRisk("bash", {
245
+ command: "timeout 30 assistant oauth request",
246
+ });
247
+ expect(risk).toBe(RiskLevel.Medium);
248
+ });
249
+
250
+ test("timeout 30 assistant oauth token is High risk", async () => {
251
+ const risk = await classifyRisk("bash", {
252
+ command: "timeout 30 assistant oauth token",
253
+ });
254
+ expect(risk).toBe(RiskLevel.High);
255
+ });
256
+
257
+ test("timeout 30 git push is Medium risk", async () => {
258
+ const risk = await classifyRisk("bash", {
259
+ command: "timeout 30 git push",
260
+ });
261
+ expect(risk).toBe(RiskLevel.Medium);
262
+ });
263
+
264
+ test("timeout 30 git status is Low risk", async () => {
265
+ const risk = await classifyRisk("bash", {
266
+ command: "timeout 30 git status",
267
+ });
268
+ expectLowRisk("timeout 30 git status", risk);
269
+ });
270
+
271
+ test("env assistant config is Low risk", async () => {
272
+ const risk = await classifyRisk("bash", {
273
+ command: "env assistant config",
274
+ });
275
+ expectLowRisk("env assistant config", risk);
276
+ });
277
+
278
+ test("env git push is Medium risk (not Low)", async () => {
279
+ const risk = await classifyRisk("bash", { command: "env git push" });
280
+ expect(risk).toBe(RiskLevel.Medium);
281
+ });
282
+
283
+ test("env git status is Low risk", async () => {
284
+ const risk = await classifyRisk("bash", { command: "env git status" });
285
+ expectLowRisk("env git status", risk);
286
+ });
287
+ });
@@ -135,6 +135,7 @@ describe("AssistantConfigSchema", () => {
135
135
  expect(result.secretDetection).toEqual({
136
136
  enabled: true,
137
137
  action: "redact",
138
+ blockIngress: true,
138
139
  entropyThreshold: 4.0,
139
140
  allowOneTimeSend: false,
140
141
  });
@@ -157,6 +158,7 @@ describe("AssistantConfigSchema", () => {
157
158
  secretDetection: {
158
159
  enabled: false,
159
160
  action: "block" as const,
161
+ blockIngress: false,
160
162
  entropyThreshold: 5.5,
161
163
  },
162
164
  auditLog: { retentionDays: 30 },
@@ -436,7 +438,10 @@ describe("AssistantConfigSchema", () => {
436
438
 
437
439
  test("defaults permissions.mode to workspace", () => {
438
440
  const result = AssistantConfigSchema.parse({});
439
- expect(result.permissions).toEqual({ mode: "workspace", dangerouslySkipPermissions: false });
441
+ expect(result.permissions).toEqual({
442
+ mode: "workspace",
443
+ dangerouslySkipPermissions: false,
444
+ });
440
445
  });
441
446
 
442
447
  test("accepts explicit permissions.mode strict", () => {
@@ -1141,7 +1146,10 @@ describe("loadConfig with schema validation", () => {
1141
1146
  test("defaults permissions.mode to workspace when not specified", () => {
1142
1147
  writeConfig({});
1143
1148
  const config = loadConfig();
1144
- expect(config.permissions).toEqual({ mode: "workspace", dangerouslySkipPermissions: false });
1149
+ expect(config.permissions).toEqual({
1150
+ mode: "workspace",
1151
+ dangerouslySkipPermissions: false,
1152
+ });
1145
1153
  });
1146
1154
 
1147
1155
  test("loads explicit permissions.mode strict", () => {