@mycadet/assistant 0.12.8 → 0.12.9

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 (42) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +1 -1
  2. package/node_modules/@mycadet/ces-client/package.json +1 -1
  3. package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +1 -1
  5. package/node_modules/@mycadet/service-contracts/package.json +1 -1
  6. package/openapi.yaml +2 -18
  7. package/package.json +2 -2
  8. package/src/__tests__/cloud-profile-guard.test.ts +21 -19
  9. package/src/__tests__/config-set-route.test.ts +12 -8
  10. package/src/__tests__/credential-health-service.test.ts +4 -2
  11. package/src/__tests__/get-skill-detail-audit.test.ts +134 -289
  12. package/src/__tests__/mcp-abort-signal.test.ts +0 -1
  13. package/src/__tests__/mcp-client-auth.test.ts +0 -1
  14. package/src/__tests__/mcp-config-secret-boundary.test.ts +44 -40
  15. package/src/__tests__/mcp-tool-annotations-risk.test.ts +0 -1
  16. package/src/__tests__/plugin-api-store-credential.test.ts +4 -1
  17. package/src/__tests__/skills-file-content-endpoint.test.ts +221 -535
  18. package/src/__tests__/skills-files-catalog-fallback.test.ts +258 -576
  19. package/src/api/events/show-cloud-login.ts +2 -2
  20. package/src/cli/commands/cloud/__tests__/callback-routes-list.test.ts +2 -2
  21. package/src/cli/commands/cloud/index.ts +1 -1
  22. package/src/cli/commands/config.ts +1 -1
  23. package/src/config/__tests__/substrate-twin-shadowing.test.ts +36 -26
  24. package/src/config/env-registry.ts +2 -9
  25. package/src/config/schemas/ingress.ts +30 -0
  26. package/src/config/schemas/llm.ts +0 -9
  27. package/src/config/seed-inference-profiles.ts +2 -2
  28. package/src/config/substrate-twin-shadowing.ts +12 -17
  29. package/src/config/sync-gated-profiles.ts +3 -4
  30. package/src/credential-execution/prompted-credential.ts +1 -10
  31. package/src/credential-health/credential-health-service.ts +8 -4
  32. package/src/daemon/credential-transcript-scrub.ts +11 -0
  33. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -0
  34. package/src/providers/inference/backfill.ts +2 -2
  35. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +100 -57
  36. package/src/runtime/routes/cloud-routes.ts +5 -5
  37. package/src/runtime/routes/conversation-query-routes.ts +275 -397
  38. package/src/runtime/routes/secret-routes.ts +2 -17
  39. package/src/tools/credentials/store.ts +1 -10
  40. package/src/util/layout.ts +479 -0
  41. package/src/util/platform.ts +11 -467
  42. package/src/workspace/migrations/127-backfill-default-provider.ts +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `show_platform_login` SSE event.
2
+ * `show_cloud_login` SSE event.
3
3
  *
4
4
  * Emitted by the cloud-connect route when no cloud credentials are
5
5
  * stored yet, signalling connected clients to surface the cloud login UI.
@@ -12,7 +12,7 @@
12
12
  import { z } from "zod";
13
13
 
14
14
  export const ShowCloudLoginEventSchema = z.object({
15
- type: z.literal("show_platform_login"),
15
+ type: z.literal("show_cloud_login"),
16
16
  });
17
17
 
18
18
  export type ShowCloudLoginEvent = z.infer<typeof ShowCloudLoginEventSchema>;
@@ -66,7 +66,7 @@ describe("assistant cloud callback-routes list", () => {
66
66
  expect((thrown as Error).message).toBe("exitFromIpcResult called");
67
67
  });
68
68
 
