@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
@@ -56,7 +56,6 @@ import {
56
56
  import { searchConversations } from "../../memory/conversation-queries.js";
57
57
  import { getConfiguredProvider } from "../../providers/provider-send-message.js";
58
58
  import type { Provider } from "../../providers/types.js";
59
- import { checkIngressForSecrets } from "../../security/secret-ingress.js";
60
59
  import { getLogger } from "../../util/logger.js";
61
60
  import { silentlyWithLog } from "../../util/silently.js";
62
61
  import { buildAssistantEvent } from "../assistant-event.js";
@@ -637,7 +636,6 @@ export async function handleSendMessage(
637
636
  interface?: string;
638
637
  conversationType?: string;
639
638
  automated?: boolean;
640
- bypassSecretCheck?: boolean;
641
639
  };
642
640
 
643
641
  const { conversationKey, content, attachmentIds } = body;
@@ -705,29 +703,6 @@ export async function handleSendMessage(
705
703
  }
706
704
  }
707
705
 
708
- // Block inbound messages containing secrets before they reach the model.
709
- // This mirrors the legacy handleUserMessage behavior: secrets are
710
- // detected and the message is rejected with a safe notice. The client
711
- // should prompt the user to use the secure credential flow instead.
712
- if (trimmedContent.length > 0 && !body.bypassSecretCheck) {
713
- const ingressCheck = checkIngressForSecrets(trimmedContent);
714
- if (ingressCheck.blocked) {
715
- log.warn(
716
- { detectedTypes: ingressCheck.detectedTypes },
717
- "Blocked user message containing secrets (POST /v1/messages)",
718
- );
719
- return Response.json(
720
- {
721
- accepted: false,
722
- error: "secret_blocked",
723
- message: ingressCheck.userNotice,
724
- detectedTypes: ingressCheck.detectedTypes,
725
- },
726
- { status: 422 },
727
- );
728
- }
729
- }
730
-
731
706
  if (!deps.sendMessageDeps) {
732
707
  return httpError(
733
708
  "SERVICE_UNAVAILABLE",
@@ -1259,12 +1234,12 @@ async function generateLlmSuggestion(
1259
1234
  const truncated =
1260
1235
  assistantText.length > 2000 ? assistantText.slice(-2000) : assistantText;
1261
1236
 
1262
- const prompt = `Given this assistant message, write a very short tab-complete suggestion (max 50 chars) the user could send next to keep the conversation going. Be casual, curious, or actionable — like a quick reply, not a formal request. Reply with ONLY the suggestion text.\n\nAssistant's message:\n${truncated}`;
1237
+ const prompt = `Given this assistant message, write a very short tab-complete suggestion the user could send next to keep the conversation going. Be casual, curious, or actionable — like a quick reply, not a formal request. Reply with ONLY the suggestion text.\n\nAssistant's message:\n${truncated}`;
1263
1238
  const response = await provider.sendMessage(
1264
1239
  [{ role: "user", content: [{ type: "text", text: prompt }] }],
1265
1240
  [], // no tools
1266
1241
  undefined, // no system prompt
1267
- { config: { max_tokens: 40, modelIntent: "latency-optimized" } },
1242
+ { config: { modelIntent: "latency-optimized" } },
1268
1243
  );
1269
1244
 
1270
1245
  const textBlock = response.content.find((b) => b.type === "text");
@@ -1276,7 +1251,7 @@ async function generateLlmSuggestion(
1276
1251
  return null;
1277
1252
  }
1278
1253
 
1279
- // Take first line only, then enforce the length cap
1254
+ // Take first line only
1280
1255
  const firstLine = stripped.split("\n")[0].trim();
1281
1256
  if (!firstLine) {
1282
1257
  log.debug(
@@ -1285,22 +1260,7 @@ async function generateLlmSuggestion(
1285
1260
  );
1286
1261
  return null;
1287
1262
  }
1288
- if (firstLine.length <= 50) return firstLine;
1289
- // Truncate at last word boundary within 50 chars.
1290
- // Only strip the trailing partial word if the slice actually cut mid-word;
1291
- // if the character right after the cut is whitespace, the slice is already clean.
1292
- const sliced = firstLine.slice(0, 50);
1293
- const wordTruncated = (
1294
- /\s/.test(firstLine[50]) ? sliced : sliced.replace(/\s+\S*$/, "")
1295
- ).trim();
1296
- if (wordTruncated.length < 15) {
1297
- log.debug(
1298
- { rawLength: firstLine.length, truncatedLength: wordTruncated.length },
1299
- "Suggestion rejected: too short after word-boundary truncation",
1300
- );
1301
- return null;
1302
- }
1303
- return wordTruncated;
1263
+ return firstLine;
1304
1264
  }
1305
1265
 
1306
1266
  export async function handleGetSuggestion(
@@ -8,7 +8,10 @@ import { getConfig } from "../../config/loader.js";
8
8
  import { countConversations } from "../../memory/conversation-queries.js";
9
9
  import { rawAll } from "../../memory/db.js";
10
10
  import { getMemoryJobCounts } from "../../memory/jobs-store.js";
11
- import { getProviderDebugStatus } from "../../providers/registry.js";
11
+ import {
12
+ getProviderRoutingSource,
13
+ listProviders,
14
+ } from "../../providers/registry.js";
12
15
  import { countSchedules } from "../../schedule/schedule-store.js";
13
16
  import { getDbPath } from "../../util/platform.js";
14
17
  import type { RouteDefinition } from "../http-router.js";
@@ -48,13 +51,11 @@ function handleDebug(): Response {
48
51
  const scheduleCounts = countSchedules();
49
52
 
50
53
  const config = getConfig();
51
- const providerOrder = Array.isArray(config.providerOrder)
52
- ? config.providerOrder
53
- : [];
54
- const providerStatus = getProviderDebugStatus(
55
- config.services.inference.provider,
56
- providerOrder,
57
- );
54
+ const registeredProviders = listProviders();
55
+ const routingSources: Record<string, string | undefined> = {};
56
+ for (const name of registeredProviders) {
57
+ routingSources[name] = getProviderRoutingSource(name);
58
+ }
58
59
 
59
60
  return Response.json({
60
61
  session: {
@@ -62,7 +63,9 @@ function handleDebug(): Response {
62
63
  startedAt: new Date(startedAt).toISOString(),
63
64
  },
64
65
  provider: {
65
- ...providerStatus,
66
+ configuredProvider: config.services.inference.provider,
67
+ registeredProviders,
68
+ routingSources,
66
69
  inferenceMode: config.services.inference.mode,
67
70
  },
68
71
  memory: {
@@ -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",