@mycadet/assistant 0.12.10 → 0.13.0

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 (180) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  3. package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  5. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  6. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  7. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  8. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  9. package/openapi.yaml +2 -708
  10. package/package.json +2 -3
  11. package/scripts/sync-web-search-catalog.ts +1 -2
  12. package/src/__tests__/cloud-profile-guard.test.ts +41 -5
  13. package/src/__tests__/config-schema.test.ts +6 -38
  14. package/src/__tests__/conversation-queue.test.ts +45 -6
  15. package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
  16. package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
  17. package/src/__tests__/persona-resolver.test.ts +0 -51
  18. package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
  19. package/src/__tests__/run-due-schedules.test.ts +0 -4
  20. package/src/__tests__/schedule-store.test.ts +0 -12
  21. package/src/__tests__/stt-stream-session.test.ts +4 -4
  22. package/src/__tests__/telephony-tts-guard.test.ts +2 -50
  23. package/src/__tests__/voice-config-update.test.ts +12 -167
  24. package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
  25. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
  26. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  27. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
  28. package/src/calls/resolve-call-tts-provider.ts +6 -25
  29. package/src/calls/telephony-credential-preflight.ts +0 -10
  30. package/src/calls/telephony-tts-capability.ts +1 -17
  31. package/src/cli/commands/tts.ts +1 -2
  32. package/src/cli/index.help.ts +0 -2
  33. package/src/cli/program.ts +0 -2
  34. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
  35. package/src/config/schemas/__tests__/stt.test.ts +10 -14
  36. package/src/config/schemas/llm.ts +3 -5
  37. package/src/config/schemas/services.ts +5 -6
  38. package/src/config/schemas/stt.ts +1 -3
  39. package/src/config/schemas/tts.ts +1 -30
  40. package/src/daemon/daemon-readiness.ts +4 -29
  41. package/src/daemon/lifecycle.ts +0 -31
  42. package/src/daemon/providers-setup.ts +0 -8
  43. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  44. package/src/heartbeat/heartbeat-service.ts +1 -66
  45. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
  46. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
  47. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
  48. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  49. package/src/live-voice/live-voice-credential-preflight.ts +9 -33
  50. package/src/live-voice/live-voice-session.ts +10 -6
  51. package/src/live-voice/live-voice-tts.ts +1 -8
  52. package/src/plugins/defaults/memory/worker.ts +3 -3
  53. package/src/prompts/persona-resolver.ts +0 -35
  54. package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
  55. package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
  56. package/src/providers/search-provider-catalog.ts +7 -39
  57. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
  58. package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
  59. package/src/providers/speech-to-text/provider-catalog.ts +2 -73
  60. package/src/providers/speech-to-text/resolve.ts +3 -120
  61. package/src/runtime/http-router-types.ts +1 -3
  62. package/src/runtime/http-server.ts +1 -1
  63. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
  64. package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
  65. package/src/runtime/routes/conversation-query-routes.ts +0 -96
  66. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
  67. package/src/runtime/routes/index.ts +0 -4
  68. package/src/runtime/routes/live-voice-routes.test.ts +4 -11
  69. package/src/runtime/routes/live-voice-routes.ts +8 -11
  70. package/src/runtime/routes/secret-routes.ts +0 -6
  71. package/src/runtime/routes/tts-routes.ts +0 -38
  72. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
  73. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
  74. package/src/schedule/schedule-store.ts +0 -7
  75. package/src/schedule/scheduler.ts +0 -5
  76. package/src/schedule/worker.ts +1 -1
  77. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
  78. package/src/stt/daemon-batch-transcriber.ts +4 -38
  79. package/src/stt/types.ts +1 -9
  80. package/src/tools/network/__tests__/web-search.test.ts +3 -250
  81. package/src/tools/network/web-search.ts +4 -165
  82. package/src/tts/__tests__/provider-resolution.test.ts +0 -5
  83. package/src/tts/provider-catalog.ts +0 -4
  84. package/src/tts/types.ts +0 -1
  85. package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
  86. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
  87. package/src/__tests__/migration-export-http.test.ts +0 -557
  88. package/src/__tests__/migration-export-streaming.test.ts +0 -350
  89. package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
  90. package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
  91. package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
  92. package/src/__tests__/migration-import-from-path.test.ts +0 -324
  93. package/src/__tests__/migration-import-from-url.test.ts +0 -588
  94. package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
  95. package/src/__tests__/migration-jobs-status.test.ts +0 -139
  96. package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
  97. package/src/__tests__/migration-transport.test.ts +0 -1089
  98. package/src/__tests__/migration-validate-http.test.ts +0 -634
  99. package/src/__tests__/migration-wizard.test.ts +0 -1395
  100. package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
  101. package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
  102. package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
  103. package/src/__tests__/validation-results-screen.test.ts +0 -1130
  104. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
  105. package/src/background-wake/background-wake-routes.test.ts +0 -906
  106. package/src/background-wake/cloud-client.test.ts +0 -328
  107. package/src/background-wake/cloud-client.ts +0 -196
  108. package/src/background-wake/next-wake.test.ts +0 -335
  109. package/src/background-wake/next-wake.ts +0 -188
  110. package/src/background-wake/publisher.ts +0 -93
  111. package/src/background-wake/wake-intent-hooks.test.ts +0 -234
  112. package/src/cli/commands/__tests__/backup.test.ts +0 -363
  113. package/src/cli/commands/backup.help.ts +0 -180
  114. package/src/cli/commands/backup.ts +0 -398
  115. package/src/cloud/cloud-speech.test.ts +0 -335
  116. package/src/cloud/cloud-speech.ts +0 -300
  117. package/src/config/cloud-speech-defaults.ts +0 -300
  118. package/src/config/sanitize-for-transfer.ts +0 -111
  119. package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
  120. package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
  121. package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
  122. package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
  123. package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
  124. package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
  125. package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
  126. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
  127. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
  128. package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
  129. package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
  130. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
  131. package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
  132. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
  133. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
  134. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
  135. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
  136. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
  137. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
  138. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
  139. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
  140. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
  141. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
  142. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
  143. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
  144. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
  145. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
  146. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
  147. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
  148. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
  149. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
  150. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
  151. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
  152. package/src/runtime/migrations/gcs-signed-url.ts +0 -190
  153. package/src/runtime/migrations/job-registry.ts +0 -297
  154. package/src/runtime/migrations/migration-transport.ts +0 -612
  155. package/src/runtime/migrations/migration-wizard.ts +0 -747
  156. package/src/runtime/migrations/origin-mode.ts +0 -42
  157. package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
  158. package/src/runtime/migrations/staged-import-path.ts +0 -116
  159. package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
  160. package/src/runtime/migrations/validation-results-screen.ts +0 -469
  161. package/src/runtime/migrations/vbundle-builder.ts +0 -1382
  162. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
  163. package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
  164. package/src/runtime/migrations/vbundle-importer.ts +0 -826
  165. package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
  166. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
  167. package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
  168. package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
  169. package/src/runtime/migrations/vbundle-validator.ts +0 -724
  170. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
  171. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
  172. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
  173. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
  174. package/src/runtime/routes/background-wake-routes.ts +0 -387
  175. package/src/runtime/routes/migration-routes.ts +0 -2584
  176. package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
  177. package/src/tools/network/cloud-search-proxy.ts +0 -211
  178. package/src/tts/__tests__/cadet-provider.test.ts +0 -253
  179. package/src/tts/providers/cadet-provider.ts +0 -244
  180. package/src/types/tar-stream.d.ts +0 -66
