@vellumai/assistant 0.5.6 → 0.5.8

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 (442) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +3 -2
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docker-entrypoint.sh +9 -0
  7. package/docs/architecture/keychain-broker.md +45 -240
  8. package/docs/architecture/memory.md +13 -11
  9. package/docs/architecture/security.md +0 -17
  10. package/docs/credential-execution-service.md +2 -2
  11. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  13. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  14. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  15. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  16. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
  17. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  18. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  19. package/package.json +2 -3
  20. package/src/__tests__/actor-token-service.test.ts +0 -114
  21. package/src/__tests__/approval-cascade.test.ts +0 -1
  22. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  23. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  24. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  25. package/src/__tests__/btw-routes.test.ts +0 -39
  26. package/src/__tests__/call-controller.test.ts +0 -1
  27. package/src/__tests__/call-domain.test.ts +0 -128
  28. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  29. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  31. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  32. package/src/__tests__/checker.test.ts +4 -2
  33. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  34. package/src/__tests__/config-schema-cmd.test.ts +0 -2
  35. package/src/__tests__/config-schema.test.ts +3 -1
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  37. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  38. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  39. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  40. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  41. package/src/__tests__/conversation-error.test.ts +15 -1
  42. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  43. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  44. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  45. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  46. package/src/__tests__/conversation-queue.test.ts +0 -1
  47. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  48. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  49. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  50. package/src/__tests__/conversation-title-service.test.ts +87 -0
  51. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  53. package/src/__tests__/credential-execution-client.test.ts +5 -2
  54. package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
  55. package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
  56. package/src/__tests__/credential-security-e2e.test.ts +1 -67
  57. package/src/__tests__/credential-security-invariants.test.ts +6 -50
  58. package/src/__tests__/credentials-cli.test.ts +82 -3
  59. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  60. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  62. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  63. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  64. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  65. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  66. package/src/__tests__/host-shell-tool.test.ts +6 -7
  67. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  68. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  69. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  70. package/src/__tests__/intent-routing.test.ts +0 -13
  71. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  72. package/src/__tests__/journal-context.test.ts +335 -0
  73. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  74. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  75. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  76. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  77. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  78. package/src/__tests__/memory-regressions.test.ts +408 -363
  79. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  80. package/src/__tests__/migration-export-http.test.ts +2 -2
  81. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  82. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  83. package/src/__tests__/migration-validate-http.test.ts +2 -2
  84. package/src/__tests__/non-member-access-request.test.ts +2 -7
  85. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  86. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  87. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  88. package/src/__tests__/oauth-cli.test.ts +5 -1
  89. package/src/__tests__/permission-types.test.ts +1 -0
  90. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  91. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  93. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  94. package/src/__tests__/qdrant-manager.test.ts +28 -2
  95. package/src/__tests__/registry.test.ts +0 -6
  96. package/src/__tests__/relay-server.test.ts +1 -2
  97. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  98. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  99. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  100. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  101. package/src/__tests__/secure-keys.test.ts +95 -272
  102. package/src/__tests__/shell-identity.test.ts +96 -6
  103. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  104. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  105. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  106. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  107. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  108. package/src/__tests__/skill-load-tool.test.ts +0 -2
  109. package/src/__tests__/skill-memory.test.ts +17 -3
  110. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  111. package/src/__tests__/skills.test.ts +0 -2
  112. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  113. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  114. package/src/__tests__/stt-hints.test.ts +437 -0
  115. package/src/__tests__/suggestion-routes.test.ts +1 -32
  116. package/src/__tests__/system-prompt.test.ts +0 -1
  117. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  118. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  119. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  120. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  121. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  122. package/src/__tests__/update-bulletin.test.ts +0 -2
  123. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  124. package/src/__tests__/voice-quality.test.ts +58 -0
  125. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
  126. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  127. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
  128. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  129. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  130. package/src/acp/agent-process.ts +9 -1
  131. package/src/agent/loop.ts +1 -1
  132. package/src/approvals/guardian-request-resolvers.ts +164 -38
  133. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  134. package/src/calls/audio-store.test.ts +97 -0
  135. package/src/calls/audio-store.ts +205 -0
  136. package/src/calls/call-controller.ts +90 -8
  137. package/src/calls/call-domain.ts +3 -0
  138. package/src/calls/call-store.ts +10 -3
  139. package/src/calls/fish-audio-client.ts +129 -0
  140. package/src/calls/relay-server.ts +27 -0
  141. package/src/calls/stt-hints.ts +189 -0
  142. package/src/calls/tts-text-sanitizer.ts +61 -0
  143. package/src/calls/twilio-routes.ts +34 -5
  144. package/src/calls/types.ts +1 -0
  145. package/src/calls/voice-ingress-preflight.ts +0 -42
  146. package/src/calls/voice-quality.ts +38 -5
  147. package/src/calls/voice-session-bridge.ts +7 -12
  148. package/src/cli/commands/avatar.ts +2 -2
  149. package/src/cli/commands/config.ts +1 -4
  150. package/src/cli/commands/credentials.ts +128 -82
  151. package/src/cli/commands/doctor.ts +2 -2
  152. package/src/cli/commands/keys.ts +7 -7
  153. package/src/cli/commands/memory.ts +1 -1
  154. package/src/cli/commands/oauth/connections.ts +11 -29
  155. package/src/cli/commands/oauth/index.ts +7 -0
  156. package/src/cli/commands/oauth/platform.ts +525 -0
  157. package/src/cli/commands/platform.ts +3 -3
  158. package/src/cli/lib/daemon-credential-client.ts +284 -0
  159. package/src/cli.ts +1 -1
  160. package/src/config/assistant-feature-flags.ts +186 -5
  161. package/src/config/bundled-skills/AGENTS.md +34 -0
  162. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  163. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  164. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  165. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  166. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  167. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  168. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  169. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  170. package/src/config/bundled-skills/settings/TOOLS.json +47 -2
  171. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  172. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  173. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  174. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  175. package/src/config/bundled-tool-registry.ts +5 -11
  176. package/src/config/defaults.ts +0 -2
  177. package/src/config/env-registry.ts +5 -5
  178. package/src/config/env.ts +21 -14
  179. package/src/config/feature-flag-registry.json +49 -9
  180. package/src/config/loader.ts +106 -42
  181. package/src/config/schema.ts +9 -29
  182. package/src/config/schemas/calls.ts +30 -0
  183. package/src/config/schemas/fish-audio.ts +39 -0
  184. package/src/config/schemas/inference.ts +2 -2
  185. package/src/config/schemas/journal.ts +16 -0
  186. package/src/config/schemas/memory-processing.ts +2 -2
  187. package/src/config/schemas/security.ts +0 -4
  188. package/src/config/types.ts +1 -1
  189. package/src/contacts/contact-store.ts +39 -0
  190. package/src/contacts/types.ts +2 -0
  191. package/src/credential-execution/approval-bridge.ts +1 -0
  192. package/src/credential-execution/executable-discovery.ts +28 -4
  193. package/src/credential-execution/feature-gates.ts +16 -0
  194. package/src/credential-execution/process-manager.ts +38 -0
  195. package/src/credential-execution/startup-timeout.ts +36 -0
  196. package/src/daemon/approval-generators.ts +3 -9
  197. package/src/daemon/assistant-attachments.ts +9 -0
  198. package/src/daemon/config-watcher.ts +5 -0
  199. package/src/daemon/conversation-error.ts +13 -1
  200. package/src/daemon/conversation-memory.ts +1 -2
  201. package/src/daemon/conversation-process.ts +18 -1
  202. package/src/daemon/conversation-surfaces.ts +30 -1
  203. package/src/daemon/conversation-tool-setup.ts +0 -105
  204. package/src/daemon/conversation.ts +21 -1
  205. package/src/daemon/guardian-action-generators.ts +3 -9
  206. package/src/daemon/handlers/config-vercel.ts +92 -0
  207. package/src/daemon/handlers/skills.ts +2 -15
  208. package/src/daemon/install-symlink.ts +195 -0
  209. package/src/daemon/lifecycle.ts +234 -51
  210. package/src/daemon/message-types/conversations.ts +4 -4
  211. package/src/daemon/message-types/diagnostics.ts +3 -22
  212. package/src/daemon/message-types/messages.ts +0 -2
  213. package/src/daemon/message-types/upgrades.ts +8 -0
  214. package/src/daemon/server.ts +32 -95
  215. package/src/events/domain-events.ts +2 -1
  216. package/src/inbound/platform-callback-registration.ts +3 -3
  217. package/src/instrument.ts +8 -5
  218. package/src/memory/app-store.ts +31 -0
  219. package/src/memory/conversation-title-service.ts +50 -1
  220. package/src/memory/db-init.ts +16 -0
  221. package/src/memory/indexer.ts +19 -10
  222. package/src/memory/items-extractor.ts +328 -321
  223. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  224. package/src/memory/job-handlers/summarization.ts +26 -16
  225. package/src/memory/jobs-store.ts +63 -6
  226. package/src/memory/jobs-worker.ts +31 -7
  227. package/src/memory/journal-memory.ts +214 -0
  228. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  229. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  230. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  231. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  232. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  233. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  234. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  235. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  236. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  237. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  238. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  239. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  240. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  241. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  242. package/src/memory/migrations/116-messages-fts.ts +106 -1
  243. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  244. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  245. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  246. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  247. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  248. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  249. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  250. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  251. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  252. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  253. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  254. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  255. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  256. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  257. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  258. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  259. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  260. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  261. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  262. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  263. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  264. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  265. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  266. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  267. package/src/memory/migrations/index.ts +5 -0
  268. package/src/memory/migrations/registry.ts +98 -0
  269. package/src/memory/migrations/validate-migration-state.ts +137 -11
  270. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  271. package/src/memory/qdrant-manager.ts +64 -7
  272. package/src/memory/retriever.test.ts +37 -25
  273. package/src/memory/retriever.ts +24 -49
  274. package/src/memory/schema/calls.ts +1 -0
  275. package/src/memory/schema/contacts.ts +1 -0
  276. package/src/memory/schema/memory-core.ts +2 -0
  277. package/src/memory/search/formatting.ts +7 -44
  278. package/src/memory/search/staleness.ts +4 -0
  279. package/src/memory/search/tier-classifier.ts +10 -2
  280. package/src/memory/search/types.ts +2 -5
  281. package/src/memory/task-memory-cleanup.ts +4 -3
  282. package/src/notifications/adapters/slack.ts +168 -6
  283. package/src/notifications/broadcaster.ts +1 -0
  284. package/src/notifications/copy-composer.ts +59 -2
  285. package/src/notifications/decision-engine.ts +4 -1
  286. package/src/notifications/signal.ts +2 -0
  287. package/src/notifications/types.ts +2 -0
  288. package/src/oauth/connection-resolver.ts +6 -4
  289. package/src/permissions/checker.ts +0 -38
  290. package/src/permissions/shell-identity.ts +76 -22
  291. package/src/permissions/types.ts +4 -2
  292. package/src/platform/client.ts +35 -7
  293. package/src/prompts/journal-context.ts +133 -0
  294. package/src/prompts/persona-resolver.ts +194 -0
  295. package/src/prompts/system-prompt.ts +44 -4
  296. package/src/prompts/templates/SOUL.md +10 -0
  297. package/src/prompts/templates/users/default.md +1 -0
  298. package/src/providers/provider-send-message.ts +3 -32
  299. package/src/providers/registry.ts +29 -179
  300. package/src/providers/types.ts +1 -1
  301. package/src/runtime/access-request-helper.ts +4 -0
  302. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  303. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  304. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  305. package/src/runtime/auth/external-assistant-id.ts +13 -59
  306. package/src/runtime/auth/route-policy.ts +17 -1
  307. package/src/runtime/auth/token-service.ts +43 -138
  308. package/src/runtime/channel-readiness-service.ts +1 -16
  309. package/src/runtime/gateway-client.ts +47 -4
  310. package/src/runtime/guardian-decision-types.ts +45 -4
  311. package/src/runtime/http-server.ts +31 -3
  312. package/src/runtime/middleware/error-handler.ts +1 -9
  313. package/src/runtime/routes/access-request-decision.ts +2 -2
  314. package/src/runtime/routes/app-management-routes.ts +2 -1
  315. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  316. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  317. package/src/runtime/routes/audio-routes.ts +40 -0
  318. package/src/runtime/routes/btw-routes.ts +0 -17
  319. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  321. package/src/runtime/routes/conversation-routes.ts +4 -44
  322. package/src/runtime/routes/debug-routes.ts +12 -9
  323. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  324. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  325. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  326. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  327. package/src/runtime/routes/identity-routes.ts +19 -30
  328. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  331. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  332. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  333. package/src/runtime/routes/integrations/twilio.ts +52 -10
  334. package/src/runtime/routes/integrations/vercel.ts +89 -0
  335. package/src/runtime/routes/log-export-routes.ts +5 -0
  336. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  337. package/src/runtime/routes/memory-item-routes.ts +46 -14
  338. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  339. package/src/runtime/routes/migration-routes.ts +17 -1
  340. package/src/runtime/routes/notification-routes.ts +58 -0
  341. package/src/runtime/routes/schedule-routes.ts +65 -0
  342. package/src/runtime/routes/secret-routes.ts +141 -10
  343. package/src/runtime/routes/settings-routes.ts +41 -1
  344. package/src/runtime/routes/tts-routes.ts +96 -0
  345. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  346. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  347. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  348. package/src/runtime/routes/workspace-routes.ts +1 -1
  349. package/src/runtime/routes/workspace-utils.ts +86 -2
  350. package/src/security/ces-credential-client.ts +75 -29
  351. package/src/security/ces-rpc-credential-backend.ts +86 -0
  352. package/src/security/credential-backend.ts +22 -92
  353. package/src/security/keychain-broker-client.ts +10 -2
  354. package/src/security/secure-keys.ts +113 -115
  355. package/src/skills/catalog-install.ts +6 -32
  356. package/src/skills/skill-memory.ts +1 -0
  357. package/src/subagent/manager.ts +2 -5
  358. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  359. package/src/tools/acp/spawn.ts +78 -1
  360. package/src/tools/calls/call-start.ts +1 -0
  361. package/src/tools/credentials/vault.ts +5 -3
  362. package/src/tools/executor.ts +0 -4
  363. package/src/tools/memory/definitions.ts +3 -2
  364. package/src/tools/memory/handlers.ts +10 -7
  365. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  366. package/src/tools/network/web-fetch.ts +3 -1
  367. package/src/tools/skills/execute.ts +1 -1
  368. package/src/tools/terminal/safe-env.ts +1 -0
  369. package/src/tools/types.ts +0 -8
  370. package/src/util/browser.ts +15 -0
  371. package/src/util/errors.ts +0 -12
  372. package/src/util/platform.ts +4 -51
  373. package/src/workspace/git-service.ts +5 -2
  374. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  375. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  376. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  377. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  378. package/src/workspace/migrations/006-services-config.ts +49 -0
  379. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  380. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  381. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  382. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  383. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  384. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  385. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  386. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  387. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  388. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  389. package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
  390. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  391. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  392. package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
  393. package/src/workspace/migrations/registry.ts +12 -0
  394. package/src/workspace/migrations/runner.ts +106 -2
  395. package/src/workspace/migrations/types.ts +4 -0
  396. package/src/workspace/provider-commit-message-generator.ts +12 -21
  397. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  398. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  399. package/src/__tests__/diagnostics-export.test.ts +0 -288
  400. package/src/__tests__/local-gateway-health.test.ts +0 -209
  401. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  402. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  403. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  404. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  405. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  406. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  407. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  408. package/src/__tests__/swarm-recursion.test.ts +0 -197
  409. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  410. package/src/__tests__/swarm-tool.test.ts +0 -185
  411. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  412. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  413. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  414. package/src/commands/cc-command-registry.ts +0 -248
  415. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  416. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  417. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  418. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  419. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  420. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  421. package/src/config/schemas/swarm.ts +0 -82
  422. package/src/logfire.ts +0 -135
  423. package/src/memory/search/lexical.ts +0 -48
  424. package/src/providers/failover.ts +0 -186
  425. package/src/runtime/local-gateway-health.ts +0 -275
  426. package/src/security/secret-ingress.ts +0 -68
  427. package/src/swarm/backend-claude-code.ts +0 -225
  428. package/src/swarm/checkpoint.ts +0 -137
  429. package/src/swarm/graph-utils.ts +0 -53
  430. package/src/swarm/index.ts +0 -55
  431. package/src/swarm/limits.ts +0 -66
  432. package/src/swarm/orchestrator.ts +0 -424
  433. package/src/swarm/plan-validator.ts +0 -117
  434. package/src/swarm/router-planner.ts +0 -162
  435. package/src/swarm/router-prompts.ts +0 -39
  436. package/src/swarm/synthesizer.ts +0 -81
  437. package/src/swarm/types.ts +0 -72
  438. package/src/swarm/worker-backend.ts +0 -131
  439. package/src/swarm/worker-prompts.ts +0 -80
  440. package/src/swarm/worker-runner.ts +0 -170
  441. package/src/tools/claude-code/claude-code.ts +0 -610
  442. package/src/tools/swarm/delegate.ts +0 -205