69
- test("callback-routes register calls platform_callback_routes_register", async () => {
69
+ test("callback-routes register calls cloud_callback_routes_register", async () => {
70
70
  ipc.response = {
71
71
  ok: true,
72
72
  result: {
@@ -87,7 +87,7 @@ describe("assistant cloud callback-routes list", () => {
87
87
  "--json",
88
88
  ]);
89
89
 
90
- expect(ipc.calls[0][0]).toBe("platform_callback_routes_register");
90
+ expect(ipc.calls[0][0]).toBe("cloud_callback_routes_register");
91
91
  expect((ipc.calls[0][1].body as Record<string, unknown>).path).toBe(
92
92
  "webhooks/telegram",
93
93
  );
@@ -260,7 +260,7 @@ export function registerCloudCommand(program: Command): void {
260
260
  callbackUrl: string;
261
261
  callbackPath: string;
262
262
  type: string;
263
- }>("platform_callback_routes_register", {
263
+ }>("cloud_callback_routes_register", {
264
264
  body: { path: opts.path, type: opts.type },
265
265
  });
266
266
  if (!r.ok) {
@@ -153,7 +153,7 @@ export function registerConfigCommand(program: Command): void {
153
153
  // the value above cannot be mistaken for the effective one.
154
154
  const { describeShadowedConfigGet, findSubstrateShadowing } =
155
155
  await import("../../config/substrate-twin-shadowing.js");
156
- const shadowing = findSubstrateShadowing(raw, key);
156
+ const shadowing = findSubstrateShadowing(key);
157
157
  if (shadowing) {
158
158
  log.warn(describeShadowedConfigGet(shadowing, key));
159
159
  }
@@ -1,3 +1,5 @@
1
+ import { utimesSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { describe, expect, test } from "bun:test";
2
4
 
3
5
  import { MemorySubstrateConfigSchema } from "../schemas/memory-substrate.js";
@@ -9,6 +11,18 @@ import {
9
11
  SUBSTRATE_TWIN_BY_V2_KEY,
10
12
  } from "../substrate-twin-shadowing.js";
11
13
 
14
+ /**
15
+ * Write `raw` to the workspace config.json as the state the loader serves.
16
+ * A distinct mtime per write keeps the loader's file-signature cache from
17
+ * reading two consecutive seeds as identical.
18
+ */
19
+ function seedRawConfig(raw: Record<string, unknown>): void {
20
+ const path = join(process.env.CADET_WORKSPACE_DIR!, "config.json");
21
+ writeFileSync(path, JSON.stringify(raw));
22
+ const stamp = new Date(Date.now() + Math.random());
23
+ utimesSync(path, stamp, stamp);
24
+ }
25
+
12
26
  describe("substrate twin table", () => {
13
27
  test("pairs exactly the memory.substrate schema's keys", () => {
14
28
  // Drift guard: a substrate key with no entry here is a tunable whose
@@ -44,15 +58,15 @@ describe("substrate twin table", () => {
44
58
 
45
59
  describe("findSubstrateShadowing", () => {
46
60
  test("reports the winning substrate twin for a shadowed memory.v2 key", () => {
47
- const raw = {
61
+ seedRawConfig({
48
62
  memory: {
49
63
  v2: { consolidation_interval_hours: 4 },
50
64
  substrate: { consolidation_interval_hours: 876000 },
51
65
  },
52
- };
66
+ });
53
67
 
54
68
  expect(
55
- findSubstrateShadowing(raw, "memory.v2.consolidation_interval_hours"),
69
+ findSubstrateShadowing("memory.v2.consolidation_interval_hours"),
56
70
  ).toEqual({
57
71
  substratePath: "memory.substrate.consolidation_interval_hours",
58
72
  substrateValue: 876000,
@@ -61,29 +75,31 @@ describe("findSubstrateShadowing", () => {
61
75
  });
62
76
 
63
77
  test("maps the renamed spread keys to their substrate names", () => {
64
- const raw = { memory: { v2: {}, substrate: { spread_k: 0.4 } } };
78
+ seedRawConfig({ memory: { v2: {}, substrate: { spread_k: 0.4 } } });
65
79
 
66
- expect(findSubstrateShadowing(raw, "memory.v2.k")).toEqual({
80
+ expect(findSubstrateShadowing("memory.v2.k")).toEqual({
67
81
  substratePath: "memory.substrate.spread_k",
68
82
  substrateValue: 0.4,
69
83
  alsoReadByV2Engine: true,
70
84
  });
71
85
  // hops has no twin set, so nothing shadows it.
72
- expect(findSubstrateShadowing(raw, "memory.v2.hops")).toBeUndefined();
86
+ expect(findSubstrateShadowing("memory.v2.hops")).toBeUndefined();
73
87
  });
74
88
 
75
89
  test("treats an explicit null twin as set, matching resolveSubstrateTuning", () => {
76
90
  // `orV2` falls back only on `undefined`, so a null substrate value wins —
77
- // and it is exactly the value the consolidation freeze writes.
78
- const raw = {
91
+ // and it is exactly the value the consolidation freeze writes. The field
92
+ // is nullable in the schema, so the null survives the loader's parse and
93
+ // stays present in the running config.
94
+ seedRawConfig({
79
95
  memory: {
80
96
  v2: { consolidation_max_buffer_lines: 100 },
81
97
  substrate: { consolidation_max_buffer_lines: null },
82
98
  },
83
- };
99
+ });
84
100
 
85
101
  expect(
86
- findSubstrateShadowing(raw, "memory.v2.consolidation_max_buffer_lines"),
102
+ findSubstrateShadowing("memory.v2.consolidation_max_buffer_lines"),
87
103
  ).toEqual({
88
104
  substratePath: "memory.substrate.consolidation_max_buffer_lines",
89
105
  substrateValue: null,
@@ -92,29 +108,23 @@ describe("findSubstrateShadowing", () => {
92
108
  });
93
109
 
94
110
  test("returns nothing for unshadowed paths", () => {
95
- const raw = {
111
+ // No memory.substrate subtree at all.
112
+ seedRawConfig({ memory: { v2: { bm25_b: 0.6 } } });
113
+ expect(findSubstrateShadowing("memory.v2.bm25_b")).toBeUndefined();
114
+
115
+ seedRawConfig({
96
116
  memory: {
97
117
  v2: { bm25_b: 0.6, enabled: true },
98
118
  substrate: { bm25_b: 0.3 },
99
119
  },
100
- };
101
-
102
- // No memory.substrate subtree at all.
103
- expect(
104
- findSubstrateShadowing(
105
- { memory: { v2: { bm25_b: 0.6 } } },
106
- "memory.v2.bm25_b",
107
- ),
108
- ).toBeUndefined();
120
+ });
109
121
  // A v2 key with no substrate twin.
110
- expect(findSubstrateShadowing(raw, "memory.v2.enabled")).toBeUndefined();
122
+ expect(findSubstrateShadowing("memory.v2.enabled")).toBeUndefined();
111
123
  // The substrate key itself is what wins — writing it is never shadowed.
112
- expect(
113
- findSubstrateShadowing(raw, "memory.substrate.bm25_b"),
114
- ).toBeUndefined();
124
+ expect(findSubstrateShadowing("memory.substrate.bm25_b")).toBeUndefined();
115
125
  // Paths outside the memory namespace, and the wrong depth.
116
- expect(findSubstrateShadowing(raw, "memory.v2")).toBeUndefined();
117
- expect(findSubstrateShadowing(raw, "llm.v2.bm25_b")).toBeUndefined();
126
+ expect(findSubstrateShadowing("memory.v2")).toBeUndefined();
127
+ expect(findSubstrateShadowing("llm.v2.bm25_b")).toBeUndefined();
118
128
  });
119
129
  });
120
130
 
@@ -20,7 +20,7 @@ function str(name: string): string | undefined {
20
20
  }
21
21
 
22
22
  function flag(name: string): boolean {
23
- const raw = str(name);
23
+ const raw = str(name)?.toLowerCase();
24
24
  return raw === "true" || raw === "1";
25
25
  }
26
26
 
@@ -65,14 +65,7 @@ export function getIsContainerized(): boolean {
65
65
  * deployments provisioned before the rename keep working.
66
66
  */
67
67
  export function getIsCloud(): boolean {
68
- return flag("IS_CLOUD") || flag("IS_CLOUD");
69
- }
70
-
71
- /**
72
- * Whether this assistant is running as a Cadet Cloud-managed remote instance.
73
- */
74
- export function isCloudRemote(): boolean {
75
- return getIsCloud();
68
+ return flag("IS_CLOUD");
76
69
  }
77
70
 
78
71
  /**
@@ -1,5 +1,23 @@
1
+ import {
2
+ INGRESS_ASSISTANT_ID_KEY,
3
+ INGRESS_LAST_TUNNEL_KEY,
4
+ INGRESS_PAIRING_TUNNEL_KEY,
5
+ TUNNEL_PROVIDERS,
6
+ } from "@mycadet/service-contracts/ingress";
1
7
  import { z } from "zod";
2
8
 
9
+ /**
10
+ * A recorded tunnel under `ingress.lastTunnel` / `ingress.pairingTunnel`.
11
+ * The CLI writes these records (validated by `parseTunnelRecord` in
12
+ * service-contracts — the single source for the key names, the provider
13
+ * allowlist, and record validation); the schema carries them so schema-
14
+ * canonical config writes preserve live tunnel state instead of dropping it.
15
+ */
16
+ const TunnelRecordSchema = z.object({
17
+ provider: z.enum(TUNNEL_PROVIDERS),
18
+ publicBaseUrl: z.string(),
19
+ });
20
+
3
21
  function emptyOrAbsoluteHttpUrl(fieldPath: string) {
4
22
  return z
5
23
  .string({ error: `${fieldPath} must be a string` })
@@ -94,6 +112,18 @@ const IngressBaseSchema = z
94
112
  rateLimit: IngressRateLimitConfigSchema.default(
95
113
  IngressRateLimitConfigSchema.parse({}),
96
114
  ),
115
+ [INGRESS_ASSISTANT_ID_KEY]: z
116
+ .string()
117
+ .optional()
118
+ .describe("The assistant id the tunnel fronted (CLI-owned record)"),
119
+ [INGRESS_LAST_TUNNEL_KEY]: TunnelRecordSchema.optional(),
120
+ [INGRESS_PAIRING_TUNNEL_KEY]: TunnelRecordSchema.optional(),
121
+ publicBaseUrlManagedBy: z
122
+ .string()
123
+ .optional()
124
+ .describe(
125
+ 'Who owns the current publicBaseUrl — "tunnel" when the tunnel machinery stamped it, anything else is user-managed (CLI/route-owned bookkeeping)',
126
+ ),
97
127
  })
98
128
  .describe(
99
129
  "Ingress HTTP server configuration — handles incoming webhooks, API requests, and channel events",
@@ -733,15 +733,6 @@ export const ProfileEntry = LLMConfigFragment.extend({
733
733
  * not supported.
734
734
  */
735
735
  fallbackProfile: z.string().min(1).optional().meta({ readOnly: true }),
736
- /**
737
- * Wire-only capability flags: stamped onto config-read responses by
738
- * `enrichProfilesForWire` and stripped from incoming writes by
739
- * `stripWireOnlyProfileKeys` (conversation-query-routes), so they never
740
- * persist. Declared here so the wire contract (AssistantConfig → SDK
741
- * types) describes the enriched profile shape clients consume.
742
- */
743
- supportsVision: z.boolean().optional(),
744
- invariant: z.boolean().optional(),
745
736
  }).meta({ id: "ProfileEntry" });
746
737
  export type ProfileEntry = z.infer<typeof ProfileEntry>;
747
738
 
@@ -14,6 +14,7 @@ import {
14
14
  MANAGED_PROFILE_NAMES,
15
15
  MANAGED_PROFILE_TEMPLATES,
16
16
  } from "./default-profile-catalog.js";
17
+ import { getIsCloud } from "./env-registry.js";
17
18
  import { loadConfig, loadRawConfig, saveRawConfig } from "./loader.js";
18
19
  import { isDispatchableProfile } from "./profile-dispatchability.js";
19
20
  import {
@@ -67,8 +68,7 @@ export function seedInferenceProfiles(
67
68
  const llm = config.llm;
68
69
  const profiles = llm.profiles;
69
70
 
70
- const isCloud =
71
- process.env.IS_CLOUD === "true" || process.env.IS_CLOUD === "1";
71
+ const isCloud = getIsCloud();
72
72
 
73
73
  // A hatch overlay that explicitly selected a cloud connection routes the
74
74
  // default provider through cadet rather than the entered BYOK key.
@@ -2,6 +2,8 @@
2
2
  // Memory substrate — twin-namespace shadowing detection
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
+ import { getConfig } from "./loader.js";
6
+
5
7
  /** One `memory.v2` tunable's `memory.substrate` twin. */
6
8
  interface SubstrateTwin {
7
9
  /** The twin's `memory.substrate` key, e.g. `spread_k` for `k`. */
@@ -93,29 +95,22 @@ export type SubstrateShadowing = {
93
95
  alsoReadByV2Engine: boolean;
94
96
  };
95
97
 
96
- function readPlainObject(value: unknown): Record<string, unknown> | undefined {
97
- return value != null && typeof value === "object" && !Array.isArray(value)
98
- ? (value as Record<string, unknown>)
99
- : undefined;
100
- }
101
-
102
98
  /**
103
99
  * The `memory.substrate` twin covering `path`, or `undefined` when `path` is
104
100
  * not a shadowable `memory.v2` tunable or its twin is unset.
105
101
  *
106
102
  * `memory.substrate` is override-only and wins for substrate recall whenever
107
- * its key is present — including when it holds an explicit `null`, which
108
- * `resolveSubstrateTuning` treats as present. Detection therefore keys on
109
- * presence, not truthiness. What that means for the `memory.v2` key depends on
110
- * `alsoReadByV2Engine`: for a substrate-only twin the `memory.v2` value has no
111
- * remaining consumer, while for the three engine-read twins it still tunes v2
112
- * injection.
103
+ * its key is set. Detection keys on presence in the RUNNING config — the same
104
+ * typed view `resolveSubstrateTuning` consults, so the check can never
105
+ * disagree with runtime precedence: a key absent from the parsed config is
106
+ * absent for the resolver too.
113
107
  *
114
- * `raw` is the unparsed config object (`loadRawConfig()` / the `config_get`
115
- * payload), so the check sees exactly what is persisted.
108
+ * What a set twin means for the `memory.v2` key depends on
109
+ * {@link SubstrateTwin.alsoReadByV2Engine}: for a substrate-only twin the
110
+ * `memory.v2` value has no remaining consumer, while for the three
111
+ * engine-read twins it still tunes v2 injection.
116
112
  */
117
113
  export function findSubstrateShadowing(
118
- raw: Record<string, unknown>,
119
114
  path: string,
120
115
  ): SubstrateShadowing | undefined {
121
116
  const segments = path.split(".");
@@ -130,8 +125,8 @@ export function findSubstrateShadowing(
130
125
  if (twin === undefined) {
131
126
  return undefined;
132
127
  }
133
- const substrate = readPlainObject(readPlainObject(raw.memory)?.substrate);
134
- if (!substrate || !(twin.substrateKey in substrate)) {
128
+ const substrate: Record<string, unknown> = getConfig().memory.substrate;
129
+ if (!(twin.substrateKey in substrate)) {
135
130
  return undefined;
136
131
  }
137
132
  return {
@@ -12,6 +12,7 @@ import {
12
12
  AUTO_PROFILE_KEY,
13
13
  OS_BETA_PROFILE_KEY,
14
14
  } from "./default-profile-names.js";
15
+ import { getIsCloud } from "./env-registry.js";
15
16
  import {
16
17
  getConfigReadOnly,
17
18
  invalidateConfigCache,
@@ -97,9 +98,7 @@ export function reconcileFlagGatedProfiles(): boolean {
97
98
  : [];
98
99
  llm.profileOrder = profileOrder;
99
100
 
100
- const isCloud =
101
- process.env.IS_CLOUD === "true" || process.env.IS_CLOUD === "1";
102
- const isByokMode = !isCloud;
101
+ const isCloud = getIsCloud();
103
102
 
104
103
  let changed = false;
105
104
  for (const gated of GATED_PROFILES) {
@@ -123,7 +122,7 @@ export function reconcileFlagGatedProfiles(): boolean {
123
122
 
124
123
  const order = llm.profileOrder as string[];
125
124
  const profileChanged = enabled
126
- ? enableProfile(gated, profiles, order, previous, isByokMode)
125
+ ? enableProfile(gated, profiles, order, previous, !isCloud)
127
126
  : disableProfile(gated.key, llm, profiles, order, previous);
128
127
  if (profileChanged) {
129
128
  changed = true;
@@ -201,16 +201,7 @@ export async function persistPromptedCredential(args: {
201
201
  }
202
202
  }
203
203
 
204
- // The prompt UI never puts the value in the transcript, but the flow is
205
- // routinely used to RE-collect a secret the user already pasted into chat
206
- // (the 06-credential-security rail promises that pasted message is
207
- // scrubbed after storage). Run the scrub right after the store succeeds,
208
- // best-effort and invisible to the caller, mirroring credentials_set.
209
- const scrubbed = await scrubStoredCredentialFromTranscripts(value);
210
- log.info(
211
- { service, field, ...scrubbed },
212
- "Prompted credential stored; scrubbed value from recent transcripts",
213
- );
204
+ await scrubStoredCredentialFromTranscripts(value, { service, field });
214
205
 
215
206
  try {
216
207
  upsertCredentialMetadata(service, field, {
@@ -668,10 +668,14 @@ export async function checkAssistantApiKey(): Promise<CredentialHealthResult | n
668
668
 
669
669
  if (stored.value == null) {
670
670
  // A workspace that was never connected to the cloud has no managed
671
- // credential and is not unhealthy. The identity ids resolve from the
672
- // api key via the validate endpoint, so a missing key means nothing is
673
- // bound — there is nothing to check.
674
- return null;
671
+ // credential yet — missing, not unhealthy.
672
+ return {
673
+ ...base,
674
+ status: "missing_token",
675
+ details:
676
+ "No Cadet Cloud credentials are stored yet. Log in to Cadet Cloud to set them up.",
677
+ canAutoRecover: true,
678
+ };
675
679
  }
676
680
 
677
681
  const { CadetCloudClient } = await import("../cloud/client.js");
@@ -86,8 +86,15 @@ export interface TranscriptScrubResult {
86
86
  * Never throws — partial failures are logged (counts only, never content)
87
87
  * and reflected in the best-effort result.
88
88
  */
89
+ export interface TranscriptScrubContext {
90
+ service?: string;
91
+ field?: string;
92
+ provider?: string;
93
+ }
94
+
89
95
  export async function scrubStoredCredentialFromTranscripts(
90
96
  value: string,
97
+ context: TranscriptScrubContext = {},
91
98
  ): Promise<TranscriptScrubResult> {
92
99
  const result: TranscriptScrubResult = {
93
100
  dbMessagesScrubbed: 0,
@@ -120,6 +127,10 @@ export async function scrubStoredCredentialFromTranscripts(
120
127
  "Credential transcript scrub failed",
121
128
  );
122
129
  }
130
+ log.info(
131
+ { ...context, ...result },
132
+ "Credential stored; scrubbed value from recent transcripts",
133
+ );
123
134
  return result;
124
135
  }
125
136
 
@@ -209,8 +209,13 @@ interface TierKeyExemption {
209
209
  * tuning when building the graph view.
210
210
  * - `src/memory-v2-routes.ts` (v2) — the v2 tuning routes read and merge
211
211
  * `memory.v2` config directly.
212
+ * - `config/substrate-twin-shadowing.ts` (substrate) — the operator-facing
213
+ * twin detector: it reads substrate PRESENCE so a `memory.v2` write can
214
+ * warn that the twin namespace wins. It never resolves a tunable itself —
215
+ * resolution stays in `substrate/tuning.ts`.
212
216
  */
213
217
  const TIER_KEY_READ_ALLOWLIST: readonly TierKeyExemption[] = [
218
+ { path: "src/config/substrate-twin-shadowing.ts", keys: ["substrate"] },
214
219
  { path: "src/config/loader.ts", keys: ["v3"] },
215
220
  { path: "src/config/memory-v3-gate.ts", keys: ["v2", "v3"] },
216
221
  {
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import { MANAGED_PROFILE_NAMES } from "../../config/default-profile-catalog.js";
21
+ import { getIsCloud } from "../../config/env-registry.js";
21
22
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
22
23
  import type { DrizzleDb } from "../../persistence/db-connection.js";
23
24
  import { credentialKey } from "../../security/credential-key.js";
@@ -73,8 +74,7 @@ function backfillConfigProfiles(db: DrizzleDb): void {
73
74
  return;
74
75
  }
75
76
 
76
- const isCloud =
77
- process.env.IS_CLOUD === "true" || process.env.IS_CLOUD === "1";
77
+ const isCloud = getIsCloud();
78
78
  const globalMode = isCloud ? "managed" : "your-own";
79
79
 
80
80
  let changed = false;