@@ -13,7 +13,6 @@ import {
13
13
  batchBoundaryGapReason,
14
14
  getCredentialProvider,
15
15
  getProviderEntry,
16
- isCloudSttProvider,
17
16
  resolveSttCatalogKey,
18
17
  supportsBoundary,
19
18
  supportsDiarization,
@@ -37,11 +36,6 @@ const log = getLogger("stt-resolver");
37
36
  */
38
37
  const MULTILINGUAL_DEFAULT_PROVIDERS: ReadonlySet<SttProviderId> = new Set([
39
38
  "deepgram",
40
- "cadet",
41
- // Cloud Flux resolves an absent language to `flux-general-en` relay-side,
42
- // so unset is an English pin here too. With "multi" the relay selects
43
- // `flux-general-multi` and lets Flux detect and code-switch.
44
- "cadet-flux",
45
39
  ] as SttProviderId[]);
46
40
 
47
41
  /**
@@ -156,16 +150,6 @@ export async function resolveBatchTranscriber(
156
150
  throw new SttError("provider-error", reason, { userFacing: true });
157
151
  }
158
152
 
159
- if (provider === "cadet") {
160
- // Managed batch rides the cloud's speech proxy, which forwards the
161
- // language to Deepgram server-side. A platform build predating that
162
- // field ignores it rather than failing, so the daemon can send one
163
- // before the cloud side deploys.
164
- return (await sttProviderKeyResolves("cadet"))
165
- ? createDaemonBatchTranscriber(null, "cadet", language)
166
- : null;
167
- }
168
-
169
153
  const apiKey = await getProviderKeyAsync(credentialProviderName);
170
154
  return createDaemonBatchTranscriber(
171
155
  apiKey,
@@ -515,18 +499,8 @@ export async function resolveStreamingTranscriber(
515
499
  (diarizePreference === "preferred" || diarizePreference === "required") &&
516
500
  providerSupportsDiarization;
517
501
 
518
- // Both managed entries authenticate with the cloud connection, not a
519
- // stored API key: there is no key to fetch, and readiness is whether the
520
- // connection resolves.
521
- const managed = isCloudSttProvider(provider);
522
- const apiKey = managed
523
- ? null
524
- : await getProviderKeyAsync(credentialProviderName);
525
- if (managed) {
526
- if (!(await sttProviderKeyResolves("cadet"))) {
527
- return null;
528
- }
529
- } else if (!apiKey) {
502
+ const apiKey = await getProviderKeyAsync(credentialProviderName);
503
+ if (!apiKey) {
530
504
  return null;
531
505
  }
532
506
 
@@ -656,82 +630,6 @@ async function createStreamingTranscriber(
656
630
  ...(options.diarize ? { diarize: true } : {}),
657
631
  });
658
632
  }
659
- case "cadet": {
660
- // Cloud speech dials the GATEWAY's speech relay (tunnel contact is
661
- // gateway-only); the apiKey argument is unused. Diarization is
662
- // unsupported (not in the relay's param allowlist) and silently
663
- // ignored, matching Gemini/Whisper — as is utteranceEndMs (the relay
664
- // allowlist has no utterance_end_ms; boundary finals ride on
665
- // endpointing alone).
666
- // The gateway is the credential authority, but the cloud
667
- // connection (API key + assistant ID) is checkable locally — gate
668
- // here so preflight reports "connect your account" instead of
669
- // resolving a transcriber whose dial is doomed.
670
- const { cadetCloudSpeechAvailable } = await import("./cadet-cloud.js");
671
- if (!(await cadetCloudSpeechAvailable())) {
672
- return null;
673
- }
674
- const { resolveSpeechRelayConnection } =
675
- await import("./cadet-speech-relay-connection.js");
676
- const connection = await resolveSpeechRelayConnection();
677
- if (!connection) {
678
- return null;
679
- }
680
- const { CadetManagedRealtimeTranscriber } =
681
- await import("./cadet-cloud-realtime.js");
682
- return new CadetManagedRealtimeTranscriber(connection, {
683
- sampleRate: options.sampleRate,
684
- // `language` IS in the relay's param allowlist, and the relay pins
685
- // the STT model to nova-3 server-side, so "multi" code-switching
686
- // needs nothing from the cloud, only this forward. Source:
687
- // mycadet-platform: tunnel/internal/tunnel/deepgram.go
688
- // (deepgramSTTParams allowlist; deepgramSTTModel pin).
689
- ...(options.language ? { language: options.language } : {}),
690
- ...(options.utteranceBoundaryFinals
691
- ? { utteranceBoundaryFinals: true }
692
- : {}),
693
- });
694
- }
695
- case "cadet-flux": {
696
- // Cloud Flux: same relay as `cadet`, but the STT v2 endpoint with
697
- // `contract=flux` so the turn events arrive intact instead of being
698
- // translated into the released v1 Deepgram dialect. Gated on the local
699
- // cloud connection for the same reason as `cadet` above.
700
- const { cadetCloudSpeechAvailable } = await import("./cadet-cloud.js");
701
- if (!(await cadetCloudSpeechAvailable())) {
702
- return null;
703
- }
704
- // The relay maps the language onto a Flux model and refuses the dial
705
- // when there is none, so a language outside Flux's roster fails as a
706
- // relay param error the caller cannot explain. Refuse here instead, on
707
- // the same rule the BYOK twin above applies, so the reason is named
708
- // where it can be read.
709
- const { fluxModelForLanguage: managedFluxModelForLanguage } =
710
- await import("./deepgram-flux-frames.js");
711
- if (managedFluxModelForLanguage(options.language) === null) {
712
- log.warn(
713
- { providerId, language: options.language },
714
- "Cloud Flux has no model for the configured language; refusing rather than transcribing it as another",
715
- );
716
- return null;
717
- }
718
- const { resolveSpeechRelayConnection } =
719
- await import("./cadet-speech-relay-connection.js");
720
- const connection = await resolveSpeechRelayConnection();
721
- if (!connection) {
722
- return null;
723
- }
724
- const { CadetManagedFluxRealtimeTranscriber } =
725
- await import("./cadet-cloud-flux-realtime.js");
726
- return new CadetManagedFluxRealtimeTranscriber(connection, {
727
- sampleRate: options.sampleRate,
728
- // The relay picks `flux-general-en` or `flux-general-multi` from
729
- // this, so it is the only lever over the Flux model from here.
730
- // `utteranceBoundaryFinals` is a nova-3 concept and the catalog
731
- // keeps this provider off telephony, so it never arrives.
732
- ...(options.language ? { language: options.language } : {}),
733
- });
734
- }
735
633
  case "deepgram-flux": {
736
634
  // Flux is streaming-only and dials Deepgram's /v2/listen conversational
737
635
  // endpoint. Turn-detection tuning comes from `liveVoice.flux`, which the
@@ -775,29 +673,14 @@ async function createStreamingTranscriber(
775
673
  * need a key-existence check don't import secure-keys directly.
776
674
  */
777
675
  /**
778
- * Human-readable reason for a credential gap, aware that connection-based
779
- * providers (cadet) are fixed by connecting the cloud account, not by
780
- * entering an API key.
676
+ * Human-readable reason for a credential gap.
781
677
  */
782
678
  export function sttCredentialGapReason(credentialProviderName: string): string {
783
- // Called with either the catalog id ("cadet") or its credential-service
784
- // key ("cadet", the persisted identity family) — both mean cloud speech.
785
- if (
786
- credentialProviderName === "cadet" ||
787
- credentialProviderName === "cadet"
788
- ) {
789
- return "No Cadet Cloud connection for cloud speech — run 'assistant cloud connect'";
790
- }
791
679
  return `No API key configured for credential provider "${credentialProviderName}"`;
792
680
  }
793
681
 
794
682
  export async function sttProviderKeyResolves(
795
683
  credentialProviderName: string,
796
684
  ): Promise<boolean> {
797
- // cadet has no stored API key — the cloud connection is the credential.
798
- if (credentialProviderName === "cadet") {
799
- const { cadetCloudSpeechAvailable } = await import("./cadet-cloud.js");
800
- return cadetCloudSpeechAvailable();
801
- }
802
685
  return (await getProviderKeyAsync(credentialProviderName)) !== undefined;
803
686
  }
@@ -45,9 +45,7 @@ export type RouteBodySchema = z.ZodType;
45
45
 
46
46
  /**
47
47
  * Description for a non-200 response variant. Used when an endpoint has
48
- * meaningful alternate response shapes that clients should handle (e.g.
49
- * 502 `fetch_failed` on POST /v1/migrations/import when the URL body path
50
- * can't reach the upstream bundle host).
48
+ * meaningful alternate response shapes that clients should handle.
51
49
  */
52
50
  export interface RouteAdditionalResponse {
53
51
  description: string;
@@ -122,7 +122,7 @@ const log = getLogger("runtime-http");
122
122
  const DEFAULT_PORT = 7821;
123
123
  const DEFAULT_HOSTNAME = "127.0.0.1";
124
124
 
125
- /** Global hard cap on request body size (512 MB — accommodates large .vbundle backup imports). */
125
+ /** Global hard cap on request body size (512 MB, accommodates app package uploads). */
126
126
  const MAX_REQUEST_BODY_BYTES = 512 * 1024 * 1024;
127
127
 
128
128
  function dbMigrationUnavailableForEndpoint(endpoint: string): Response | null {
@@ -1625,7 +1625,7 @@ describe("config GET serves profiles verbatim", () => {
1625
1625
  });
1626
1626
  });
1627
1627
 
1628
- describe("provider membership at the write choke point", () => {
1628
+ describe("provider labels follow the open-string Zod schema", () => {
1629
1629
  const configPatchRoute = ROUTES.find(
1630
1630
  (r) => r.operationId === "config_patch",
1631
1631
  )!;
@@ -1659,21 +1659,23 @@ describe("provider membership at the write choke point", () => {
1659
1659
  expect(llm.profiles?.work?.provider).toBe("anthropic-work");
1660
1660
  });
1661
1661
 
1662
- test("introducing an unknown provider is rejected", async () => {
1662
+ test("a new provider label is accepted by the schema", async () => {
1663
1663
  rawConfigFixture = { llm: {} };
1664
1664
  seedRawConfig();
1665
-
1666
- await expect(
1667
- configPatchRoute.handler({
1668
- body: {
1669
- llm: {
1670
- profiles: {
1671
- rogue: { source: "user", provider: "not-a-provider", model: "m" },
1665
+ await configPatchRoute.handler({
1666
+ body: {
1667
+ llm: {
1668
+ profiles: {
1669
+ work: {
1670
+ source: "user",
1671
+ provider: "anthropic-work",
1672
+ model: "claude-opus-4-8",
1672
1673
  },
1673
1674
  },
1674
1675
  },
1675
- }),
1676
- ).rejects.toThrow(/Invalid provider/);
1676
+ },
1677
+ });
1678
+ expect(persistedProfile("work").provider).toBe("anthropic-work");
1677
1679
  });
1678
1680
  });
1679
1681
 
@@ -127,17 +127,13 @@ afterEach(() => {
127
127
  // ---------------------------------------------------------------------------
128
128
 
129
129
  describe("tts-routes", () => {
130
- test("exports route definitions for tts/providers, tts/managed-voices, tts/synthesize, and tts/synthesize-cli", () => {
131
- expect(ROUTES).toHaveLength(4);
130
+ test("exports route definitions for tts/providers, tts/synthesize, and tts/synthesize-cli", () => {
131
+ expect(ROUTES).toHaveLength(3);
132
132
 
133
133
  const providers = getRoute("tts/providers");
134
134
  expect(providers.method).toBe("GET");
135
135
  expect(providers.policy?.requiredScopes).toContain("settings.read");
136
136
 
137
- const managedVoices = getRoute("tts/managed-voices");
138
- expect(managedVoices.method).toBe("GET");
139
- expect(managedVoices.policy?.requiredScopes).toContain("settings.read");
140
-
141
137
  const synthesize = getRoute("tts/synthesize");
142
138
  expect(synthesize.method).toBe("POST");
143
139
  expect(synthesize.policy?.requiredScopes).toContain("chat.read");
@@ -53,8 +53,6 @@ import {
53
53
  LLMDefaultConfigSchema,
54
54
  LLMSchema,
55
55
  ProfileEntry,
56
- routingIdentityModelIssue,
57
- unknownLlmProviderIssue,
58
56
  } from "../../config/schemas/llm.js";
59
57
  import { VALID_MEMORY_EMBEDDING_PROVIDERS } from "../../config/schemas/memory-storage.js";
60
58
  import { ServiceModeSchema } from "../../config/schemas/services.js";
@@ -106,7 +104,6 @@ import { getMemoryRecallLogByMessageIds } from "../../plugins/defaults/memory/me
106
104
  import { getMemoryV2ActivationLogByMessageIds } from "../../plugins/defaults/memory/v2/activation-log-store.js";
107
105
  import { getMemoryV3SelectionForInspectorByMessageIds } from "../../plugins/defaults/memory/v3/selection-log-store.js";
108
106
  import { CLOUD_ROUTABLE_PROVIDERS } from "../../providers/cadet-model-routing.js";
109
- import { writableProfileProviderIssue } from "../../providers/connection-resolution.js";
110
107
  import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
111
108
  import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "../../providers/inference/auth.js";
112
109
  import {
@@ -684,98 +681,6 @@ function handleGetConfigSchema({ queryParams = {} }: RouteHandlerArgs) {
684
681
  };
685
682
  }
686
683
 
687
- /**
688
- * Reject writes that would store a routing-identity provider with a missing
689
- * or unroutable model (see `routingIdentityModelIssue`). saveRawConfig
690
- * persists without schema validation, so the parse-time rejection alone
691
- * would let the pair reach disk and be stripped on the next read — a silent
692
- * no-op where the user expects a saved override.
693
- */
694
- function collectProviderEntries(
695
- raw: Record<string, unknown>,
696
- ): [string, { provider?: unknown; model?: unknown }][] {
697
- const llm = raw.llm as
698
- | {
699
- default?: { provider?: unknown; model?: unknown } | null;
700
- profiles?: Record<
701
- string,
702
- { provider?: unknown; model?: unknown } | null | undefined
703
- >;
704
- callSites?: Record<
705
- string,
706
- { provider?: unknown; model?: unknown } | null | undefined
707
- >;
708
- }
709
- | undefined;
710
- const entries: [string, { provider?: unknown; model?: unknown }][] = [];
711
- // llm.default is a raw compatibility field outside the parsed schema;
712
- // profile materialization uses the on-disk blob as its fill base, so it is
713
- // checked like the schema-carried sections.
714
- if (llm?.default) {
715
- entries.push(["llm.default", llm.default]);
716
- }
717
- for (const section of ["profiles", "callSites"] as const) {
718
- for (const [name, entry] of Object.entries(llm?.[section] ?? {})) {
719
- if (entry) {
720
- entries.push([`llm.${section}.${name}`, entry]);
721
- }
722
- }
723
- }
724
- return entries;
725
- }
726
-
727
- function assertRoutableIdentityEntries(
728
- preWrite: Record<string, unknown>,
729
- raw: Record<string, unknown>,
730
- ): void {
731
- const entries = collectProviderEntries(raw);
732
- const priorProviders = new Map(
733
- collectProviderEntries(preWrite).flatMap(([label, entry]) =>
734
- typeof entry.provider === "string" ? [[label, entry.provider]] : [],
735
- ),
736
- );
737
- for (const [label, entry] of entries) {
738
- if (typeof entry.provider !== "string") {
739
- continue;
740
- }
741
- // The provider schema is an open string (a stored value outside the
742
- // known set parses instead of stripping its profile), so write-time
743
- // membership is enforced here and at the profiles route. Scoped to
744
- // providers this write introduces or changes: a stored value outside
745
- // the known set is readable and dispatchable by design, and
746
- // re-validating it on every write would make all later settings saves
747
- // fail with no in-product repair path. Profiles accept entry names;
748
- // call-site fragments and the legacy default blob stay vendor-only
749
- // (overrides become model-only with the entries demolition).
750
- if (entry.provider !== priorProviders.get(label)) {
751
- const providerIssue = label.startsWith("llm.profiles.")
752
- ? writableProfileProviderIssue(entry.provider)
753
- : unknownLlmProviderIssue(entry.provider);
754
- if (providerIssue) {
755
- throw new BadRequestError(`${providerIssue} (${label})`);
756
- }
757
- }
758
- // A model-less entry under a code-defined default name is the thin
759
- // managed stub — its content is code-owned, so the routing-identity
760
- // model check applies only to entries carrying their own body.
761
- const profileName = label.startsWith("llm.profiles.")
762
- ? label.slice("llm.profiles.".length)
763
- : undefined;
764
- const isManagedName =
765
- profileName != null && MANAGED_PROFILE_NAMES.has(profileName);
766
- if (entry.model == null && isManagedName) {
767
- continue;
768
- }
769
- const issue = routingIdentityModelIssue(
770
- entry.provider,
771
- typeof entry.model === "string" ? entry.model : undefined,
772
- );
773
- if (issue) {
774
- throw new BadRequestError(`${issue} (${label})`);
775
- }
776
- }
777
- }
778
-
779
684
  export async function commitConfigWrite(
780
685
  raw: Record<string, unknown>,
781
686
  opLabel: string,
@@ -792,7 +697,6 @@ export async function commitConfigWrite(
792
697
  if (!chatSettings.success) {
793
698
  throw new BadRequestError(chatSettings.error.message);
794
699
  }
795
- assertRoutableIdentityEntries(preWrite, raw);
796
700
 
797
701
  // Suppress the file-watcher callback for the duration of the debounce
798
702
  // window. Without this, the ConfigWatcher detects the config.json write
@@ -200,17 +200,17 @@ describe("tryTranscribeAudioAttachments", () => {
200
200
  expect(reason).toContain("Deepgram");
201
201
  });
202
202
 
203
- test("managed auth failure surfaces its remediation, not the BYOK API-key rewrite", async () => {
203
+ test("a userFacing auth failure surfaces its remediation, not the BYOK API-key rewrite", async () => {
204
204
  const audio = makeAudioAttachment("a1");
205
205
  mockAttachments = [audio];
206
206
  const managedMessage =
207
207
  "Cloud speech needs a working Cadet Cloud connection — reconnect with 'assistant cloud connect'.";
208
208
  mockTranscriber = {
209
- providerId: "cadet",
209
+ providerId: "deepgram",
210
210
  boundaryId: "daemon-batch",
211
211
  transcribe: async () => {
212
- // Managed adapters flag their SttError userFacing so the friendly
213
- // copy layer passes the message through verbatim.
212
+ // Adapters flag their SttError userFacing so the friendly copy
213
+ // layer passes the message through verbatim.
214
214
  throw new SttError("auth", managedMessage, { userFacing: true });
215
215
  },
216
216
  };
@@ -220,18 +220,18 @@ describe("tryTranscribeAudioAttachments", () => {
220
220
  expect(result.status).toBe("error");
221
221
  const reason = (result as { reason: string }).reason;
222
222
  expect(reason).toBe(managedMessage);
223
- // Managed users have no STT key — the BYOK rewrite must not appear.
223
+ // The BYOK rewrite must not appear over a userFacing message.
224
224
  expect(reason).not.toContain("API key");
225
225
  expect(reason).not.toContain("Settings → Voice");
226
226
  });
227
227
 
228
- test("managed credits-exhausted failure is not hidden behind generic copy", async () => {
228
+ test("a credits-exhausted userFacing failure is not hidden behind generic copy", async () => {
229
229
  const audio = makeAudioAttachment("a1");
230
230
  mockAttachments = [audio];
231
231
  const creditsMessage =
232
232
  "Cadet credits are exhausted — add funds to your Cadet account to continue using managed transcription.";
233
233
  mockTranscriber = {
234
- providerId: "cadet",
234
+ providerId: "deepgram",
235
235
  boundaryId: "daemon-batch",
236
236
  transcribe: async () => {
237
237
  throw new SttError("provider-error", creditsMessage, {
@@ -27,7 +27,6 @@ import { ROUTES as AUDIT_ROUTES } from "./audit-routes.js";
27
27
  import { ROUTES as AUTH_ROUTES } from "./auth-routes.js";
28
28
  import { ROUTES as AVATAR_ROUTES } from "./avatar-routes.js";
29
29
  import { ROUTES as BACKGROUND_TOOL_ROUTES } from "./background-tool-routes.js";
30
- import { ROUTES as BACKGROUND_WAKE_ROUTES } from "./background-wake-routes.js";
31
30
  import { ROUTES as BOOKMARK_ROUTES } from "./bookmark-routes.js";
32
31
  import { ROUTES as BRAIN_GRAPH_ROUTES } from "./brain-graph-routes.js";
33
32
  import { ROUTES as BROWSER_ROUTES } from "./browser-routes.js";
@@ -114,7 +113,6 @@ import { ROUTES as LOG_EXPORT_ROUTES } from "./log-export-routes.js";
114
113
  import { ROUTES as MCP_AUTH_ROUTES } from "./mcp-auth-routes.js";
115
114
  import { ROUTES as MESSAGES_LEXICAL_ROUTES } from "./messages-lexical-routes.js";
116
115
  import { ROUTES as MIGRATION_ROLLBACK_ROUTES } from "./migration-rollback-routes.js";
117
- import { ROUTES as MIGRATION_ROUTES } from "./migration-routes.js";
118
116
  import { ROUTES as MONITORING_ROUTES } from "./monitoring-routes.js";
119
117
  import { ROUTES as NOTIFICATION_ROUTES } from "./notification-routes.js";
120
118
  import { ROUTES as OAUTH_APPS_ROUTES } from "./oauth-apps.js";
@@ -179,7 +177,6 @@ export const ROUTES: RouteDefinition[] = [
179
177
  ...AUDIT_ROUTES,
180
178
  ...AUTH_ROUTES,
181
179
  ...AVATAR_ROUTES,
182
- ...BACKGROUND_WAKE_ROUTES,
183
180
  ...BACKGROUND_TOOL_ROUTES,
184
181
  ...BOOKMARK_ROUTES,
185
182
  ...CACHE_ROUTES,
@@ -267,7 +264,6 @@ export const ROUTES: RouteDefinition[] = [
267
264
  ...MEMORY_WORKER_ROUTES,
268
265
  ...MESSAGES_LEXICAL_ROUTES,
269
266
  ...MIGRATION_ROLLBACK_ROUTES,
270
- ...MIGRATION_ROUTES,
271
267
  ...NOTIFICATION_ROUTES,
272
268
  ...OAUTH_APPS_ROUTES,
273
269
  ...OAUTH_LIFECYCLE_ROUTES,
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Tests for the live-voice preflight and session-end routes.
3
3
  *
4
- * The handler must run cloud-speech defaulting first, then return the
5
- * live-voice credential readiness verdict verbatim:
4
+ * The preflight handler returns the live-voice credential readiness verdict
5
+ * verbatim:
6
6
  * - ready → { status: "ready" }
7
7
  * - not-ready → passes through the missing gaps + userMessage
8
- * - defaulting is invoked BEFORE readiness is resolved
9
8
  *
10
9
  * The session-end route is the out-of-band recovery path (LUM-3440): it
11
10
  * reports whether it actually reclaimed the daemon's single live-voice slot.
@@ -23,12 +22,6 @@ const calls: string[] = [];
23
22
  let readiness: LiveVoiceCredentialReadiness = { status: "ready" };
24
23
  let releaseResult: LiveVoiceSessionReleaseResult = { released: false };
25
24
 
26
- mock.module("../../config/cloud-speech-defaults.js", () => ({
27
- maybeDefaultSpeechToCloud: async () => {
28
- calls.push("default");
29
- },
30
- }));
31
-
32
25
  mock.module("../../live-voice/live-voice-credential-preflight.js", () => ({
33
26
  resolveLiveVoiceCredentialReadiness: async () => {
34
27
  calls.push("resolve");
@@ -80,13 +73,13 @@ describe("live_voice_preflight_post", () => {
80
73
  expect(result).toEqual(readiness);
81
74
  });
82
75
 
83
- test("runs cloud-speech defaulting before resolving readiness", async () => {
76
+ test("resolves readiness without touching persisted config", async () => {
84
77
  calls.length = 0;
85
78
  readiness = { status: "ready" };
86
79
 
87
80
  await preflightRoute.handler({});
88
81
 
89
- expect(calls).toEqual(["default", "resolve"]);
82
+ expect(calls).toEqual(["resolve"]);
90
83
  });
91
84
  });
92
85
 
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Transport-agnostic route definitions for live-voice preflight.
3
3
  *
4
- * POST /v1/live-voice/preflight — ensure cloud-speech defaulting has run,
5
- * then report whether the daemon can run both audio legs of a live voice
6
- * session. Lets the web client verify voice is configured BEFORE opening the
4
+ * POST /v1/live-voice/preflight — report whether the daemon can run both
5
+ * audio legs of a live voice session. Lets the web client verify voice is
6
+ * configured BEFORE opening the
7
7
  * voice-room WebSocket, instead of opening it and reacting to an error frame.
8
8
  *
9
9
  * POST /v1/live-voice/session/end releases whatever session holds the
@@ -22,12 +22,9 @@ import type { RouteDefinition } from "./types.js";
22
22
  // ---------------------------------------------------------------------------
23
23
 
24
24
  async function handleLiveVoicePreflight() {
25
- const { maybeDefaultSpeechToCloud } =
26
- await import("../../config/cloud-speech-defaults.js");
27
25
  const { resolveLiveVoiceCredentialReadiness } =
28
26
  await import("../../live-voice/live-voice-credential-preflight.js");
29
27
 
30
- await maybeDefaultSpeechToCloud();
31
28
  return resolveLiveVoiceCredentialReadiness();
32
29
  }
33
30
 
@@ -51,17 +48,17 @@ export const ROUTES: RouteDefinition[] = [
51
48
  operationId: "live_voice_preflight_post",
52
49
  endpoint: "live-voice/preflight",
53
50
  method: "POST",
54
- // Requires settings.write, not a read scope: the handler runs
55
- // maybeDefaultSpeechToCloud(), which can persist config changes
56
- // (switching services.stt/tts.provider to "cadet"). This matches the
57
- // secret/settings config writers, which all require settings.write.
51
+ // Requires settings.write, not a read scope: preflight reports the
52
+ // assistant's speech credential state, which the caller may act on to
53
+ // rewrite speech config. This matches the secret/settings config
54
+ // writers, which all require settings.write.
58
55
  policy: {
59
56
  requiredScopes: ["settings.write"],
60
57
  allowedPrincipalTypes: ACTOR_PRINCIPALS,
61
58
  },
62
59
  summary: "Live voice preflight",
63
60
  description:
64
- "Ensure cloud speech defaulting has run, then report whether live voice can start.",
61
+ "Report whether live voice can start with the current speech credentials.",
65
62
  tags: ["live-voice"],
66
63
  responseBody: z.object({
67
64
  status: z.enum(["ready", "not-ready"]),
@@ -16,7 +16,6 @@ import {
16
16
  } from "../../acp/acp-credentials.js";
17
17
  import { syncAvatarToCloud } from "../../avatar/sync-avatar.js";
18
18
  import { syncWorkspaceIdentityToPlatform } from "../../cloud/sync-identity.js";
19
- import { maybeDefaultSpeechToCloud } from "../../config/cloud-speech-defaults.js";
20
19
  import { getCloudAssistantId } from "../../config/env.js";
21
20
  import { getConfig } from "../../config/loader.js";
22
21
  import { getCesClient } from "../../credential-execution/ces-runtime.js";
@@ -342,11 +341,6 @@ async function handleAddSecret({ body }: RouteHandlerArgs) {
342
341
  await refreshProvidersForRotatedCredential(service, field);
343
342
  }
344
343
  if (field === "assistant_api_key") {
345
- // Managed-speech availability needs the API key and the assistant
346
- // ID. Fire on each write that completes a piece of that set; the
347
- // hook no-ops until the connection is complete.
348
- // Detached, must not block the response.
349
- void maybeDefaultSpeechToCloud();
350
344
  // Same last-write-wins shape: the startup syncs no-op before live
351
345
  // registration, so re-enqueue them here. Both dedup and no-op until
352
346
  // the client and assistant id exist.
@@ -8,7 +8,6 @@
8
8
  import { z } from "zod";
9
9
 
10
10
  import { sanitizeForTts } from "../../calls/tts-text-sanitizer.js";
11
- import { managedSpeechVoices } from "../../cloud/cloud-speech.js";
12
11
  import { getConfig } from "../../config/loader.js";
13
12
  import { listCatalogProvidersForDisplay } from "../../tts/provider-catalog.js";
14
13
  import {
@@ -132,17 +131,6 @@ function handleListTtsProviders() {
132
131
  return { providers: listCatalogProvidersForDisplay() };
133
132
  }
134
133
 
135
- async function handleListCloudVoices() {
136
- const result = await managedSpeechVoices();
137
- if (!result.ok) {
138
- if (result.kind === "unavailable") {
139
- throw new ServiceUnavailableError(result.message);
140
- }
141
- throw new BadGatewayError(result.message);
142
- }
143
- return result.value;
144
- }
145
-
146
134
  async function handleSynthesizeTts({ body }: RouteHandlerArgs) {
147
135
  if (!body?.text || typeof body.text !== "string") {
148
136
  throw new BadRequestError("text is required");
@@ -234,32 +222,6 @@ export const ROUTES: RouteDefinition[] = [
234
222
  }),
235
223
  handler: handleListTtsProviders,
236
224
  },
237
- {
238
- operationId: "tts_managed_voices",
239
- endpoint: "tts/managed-voices",
240
- method: "GET",
241
- policy: {
242
- requiredScopes: ["settings.read"],
243
- allowedPrincipalTypes: ACTOR_PRINCIPALS,
244
- },
245
- summary: "List managed TTS voices",
246
- description:
247
- "Return the voices offered by Cadet Cloud TTS, fetched live from the cloud. Only currently-usable voices are returned; defaultModel is always one of them (null when none are offered).",
248
- tags: ["tts"],
249
- responseBody: z.object({
250
- voices: z.array(
251
- z.object({
252
- model: z.string(),
253
- label: z.string(),
254
- description: z.string(),
255
- sampleUrl: z.string(),
256
- source: z.string(),
257
- }),
258
- ),
259
- defaultModel: z.string().nullable(),
260
- }),
261
- handler: handleListCloudVoices,
262
- },
263
225
  {
264
226
  operationId: "tts_synthesize",
265
227
  endpoint: "tts/synthesize",
@@ -3,19 +3,13 @@
3
3
  *
4
4
  * Fixture plugin directories are written under the (per-test-process)
5
5
  * workspace plugins dir and converged against the real schedule store; the
6
- * only stubs are the notification emitter (recorded for assertions) and the
7
- * background-wake publisher (kept out for hermeticity, mirroring
8
- * schedule-store.test.ts).
6
+ * only stub is the notification emitter (recorded for assertions).
9
7
  */
10
8
 
11
9
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
12
10
  import { dirname, join } from "node:path";
13
11
  import { beforeEach, describe, expect, mock, test } from "bun:test";
14
12
 
15
- mock.module("../../background-wake/publisher.js", () => ({
16
- refreshBackgroundWakeIntent: () => {},
17
- }));
18
-
19
13
  const emittedSignals: Array<Record<string, unknown>> = [];
20
14
 
21
15
  /** When set, merged into the next emit results (e.g. a failure reason). */
@@ -32,10 +32,6 @@ mock.module("../../daemon/process-message.js", () => ({
32
32
  processMessageInBackground: async () => {},
33
33
  }));
34
34
 
35
- mock.module("../../background-wake/publisher.js", () => ({
36
- refreshBackgroundWakeIntent: () => {},
37
- }));
38
-
39
35
  mock.module("../../daemon/disk-pressure-background-gate.js", () => ({
40
36
  checkDiskPressureBackgroundGate: () => ({
41
37
  action: "allow",