@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
@@ -1,25 +1,7 @@
1
1
  /**
2
- * HTTP route handlers for diagnostics export and dictation processing.
3
- *
4
- * Handles diagnostics export and dictation processing requests.
2
+ * HTTP route handlers for dictation processing.
5
3
  */
6
4
 
7
- import { randomBytes } from "node:crypto";
8
- import {
9
- createWriteStream,
10
- mkdirSync,
11
- readdirSync,
12
- readFileSync,
13
- rmSync,
14
- statSync,
15
- writeFileSync,
16
- } from "node:fs";
17
- import { homedir, tmpdir } from "node:os";
18
- import { basename, join } from "node:path";
19
-
20
- import archiver from "archiver";
21
- import { and, asc, desc, eq, gte, lte } from "drizzle-orm";
22
-
23
5
  import {
24
6
  type ProfileResolution,
25
7
  resolveProfile,
@@ -31,14 +13,6 @@ import {
31
13
  import { detectDictationModeHeuristic } from "../../daemon/handlers/dictation.js";
32
14
  import type { DictationRequest } from "../../daemon/message-types/diagnostics.js";
33
15
  import type { DictationContext } from "../../daemon/message-types/shared.js";
34
- import { resolveConversationId } from "../../memory/conversation-key-store.js";
35
- import { getDb } from "../../memory/db.js";
36
- import {
37
- llmRequestLogs,
38
- llmUsageEvents,
39
- messages,
40
- toolInvocations,
41
- } from "../../memory/schema.js";
42
16
  import {
43
17
  createTimeout,
44
18
  extractToolUse,
@@ -51,444 +25,6 @@ import type { RouteDefinition } from "../http-router.js";
51
25
 
52
26
  const log = getLogger("diagnostics-routes");
53
27
 
54
- // ---------------------------------------------------------------------------
55
- // Diagnostics export — redaction helpers
56
- // ---------------------------------------------------------------------------
57
-
58
- const MAX_CONTENT_LENGTH = 500;
59
-
60
- const REDACT_PATTERNS = [
61
- /\b(sk|key|api[_-]?key|token|secret|password|passwd|credential)[_\-]?[a-zA-Z0-9]{16,}\b/gi,
62
- /Bearer\s+[A-Za-z0-9\-._~+\/]+=*/gi,
63
- /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g,
64
- /\b(AKIA|ASIA)[A-Z0-9]{16}\b/g,
65
- /\b[A-Fa-f0-9]{32,}\b/g,
66
- ];
67
-
68
- function redact(text: string): string {
69
- let result = text;
70
- for (const pattern of REDACT_PATTERNS) {
71
- result = result.replace(pattern, "[REDACTED]");
72
- }
73
- return result;
74
- }
75
-
76
- function truncateAndRedact(text: string): string {
77
- const truncated =
78
- text.length > MAX_CONTENT_LENGTH
79
- ? text.slice(0, MAX_CONTENT_LENGTH) + "...[truncated]"
80
- : text;
81
- return redact(truncated);
82
- }
83
-
84
- const SENSITIVE_KEYS = new Set([
85
- "api_key",
86
- "apikey",
87
- "api-key",
88
- "authorization",
89
- "x-api-key",
90
- "secret",
91
- "password",
92
- "token",
93
- "credential",
94
- "credentials",
95
- ]);
96
-
97
- function redactDeep(value: unknown): unknown {
98
- if (typeof value === "string") return redact(value);
99
- if (Array.isArray(value)) return value.map(redactDeep);
100
- if (value != null && typeof value === "object") {
101
- const out: Record<string, unknown> = {};
102
- for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
103
- if (SENSITIVE_KEYS.has(k.toLowerCase())) {
104
- out[k] = "[REDACTED]";
105
- } else {
106
- out[k] = redactDeep(v);
107
- }
108
- }
109
- return out;
110
- }
111
- return value;
112
- }
113
-
114
- // ---------------------------------------------------------------------------
115
- // Crash report discovery
116
- // ---------------------------------------------------------------------------
117
-
118
- const CRASH_REPORT_EXTENSIONS = new Set([".crash", ".ips", ".diag"]);
119
- const CRASH_REPORT_TAR_GZ = ".tar.gz";
120
- const CRASH_REPORT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
121
-
122
- function findRecentCrashReports(): string[] {
123
- const diagnosticReportsDir = join(
124
- homedir(),
125
- "Library",
126
- "Logs",
127
- "DiagnosticReports",
128
- );
129
-
130
- try {
131
- const entries = readdirSync(diagnosticReportsDir);
132
- const now = Date.now();
133
- const results: string[] = [];
134
-
135
- for (const entry of entries) {
136
- // Case-insensitive prefix match for "vellum-assistant"
137
- if (!entry.toLowerCase().startsWith("vellum-assistant")) continue;
138
-
139
- // Check extension
140
- const lowerEntry = entry.toLowerCase();
141
- const hasValidExt =
142
- CRASH_REPORT_EXTENSIONS.has(
143
- lowerEntry.slice(lowerEntry.lastIndexOf(".")),
144
- ) || lowerEntry.endsWith(CRASH_REPORT_TAR_GZ);
145
-
146
- if (!hasValidExt) continue;
147
-
148
- const filePath = join(diagnosticReportsDir, entry);
149
- try {
150
- const stat = statSync(filePath);
151
- if (!stat.isFile()) continue;
152
- if (now - stat.mtimeMs > CRASH_REPORT_MAX_AGE_MS) continue;
153
- results.push(filePath);
154
- } catch {
155
- // Skip files we can't stat
156
- }
157
- }
158
-
159
- return results;
160
- } catch {
161
- // Directory doesn't exist or can't be read — not an error
162
- return [];
163
- }
164
- }
165
-
166
- // ---------------------------------------------------------------------------
167
- // Diagnostics export handler
168
- // ---------------------------------------------------------------------------
169
-
170
- async function handleDiagnosticsExport(body: {
171
- conversationId?: string;
172
- anchorMessageId?: string;
173
- }): Promise<Response> {
174
- if (!body.conversationId) {
175
- return httpError("BAD_REQUEST", "conversationId is required", 400);
176
- }
177
-
178
- // The client may send a conversation key (client-side UUID) rather than
179
- // the daemon's internal conversation ID. Resolve to the canonical ID.
180
- const conversationId =
181
- resolveConversationId(body.conversationId) ?? body.conversationId;
182
- const { anchorMessageId } = body;
183
-
184
- try {
185
- const db = getDb();
186
-
187
- // 1. Find the anchor message.
188
- // Try in order: specific ID → most recent assistant message → any message.
189
- // The final fallback handles the race condition where the user clicks
190
- // "export" before message_complete fires and the assistant message has
191
- // been persisted — the user message and in-flight tool/usage data are
192
- // still captured.
193
- let anchorMessage;
194
- let anchorIsFallback = false;
195
- if (anchorMessageId) {
196
- anchorMessage = db
197
- .select()
198
- .from(messages)
199
- .where(
200
- and(
201
- eq(messages.id, anchorMessageId),
202
- eq(messages.conversationId, conversationId),
203
- ),
204
- )
205
- .get();
206
- }
207
- if (!anchorMessage) {
208
- anchorMessage = db
209
- .select()
210
- .from(messages)
211
- .where(
212
- and(
213
- eq(messages.conversationId, conversationId),
214
- eq(messages.role, "assistant"),
215
- ),
216
- )
217
- .orderBy(desc(messages.createdAt))
218
- .limit(1)
219
- .get();
220
- }
221
- if (!anchorMessage) {
222
- anchorMessage = db
223
- .select()
224
- .from(messages)
225
- .where(eq(messages.conversationId, conversationId))
226
- .orderBy(desc(messages.createdAt))
227
- .limit(1)
228
- .get();
229
- anchorIsFallback = true;
230
- }
231
-
232
- // 2. Compute the export time range.
233
- // When an anchor message exists, scope from the earliest message in the
234
- // conversation through the anchor so the full conversation context is
235
- // captured. When no messages exist at all (empty conversation or race
236
- // condition), use the current timestamp so the export still captures any
237
- // in-flight usage/tool data.
238
- const now = Date.now();
239
- let rangeEnd: number;
240
- let rangeStart: number;
241
- let usageRangeEnd: number;
242
-
243
- if (anchorMessage) {
244
- const earliestMessage = db
245
- .select()
246
- .from(messages)
247
- .where(eq(messages.conversationId, conversationId))
248
- .orderBy(asc(messages.createdAt))
249
- .limit(1)
250
- .get();
251
-
252
- rangeStart = earliestMessage?.createdAt ?? anchorMessage.createdAt - 2000;
253
-
254
- // When the anchor was selected via the fallback "any message" path
255
- // (because the assistant reply hasn't been persisted yet), extend the
256
- // range to the current time so in-flight tool invocations and usage
257
- // recorded after the user message are captured. An explicit anchor to a
258
- // non-assistant message uses the message's own timestamp.
259
- rangeEnd = anchorIsFallback ? now : anchorMessage.createdAt;
260
- usageRangeEnd = anchorIsFallback
261
- ? now + 5000
262
- : anchorMessage.createdAt + 5000;
263
- } else {
264
- // No messages at all — use the current time so we capture any
265
- // in-flight LLM usage or tool invocations.
266
- rangeStart = now - 60_000;
267
- rangeEnd = now;
268
- usageRangeEnd = now + 5000;
269
- }
270
-
271
- // 3. Query all messages in the range
272
- const rangeMessages = db
273
- .select()
274
- .from(messages)
275
- .where(
276
- and(
277
- eq(messages.conversationId, conversationId),
278
- gte(messages.createdAt, rangeStart),
279
- lte(messages.createdAt, rangeEnd),
280
- ),
281
- )
282
- .orderBy(messages.createdAt)
283
- .all();
284
-
285
- // 4. Query tool invocations in the range
286
- const rangeToolInvocations = db
287
- .select()
288
- .from(toolInvocations)
289
- .where(
290
- and(
291
- eq(toolInvocations.conversationId, conversationId),
292
- gte(toolInvocations.createdAt, rangeStart),
293
- lte(toolInvocations.createdAt, rangeEnd),
294
- ),
295
- )
296
- .orderBy(toolInvocations.createdAt)
297
- .all();
298
-
299
- // 5. Query LLM usage events
300
- const rangeUsageEvents = db
301
- .select()
302
- .from(llmUsageEvents)
303
- .where(
304
- and(
305
- eq(llmUsageEvents.conversationId, conversationId),
306
- gte(llmUsageEvents.createdAt, rangeStart),
307
- lte(llmUsageEvents.createdAt, usageRangeEnd),
308
- ),
309
- )
310
- .orderBy(llmUsageEvents.createdAt)
311
- .all();
312
-
313
- // 5b. Query raw LLM request/response logs
314
- const rangeRequestLogs = db
315
- .select()
316
- .from(llmRequestLogs)
317
- .where(
318
- and(
319
- eq(llmRequestLogs.conversationId, conversationId),
320
- gte(llmRequestLogs.createdAt, rangeStart),
321
- lte(llmRequestLogs.createdAt, usageRangeEnd),
322
- ),
323
- )
324
- .orderBy(llmRequestLogs.createdAt)
325
- .all();
326
-
327
- // 6. Write export files to a temp directory
328
- const exportId = `diagnostics-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(4).toString("hex")}`;
329
- const tempDir = join(tmpdir(), exportId);
330
- mkdirSync(tempDir, { recursive: true });
331
-
332
- try {
333
- const manifest = {
334
- version: "1.1",
335
- exportedAt: new Date().toISOString(),
336
- conversationId,
337
- messageId: anchorMessage?.id ?? null,
338
- };
339
- writeFileSync(
340
- join(tempDir, "manifest.json"),
341
- JSON.stringify(manifest, null, 2),
342
- );
343
-
344
- const messagesLines = rangeMessages.map((m) =>
345
- JSON.stringify({
346
- id: m.id,
347
- conversationId: m.conversationId,
348
- role: m.role,
349
- content: truncateAndRedact(m.content),
350
- createdAt: m.createdAt,
351
- }),
352
- );
353
- writeFileSync(
354
- join(tempDir, "messages.jsonl"),
355
- messagesLines.join("\n") + (messagesLines.length > 0 ? "\n" : ""),
356
- );
357
-
358
- const toolLines = rangeToolInvocations.map((t) =>
359
- JSON.stringify({
360
- id: t.id,
361
- conversationId: t.conversationId,
362
- toolName: t.toolName,
363
- input: truncateAndRedact(t.input),
364
- result: truncateAndRedact(t.result),
365
- decision: t.decision,
366
- riskLevel: t.riskLevel,
367
- durationMs: t.durationMs,
368
- createdAt: t.createdAt,
369
- }),
370
- );
371
- writeFileSync(
372
- join(tempDir, "tool_invocations.jsonl"),
373
- toolLines.join("\n") + (toolLines.length > 0 ? "\n" : ""),
374
- );
375
-
376
- const usageLines = rangeUsageEvents.map((u) =>
377
- JSON.stringify({
378
- id: u.id,
379
- conversationId: u.conversationId,
380
- actor: u.actor,
381
- provider: u.provider,
382
- model: u.model,
383
- inputTokens: u.inputTokens,
384
- outputTokens: u.outputTokens,
385
- cacheCreationInputTokens: u.cacheCreationInputTokens,
386
- cacheReadInputTokens: u.cacheReadInputTokens,
387
- estimatedCostUsd: u.estimatedCostUsd,
388
- pricingStatus: u.pricingStatus,
389
- createdAt: u.createdAt,
390
- }),
391
- );
392
- writeFileSync(
393
- join(tempDir, "usage.jsonl"),
394
- usageLines.join("\n") + (usageLines.length > 0 ? "\n" : ""),
395
- );
396
-
397
- const requestLogLines = rangeRequestLogs.map((r) => {
398
- let request: unknown;
399
- let response: unknown;
400
- try {
401
- request = JSON.parse(r.requestPayload);
402
- } catch {
403
- request = r.requestPayload;
404
- }
405
- try {
406
- response = JSON.parse(r.responsePayload);
407
- } catch {
408
- response = r.responsePayload;
409
- }
410
- return JSON.stringify({
411
- id: r.id,
412
- conversationId: r.conversationId,
413
- provider: r.provider,
414
- request: redactDeep(request),
415
- response: redactDeep(response),
416
- createdAt: r.createdAt,
417
- });
418
- });
419
- writeFileSync(
420
- join(tempDir, "llm_requests.jsonl"),
421
- requestLogLines.join("\n") + (requestLogLines.length > 0 ? "\n" : ""),
422
- );
423
-
424
- // 7. Zip the temp directory
425
- const downloadsDir = join(homedir(), "Downloads");
426
- mkdirSync(downloadsDir, { recursive: true });
427
- const zipFilename = `${exportId}.zip`;
428
- const zipPath = join(downloadsDir, zipFilename);
429
-
430
- await new Promise<void>((resolve, reject) => {
431
- const output = createWriteStream(zipPath);
432
- const archive = archiver("zip", { zlib: { level: 9 } });
433
-
434
- output.on("close", () => resolve());
435
- output.on("error", (err: Error) => reject(err));
436
- archive.on("error", (err: Error) => reject(err));
437
- archive.on("warning", (err: Error) => {
438
- log.warn({ err }, "Archiver warning during diagnostics export");
439
- });
440
-
441
- archive.pipe(output);
442
- archive.directory(tempDir, false);
443
-
444
- // Add recent crash report files under crash-reports/.
445
- // Text-based crash files (.crash, .ips, .diag) are redacted using the
446
- // same patterns as conversation data. Binary archives (.tar.gz) are
447
- // added as-is since they can't be meaningfully text-redacted.
448
- const crashReportFiles = findRecentCrashReports();
449
- for (const filePath of crashReportFiles) {
450
- try {
451
- const fileName = basename(filePath);
452
- if (fileName.toLowerCase().endsWith(CRASH_REPORT_TAR_GZ)) {
453
- archive.file(filePath, { name: "crash-reports/" + fileName });
454
- } else {
455
- const content = readFileSync(filePath, "utf-8");
456
- archive.append(redact(content), {
457
- name: "crash-reports/" + fileName,
458
- });
459
- }
460
- } catch {
461
- // Skip files that can't be read
462
- }
463
- }
464
-
465
- archive.finalize();
466
- });
467
-
468
- log.info(
469
- { conversationId, zipPath, messageCount: rangeMessages.length },
470
- "Diagnostics export completed via HTTP",
471
- );
472
-
473
- return Response.json({ success: true, filePath: zipPath });
474
- } finally {
475
- try {
476
- rmSync(tempDir, { recursive: true, force: true });
477
- } catch {
478
- // Best-effort cleanup
479
- }
480
- }
481
- } catch (err) {
482
- const errorMessage = err instanceof Error ? err.message : String(err);
483
- log.error({ err, conversationId }, "Failed to export diagnostics");
484
- return httpError(
485
- "INTERNAL_ERROR",
486
- `Failed to export diagnostics: ${errorMessage}`,
487
- 500,
488
- );
489
- }
490
- }
491
-
492
28
  // ---------------------------------------------------------------------------
493
29
  // Dictation
494
30
  // ---------------------------------------------------------------------------
@@ -895,18 +431,6 @@ async function handleCommandMode(
895
431
 
896
432
  export function diagnosticsRouteDefinitions(): RouteDefinition[] {
897
433
  return [
898
- {
899
- endpoint: "diagnostics/export",
900
- method: "POST",
901
- policyKey: "diagnostics/export",
902
- handler: async ({ req }) => {
903
- const body = (await req.json()) as {
904
- conversationId?: string;
905
- anchorMessageId?: string;
906
- };
907
- return handleDiagnosticsExport(body);
908
- },
909
- },
910
434
  {
911
435
  endpoint: "dictation",
912
436
  method: "POST",
@@ -9,7 +9,10 @@ import { fileURLToPath } from "node:url";
9
9
 
10
10
  import { getBaseDataDir } from "../../config/env-registry.js";
11
11
  import { parseIdentityFields } from "../../daemon/handlers/identity.js";
12
- import { getWorkspacePromptPath, readLockfile } from "../../util/platform.js";
12
+ import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
13
+ import { getWorkspacePromptPath } from "../../util/platform.js";
14
+ import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
15
+ import { getLastWorkspaceMigrationId } from "../../workspace/migrations/runner.js";
13
16
  import { httpError } from "../http-errors.js";
14
17
  import type { RouteDefinition } from "../http-router.js";
15
18
  import { getCachedIntro } from "./identity-intro-cache.js";
@@ -133,6 +136,10 @@ function getPackageVersion(): string | undefined {
133
136
  }
134
137
 
135
138
  export function handleHealth(): Response {
139
+ return Response.json({ status: "ok" });
140
+ }
141
+
142
+ export function handleDetailedHealth(): Response {
136
143
  return Response.json({
137
144
  status: "healthy",
138
145
  timestamp: new Date().toISOString(),
@@ -140,9 +147,18 @@ export function handleHealth(): Response {
140
147
  disk: getDiskSpaceInfo(),
141
148
  memory: getMemoryInfo(),
142
149
  cpu: getCpuInfo(),
150
+ migrations: {
151
+ dbVersion: getMaxMigrationVersion(),
152
+ lastWorkspaceMigrationId:
153
+ getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS),
154
+ },
143
155
  });
144
156
  }
145
157
 
158
+ export function handleReadyz(): Response {
159
+ return Response.json({ status: "ok" });
160
+ }
161
+
146
162
  export function handleGetIdentity(): Response {
147
163
  const identityPath = getWorkspacePromptPath("IDENTITY.md");
148
164
  if (!existsSync(identityPath)) {
@@ -163,31 +179,6 @@ export function handleGetIdentity(): Response {
163
179
  // ignore
164
180
  }
165
181
 
166
- // Read lockfile for assistantId, cloud, and originSystem
167
- let assistantId: string | undefined;
168
- let cloud: string | undefined;
169
- let originSystem: string | undefined;
170
- try {
171
- const lockData = readLockfile();
172
- const assistants = lockData?.assistants as
173
- | Array<Record<string, unknown>>
174
- | undefined;
175
- if (assistants && assistants.length > 0) {
176
- // Use the most recently hatched assistant
177
- const sorted = [...assistants].sort((a, b) => {
178
- const dateA = new Date((a.hatchedAt as string) || 0).getTime();
179
- const dateB = new Date((b.hatchedAt as string) || 0).getTime();
180
- return dateB - dateA;
181
- });
182
- const latest = sorted[0];
183
- assistantId = latest.assistantId as string | undefined;
184
- cloud = latest.cloud as string | undefined;
185
- originSystem = cloud === "local" ? "local" : cloud;
186
- }
187
- } catch {
188
- // ignore -- lockfile may not exist
189
- }
190
-
191
182
  return Response.json({
192
183
  name: fields.name ?? "",
193
184
  role: fields.role ?? "",
@@ -195,9 +186,7 @@ export function handleGetIdentity(): Response {
195
186
  emoji: fields.emoji ?? "",
196
187
  home: fields.home ?? "",
197
188
  version,
198
- assistantId,
199
189
  createdAt,
200
- originSystem,
201
190
  });
202
191
  }
203
192
 
@@ -255,7 +244,7 @@ export function identityRouteDefinitions(): RouteDefinition[] {
255
244
  {
256
245
  endpoint: "health",
257
246
  method: "GET",
258
- handler: () => handleHealth(),
247
+ handler: () => handleDetailedHealth(),
259
248
  },
260
249
  {
261
250
  endpoint: "identity",
@@ -1,12 +1,6 @@
1
1
  /**
2
- * Secret ingress check stage: persists the raw inbound payload, runs the
3
- * secret detection scan, and records a conversation-seen signal for
4
- * Telegram messages.
5
- *
6
- * The payload is stored before the scan so dead-lettered events can be
7
- * replayed. If the scan detects embedded secrets the stored payload is
8
- * cleared before the IngressBlockedError propagates, ensuring
9
- * secret-bearing content is never left on disk.
2
+ * Inbound payload persistence stage: persists the raw inbound payload and
3
+ * records a conversation-seen signal for Telegram messages.
10
4
  *
11
5
  * Extracted from inbound-message-handler.ts to keep the top-level handler
12
6
  * focused on orchestration.
@@ -15,8 +9,6 @@ import type { ChannelId } from "../../../channels/types.js";
15
9
  import type { TrustContext } from "../../../daemon/conversation-runtime-assembly.js";
16
10
  import { recordConversationSeenSignal } from "../../../memory/conversation-attention-store.js";
17
11
  import * as deliveryCrud from "../../../memory/delivery-crud.js";
18
- import { checkIngressForSecrets } from "../../../security/secret-ingress.js";
19
- import { IngressBlockedError } from "../../../util/errors.js";
20
12
  import { getLogger } from "../../../util/logger.js";
21
13
 
22
14
  const log = getLogger("runtime-http");
@@ -44,10 +36,7 @@ export interface SecretIngressCheckParams {
44
36
  }
45
37
 
46
38
  /**
47
- * Persist the raw payload, run the secret ingress scan, and record a
48
- * Telegram seen signal.
49
- *
50
- * Throws IngressBlockedError if the content contains secrets.
39
+ * Persist the raw payload and record a Telegram seen signal.
51
40
  */
52
41
  export function runSecretIngressCheck(params: SecretIngressCheckParams): void {
53
42
  const {
@@ -68,9 +57,7 @@ export function runSecretIngressCheck(params: SecretIngressCheckParams): void {
68
57
  canonicalAssistantId,
69
58
  } = params;
70
59
 
71
- // Persist the raw payload first so dead-lettered events can always be
72
- // replayed. If the ingress check later detects secrets we clear it
73
- // before throwing, so secret-bearing content is never left on disk.
60
+ // Persist the raw payload so dead-lettered events can always be replayed.
74
61
  deliveryCrud.storePayload(eventId, {
75
62
  sourceChannel,
76
63
  externalChatId: conversationExternalId,
@@ -86,22 +73,6 @@ export function runSecretIngressCheck(params: SecretIngressCheckParams): void {
86
73
  assistantId: canonicalAssistantId,
87
74
  });
88
75
 
89
- const contentToCheck = content ?? "";
90
- let ingressCheck: ReturnType<typeof checkIngressForSecrets>;
91
- try {
92
- ingressCheck = checkIngressForSecrets(contentToCheck);
93
- } catch (checkErr) {
94
- deliveryCrud.clearPayload(eventId);
95
- throw checkErr;
96
- }
97
- if (ingressCheck.blocked) {
98
- deliveryCrud.clearPayload(eventId);
99
- throw new IngressBlockedError(
100
- ingressCheck.userNotice!,
101
- ingressCheck.detectedTypes,
102
- );
103
- }
104
-
105
76
  // Record inferred seen signal for non-duplicate Telegram inbound messages
106
77
  if (sourceChannel === "telegram") {
107
78
  try {
@@ -24,7 +24,7 @@ mock.module("../../../config/assistant-feature-flags.js", () => ({
24
24
  }));
25
25
 
26
26
  mock.module("../../../config/loader.js", () => ({
27
- getConfig: () => ({ assistantFeatureFlagValues: {} }),
27
+ getConfig: () => ({}),
28
28
  }));
29
29
 
30
30
  mock.module("../../../memory/attachments-store.js", () => ({