@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
@@ -7,7 +7,6 @@ import { reconcileCallsOnStartup } from "../calls/call-recovery.js";
7
7
  import { setRelayBroadcast } from "../calls/relay-server.js";
8
8
  import { TwilioConversationRelayProvider } from "../calls/twilio-provider.js";
9
9
  import { setVoiceBridgeDeps } from "../calls/voice-session-bridge.js";
10
- import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
11
10
  import {
12
11
  getQdrantHttpPortEnv,
13
12
  getQdrantUrlEnv,
@@ -16,12 +15,23 @@ import {
16
15
  setIngressPublicBaseUrl,
17
16
  validateEnv,
18
17
  } from "../config/env.js";
19
- import { loadConfig } from "../config/loader.js";
18
+ import { loadConfig, mergeDefaultWorkspaceConfig } from "../config/loader.js";
19
+ import type { AssistantConfig } from "../config/schema.js";
20
+ import type { CesClient } from "../credential-execution/client.js";
21
+ import { createCesClient } from "../credential-execution/client.js";
22
+ import {
23
+ isCesCredentialBackendEnabled,
24
+ isCesToolsEnabled,
25
+ } from "../credential-execution/feature-gates.js";
26
+ import {
27
+ type CesProcessManager,
28
+ CesUnavailableError,
29
+ createCesProcessManager,
30
+ } from "../credential-execution/process-manager.js";
20
31
  import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
21
32
  import { getHookManager } from "../hooks/manager.js";
22
33
  import { installTemplates } from "../hooks/templates.js";
23
34
  import { closeSentry, initSentry, setSentryDeviceId } from "../instrument.js";
24
- import { disableLogfire, initLogfire } from "../logfire.js";
25
35
  import { getMcpServerManager } from "../mcp/manager.js";
26
36
  import * as attachmentsStore from "../memory/attachments-store.js";
27
37
  import { expireAllPendingCanonicalRequests } from "../memory/canonical-guardian-store.js";
@@ -51,6 +61,7 @@ import { backfillManualTokenConnections } from "../oauth/manual-token-connection
51
61
  import { seedOAuthProviders } from "../oauth/seed-providers.js";
52
62
  import { ensurePromptFiles } from "../prompts/system-prompt.js";
53
63
  import { syncUpdateBulletinOnStartup } from "../prompts/update-bulletin.js";
64
+ import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
54
65
  import { buildAssistantEvent } from "../runtime/assistant-event.js";
55
66
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
56
67
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
@@ -62,6 +73,7 @@ import {
62
73
  import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
63
74
  import { RuntimeHttpServer } from "../runtime/http-server.js";
64
75
  import { startScheduler } from "../schedule/scheduler.js";
76
+ import { setCesClient } from "../security/secure-keys.js";
65
77
  import { seedCatalogSkillMemories } from "../skills/skill-memory.js";
66
78
  import { UsageTelemetryReporter } from "../telemetry/usage-telemetry-reporter.js";
67
79
  import { getDeviceId } from "../util/device-id.js";
@@ -101,6 +113,7 @@ import {
101
113
  switchConversation,
102
114
  undoLastMessage,
103
115
  } from "./handlers/conversations.js";
116
+ import { installAssistantSymlink } from "./install-symlink.js";
104
117
  import type { ServerMessage } from "./message-protocol.js";
105
118
  import {
106
119
  initializeProvidersAndTools,
@@ -129,6 +142,103 @@ function loadDotEnv(): void {
129
142
  dotenvConfig({ path: join(getRootDir(), ".env"), quiet: true });
130
143
  }
131
144
 
145
+ export interface CesStartupResult {
146
+ client: CesClient | undefined;
147
+ processManager: CesProcessManager | undefined;
148
+ clientPromise: Promise<CesClient | undefined> | undefined;
149
+ abortController: AbortController | undefined;
150
+ }
151
+
152
+ /**
153
+ * Start the CES (Credential Execution Service) process and perform the RPC
154
+ * handshake. Returns a promise that resolves with the CES client and process
155
+ * manager. Callers can fire-and-forget — the daemon does not need to await
156
+ * this for startup to continue.
157
+ *
158
+ * The managed sidecar accepts exactly one bootstrap connection, so this must
159
+ * be called at the process level (not per-conversation).
160
+ */
161
+ export async function startCesProcess(
162
+ config: AssistantConfig,
163
+ ): Promise<CesStartupResult> {
164
+ const shouldStartCes = isCesToolsEnabled(config) || isCesCredentialBackendEnabled(config);
165
+ if (!shouldStartCes) {
166
+ return {
167
+ client: undefined,
168
+ processManager: undefined,
169
+ clientPromise: undefined,
170
+ abortController: undefined,
171
+ };
172
+ }
173
+
174
+ const pm = createCesProcessManager({ assistantConfig: config });
175
+ const abortController = new AbortController();
176
+ let clientRef: CesClient | undefined;
177
+
178
+ const clientPromise = (async (): Promise<CesClient | undefined> => {
179
+ try {
180
+ const transport = await pm.start();
181
+ if (abortController.signal.aborted) {
182
+ throw new Error("CES initialization aborted during shutdown");
183
+ }
184
+ const client = createCesClient(transport);
185
+ clientRef = client;
186
+ // Resolve the assistant API key so CES can use it for platform
187
+ // credential materialisation. In managed mode the key is provisioned
188
+ // after hatch and stored in the credential store — CES can't read
189
+ // the env var, so we pass it via the handshake.
190
+ const proxyCtx = await resolveManagedProxyContext();
191
+ const { accepted, reason } = await client.handshake(
192
+ proxyCtx.assistantApiKey
193
+ ? { assistantApiKey: proxyCtx.assistantApiKey }
194
+ : undefined,
195
+ );
196
+ if (abortController.signal.aborted) {
197
+ client.close();
198
+ throw new Error("CES initialization aborted during shutdown");
199
+ }
200
+ if (accepted) {
201
+ log.info(
202
+ "CES client initialized and handshake accepted (server-level)",
203
+ );
204
+ return client;
205
+ }
206
+ log.warn(
207
+ { reason },
208
+ "CES handshake rejected — CES tools will be unavailable",
209
+ );
210
+ client.close();
211
+ clientRef = undefined;
212
+ await pm.stop();
213
+ return undefined;
214
+ } catch (err) {
215
+ if (err instanceof CesUnavailableError) {
216
+ log.info(
217
+ { reason: err.message },
218
+ "CES is not available — CES tools will be unavailable",
219
+ );
220
+ } else {
221
+ log.warn(
222
+ { error: err instanceof Error ? err.message : String(err) },
223
+ "Failed to initialize CES client — CES tools will be unavailable",
224
+ );
225
+ }
226
+ await pm.stop().catch(() => {});
227
+ clientRef = undefined;
228
+ return undefined;
229
+ }
230
+ })();
231
+
232
+ return {
233
+ get client() {
234
+ return clientRef;
235
+ },
236
+ processManager: pm,
237
+ clientPromise,
238
+ abortController,
239
+ };
240
+ }
241
+
132
242
  // Entry point for the daemon process itself
133
243
  export async function runDaemon(): Promise<void> {
134
244
  loadDotEnv();
@@ -140,14 +250,12 @@ export async function runDaemon(): Promise<void> {
140
250
  // closeSentry() if the user has disabled it.
141
251
  initSentry();
142
252
 
143
- await initLogfire();
144
-
145
253
  ensureDataDir();
146
254
 
147
255
  // Load (or generate + persist) the auth signing key so tokens survive
148
256
  // daemon restarts. Must happen after ensureDataDir() creates the
149
257
  // protected directory.
150
- const signingKey = await resolveSigningKey();
258
+ const signingKey = resolveSigningKey();
151
259
  initAuthSigningKey(signingKey);
152
260
 
153
261
  seedInterfaceFiles();
@@ -163,9 +271,19 @@ export async function runDaemon(): Promise<void> {
163
271
  initializeDb();
164
272
  // Seed well-known OAuth provider configurations (insert-if-not-exists)
165
273
  seedOAuthProviders();
274
+ log.info("Daemon startup: DB initialized");
275
+
276
+ await runWorkspaceMigrations(getWorkspaceDir(), WORKSPACE_MIGRATIONS);
277
+ log.info("Daemon startup: workspace migrations complete");
278
+
166
279
  // Backfill oauth_connection rows for manual-token providers (Telegram,
167
280
  // Slack channel) that already have keychain credentials from before the
168
281
  // oauth_connection migration. Safe to call on every startup.
282
+ //
283
+ // Must run AFTER workspace migrations so that migration 015 (which copies
284
+ // encrypted-store credentials to the keychain) has already executed.
285
+ // Otherwise syncManualTokenConnection sees no keychain credentials and
286
+ // incorrectly removes existing connection rows.
169
287
  try {
170
288
  await backfillManualTokenConnections();
171
289
  } catch (err) {
@@ -174,10 +292,6 @@ export async function runDaemon(): Promise<void> {
174
292
  "Manual-token connection backfill failed — continuing startup",
175
293
  );
176
294
  }
177
- log.info("Daemon startup: DB initialized");
178
-
179
- await runWorkspaceMigrations(getWorkspaceDir(), WORKSPACE_MIGRATIONS);
180
- log.info("Daemon startup: workspace migrations complete");
181
295
 
182
296
  // Now that workspace migrations have run (including 003-seed-device-id
183
297
  // which may copy the legacy installationId into device.json), it is safe
@@ -290,6 +404,11 @@ export async function runDaemon(): Promise<void> {
290
404
  log.warn({ err }, "Call recovery failed — continuing startup");
291
405
  }
292
406
 
407
+ // Merge CLI-provided default config (from VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH)
408
+ // into the workspace config file before the first loadConfig() call so
409
+ // onboarding preferences are persisted alongside schema defaults.
410
+ mergeDefaultWorkspaceConfig();
411
+
293
412
  log.info("Daemon startup: loading config");
294
413
  const config = loadConfig();
295
414
 
@@ -330,16 +449,34 @@ export async function runDaemon(): Promise<void> {
330
449
  log.info("Usage telemetry reporter started");
331
450
  }
332
451
 
333
- // If Logfire observability is not explicitly enabled, disable it so
334
- // wrapWithLogfire() calls during provider setup become no-ops. Logfire
335
- // is initialized eagerly (before config loads) for the same reason as
336
- // Sentry but the feature flag gates whether it actually traces.
337
- const logfireEnabled = isAssistantFeatureFlagEnabled(
338
- "feature_flags.logfire.enabled",
339
- config,
340
- );
341
- if (!logfireEnabled) {
342
- disableLogfire();
452
+ // CES lifecycle kick off early so CES handshake runs concurrently with
453
+ // provider/tool initialization. The CES sidecar accepts exactly one
454
+ // bootstrap connection, so startup must happen at the process level.
455
+ const cesStartupPromise = startCesProcess(config);
456
+
457
+ // When the credential backend flag is enabled, CES startup must complete
458
+ // BEFORE provider initialization so credential reads can go through CES.
459
+ // Block with a 3-second timeout — fall back to direct credential store
460
+ // on timeout.
461
+ if (isCesCredentialBackendEnabled(config)) {
462
+ const cesResult = await cesStartupPromise;
463
+ // startCesProcess() returns immediately — the actual handshake runs
464
+ // inside clientPromise. Await it (with a 3s timeout) so the CES client
465
+ // is available before provider initialization.
466
+ if (cesResult.clientPromise) {
467
+ const client = await Promise.race([
468
+ cesResult.clientPromise,
469
+ new Promise<undefined>((resolve) =>
470
+ setTimeout(() => {
471
+ log.warn("CES handshake timed out after 3s — falling back to direct credential store");
472
+ resolve(undefined);
473
+ }, 3000),
474
+ ),
475
+ ]);
476
+ if (client) {
477
+ setCesClient(client);
478
+ }
479
+ }
343
480
  }
344
481
 
345
482
  await initializeProvidersAndTools(config);
@@ -348,6 +485,7 @@ export async function runDaemon(): Promise<void> {
348
485
  // routes can begin accepting requests while Qdrant initializes.
349
486
  log.info("Daemon startup: starting DaemonServer");
350
487
  const server = new DaemonServer();
488
+ server.setCes(await cesStartupPromise);
351
489
  await server.start();
352
490
  log.info("Daemon startup: DaemonServer started");
353
491
 
@@ -371,39 +509,69 @@ export async function runDaemon(): Promise<void> {
371
509
  log.info({ qdrantUrl }, "Daemon startup: initializing Qdrant");
372
510
  const manager = new QdrantManager({ url: qdrantUrl });
373
511
  bgRefs.qdrantManager = manager;
374
- try {
375
- await manager.start();
376
- const embeddingSelection = await selectEmbeddingBackend(config);
377
- const embeddingModel = embeddingSelection.backend
378
- ? `${embeddingSelection.backend.provider}:${embeddingSelection.backend.model}:sparse-v${SPARSE_EMBEDDING_VERSION}`
379
- : undefined;
380
- const qdrantClient = initQdrantClient({
381
- url: qdrantUrl,
382
- collection: config.memory.qdrant.collection,
383
- vectorSize: config.memory.qdrant.vectorSize,
384
- onDisk: config.memory.qdrant.onDisk,
385
- quantization: config.memory.qdrant.quantization,
386
- embeddingModel,
387
- });
512
+ const QDRANT_START_MAX_ATTEMPTS = 3;
513
+ let qdrantStarted = false;
514
+ for (let attempt = 1; attempt <= QDRANT_START_MAX_ATTEMPTS; attempt++) {
515
+ try {
516
+ await manager.start();
517
+ qdrantStarted = true;
518
+ break;
519
+ } catch (err) {
520
+ if (attempt < QDRANT_START_MAX_ATTEMPTS) {
521
+ const backoffMs = attempt * 5_000; // 5s, 10s
522
+ log.warn(
523
+ {
524
+ err,
525
+ attempt,
526
+ maxAttempts: QDRANT_START_MAX_ATTEMPTS,
527
+ backoffMs,
528
+ },
529
+ "Qdrant startup failed, retrying",
530
+ );
531
+ await Bun.sleep(backoffMs);
532
+ } else {
533
+ log.warn(
534
+ { err },
535
+ "Qdrant failed to start after all attempts — memory features will be unavailable",
536
+ );
537
+ }
538
+ }
539
+ }
540
+
541
+ if (qdrantStarted) {
542
+ try {
543
+ const embeddingSelection = await selectEmbeddingBackend(config);
544
+ const embeddingModel = embeddingSelection.backend
545
+ ? `${embeddingSelection.backend.provider}:${embeddingSelection.backend.model}:sparse-v${SPARSE_EMBEDDING_VERSION}`
546
+ : undefined;
547
+ const qdrantClient = initQdrantClient({
548
+ url: qdrantUrl,
549
+ collection: config.memory.qdrant.collection,
550
+ vectorSize: config.memory.qdrant.vectorSize,
551
+ onDisk: config.memory.qdrant.onDisk,
552
+ quantization: config.memory.qdrant.quantization,
553
+ embeddingModel,
554
+ });
388
555
 
389
- // Eagerly ensure the collection exists so we detect migrations
390
- // (unnamed→named vectors, dimension/model changes) at startup.
391
- // If a destructive migration occurred, enqueue a rebuild_index job
392
- // to re-embed all memory items from the SQLite cache.
393
- const { migrated } = await qdrantClient.ensureCollection();
394
- if (migrated) {
395
- enqueueMemoryJob("rebuild_index", {});
396
- log.info(
397
- "Qdrant collection was migrated — enqueued rebuild_index job",
556
+ // Eagerly ensure the collection exists so we detect migrations
557
+ // (unnamed→named vectors, dimension/model changes) at startup.
558
+ // If a destructive migration occurred, enqueue a rebuild_index job
559
+ // to re-embed all memory items from the SQLite cache.
560
+ const { migrated } = await qdrantClient.ensureCollection();
561
+ if (migrated) {
562
+ enqueueMemoryJob("rebuild_index", {});
563
+ log.info(
564
+ "Qdrant collection was migrated — enqueued rebuild_index job",
565
+ );
566
+ }
567
+
568
+ log.info("Qdrant vector store initialized");
569
+ } catch (err) {
570
+ log.warn(
571
+ { err },
572
+ "Qdrant client initialization failed — memory features will be degraded",
398
573
  );
399
574
  }
400
-
401
- log.info("Qdrant vector store initialized");
402
- } catch (err) {
403
- log.warn(
404
- { err },
405
- "Qdrant failed to start — memory features will be unavailable",
406
- );
407
575
  }
408
576
 
409
577
  log.info("Daemon startup: starting memory worker");
@@ -833,7 +1001,7 @@ export async function runDaemon(): Promise<void> {
833
1001
  server.broadcast(msg as ServerMessage),
834
1002
  );
835
1003
  initSlashPairingContext(runtimeHttp.getPairingStore());
836
- server.setHttpPort(httpPort);
1004
+ server.broadcastStatus();
837
1005
  log.info(
838
1006
  { port: httpPort, hostname },
839
1007
  "Daemon startup: runtime HTTP server listening",
@@ -849,6 +1017,14 @@ export async function runDaemon(): Promise<void> {
849
1017
  writePid(process.pid);
850
1018
  log.info({ pid: process.pid }, "Daemon started");
851
1019
 
1020
+ // Install the `assistant` CLI symlink idempotently on every daemon start.
1021
+ // Non-blocking — failures are logged but don't affect startup.
1022
+ try {
1023
+ installAssistantSymlink();
1024
+ } catch (err) {
1025
+ log.warn({ err }, "Assistant symlink installation failed — continuing");
1026
+ }
1027
+
852
1028
  const hookManager = getHookManager();
853
1029
  hookManager.watch();
854
1030
 
@@ -221,9 +221,8 @@ export interface PongMessage {
221
221
  type: "pong";
222
222
  }
223
223
 
224
- export interface DaemonStatusMessage {
225
- type: "daemon_status";
226
- httpPort?: number;
224
+ export interface AssistantStatusMessage {
225
+ type: "assistant_status";
227
226
  version?: string;
228
227
  keyFingerprint?: string;
229
228
  }
@@ -419,7 +418,7 @@ export type _ConversationsClientMessages =
419
418
  export type _ConversationsServerMessages =
420
419
  | AuthResult
421
420
  | PongMessage
422
- | DaemonStatusMessage
421
+ | AssistantStatusMessage
423
422
  | GenerationCancelled
424
423
  | GenerationHandoff
425
424
  | ModelInfo
@@ -1,15 +1,9 @@
1
- // Diagnostics, environment, blob probe, and dictation types.
1
+ // Diagnostics, environment, and dictation types.
2
2
 
3
3
  import type { DictationContext } from "./shared.js";
4
4
 
5
5
  // === Client → Server ===
6
6
 
7
- export interface DiagnosticsExportRequest {
8
- type: "diagnostics_export_request";
9
- conversationId: string;
10
- anchorMessageId?: string; // if omitted, use latest assistant message
11
- }
12
-
13
7
  export interface EnvVarsRequest {
14
8
  type: "env_vars_request";
15
9
  }
@@ -23,13 +17,6 @@ export interface DictationRequest {
23
17
 
24
18
  // === Server → Client ===
25
19
 
26
- export interface DiagnosticsExportResponse {
27
- type: "diagnostics_export_response";
28
- success: boolean;
29
- filePath?: string; // path to the zip file on success
30
- error?: string; // error message on failure
31
- }
32
-
33
20
  export interface EnvVarsResponse {
34
21
  type: "env_vars_response";
35
22
  vars: Record<string, string>;
@@ -46,12 +33,6 @@ export interface DictationResponse {
46
33
 
47
34
  // --- Domain-level union aliases (consumed by the barrel file) ---
48
35
 
49
- export type _DiagnosticsClientMessages =
50
- | DiagnosticsExportRequest
51
- | EnvVarsRequest
52
- | DictationRequest;
36
+ export type _DiagnosticsClientMessages = EnvVarsRequest | DictationRequest;
53
37
 
54
- export type _DiagnosticsServerMessages =
55
- | DiagnosticsExportResponse
56
- | EnvVarsResponse
57
- | DictationResponse;
38
+ export type _DiagnosticsServerMessages = EnvVarsResponse | DictationResponse;
@@ -13,8 +13,6 @@ export interface UserMessage {
13
13
  activeSurfaceId?: string;
14
14
  /** The page currently displayed in the WebView (e.g. "settings.html"). */
15
15
  currentPage?: string;
16
- /** When true, skip the secret-ingress check. Set by the client when the user clicks "Send Anyway". */
17
- bypassSecretCheck?: boolean;
18
16
  /** Originating channel identifier (e.g. 'vellum'). Defaults to 'vellum' when absent. */
19
17
  channel?: ChannelId;
20
18
  /** Originating interface identifier (e.g. 'macos'). */
@@ -7,6 +7,13 @@ export interface ServiceGroupUpdateStarting {
7
7
  expectedDowntimeSeconds: number;
8
8
  }
9
9
 
10
+ /** Broadcast to connected clients with a progress update during an upgrade or rollback. */
11
+ export interface ServiceGroupUpdateProgress {
12
+ type: "service_group_update_progress";
13
+ /** A short, user-friendly status message describing what's happening right now. */
14
+ statusMessage: string;
15
+ }
16
+
10
17
  /** Broadcast to connected clients when a service group update has completed. */
11
18
  export interface ServiceGroupUpdateComplete {
12
19
  type: "service_group_update_complete";
@@ -20,4 +27,5 @@ export interface ServiceGroupUpdateComplete {
20
27
 
21
28
  export type _UpgradesServerMessages =
22
29
  | ServiceGroupUpdateStarting
30
+ | ServiceGroupUpdateProgress
23
31
  | ServiceGroupUpdateComplete;
@@ -20,13 +20,7 @@ import {
20
20
  import { getConfig } from "../config/loader.js";
21
21
  import { onContactChange } from "../contacts/contact-events.js";
22
22
  import type { CesClient } from "../credential-execution/client.js";
23
- import { createCesClient } from "../credential-execution/client.js";
24
- import { isCesToolsEnabled } from "../credential-execution/feature-gates.js";
25
- import {
26
- type CesProcessManager,
27
- CesUnavailableError,
28
- createCesProcessManager,
29
- } from "../credential-execution/process-manager.js";
23
+ import type { CesProcessManager } from "../credential-execution/process-manager.js";
30
24
  import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
31
25
  import * as attachmentsStore from "../memory/attachments-store.js";
32
26
  import {
@@ -45,7 +39,6 @@ import {
45
39
  import { updateMetaFile } from "../memory/conversation-disk-view.js";
46
40
  import { getOrCreateConversation } from "../memory/conversation-key-store.js";
47
41
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
48
- import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
49
42
  import { RateLimitProvider } from "../providers/ratelimit.js";
50
43
  import {
51
44
  getFailoverProvider,
@@ -57,13 +50,11 @@ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
57
50
  import { getSigningKeyFingerprint } from "../runtime/auth/token-service.js";
58
51
  import { bridgeConfirmationRequestToGuardian } from "../runtime/confirmation-request-guardian-bridge.js";
59
52
  import * as pendingInteractions from "../runtime/pending-interactions.js";
60
- import { checkIngressForSecrets } from "../security/secret-ingress.js";
61
53
  import { registerCancelCallback } from "../signals/cancel.js";
62
54
  import { registerConversationUndoCallback } from "../signals/conversation-undo.js";
63
55
  import { appendEventToStream } from "../signals/event-stream.js";
64
56
  import { registerUserMessageCallback } from "../signals/user-message.js";
65
57
  import { getSubagentManager } from "../subagent/index.js";
66
- import { IngressBlockedError } from "../util/errors.js";
67
58
  import { getLogger } from "../util/logger.js";
68
59
  import {
69
60
  getSandboxWorkingDir,
@@ -253,7 +244,6 @@ export class DaemonServer {
253
244
  private conversationOptions = new Map<string, ConversationCreateOptions>();
254
245
  private conversationCreating = new Map<string, Promise<Conversation>>();
255
246
  private sharedRequestTimestamps: number[] = [];
256
- private httpPort: number | undefined;
257
247
  private unsubscribeContactChange: (() => void) | null = null;
258
248
  private evictor: ConversationEvictor;
259
249
  private _hubChain: Promise<void> = Promise.resolve();
@@ -262,8 +252,8 @@ export class DaemonServer {
262
252
  private configWatcher = new ConfigWatcher();
263
253
 
264
254
  // CES (Credential Execution Service) — process-level singleton.
265
- // The CES sidecar accepts exactly one bootstrap connection, so we must
266
- // hold that connection at the server level rather than per-conversation.
255
+ // Lifecycle is managed by startCesProcess() in lifecycle.ts; the server
256
+ // receives the result via setCes().
267
257
  private cesProcessManager?: CesProcessManager;
268
258
  private cesClientPromise?: Promise<CesClient | undefined>;
269
259
  private cesInitAbortController?: AbortController;
@@ -274,6 +264,31 @@ export class DaemonServer {
274
264
  */
275
265
  assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID;
276
266
 
267
+ /**
268
+ * Inject the CES client and process manager from the caller (lifecycle.ts).
269
+ * Must be called before start().
270
+ */
271
+ setCes(result: {
272
+ client: CesClient | undefined;
273
+ processManager: CesProcessManager | undefined;
274
+ clientPromise: Promise<CesClient | undefined> | undefined;
275
+ abortController: AbortController | undefined;
276
+ }): void {
277
+ this.cesClientRef = result.client;
278
+ this.cesProcessManager = result.processManager;
279
+ this.cesInitAbortController = result.abortController;
280
+
281
+ // Wrap the external promise so that cesClientRef stays in sync once the
282
+ // handshake completes — the async work runs in lifecycle.ts but the
283
+ // server needs the resolved client reference for getCesClient().
284
+ if (result.clientPromise) {
285
+ this.cesClientPromise = result.clientPromise.then((client) => {
286
+ this.cesClientRef = client;
287
+ return client;
288
+ });
289
+ }
290
+ }
291
+
277
292
  /**
278
293
  * Return the CES client reference (if available).
279
294
  * Used by routes that need to push updates to CES (e.g. secret-routes).
@@ -538,73 +553,6 @@ export class DaemonServer {
538
553
  this.broadcast({ type: "contacts_changed" });
539
554
  });
540
555
 
541
- // CES lifecycle — start the CES process and perform the RPC handshake
542
- // once at server level. The managed sidecar accepts exactly one bootstrap
543
- // connection, so this must be a process-level singleton.
544
- if (isCesToolsEnabled(config)) {
545
- const pm = createCesProcessManager({ assistantConfig: config });
546
- this.cesProcessManager = pm;
547
- const abortController = new AbortController();
548
- this.cesInitAbortController = abortController;
549
- this.cesClientPromise = (async () => {
550
- try {
551
- const transport = await pm.start();
552
- if (abortController.signal.aborted) {
553
- throw new Error("CES initialization aborted during shutdown");
554
- }
555
- const client = createCesClient(transport);
556
- this.cesClientRef = client;
557
- // Resolve the assistant API key so CES can use it for platform
558
- // credential materialisation. In managed mode the key is provisioned
559
- // after hatch and stored in the credential store — CES can't read
560
- // the env var, so we pass it via the handshake.
561
- const proxyCtx = await resolveManagedProxyContext();
562
- const { accepted, reason } = await client.handshake(
563
- proxyCtx.assistantApiKey
564
- ? { assistantApiKey: proxyCtx.assistantApiKey }
565
- : undefined,
566
- );
567
- if (abortController.signal.aborted) {
568
- client.close();
569
- throw new Error("CES initialization aborted during shutdown");
570
- }
571
- if (accepted) {
572
- log.info(
573
- "CES client initialized and handshake accepted (server-level)",
574
- );
575
- return client;
576
- }
577
- log.warn(
578
- { reason },
579
- "CES handshake rejected — CES tools will be unavailable",
580
- );
581
- client.close();
582
- this.cesClientRef = undefined;
583
- await pm.stop();
584
- // Reset so next session can retry initialization
585
- this.cesClientPromise = undefined;
586
- return undefined;
587
- } catch (err) {
588
- if (err instanceof CesUnavailableError) {
589
- log.info(
590
- { reason: err.message },
591
- "CES is not available — CES tools will be unavailable",
592
- );
593
- } else {
594
- log.warn(
595
- { error: err instanceof Error ? err.message : String(err) },
596
- "Failed to initialize CES client — CES tools will be unavailable",
597
- );
598
- }
599
- await pm.stop().catch(() => {});
600
- // Reset so next session can retry initialization
601
- this.cesClientRef = undefined;
602
- this.cesClientPromise = undefined;
603
- return undefined;
604
- }
605
- })();
606
- }
607
-
608
556
  log.info("DaemonServer started (HTTP-only mode)");
609
557
  }
610
558
 
@@ -656,11 +604,9 @@ export class DaemonServer {
656
604
 
657
605
  // ── Conversation management ──────────────────────────────────────────────
658
606
 
659
- setHttpPort(port: number): void {
660
- this.httpPort = port;
607
+ broadcastStatus(): void {
661
608
  this.broadcast({
662
- type: "daemon_status",
663
- httpPort: port,
609
+ type: "assistant_status",
664
610
  version: daemonVersion,
665
611
  keyFingerprint: getSigningKeyFingerprint(),
666
612
  });
@@ -884,14 +830,6 @@ export class DaemonServer {
884
830
  filePath?: string;
885
831
  }[];
886
832
  }> {
887
- const ingressCheck = checkIngressForSecrets(content);
888
- if (ingressCheck.blocked) {
889
- throw new IngressBlockedError(
890
- ingressCheck.userNotice!,
891
- ingressCheck.detectedTypes,
892
- );
893
- }
894
-
895
833
  const conversation = await this.getOrCreateConversation(
896
834
  conversationId,
897
835
  options,