@vellumai/assistant 0.5.6 → 0.5.7

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 (305) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +1 -1
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +0 -114
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +1 -1
  26. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  27. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  28. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  29. package/src/__tests__/conversation-title-service.test.ts +87 -0
  30. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  31. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  32. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  33. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  34. package/src/__tests__/credentials-cli.test.ts +78 -0
  35. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  36. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  38. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  39. package/src/__tests__/host-shell-tool.test.ts +6 -7
  40. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  42. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  43. package/src/__tests__/intent-routing.test.ts +0 -13
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  45. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  46. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  47. package/src/__tests__/migration-export-http.test.ts +2 -2
  48. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  49. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  50. package/src/__tests__/migration-validate-http.test.ts +2 -2
  51. package/src/__tests__/non-member-access-request.test.ts +0 -5
  52. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  53. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  54. package/src/__tests__/permission-types.test.ts +1 -0
  55. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  56. package/src/__tests__/qdrant-manager.test.ts +28 -2
  57. package/src/__tests__/registry.test.ts +0 -6
  58. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  59. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  60. package/src/__tests__/secure-keys.test.ts +83 -263
  61. package/src/__tests__/shell-identity.test.ts +96 -6
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  63. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  64. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  65. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  66. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  67. package/src/__tests__/skill-load-tool.test.ts +0 -2
  68. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  69. package/src/__tests__/skills.test.ts +0 -2
  70. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  71. package/src/__tests__/suggestion-routes.test.ts +1 -32
  72. package/src/__tests__/system-prompt.test.ts +0 -1
  73. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  74. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  75. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  76. package/src/__tests__/update-bulletin.test.ts +0 -2
  77. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  78. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  79. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  80. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  81. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  82. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  83. package/src/calls/audio-store.test.ts +97 -0
  84. package/src/calls/audio-store.ts +205 -0
  85. package/src/calls/call-controller.ts +85 -7
  86. package/src/calls/call-domain.ts +3 -0
  87. package/src/calls/call-store.ts +10 -3
  88. package/src/calls/fish-audio-client.ts +117 -0
  89. package/src/calls/relay-server.ts +27 -0
  90. package/src/calls/twilio-routes.ts +2 -1
  91. package/src/calls/types.ts +1 -0
  92. package/src/calls/voice-ingress-preflight.ts +0 -42
  93. package/src/calls/voice-quality.ts +26 -5
  94. package/src/calls/voice-session-bridge.ts +6 -12
  95. package/src/cli/commands/config.ts +1 -4
  96. package/src/cli/commands/credentials.ts +34 -4
  97. package/src/cli/commands/oauth/index.ts +7 -0
  98. package/src/cli/commands/oauth/platform.ts +179 -0
  99. package/src/cli/commands/platform.ts +3 -3
  100. package/src/config/assistant-feature-flags.ts +186 -5
  101. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  102. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  103. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  104. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  105. package/src/config/bundled-tool-registry.ts +1 -11
  106. package/src/config/env-registry.ts +1 -1
  107. package/src/config/env.ts +8 -14
  108. package/src/config/feature-flag-registry.json +48 -8
  109. package/src/config/loader.ts +98 -31
  110. package/src/config/schema.ts +4 -13
  111. package/src/config/schemas/calls.ts +13 -0
  112. package/src/config/schemas/fish-audio.ts +39 -0
  113. package/src/config/schemas/security.ts +0 -4
  114. package/src/config/types.ts +0 -1
  115. package/src/contacts/contact-store.ts +39 -0
  116. package/src/contacts/types.ts +2 -0
  117. package/src/credential-execution/approval-bridge.ts +1 -0
  118. package/src/credential-execution/executable-discovery.ts +28 -4
  119. package/src/credential-execution/feature-gates.ts +16 -0
  120. package/src/credential-execution/process-manager.ts +38 -0
  121. package/src/daemon/assistant-attachments.ts +9 -0
  122. package/src/daemon/config-watcher.ts +5 -0
  123. package/src/daemon/conversation-tool-setup.ts +0 -105
  124. package/src/daemon/conversation.ts +10 -1
  125. package/src/daemon/handlers/config-vercel.ts +92 -0
  126. package/src/daemon/handlers/skills.ts +2 -15
  127. package/src/daemon/install-symlink.ts +195 -0
  128. package/src/daemon/lifecycle.ts +227 -51
  129. package/src/daemon/message-types/conversations.ts +3 -4
  130. package/src/daemon/message-types/diagnostics.ts +3 -22
  131. package/src/daemon/message-types/messages.ts +0 -2
  132. package/src/daemon/message-types/upgrades.ts +8 -0
  133. package/src/daemon/server.ts +30 -92
  134. package/src/events/domain-events.ts +2 -1
  135. package/src/inbound/platform-callback-registration.ts +3 -3
  136. package/src/instrument.ts +8 -5
  137. package/src/memory/conversation-title-service.ts +50 -1
  138. package/src/memory/db-init.ts +12 -0
  139. package/src/memory/items-extractor.ts +15 -1
  140. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  141. package/src/memory/jobs-store.ts +30 -5
  142. package/src/memory/jobs-worker.ts +31 -7
  143. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  144. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  145. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  146. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  147. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  148. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  149. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  150. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  151. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  152. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  153. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  154. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  155. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  156. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  157. package/src/memory/migrations/116-messages-fts.ts +106 -1
  158. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  159. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  160. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  161. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  162. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  163. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  164. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  165. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  166. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  167. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  168. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  169. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  170. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  171. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  172. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  173. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  174. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  175. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  176. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  177. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  178. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  179. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  180. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  181. package/src/memory/migrations/index.ts +4 -0
  182. package/src/memory/migrations/registry.ts +90 -0
  183. package/src/memory/migrations/validate-migration-state.ts +137 -11
  184. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  185. package/src/memory/qdrant-manager.ts +64 -7
  186. package/src/memory/schema/calls.ts +1 -0
  187. package/src/memory/schema/contacts.ts +1 -0
  188. package/src/notifications/decision-engine.ts +4 -1
  189. package/src/oauth/connection-resolver.ts +6 -4
  190. package/src/permissions/checker.ts +0 -38
  191. package/src/permissions/shell-identity.ts +76 -22
  192. package/src/permissions/types.ts +4 -2
  193. package/src/platform/client.ts +35 -7
  194. package/src/prompts/persona-resolver.ts +138 -0
  195. package/src/prompts/system-prompt.ts +36 -4
  196. package/src/prompts/templates/users/default.md +1 -0
  197. package/src/providers/registry.ts +27 -40
  198. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  199. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  200. package/src/runtime/auth/external-assistant-id.ts +13 -59
  201. package/src/runtime/auth/route-policy.ts +15 -1
  202. package/src/runtime/auth/token-service.ts +43 -138
  203. package/src/runtime/channel-readiness-service.ts +1 -16
  204. package/src/runtime/http-server.ts +27 -2
  205. package/src/runtime/middleware/error-handler.ts +1 -9
  206. package/src/runtime/routes/audio-routes.ts +40 -0
  207. package/src/runtime/routes/btw-routes.ts +0 -17
  208. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  209. package/src/runtime/routes/conversation-routes.ts +4 -44
  210. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  211. package/src/runtime/routes/identity-routes.ts +18 -29
  212. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  213. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  214. package/src/runtime/routes/integrations/vercel.ts +89 -0
  215. package/src/runtime/routes/log-export-routes.ts +5 -0
  216. package/src/runtime/routes/memory-item-routes.ts +24 -6
  217. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  218. package/src/runtime/routes/migration-routes.ts +17 -1
  219. package/src/runtime/routes/notification-routes.ts +58 -0
  220. package/src/runtime/routes/schedule-routes.ts +65 -0
  221. package/src/runtime/routes/settings-routes.ts +41 -1
  222. package/src/runtime/routes/tts-routes.ts +86 -0
  223. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  224. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  225. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  226. package/src/runtime/routes/workspace-routes.ts +1 -1
  227. package/src/runtime/routes/workspace-utils.ts +86 -2
  228. package/src/security/ces-credential-client.ts +59 -22
  229. package/src/security/ces-rpc-credential-backend.ts +85 -0
  230. package/src/security/credential-backend.ts +12 -88
  231. package/src/security/keychain-broker-client.ts +10 -2
  232. package/src/security/secure-keys.ts +94 -113
  233. package/src/skills/catalog-install.ts +13 -7
  234. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  235. package/src/tools/calls/call-start.ts +1 -0
  236. package/src/tools/executor.ts +0 -4
  237. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  238. package/src/tools/network/web-fetch.ts +3 -1
  239. package/src/tools/skills/execute.ts +1 -1
  240. package/src/tools/types.ts +0 -8
  241. package/src/util/errors.ts +0 -12
  242. package/src/util/platform.ts +3 -50
  243. package/src/workspace/git-service.ts +5 -2
  244. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  245. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  246. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  247. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  248. package/src/workspace/migrations/006-services-config.ts +49 -0
  249. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  250. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  251. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  252. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  253. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  254. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  255. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  256. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  257. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  258. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  259. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  260. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  261. package/src/workspace/migrations/registry.ts +8 -0
  262. package/src/workspace/migrations/runner.ts +106 -2
  263. package/src/workspace/migrations/types.ts +4 -0
  264. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  265. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  266. package/src/__tests__/diagnostics-export.test.ts +0 -288
  267. package/src/__tests__/local-gateway-health.test.ts +0 -209
  268. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  269. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  270. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  271. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  272. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  273. package/src/__tests__/swarm-recursion.test.ts +0 -197
  274. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  275. package/src/__tests__/swarm-tool.test.ts +0 -185
  276. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  277. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  278. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  279. package/src/commands/cc-command-registry.ts +0 -248
  280. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  281. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  282. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  283. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  284. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  285. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  286. package/src/config/schemas/swarm.ts +0 -82
  287. package/src/logfire.ts +0 -135
  288. package/src/runtime/local-gateway-health.ts +0 -275
  289. package/src/security/secret-ingress.ts +0 -68
  290. package/src/swarm/backend-claude-code.ts +0 -225
  291. package/src/swarm/checkpoint.ts +0 -137
  292. package/src/swarm/graph-utils.ts +0 -53
  293. package/src/swarm/index.ts +0 -55
  294. package/src/swarm/limits.ts +0 -66
  295. package/src/swarm/orchestrator.ts +0 -424
  296. package/src/swarm/plan-validator.ts +0 -117
  297. package/src/swarm/router-planner.ts +0 -162
  298. package/src/swarm/router-prompts.ts +0 -39
  299. package/src/swarm/synthesizer.ts +0 -81
  300. package/src/swarm/types.ts +0 -72
  301. package/src/swarm/worker-backend.ts +0 -131
  302. package/src/swarm/worker-prompts.ts +0 -80
  303. package/src/swarm/worker-runner.ts +0 -170
  304. package/src/tools/claude-code/claude-code.ts +0 -610
  305. package/src/tools/swarm/delegate.ts +0 -205