@@ -13,7 +13,12 @@ import { mintDaemonDeliveryToken } from "../../runtime/auth/token-service.js";
13
13
  import { deliverChannelReply } from "../../runtime/gateway-client.js";
14
14
  import { getLogger } from "../../util/logger.js";
15
15
  import { isConversationSeedSane } from "../conversation-seed-composer.js";
16
- import { nonEmpty } from "../copy-composer.js";
16
+ import {
17
+ buildAccessRequestIdentityLine,
18
+ buildAccessRequestInviteDirective,
19
+ nonEmpty,
20
+ sanitizeIdentityField,
21
+ } from "../copy-composer.js";
17
22
  import type {
18
23
  ChannelAdapter,
19
24
  ChannelDeliveryPayload,
@@ -41,6 +46,149 @@ function resolveSlackMessageText(payload: ChannelDeliveryPayload): string {
41
46
  return payload.sourceEventName.replace(/[._]/g, " ");
42
47
  }
43
48
 
49
+ // ---------------------------------------------------------------------------
50
+ // Block Kit helpers for access request notifications
51
+ // ---------------------------------------------------------------------------
52
+
53
+ /**
54
+ * Build Block Kit blocks for an access request notification.
55
+ *
56
+ * Returns an array of Slack Block Kit block objects with structured layout:
57
+ * - Header: "New access request"
58
+ * - Section: requester identity details
59
+ * - Optional context: message preview
60
+ * - Context: approval code instructions + invite directive
61
+ */
62
+ export function buildAccessRequestBlocks(
63
+ payload: Record<string, unknown>,
64
+ ): unknown[] {
65
+ const blocks: unknown[] = [];
66
+
67
+ // Header
68
+ blocks.push({
69
+ type: "header",
70
+ text: { type: "plain_text", text: "New access request", emoji: true },
71
+ });
72
+
73
+ // Requester identity section
74
+ const identityLine = buildAccessRequestIdentityLine(payload);
75
+ blocks.push({
76
+ type: "section",
77
+ text: { type: "mrkdwn", text: identityLine },
78
+ });
79
+
80
+ // Build fields for structured requester details
81
+ const fields: Array<{ type: "mrkdwn"; text: string }> = [];
82
+
83
+ const senderIdentifier = nonEmpty(
84
+ typeof payload.senderIdentifier === "string"
85
+ ? sanitizeIdentityField(payload.senderIdentifier)
86
+ : undefined,
87
+ );
88
+ if (senderIdentifier) {
89
+ fields.push({ type: "mrkdwn", text: `*Name:*\n${senderIdentifier}` });
90
+ }
91
+
92
+ const actorUsername = nonEmpty(
93
+ typeof payload.actorUsername === "string"
94
+ ? sanitizeIdentityField(payload.actorUsername)
95
+ : undefined,
96
+ );
97
+ if (actorUsername) {
98
+ fields.push({ type: "mrkdwn", text: `*Username:*\n@${actorUsername}` });
99
+ }
100
+
101
+ const sourceChannel = nonEmpty(
102
+ typeof payload.sourceChannel === "string"
103
+ ? payload.sourceChannel
104
+ : undefined,
105
+ );
106
+ if (sourceChannel) {
107
+ fields.push({ type: "mrkdwn", text: `*Channel:*\n${sourceChannel}` });
108
+ }
109
+
110
+ const actorExternalId = nonEmpty(
111
+ typeof payload.actorExternalId === "string"
112
+ ? sanitizeIdentityField(payload.actorExternalId)
113
+ : undefined,
114
+ );
115
+ if (actorExternalId && actorExternalId !== senderIdentifier) {
116
+ fields.push({ type: "mrkdwn", text: `*ID:*\n${actorExternalId}` });
117
+ }
118
+
119
+ if (fields.length > 0) {
120
+ blocks.push({
121
+ type: "section",
122
+ fields,
123
+ });
124
+ }
125
+
126
+ // Previously revoked warning
127
+ const previousMemberStatus =
128
+ typeof payload.previousMemberStatus === "string"
129
+ ? payload.previousMemberStatus
130
+ : undefined;
131
+ if (previousMemberStatus === "revoked") {
132
+ blocks.push({
133
+ type: "context",
134
+ elements: [
135
+ {
136
+ type: "mrkdwn",
137
+ text: ":warning: This user was previously revoked.",
138
+ },
139
+ ],
140
+ });
141
+ }
142
+
143
+ // Divider before instructions
144
+ blocks.push({ type: "divider" });
145
+
146
+ // Approval code instructions
147
+ const requestCode = nonEmpty(
148
+ typeof payload.requestCode === "string" ? payload.requestCode : undefined,
149
+ );
150
+ if (requestCode) {
151
+ const code = requestCode.toUpperCase();
152
+ blocks.push({
153
+ type: "section",
154
+ text: {
155
+ type: "mrkdwn",
156
+ text: `Reply *\`${code} approve\`* to grant access or *\`${code} reject\`* to deny.`,
157
+ },
158
+ });
159
+ }
160
+
161
+ // Invite directive
162
+ const inviteDirective = buildAccessRequestInviteDirective();
163
+ blocks.push({
164
+ type: "context",
165
+ elements: [{ type: "mrkdwn", text: inviteDirective }],
166
+ });
167
+
168
+ // Guardian verification note
169
+ const guardianResolutionSource =
170
+ typeof payload.guardianResolutionSource === "string"
171
+ ? payload.guardianResolutionSource
172
+ : undefined;
173
+ if (
174
+ (guardianResolutionSource === "vellum-anchor" ||
175
+ guardianResolutionSource === "none") &&
176
+ sourceChannel
177
+ ) {
178
+ blocks.push({
179
+ type: "context",
180
+ elements: [
181
+ {
182
+ type: "mrkdwn",
183
+ text: `_You haven't verified your identity on ${sourceChannel} yet. If this was you trying to message your assistant, say "help me verify as guardian on ${sourceChannel}" to set up direct access._`,
184
+ },
185
+ ],
186
+ });
187
+ }
188
+
189
+ return blocks;
190
+ }
191
+
44
192
  export class SlackAdapter implements ChannelAdapter {
45
193
  readonly channel: NotificationChannel = "slack";
46
194
 
@@ -65,12 +213,26 @@ export class SlackAdapter implements ChannelAdapter {
65
213
 
66
214
  const messageText = resolveSlackMessageText(payload);
67
215
 
216
+ // Build Block Kit blocks for access request notifications
217
+ const isAccessRequest =
218
+ payload.sourceEventName === "ingress.access_request" &&
219
+ payload.contextPayload != null;
220
+
68
221
  try {
69
- await deliverChannelReply(
70
- deliverUrl,
71
- { chatId, text: messageText, useBlocks: true },
72
- mintDaemonDeliveryToken(),
73
- );
222
+ if (isAccessRequest) {
223
+ const blocks = buildAccessRequestBlocks(payload.contextPayload!);
224
+ await deliverChannelReply(
225
+ deliverUrl,
226
+ { chatId, text: messageText, blocks },
227
+ mintDaemonDeliveryToken(),
228
+ );
229
+ } else {
230
+ await deliverChannelReply(
231
+ deliverUrl,
232
+ { chatId, text: messageText, useBlocks: true },
233
+ mintDaemonDeliveryToken(),
234
+ );
235
+ }
74
236
 
75
237
  log.info(
76
238
  { sourceEventName: payload.sourceEventName, chatId },
@@ -271,6 +271,7 @@ export class NotificationBroadcaster {
271
271
  sourceEventName: signal.sourceEventName,
272
272
  copy,
273
273
  deepLinkTarget,
274
+ contextPayload: signal.contextPayload,
274
275
  };
275
276
 
276
277
  // Compute conversation decision audit fields for the delivery record
@@ -99,7 +99,14 @@ export function buildAccessRequestIdentityLine(
99
99
  const sanitizedExternalId = actorExternalId
100
100
  ? sanitizeIdentityField(actorExternalId)
101
101
  : undefined;
102
- const parts = [requester];
102
+ // When the requester is a raw Slack user ID (e.g. the fallback path in
103
+ // access-request-helper sets senderIdentifier to the raw actorExternalId),
104
+ // format it as a Slack mention so it renders as a clickable display name.
105
+ const formattedRequester =
106
+ sourceChannel === "slack" && /^U[A-Z0-9]+$/i.test(requester)
107
+ ? `<@${requester}>`
108
+ : requester;
109
+ const parts = [formattedRequester];
103
110
  if (sanitizedUsername && sanitizedUsername !== requester) {
104
111
  parts.push(`@${sanitizedUsername}`);
105
112
  }
@@ -108,7 +115,13 @@ export function buildAccessRequestIdentityLine(
108
115
  sanitizedExternalId !== requester &&
109
116
  sanitizedExternalId !== sanitizedUsername
110
117
  ) {
111
- parts.push(`[${sanitizedExternalId}]`);
118
+ // For Slack, use the <@U...> mention format so Slack auto-renders
119
+ // the user ID as a clickable display name.
120
+ const formattedId =
121
+ sourceChannel === "slack" && /^U[A-Z0-9]+$/i.test(sanitizedExternalId)
122
+ ? `<@${sanitizedExternalId}>`
123
+ : `[${sanitizedExternalId}]`;
124
+ parts.push(formattedId);
112
125
  }
113
126
  if (sourceChannel) {
114
127
  parts.push(`via ${sourceChannel}`);
@@ -117,6 +130,46 @@ export function buildAccessRequestIdentityLine(
117
130
  return `${parts.join(" ")} is requesting access to the assistant.`;
118
131
  }
119
132
 
133
+ export const MESSAGE_PREVIEW_MAX_LENGTH = 200;
134
+
135
+ /**
136
+ * Sanitize an untrusted message preview for inclusion in notification copy.
137
+ *
138
+ * Like {@link sanitizeIdentityField} but uses the higher
139
+ * MESSAGE_PREVIEW_MAX_LENGTH limit (200 chars) instead of the identity
140
+ * field limit (120 chars).
141
+ */
142
+ export function sanitizeMessagePreview(value: string): string {
143
+ const stripped = value.replace(/[\x00-\x1f\x7f-\x9f\r\n]+/g, " ").trim();
144
+ const clamped =
145
+ stripped.length > MESSAGE_PREVIEW_MAX_LENGTH
146
+ ? stripped.slice(0, MESSAGE_PREVIEW_MAX_LENGTH) + "…"
147
+ : stripped;
148
+ return clamped;
149
+ }
150
+
151
+ /**
152
+ * Build a quoted preview of the requester's original message for inclusion
153
+ * in guardian-facing access-request copy. Sanitizes and truncates to keep
154
+ * the notification concise.
155
+ *
156
+ * Returns `undefined` when no usable preview is available.
157
+ */
158
+ export function buildAccessRequestMessagePreview(
159
+ payload: Record<string, unknown>,
160
+ ): string | undefined {
161
+ const raw =
162
+ typeof payload.messagePreview === "string"
163
+ ? payload.messagePreview
164
+ : undefined;
165
+ if (!raw) return undefined;
166
+
167
+ const sanitized = sanitizeMessagePreview(raw);
168
+ if (sanitized.length === 0) return undefined;
169
+
170
+ return `> Their message: "${sanitized}"`;
171
+ }
172
+
120
173
  export function buildAccessRequestInviteDirective(): string {
121
174
  return 'Reply "open invite flow" to start Trusted Contacts invite flow.';
122
175
  }
@@ -227,6 +280,10 @@ export function buildAccessRequestContractText(
227
280
 
228
281
  const lines: string[] = [];
229
282
  lines.push(buildAccessRequestIdentityLine(payload));
283
+ const preview = buildAccessRequestMessagePreview(payload);
284
+ if (preview) {
285
+ lines.push(preview);
286
+ }
230
287
  if (previousMemberStatus === "revoked") {
231
288
  lines.push("Note: this user was previously revoked.");
232
289
  }
@@ -13,6 +13,7 @@ import { v4 as uuid } from "uuid";
13
13
 
14
14
  import { getDeliverableChannels } from "../channels/config.js";
15
15
  import { getConfig } from "../config/loader.js";
16
+ import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
16
17
  import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
17
18
  import {
18
19
  createTimeout,
@@ -800,7 +801,9 @@ async function classifyWithLLM(
800
801
  const candidateContext = candidateSet
801
802
  ? (serializeCandidatesForPrompt(candidateSet) ?? undefined)
802
803
  : undefined;
803
- const rawIdentityContext = buildCoreIdentityContext();
804
+ const rawIdentityContext = buildCoreIdentityContext({
805
+ userPersona: resolveGuardianPersona(),
806
+ });
804
807
  const identityContext = rawIdentityContext
805
808
  ? truncate(rawIdentityContext, MAX_IDENTITY_CONTEXT_CHARS, "\n…[truncated]")
806
809
  : undefined;
@@ -151,6 +151,8 @@ export interface AccessRequestContextPayload {
151
151
  guardianBindingChannel: string | null;
152
152
  guardianResolutionSource: GuardianResolutionSource;
153
153
  previousMemberStatus: string | null;
154
+ /** Preview of the requester's original message (first ~200 chars). */
155
+ messagePreview: string | null;
154
156
  }
155
157
 
156
158
  export interface NotificationEventContextPayloadMap {
@@ -79,6 +79,8 @@ export interface ChannelDeliveryPayload {
79
79
  sourceEventName: string;
80
80
  copy: RenderedChannelCopy;
81
81
  deepLinkTarget?: Record<string, unknown>;
82
+ /** Original signal context payload — available for channel-specific structured rendering. */
83
+ contextPayload?: Record<string, unknown>;
82
84
  }
83
85
 
84
86
  /** Interface that each channel adapter must implement. */
@@ -156,10 +156,12 @@ async function resolvePlatformConnectionId(
156
156
  );
157
157
  }
158
158
 
159
- const body = (await response.json()) as {
160
- results?: Array<{ id: string; account_label?: string }>;
161
- };
162
- const connections = body.results ?? [];
159
+ const body = (await response.json()) as unknown;
160
+ const connections = (
161
+ Array.isArray(body)
162
+ ? body
163
+ : ((body as Record<string, unknown>).results ?? [])
164
+ ) as Array<{ id: string; account_label?: string }>;
163
165
 
164
166
  if (connections.length === 0) {
165
167
  throw new Error(
@@ -147,7 +147,6 @@ const LOW_RISK_PROGRAMS = new Set([
147
147
  "du",
148
148
  "df",
149
149
  "assistant",
150
- "vellum",
151
150
  ]);
152
151
 
153
152
  // High-risk shell programs / patterns
@@ -201,32 +200,6 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
201
200
  "reflog",
202
201
  ]);
203
202
 
204
- // Mutating assistant/vellum CLI subcommands that should be escalated to Medium
205
- // risk. Most assistant/vellum subcommands are read-only and stay Low risk.
206
- // This mirrors the git subcommand pattern — only known mutating operations
207
- // get escalated.
208
- const MEDIUM_RISK_CLI_SUBCOMMANDS = new Set([
209
- "credentials",
210
- "config",
211
- "bash",
212
- "trust",
213
- "autonomy",
214
- "contacts",
215
- "mcp",
216
- "keys",
217
- "wake",
218
- "sleep",
219
- "hatch",
220
- "retire",
221
- "clean",
222
- "setup",
223
- "upgrade",
224
- "recover",
225
- "login",
226
- "use",
227
- "pair",
228
- ]);
229
-
230
203
  // Commands that wrap another program — the real program appears as the first
231
204
  // non-flag argument. When one of these is the segment program we look through
232
205
  // its args to find the effective program (e.g. `env curl …` → curl).
@@ -771,17 +744,6 @@ async function classifyRiskUncached(
771
744
  continue;
772
745
  }
773
746
 
774
- if (prog === "vellum" || prog === "assistant") {
775
- const subcommand = firstPositionalArg(seg.args);
776
- if (subcommand && MEDIUM_RISK_CLI_SUBCOMMANDS.has(subcommand)) {
777
- // Known mutating subcommands are medium
778
- maxRisk = RiskLevel.Medium;
779
- continue;
780
- }
781
- // Read-only / unknown subcommands stay at current risk
782
- continue;
783
- }
784
-
785
747
  if (!LOW_RISK_PROGRAMS.has(prog)) {
786
748
  // Unknown program → medium
787
749
  if (maxRisk === RiskLevel.Low) {
@@ -105,8 +105,10 @@ export async function analyzeShellCommand(
105
105
  * - action:gh pr
106
106
  * - action:gh
107
107
  *
108
- * Only "simple action" commands (optional setup prefix + one action) get
109
- * action keys. Pipelines and complex chains are marked non-simple.
108
+ * Simple actions (optional setup prefix + one action) and pipelines get
109
+ * action keys. Both are marked non-simple when they involve pipes, but
110
+ * pipelines extract keys from the first segment before the first pipe.
111
+ * Complex chains (semicolons, ||, &, newlines) get no action keys.
110
112
  */
111
113
  export function deriveShellActionKeys(
112
114
  analysis: ShellIdentityAnalysis,
@@ -117,18 +119,22 @@ export function deriveShellActionKeys(
117
119
  return { keys: [], isSimpleAction: false };
118
120
  }
119
121
 
120
- // For multi-segment commands, only allow simple-action classification if
121
- // ALL inter-segment operators are explicitly &&. Any other operator (|, ||,
122
- // ;, &, empty/missing) means the separator is unknown or unsafe.
123
- // This safely handles cases where the parser doesn't capture certain
124
- // separators (;, newline, &) and leaves them as empty operators.
122
+ // For multi-segment commands, check operators to determine the command shape.
123
+ // Pipes (|) get special handling we can extract action keys from the first
124
+ // segment before the pipe. Other complex operators (||, ;, &, empty/missing)
125
+ // are truly opaque and get no action keys.
125
126
  if (segments.length > 1) {
127
+ let hasPipe = false;
128
+
126
129
  for (const seg of segments) {
127
130
  const op = seg.operator;
128
- // Non-empty operator that isn't && → definitely complex
129
- if (op && op !== "&&") {
131
+ // Non-empty operator that isn't && or | → definitely complex, no keys
132
+ if (op && op !== "&&" && op !== "|") {
130
133
  return { keys: [], isSimpleAction: false };
131
134
  }
135
+ if (op === "|") {
136
+ hasPipe = true;
137
+ }
132
138
  }
133
139
  // Also check: if there are multiple segments but no operators at all
134
140
  // between them (e.g. newline-separated), that's suspicious.
@@ -140,6 +146,42 @@ export function deriveShellActionKeys(
140
146
  return { keys: [], isSimpleAction: false };
141
147
  }
142
148
  }
149
+
150
+ // For pipelines, extract action keys from the first non-setup-prefix segment
151
+ // before the first pipe. This enables broader "Any pdftotext command" rules
152
+ // that match pipelines like "pdftotext file | head -100".
153
+ if (hasPipe) {
154
+ const firstPipeIndex = segments.findIndex((s) => s.operator === "|");
155
+ if (firstPipeIndex > 0) {
156
+ const preSegments = segments.slice(0, firstPipeIndex);
157
+ const actionSegs = preSegments.filter(
158
+ (s) => !SETUP_PREFIX_PROGRAMS.has(s.program),
159
+ );
160
+ if (actionSegs.length === 1) {
161
+ const seg = actionSegs[0];
162
+ const tokens: string[] = [seg.program];
163
+ for (const arg of seg.args) {
164
+ if (tokens.length >= MAX_ACTION_KEY_DEPTH) break;
165
+ if (arg.startsWith("-")) continue;
166
+ if (arg.includes("/") || arg.startsWith(".")) continue;
167
+ if (/^\d+$/.test(arg)) continue;
168
+ if (arg.includes("$") || arg.includes('"') || arg.includes("'"))
169
+ continue;
170
+ tokens.push(arg);
171
+ }
172
+ const keys: ShellActionKey[] = [];
173
+ for (let depth = tokens.length; depth >= 1; depth--) {
174
+ keys.push({
175
+ key: `action:${tokens.slice(0, depth).join(" ")}`,
176
+ depth,
177
+ });
178
+ }
179
+ return { keys, isSimpleAction: false, primarySegment: seg };
180
+ }
181
+ }
182
+ // Pipeline but couldn't extract a single primary action — no keys
183
+ return { keys: [], isSimpleAction: false };
184
+ }
143
185
  }
144
186
 
145
187
  // Separate setup-prefix segments from action segments
@@ -190,9 +232,10 @@ export function deriveShellActionKeys(
190
232
  * Candidate ordering:
191
233
  * 1. Raw command (most specific match — the full command as written)
192
234
  * 2. Canonical primary command (if simple action) — the full primary segment text
193
- * 3. Action keys from narrowest to broadest (if simple action)
235
+ * 3. Action keys from narrowest to broadest (if simple action or pipeline)
194
236
  *
195
- * Complex commands (pipelines, multi-action chains) only return the raw candidate.
237
+ * Complex non-pipeline commands (multi-action chains, semicolons, etc.) only
238
+ * return the raw candidate.
196
239
  */
197
240
  export async function buildShellCommandCandidates(
198
241
  command: string,
@@ -206,14 +249,15 @@ export async function buildShellCommandCandidates(
206
249
 
207
250
  const candidates: string[] = [trimmed];
208
251
 
209
- if (actionResult.isSimpleAction && actionResult.primarySegment) {
210
- // Add canonical primary command text (the actual segment, not the full command with setup prefixes)
211
- const canonical = actionResult.primarySegment.command;
212
- if (canonical !== trimmed) {
213
- candidates.push(canonical);
252
+ // Add action keys as candidates if available (simple actions AND pipelines)
253
+ if (actionResult.keys.length > 0) {
254
+ // For simple actions, also add the canonical primary command text
255
+ if (actionResult.isSimpleAction && actionResult.primarySegment) {
256
+ const canonical = actionResult.primarySegment.command;
257
+ if (canonical !== trimmed) {
258
+ candidates.push(canonical);
259
+ }
214
260
  }
215
-
216
- // Add action keys
217
261
  for (const actionKey of actionResult.keys) {
218
262
  candidates.push(actionKey.key);
219
263
  }
@@ -231,8 +275,9 @@ export async function buildShellCommandCandidates(
231
275
  * 2. Deepest action key (e.g. "action:gh pr view")
232
276
  * 3. Broader action keys (e.g. "action:gh pr", "action:gh")
233
277
  *
234
- * For complex commands (pipelines, multi-action chains), only the exact
235
- * command is offered (no broad options).
278
+ * For pipelines, the exact command plus action-key-based broader options
279
+ * are offered. For other complex commands (multi-action chains, semicolons,
280
+ * etc.), only the exact command is offered.
236
281
  */
237
282
  export async function buildShellAllowlistOptions(
238
283
  command: string,
@@ -244,14 +289,23 @@ export async function buildShellAllowlistOptions(
244
289
  const actionResult = deriveShellActionKeys(analysis);
245
290
 
246
291
  if (!actionResult.isSimpleAction || !actionResult.primarySegment) {
247
- // Complex command exact only
248
- return [
292
+ const options: AllowlistOption[] = [
249
293
  {
250
294
  label: trimmed,
251
295
  description: "This exact compound command",
252
296
  pattern: trimmed,
253
297
  },
254
298
  ];
299
+ // If pipeline action keys were extracted, offer them as broader options
300
+ for (const actionKey of actionResult.keys) {
301
+ const keyTokens = actionKey.key.replace(/^action:/, "");
302
+ options.push({
303
+ label: `${keyTokens} *`,
304
+ description: `Any "${keyTokens}" command`,
305
+ pattern: actionKey.key,
306
+ });
307
+ }
308
+ return options;
255
309
  }
256
310
 
257
311
  const options: AllowlistOption[] = [];
@@ -24,7 +24,8 @@ export type UserDecision =
24
24
  | "always_allow_high_risk"
25
25
  | "deny"
26
26
  | "always_deny"
27
- | "temporary_override";
27
+ | "temporary_override"
28
+ | "dangerously_skip_permissions";
28
29
 
29
30
  /** Returns true for any allow-variant decision. Centralizes the check to prevent omissions when new allow variants are added. */
30
31
  export function isAllowDecision(decision: UserDecision): boolean {
@@ -34,7 +35,8 @@ export function isAllowDecision(decision: UserDecision): boolean {
34
35
  decision === "allow_conversation" ||
35
36
  decision === "always_allow" ||
36
37
  decision === "always_allow_high_risk" ||
37
- decision === "temporary_override"
38
+ decision === "temporary_override" ||
39
+ decision === "dangerously_skip_permissions"
38
40
  );
39
41
  }
40
42
 
@@ -7,6 +7,8 @@
7
7
 
8
8
  import { getPlatformAssistantId } from "../config/env.js";
9
9
  import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
10
+ import { credentialKey } from "../security/credential-key.js";
11
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
10
12
 
11
13
  export class VellumPlatformClient {
12
14
  private readonly platformBaseUrl: string;
@@ -26,21 +28,47 @@ export class VellumPlatformClient {
26
28
  /**
27
29
  * Create a platform client by resolving managed proxy context.
28
30
  *
31
+ * First tries the in-memory managed proxy context (available when the daemon
32
+ * has rehydrated env overrides). Falls back to reading platform credentials
33
+ * directly from the secure keychain so that standalone CLI invocations work
34
+ * without the daemon having run its rehydration step.
35
+ *
29
36
  * Returns `null` when auth prerequisites are missing (not logged in, no API
30
37
  * key). The assistant ID is resolved but not required — callers that need it
31
38
  * should check `platformAssistantId` themselves.
32
39
  */
33
40
  static async create(): Promise<VellumPlatformClient | null> {
34
41
  const ctx = await resolveManagedProxyContext();
35
- if (!ctx.enabled) return null;
36
42
 
37
- const assistantId = getPlatformAssistantId();
43
+ let baseUrl = ctx.enabled ? ctx.platformBaseUrl : "";
44
+ let apiKey = ctx.enabled ? ctx.assistantApiKey : "";
45
+ let assistantId = getPlatformAssistantId();
46
+
47
+ // Fall back to keychain for values not yet rehydrated (standalone CLI).
48
+ if (!baseUrl) {
49
+ baseUrl =
50
+ (await getSecureKeyAsync(
51
+ credentialKey("vellum", "platform_base_url"),
52
+ )) ?? "";
53
+ }
54
+ if (!apiKey) {
55
+ apiKey =
56
+ (await getSecureKeyAsync(
57
+ credentialKey("vellum", "assistant_api_key"),
58
+ )) ?? "";
59
+ }
60
+ if (!assistantId) {
61
+ assistantId =
62
+ (
63
+ await getSecureKeyAsync(
64
+ credentialKey("vellum", "platform_assistant_id"),
65
+ )
66
+ )?.trim() ?? "";
67
+ }
68
+
69
+ if (!baseUrl || !apiKey) return null;
38
70
 
39
- return new VellumPlatformClient(
40
- ctx.platformBaseUrl,
41
- ctx.assistantApiKey,
42
- assistantId,
43
- );
71
+ return new VellumPlatformClient(baseUrl, apiKey, assistantId);
44
72
  }
45
73
 
46
74
  /**