@@ -23,7 +23,6 @@ import {
23
23
  import { isAllowDecision } from "../permissions/types.js";
24
24
  import type { Message, ToolDefinition } from "../providers/types.js";
25
25
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
26
- import { getEffectiveMode } from "../runtime/conversation-approval-overrides.js";
27
26
  import { coreAppProxyTools } from "../tools/apps/definitions.js";
28
27
  import { registerConversationSender } from "../tools/browser/browser-screencast.js";
29
28
  import type { ToolExecutor } from "../tools/executor.js";
@@ -234,110 +233,6 @@ export function createToolExecutor(
234
233
  params.allowedDomains,
235
234
  );
236
235
  },
237
- requestConfirmation: async (req) => {
238
- // Check trust store before prompting
239
- const existingRule = findHighestPriorityRule(
240
- "cc:" + req.toolName,
241
- [req.toolName, `cc:${req.toolName}`, "cc:*"],
242
- ctx.workingDir,
243
- );
244
- if (existingRule && existingRule.decision !== "ask") {
245
- return {
246
- decision:
247
- existingRule.decision === "allow"
248
- ? ("allow" as const)
249
- : ("deny" as const),
250
- };
251
- }
252
- // Auto-approve sub-tool confirmations when a temporary approval
253
- // override is active for this conversation (guardian only).
254
- const guardianTrust = ctx.trustContext?.trustClass ?? "unknown";
255
- if (
256
- guardianTrust === "guardian" &&
257
- getEffectiveMode(ctx.conversationId) !== undefined
258
- ) {
259
- return { decision: "allow" as const };
260
- }
261
- const allowlistOptions = [
262
- {
263
- label: `cc:${req.toolName}`,
264
- description: `Claude Code ${req.toolName}`,
265
- pattern: `cc:${req.toolName}`,
266
- },
267
- {
268
- label: "cc:*",
269
- description: "All Claude Code sub-tools",
270
- pattern: "cc:*",
271
- },
272
- ];
273
- const scopeOptions = generateScopeOptions(ctx.workingDir);
274
- const response = await prompter.prompt(
275
- `cc:${req.toolName}`,
276
- req.input,
277
- req.riskLevel,
278
- allowlistOptions,
279
- scopeOptions,
280
- undefined,
281
- undefined,
282
- ctx.conversationId,
283
- req.executionTarget,
284
- undefined,
285
- undefined,
286
- undefined,
287
- toolUseId,
288
- );
289
- if (
290
- (response.decision === "always_allow" ||
291
- response.decision === "always_allow_high_risk") &&
292
- response.selectedPattern &&
293
- response.selectedScope
294
- ) {
295
- log.info(
296
- {
297
- toolName: "cc:" + req.toolName,
298
- pattern: response.selectedPattern,
299
- scope: response.selectedScope,
300
- highRisk: response.decision === "always_allow_high_risk",
301
- },
302
- "Persisting always-allow trust rule",
303
- );
304
- addRule(
305
- "cc:" + req.toolName,
306
- response.selectedPattern,
307
- response.selectedScope,
308
- "allow",
309
- 100,
310
- response.decision === "always_allow_high_risk"
311
- ? { allowHighRisk: true }
312
- : undefined,
313
- );
314
- }
315
- if (
316
- response.decision === "always_deny" &&
317
- response.selectedPattern &&
318
- response.selectedScope
319
- ) {
320
- log.info(
321
- {
322
- toolName: "cc:" + req.toolName,
323
- pattern: response.selectedPattern,
324
- scope: response.selectedScope,
325
- },
326
- "Persisting always-deny trust rule",
327
- );
328
- addRule(
329
- "cc:" + req.toolName,
330
- response.selectedPattern,
331
- response.selectedScope,
332
- "deny",
333
- );
334
- }
335
- return {
336
- decision: isAllowDecision(response.decision)
337
- ? ("allow" as const)
338
- : ("deny" as const),
339
- };
340
- },
341
236
  };
342
237
 
343
238
  // Intercept skill_execute: extract the real tool name and input, then
@@ -42,6 +42,7 @@ import { PermissionPrompter } from "../permissions/prompter.js";
42
42
  import { SecretPrompter } from "../permissions/secret-prompter.js";
43
43
  import { patternMatchesCandidate } from "../permissions/trust-store.js";
44
44
  import type { UserDecision } from "../permissions/types.js";
45
+ import { resolvePersonaContext } from "../prompts/persona-resolver.js";
45
46
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
46
47
  import type { Message } from "../providers/types.js";
47
48
  import type { Provider } from "../providers/types.js";
@@ -353,10 +354,18 @@ export class Conversation {
353
354
  const resolveSystemPromptCallback = (
354
355
  _history: import("../providers/types.js").Message[],
355
356
  ): ResolvedSystemPrompt => {
357
+ const persona = resolvePersonaContext(
358
+ this.trustContext,
359
+ this.channelCapabilities,
360
+ );
356
361
  const resolved = {
357
362
  systemPrompt: hasSystemPromptOverride
358
363
  ? systemPrompt
359
- : buildSystemPrompt({ hasNoClient: this.hasNoClient }),
364
+ : buildSystemPrompt({
365
+ hasNoClient: this.hasNoClient,
366
+ userPersona: persona.userPersona,
367
+ channelPersona: persona.channelPersona,
368
+ }),
360
369
  maxTokens: configuredMaxTokens,
361
370
  };
362
371
  return resolved;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Business logic for Vercel API token management.
3
+ *
4
+ * The Vercel token is stored in the secure credential vault under
5
+ * `credential/vercel/api_token`. These functions provide get/set/delete
6
+ * operations that mirror the pattern used by Telegram config handlers.
7
+ */
8
+
9
+ import { credentialKey } from "../../security/credential-key.js";
10
+ import {
11
+ deleteSecureKeyAsync,
12
+ getSecureKeyAsync,
13
+ setSecureKeyAsync,
14
+ } from "../../security/secure-keys.js";
15
+ import {
16
+ deleteCredentialMetadata,
17
+ upsertCredentialMetadata,
18
+ } from "../../tools/credentials/metadata-store.js";
19
+ import { getLogger } from "../../util/logger.js";
20
+
21
+ const log = getLogger("config-vercel");
22
+
23
+ // -- Transport-agnostic result type --
24
+
25
+ export interface VercelConfigResult {
26
+ hasToken: boolean;
27
+ success: boolean;
28
+ error?: string;
29
+ }
30
+
31
+ /**
32
+ * Check whether a Vercel API token is stored in the credential vault.
33
+ */
34
+ export async function getVercelConfig(): Promise<VercelConfigResult> {
35
+ const token = await getSecureKeyAsync(credentialKey("vercel", "api_token"));
36
+ return { hasToken: !!token, success: true };
37
+ }
38
+
39
+ /**
40
+ * Store a Vercel API token in the credential vault.
41
+ */
42
+ export async function setVercelConfig(
43
+ apiToken?: string,
44
+ ): Promise<VercelConfigResult> {
45
+ if (!apiToken) {
46
+ return { hasToken: false, success: false, error: "apiToken is required" };
47
+ }
48
+
49
+ const stored = await setSecureKeyAsync(
50
+ credentialKey("vercel", "api_token"),
51
+ apiToken,
52
+ );
53
+ if (!stored) {
54
+ return {
55
+ hasToken: false,
56
+ success: false,
57
+ error: "Failed to store Vercel API token in secure storage",
58
+ };
59
+ }
60
+
61
+ upsertCredentialMetadata("vercel", "api_token", {
62
+ allowedTools: ["deploy", "publish_page"],
63
+ });
64
+
65
+ log.info("Vercel API token stored successfully");
66
+ return { hasToken: true, success: true };
67
+ }
68
+
69
+ /**
70
+ * Delete the Vercel API token from the credential vault.
71
+ */
72
+ export async function deleteVercelConfig(): Promise<VercelConfigResult> {
73
+ const result = await deleteSecureKeyAsync(
74
+ credentialKey("vercel", "api_token"),
75
+ );
76
+
77
+ if (result === "error") {
78
+ const stillPresent = !!(await getSecureKeyAsync(
79
+ credentialKey("vercel", "api_token"),
80
+ ));
81
+ return {
82
+ hasToken: stillPresent,
83
+ success: false,
84
+ error: "Failed to delete Vercel API token from secure storage",
85
+ };
86
+ }
87
+
88
+ deleteCredentialMetadata("vercel", "api_token");
89
+
90
+ log.info("Vercel API token deleted");
91
+ return { hasToken: false, success: true };
92
+ }
@@ -24,6 +24,7 @@ import {
24
24
  getConfiguredProvider,
25
25
  userMessage,
26
26
  } from "../../providers/provider-send-message.js";
27
+ import { isTextMimeType as isTextMime } from "../../runtime/routes/workspace-utils.js";
27
28
  import {
28
29
  clawhubCheckUpdates,
29
30
  clawhubInspect,
@@ -49,20 +50,6 @@ import {
49
50
 
50
51
  // ─── MIME detection helpers ───────────────────────────────────────────────────
51
52
 
52
- const TEXT_MIME_PREFIXES = [
53
- "text/",
54
- "application/json",
55
- "application/javascript",
56
- "application/typescript",
57
- "application/xml",
58
- "application/yaml",
59
- "application/x-yaml",
60
- "application/toml",
61
- "application/x-sh",
62
- ];
63
- function isTextMime(mimeType: string): boolean {
64
- return TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));
65
- }
66
53
  const MAX_INLINE_SIZE = 2 * 1024 * 1024; // 2 MB
67
54
 
68
55
  // ─── Shared context for standalone functions ─────────────────────────────────
@@ -380,7 +367,7 @@ function readDirRecursive(dir: string, rootDir: string): SkillFileEntry[] {
380
367
  try {
381
368
  const stat = statSync(fullPath);
382
369
  const mimeType = Bun.file(fullPath).type;
383
- const isText = isTextMime(mimeType);
370
+ const isText = isTextMime(mimeType, dirent.name);
384
371
  let content: string | null = null;
385
372
  if (isText && stat.size <= MAX_INLINE_SIZE) {
386
373
  content = readFileSync(fullPath, "utf-8");
@@ -0,0 +1,195 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import {
3
+ appendFileSync,
4
+ existsSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ readlinkSync,
9
+ symlinkSync,
10
+ unlinkSync,
11
+ } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { dirname, join } from "node:path";
14
+
15
+ import { getLogger } from "../util/logger.js";
16
+
17
+ const log = getLogger("install-symlink");
18
+
19
+ /**
20
+ * Resolves the path to the `vellum-assistant` binary that should be symlinked
21
+ * as `assistant`.
22
+ *
23
+ * - **Bundled (desktop app):** The daemon runs from a compiled binary inside
24
+ * `Vellum.app/Contents/MacOS/`. `process.execPath` is the daemon binary
25
+ * itself — the `vellum-assistant` CLI binary lives alongside it in the same
26
+ * directory.
27
+ *
28
+ * - **Dev (bun):** `process.execPath` is the bun runtime. We resolve the
29
+ * assistant entrypoint relative to this file's location in the source tree
30
+ * and return a wrapper script path that would need bun to run — so we skip
31
+ * symlinking in dev mode since developers manage their own PATH.
32
+ */
33
+ function resolveAssistantBinary(): string | null {
34
+ const execPath = process.execPath;
35
+ // Detect the bundled desktop app case by checking for the app bundle path.
36
+ const isBundled = execPath.includes("/Contents/MacOS/");
37
+
38
+ if (isBundled) {
39
+ // The assistant CLI binary is a sibling of the daemon binary in
40
+ // Contents/MacOS/.
41
+ const macosDir = dirname(execPath);
42
+ const assistantBinary = join(macosDir, "vellum-assistant");
43
+ if (existsSync(assistantBinary)) {
44
+ return assistantBinary;
45
+ }
46
+ log.warn(
47
+ { expected: assistantBinary },
48
+ "Bundled vellum-assistant binary not found alongside daemon",
49
+ );
50
+ return null;
51
+ }
52
+
53
+ // Dev mode: resolve the assistant entrypoint from the source tree.
54
+ // The daemon entry is at assistant/src/daemon/main.ts and the assistant
55
+ // CLI entry is at assistant/src/index.ts.
56
+ const assistantEntry = join(dirname(__filename), "..", "index.ts");
57
+ if (existsSync(assistantEntry)) {
58
+ return assistantEntry;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Attempts to place a symlink at `symlinkPath` pointing to `target`.
65
+ * Returns true if the symlink was created or already points to the target.
66
+ */
67
+ function trySymlink(target: string, symlinkPath: string): boolean {
68
+ try {
69
+ try {
70
+ const stats = lstatSync(symlinkPath);
71
+ if (!stats.isSymbolicLink()) {
72
+ // Real file — don't overwrite (could be a developer's local install)
73
+ return false;
74
+ }
75
+ const dest = readlinkSync(symlinkPath);
76
+ if (dest === target) return true;
77
+ // Stale or dangling symlink — remove before creating new one
78
+ unlinkSync(symlinkPath);
79
+ } catch (e) {
80
+ if ((e as NodeJS.ErrnoException)?.code !== "ENOENT") return false;
81
+ // Path doesn't exist — proceed to create symlink
82
+ }
83
+
84
+ const dir = dirname(symlinkPath);
85
+ if (!existsSync(dir)) {
86
+ mkdirSync(dir, { recursive: true });
87
+ }
88
+ symlinkSync(target, symlinkPath);
89
+ return true;
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Ensures ~/.local/bin is present in the user's shell profile so that
97
+ * symlinks placed there are on PATH in new terminal sessions.
98
+ */
99
+ function ensureLocalBinInShellProfile(localBinDir: string): void {
100
+ const shell = process.env.SHELL ?? "";
101
+ const home = homedir();
102
+ const profilePath = shell.endsWith("/zsh")
103
+ ? join(home, ".zshrc")
104
+ : shell.endsWith("/bash")
105
+ ? join(home, ".bash_profile")
106
+ : null;
107
+ if (!profilePath) return;
108
+
109
+ try {
110
+ const contents = existsSync(profilePath)
111
+ ? readFileSync(profilePath, "utf-8")
112
+ : "";
113
+ if (contents.includes(localBinDir)) return;
114
+ const line = `\nexport PATH="${localBinDir}:$PATH"\n`;
115
+ appendFileSync(profilePath, line);
116
+ log.info(
117
+ { profilePath, localBinDir },
118
+ "Added ~/.local/bin to shell profile",
119
+ );
120
+ } catch {
121
+ // Not critical — user can add it manually
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Checks whether `assistant` already resolves on PATH to something other than
127
+ * our candidate symlink locations. If so, we skip symlinking to avoid
128
+ * overwriting a developer's local build.
129
+ */
130
+ function commandResolvesElsewhere(
131
+ commandName: string,
132
+ candidatePaths: Set<string>,
133
+ ): boolean {
134
+ try {
135
+ const resolved = execFileSync("/usr/bin/which", [commandName], {
136
+ encoding: "utf-8",
137
+ stdio: ["ignore", "pipe", "ignore"],
138
+ }).trim();
139
+ return resolved !== "" && !candidatePaths.has(resolved);
140
+ } catch {
141
+ // `which` exited non-zero — command not found, safe to proceed
142
+ return false;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Idempotent: installs (or verifies) a symlink for the `assistant` command.
148
+ *
149
+ * Called on every daemon startup. Handles two runtime modes:
150
+ * - **Bundled binary** (desktop app): symlinks to the compiled
151
+ * `vellum-assistant` binary in the app bundle.
152
+ * - **Bun dev mode**: symlinks to a small wrapper script that invokes
153
+ * `bun run` on the assistant entrypoint.
154
+ *
155
+ * Tries `/usr/local/bin/assistant` first, then falls back to
156
+ * `~/.local/bin/assistant` (and patches the shell profile if needed).
157
+ *
158
+ * Skipped when VELLUM_DEV=1 (developers manage their own PATH).
159
+ */
160
+ export function installAssistantSymlink(): void {
161
+ if (process.env.VELLUM_DEV === "1") return;
162
+
163
+ const target = resolveAssistantBinary();
164
+ if (!target) return;
165
+
166
+ const localBinDir = join(homedir(), ".local", "bin");
167
+ const candidateDirs = ["/usr/local/bin", localBinDir];
168
+ const candidatePaths = new Set(
169
+ candidateDirs.map((dir) => `${dir}/assistant`),
170
+ );
171
+
172
+ if (commandResolvesElsewhere("assistant", candidatePaths)) {
173
+ log.info(
174
+ "`assistant` already resolves to a non-managed path — skipping symlink",
175
+ );
176
+ return;
177
+ }
178
+
179
+ for (const dir of candidateDirs) {
180
+ const symlinkPath = join(dir, "assistant");
181
+ if (trySymlink(target, symlinkPath)) {
182
+ log.info({ symlinkPath, target }, "Installed assistant symlink");
183
+ if (dir === localBinDir) {
184
+ ensureLocalBinInShellProfile(localBinDir);
185
+ }
186
+ return;
187
+ }
188
+ log.info(
189
+ { symlinkPath },
190
+ "Could not install assistant symlink at candidate — trying next",
191
+ );
192
+ }
193
+
194
+ log.warn("Could not install assistant symlink in any candidate directory");
195
+ }