@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,13 +1,16 @@
1
- import { chmodSync, existsSync, mkdirSync } from "node:fs";
1
+ /**
2
+ * Platform detection and environment-shaping helpers.
3
+ *
4
+ * Directory-layout helpers (every canonical path under the workspace and
5
+ * its data dir) live in `util/layout.ts` — they were never platform
6
+ * concerns. This module re-exports that module for backward compatibility;
7
+ * new code should import layout helpers from `./layout.js` directly.
8
+ */
9
+
2
10
  import { homedir } from "node:os";
3
- import { delimiter, dirname, isAbsolute, join, relative, sep } from "node:path";
11
+ import { delimiter, join } from "node:path";
4
12
 
5
- import {
6
- AVATAR_IMAGE_FILENAME,
7
- AVATAR_MANIFEST_FILENAME,
8
- resolveAvatarDir,
9
- } from "@mycadet/avatar-manifest";
10
- import { assertTestPathIsEphemeral as assertEphemeralInTests } from "@mycadet/environments/test-path-guard";
13
+ export * from "./layout.js";
11
14
 
12
15
  export function isMacOS(): boolean {
13
16
  return process.platform === "darwin";
@@ -108,462 +111,3 @@ export function normalizeAssistantId(assistantId: string): string {
108
111
 
109
112
  return assistantId;
110
113
  }
111
-
112
- /**
113
- * Returns the internal data directory ($CADET_WORKSPACE_DIR/data). Runtime
114
- * databases, logs, memory indices, and other internal state live here.
115
- */
116
- export function getDataDir(): string {
117
- return join(getWorkspaceDir(), "data");
118
- }
119
-
120
- /**
121
- * Returns the path to the config-quarantine notice sentinel
122
- * (`<workspace>/data/config-quarantine-notice.json`).
123
- *
124
- * Written by the config loader when a corrupt `config.json` is quarantined and
125
- * read by the per-turn `config-quarantine-notice` injector. Lives under the
126
- * internal data dir (runtime state, config-free to resolve) rather than the
127
- * user-facing workspace root because it is daemon-written bookkeeping, not a
128
- * file the user edits. The path resolves without loading config, so it is safe
129
- * to call during early-boot config load before the DB or `getConfig().dataDir`
130
- * exist.
131
- */
132
- export function getConfigQuarantineNoticePath(): string {
133
- return join(getDataDir(), "config-quarantine-notice.json");
134
- }
135
-
136
- /**
137
- * Returns the path to the config-validation-reset notice sentinel
138
- * (`<workspace>/data/config-validation-reset-notice.json`).
139
- *
140
- * Written by the config loader when `config.json` parses as JSON but fails
141
- * schema validation so hard that the loader falls back to *full* defaults
142
- * (e.g. an unknown key that masks a `superRefine` violation until the offending
143
- * key is stripped). Unlike a quarantine, the on-disk file is left untouched —
144
- * the user's customized values are still present but inactive until the invalid
145
- * entries are fixed. Read by the per-turn `config-validation-reset-notice`
146
- * injector so the agent can explain a settings/connection change the user did
147
- * not make. Lives beside the quarantine sentinel under the internal data dir
148
- * for the same reasons (daemon-written bookkeeping; resolves without loading
149
- * config, so it is safe during early-boot config load).
150
- */
151
- export function getConfigValidationResetNoticePath(): string {
152
- return join(getDataDir(), "config-validation-reset-notice.json");
153
- }
154
-
155
- /**
156
- * Returns the embedding models directory ($CADET_WORKSPACE_DIR/embedding-models).
157
- * Downloaded embedding runtime (onnxruntime-node, transformers bundle, model weights)
158
- * is stored here, downloaded post-hatch rather than shipped with the app.
159
- */
160
- export function getEmbeddingModelsDir(): string {
161
- return join(getWorkspaceDir(), "embedding-models");
162
- }
163
-
164
- /**
165
- * Returns the sandbox root directory (<workspace>/data/sandbox).
166
- * Global sandbox state lives under this directory.
167
- */
168
- export function getSandboxRootDir(): string {
169
- return join(getDataDir(), "sandbox");
170
- }
171
-
172
- /**
173
- * Returns the default sandbox working directory ($CADET_WORKSPACE_DIR).
174
- * This is the workspace root — tool working directories should use this
175
- * path unless explicitly overridden.
176
- */
177
- export function getSandboxWorkingDir(): string {
178
- return getWorkspaceDir();
179
- }
180
-
181
- /**
182
- * Returns the sounds directory ($CADET_WORKSPACE_DIR/data/sounds).
183
- * Custom sound files and sound configuration live here.
184
- */
185
- export function getSoundsDir(): string {
186
- return join(getWorkspaceDir(), "data", "sounds");
187
- }
188
-
189
- /** Returns the avatar directory ($CADET_WORKSPACE_DIR/data/avatar). */
190
- export function getAvatarDir(): string {
191
- return resolveAvatarDir(getWorkspaceDir());
192
- }
193
-
194
- /** Returns the canonical avatar image path ($CADET_WORKSPACE_DIR/data/avatar/avatar-image.png). */
195
- export function getAvatarImagePath(): string {
196
- return join(getAvatarDir(), AVATAR_IMAGE_FILENAME);
197
- }
198
-
199
- /** Returns the canonical avatar manifest path ($CADET_WORKSPACE_DIR/data/avatar/avatar.json). */
200
- export function getAvatarManifestPath(): string {
201
- return join(getAvatarDir(), AVATAR_MANIFEST_FILENAME);
202
- }
203
-
204
- export function getPidPath(): string {
205
- return join(getWorkspaceDir(), "cadet.pid");
206
- }
207
-
208
- export function getDbPath(): string {
209
- return join(getDataDir(), "db", "assistant.db");
210
- }
211
-
212
- /**
213
- * Returns the directory where logs live: `<dataDir>/logs/`. Files rotate
214
- * daily (`assistant-YYYY-MM-DD.log`), so callers ask for the directory and
215
- * let the logger own the filename.
216
- */
217
- export function getLogsDir(): string {
218
- return join(getDataDir(), "logs");
219
- }
220
-
221
- export function getHistoryPath(): string {
222
- return join(getDataDir(), "history");
223
- }
224
-
225
- /** Returns $CADET_WORKSPACE_DIR/signals — the directory for IPC signal files. */
226
- export function getSignalsDir(): string {
227
- return join(getWorkspaceDir(), "signals");
228
- }
229
-
230
- // These expose specific root-level file paths so callers don't need to
231
- // import getRootDir() directly. getRootDir() is intentionally unexported.
232
-
233
- /** Returns the path to the daemon stderr log ($CADET_WORKSPACE_DIR/logs/daemon-stderr.log). */
234
- export function getDaemonStderrLogPath(): string {
235
- return join(getWorkspaceDir(), "logs", "daemon-stderr.log");
236
- }
237
-
238
- /** Returns the path to the daemon startup lock file ($CADET_WORKSPACE_DIR/daemon-startup.lock). */
239
- export function getDaemonStartupLockPath(): string {
240
- return join(getWorkspaceDir(), "daemon-startup.lock");
241
- }
242
-
243
- /** Returns the directory for externally-installed packages ($CADET_WORKSPACE_DIR/external). */
244
- export function getExternalDir(): string {
245
- return join(getWorkspaceDir(), "external");
246
- }
247
-
248
- /** Returns the directory for installed binaries ($CADET_WORKSPACE_DIR/bin). */
249
- export function getBinDir(): string {
250
- return join(getWorkspaceDir(), "bin");
251
- }
252
-
253
- /**
254
- * Returns the path to the dot-env file (`<cadetRoot>/.env`, the parent of
255
- * the workspace directory). Stays outside the workspace sandbox boundary
256
- * because it contains secrets.
257
- */
258
- export function getDotEnvPath(): string {
259
- return join(dirname(getWorkspaceDir()), ".env");
260
- }
261
-
262
- /** Returns the path to the embed-worker PID file ($CADET_WORKSPACE_DIR/embed-worker.pid). */
263
- export function getEmbedWorkerPidPath(): string {
264
- return join(getWorkspaceDir(), "embed-worker.pid");
265
- }
266
-
267
- /** Returns the path to the memory-worker PID file ($CADET_WORKSPACE_DIR/memory-worker.pid). */
268
- export function getMemoryWorkerPidPath(): string {
269
- return join(getWorkspaceDir(), "memory-worker.pid");
270
- }
271
-
272
- /** Returns the path to the schedule-worker PID file ($CADET_WORKSPACE_DIR/schedule-worker.pid). */
273
- export function getScheduleWorkerPidPath(): string {
274
- return join(getWorkspaceDir(), "schedule-worker.pid");
275
- }
276
-
277
- /**
278
- * Returns the directory where the resource monitor persists its forensics
279
- * ($CADET_WORKSPACE_DIR/data/monitoring). Lives on the workspace volume (the
280
- * PVC) so the sample ring buffer and high-memory snapshots survive an OOM
281
- * SIGKILL that resets all in-process state. Git-ignored from the workspace
282
- * tree (see `data/monitoring/` in git-service.ts) so the assistant's own
283
- * telemetry is not auto-committed as user changes.
284
- */
285
- export function getMonitoringDataDir(): string {
286
- return join(getDataDir(), "monitoring");
287
- }
288
-
289
- /** Returns the path to the monitoring PID file, under the monitor data dir. */
290
- export function getMonitoringPidPath(): string {
291
- return join(getMonitoringDataDir(), "monitoring.pid");
292
- }
293
-
294
- /**
295
- * Root holding every daemon-managed subprocess's runtime directory
296
- * ($CADET_WORKSPACE_DIR/procs). Each managed subprocess keeps its IPC socket,
297
- * PID file, and per-process scratch under `procs/<name>/`, so `ls procs` is a
298
- * census of managed subprocesses and cleanup is one `rm -rf` of the subdir.
299
- */
300
- export function getProcsDir(): string {
301
- return join(getWorkspaceDir(), "procs");
302
- }
303
-
304
- /** The runtime directory for one managed subprocess: `procs/<name>/`. */
305
- export function getProcDir(name: string): string {
306
- return join(getProcsDir(), name);
307
- }
308
-
309
- /** Create (if needed) and return a cloud subprocess's runtime directory. */
310
- export function ensureProcDir(name: string): string {
311
- const dir = getProcDir(name);
312
- mkdirSync(dir, { recursive: true });
313
- return dir;
314
- }
315
-
316
- /**
317
- * The IPC socket a cloud subprocess binds and the daemon connects to
318
- * (`procs/<name>/ipc.sock`). The basename is fixed — the directory already
319
- * carries the subprocess name. Keep it short: Unix `sun_path` is ~104–108 bytes.
320
- */
321
- export function getProcSocketPath(name: string): string {
322
- return join(getProcDir(name), "ipc.sock");
323
- }
324
-
325
- /** The PID file a cloud subprocess writes on readiness (`procs/<name>/<name>.pid`). */
326
- export function getProcPidPath(name: string): string {
327
- return join(getProcDir(name), `${name}.pid`);
328
- }
329
-
330
- // --- Live-workspace guard for test processes --------------------------------
331
- //
332
- // A test process must never resolve the workspace (or the cadet root) to a
333
- // real, non-temp directory: production code exercised by a test would then
334
- // read and destructively write live state. The containment assertion is
335
- // shared with the gateway via @mycadet/environments/test-path-guard;
336
- // src/__tests__/assert-not-live-db.ts keeps its own containment check
337
- // because test machinery must not import production dependencies.
338
-
339
- function assertTestPathIsEphemeral(dir: string): void {
340
- assertEphemeralInTests(dir, {
341
- // Shared with assertTestDbIsIsolated() in persistence/db-connection.ts.
342
- allowEnvVar: "CADET_ALLOW_REAL_WORKSPACE_IN_TESTS",
343
- runHint: "Run tests from the assistant package root.",
344
- });
345
- }
346
-
347
- /**
348
- * Returns the workspace root for user-facing state.
349
- *
350
- * When the CADET_WORKSPACE_DIR env var is set, returns that value (used in
351
- * containerized deployments where the workspace is a separate volume).
352
- * Otherwise falls back to `<cwd>/workspace`.
353
- *
354
- * In test processes the resolved directory must live under `os.tmpdir()`
355
- * (see the live-workspace guard above); anything else throws.
356
- */
357
- export function getWorkspaceDir(): string {
358
- const dir =
359
- process.env.CADET_WORKSPACE_DIR?.trim() || join(process.cwd(), "workspace");
360
- assertTestPathIsEphemeral(dir);
361
- return dir;
362
- }
363
-
364
- /**
365
- * Returns a display-friendly workspace path for embedding in agent-facing text
366
- * (skill bodies, tool descriptions). Replaces the home directory prefix with `~`
367
- * so paths stay concise and portable across machines.
368
- *
369
- * Examples:
370
- * /Users/alice/.local/share/cadet/workspace → ~/.local/share/cadet/workspace
371
- * /workspace → /workspace
372
- */
373
- export function getWorkspaceDirDisplay(): string {
374
- return formatHomeRelativePath(getWorkspaceDir(), homedir());
375
- }
376
-
377
- interface PathOperations {
378
- isAbsolute(path: string): boolean;
379
- relative(from: string, to: string): string;
380
- sep: string;
381
- }
382
-
383
- export function formatHomeRelativePath(
384
- absolutePath: string,
385
- homePath: string,
386
- pathOperations: PathOperations = { isAbsolute, relative, sep },
387
- ): string {
388
- const relativeToHome = pathOperations.relative(homePath, absolutePath);
389
- if (relativeToHome === "") {
390
- return "~";
391
- }
392
- if (
393
- relativeToHome !== ".." &&
394
- !relativeToHome.startsWith(`..${pathOperations.sep}`) &&
395
- !pathOperations.isAbsolute(relativeToHome)
396
- ) {
397
- return `~${pathOperations.sep}${relativeToHome}`;
398
- }
399
- return absolutePath;
400
- }
401
-
402
- /** Returns $CADET_WORKSPACE_DIR/config.json */
403
- export function getWorkspaceConfigPath(): string {
404
- return join(getWorkspaceDir(), "config.json");
405
- }
406
-
407
- /** Filename for the user-owned Agent Plugins MCP document. */
408
- export const WORKSPACE_MCP_FILENAME = "mcp.json";
409
-
410
- /** Returns $CADET_WORKSPACE_DIR/mcp.json */
411
- export function getWorkspaceMcpConfigPath(): string {
412
- return join(getWorkspaceDir(), WORKSPACE_MCP_FILENAME);
413
- }
414
-
415
- /** Returns $CADET_WORKSPACE_DIR/skills */
416
- export function getWorkspaceSkillsDir(): string {
417
- return join(getWorkspaceDir(), "skills");
418
- }
419
-
420
- /** Returns $CADET_WORKSPACE_DIR/hooks */
421
- export function getWorkspaceHooksDir(): string {
422
- return join(getWorkspaceDir(), "hooks");
423
- }
424
-
425
- /**
426
- * Returns `<workspaceDir>/plugins` — the directory scanned by the user plugin
427
- * loader at daemon startup. Writes here are security-sensitive: any
428
- * `register.{ts,js}` will be dynamic-imported on next restart, so the file
429
- * risk classifier escalates writes under this path to High.
430
- */
431
- export function getWorkspacePluginsDir(): string {
432
- return join(getWorkspaceDir(), "plugins");
433
- }
434
-
435
- /**
436
- * Returns $CADET_WORKSPACE_DIR/tools — user-defined tool overrides.
437
- *
438
- * Each subdirectory `<name>/` provides either an override of a core tool of
439
- * the same name or a net-new tool. The single canonical location removes
440
- * the "which plugin wins" ambiguity that would arise if multiple plugins
441
- * could register competing overrides for the same tool.
442
- *
443
- * Files under this directory are dynamic-imported by the workspace-tool
444
- * loader on daemon start; the file risk classifier escalates writes under
445
- * this path to High for the same reason `plugins/` is escalated.
446
- */
447
- export function getWorkspaceToolsDir(): string {
448
- return join(getWorkspaceDir(), "tools");
449
- }
450
-
451
- /**
452
- * Returns $CADET_WORKSPACE_DIR/routes — user-defined HTTP route handlers.
453
- *
454
- * Handler modules under this directory are dynamic-imported by the user-route
455
- * dispatcher and their exported HTTP-method functions are executed on the
456
- * next matching request, so the file risk classifier escalates writes under
457
- * this path to High for the same reason `plugins/` and `tools/` are escalated.
458
- */
459
- export function getWorkspaceRoutesDir(): string {
460
- return join(getWorkspaceDir(), "routes");
461
- }
462
-
463
- /**
464
- * Returns $CADET_WORKSPACE_DIR/workflows — saved (named) workflow scripts.
465
- *
466
- * A file here becomes a saved workflow whose source is executed (in the sandbox,
467
- * and unattended when triggered by a schedule), so the file risk classifier
468
- * escalates writes under this path to High like `tools/` and `routes/`.
469
- */
470
- export function getWorkspaceWorkflowsDir(): string {
471
- return join(getWorkspaceDir(), "workflows");
472
- }
473
-
474
- /** Returns $CADET_WORKSPACE_DIR/deprecated — transitional files slated for removal. */
475
- export function getDeprecatedDir(): string {
476
- return join(getWorkspaceDir(), "deprecated");
477
- }
478
-
479
- /** Returns $CADET_WORKSPACE_DIR/conversations */
480
- export function getConversationsDir(): string {
481
- return join(getWorkspaceDir(), "conversations");
482
- }
483
-
484
- /** Returns the workspace path for a prompt file (e.g. IDENTITY.md, SOUL.md). */
485
- export function getWorkspacePromptPath(file: string): string {
486
- return join(getWorkspaceDir(), file);
487
- }
488
-
489
- /**
490
- * Returns `<workspaceDir>/prompts/system` — the workspace override layer for
491
- * system prompt sections. Layout: `prompts/system/<NN-name>.md`.
492
- *
493
- * The bundled section registry (`prompts/templates/system-sections.ts`) is
494
- * the source of default truth; a file here with the same id replaces the
495
- * bundled body (or, stripped to nothing, silences it), and a brand-new
496
- * `<NN-name>` adds a workspace-only section. Because that includes the
497
- * security-policy sections, writes under this directory are gated as a
498
- * control-plane prompt surface (`permissions/workspace-policy.ts`).
499
- */
500
- export function getWorkspaceSystemPromptDir(): string {
501
- return join(getWorkspaceDir(), "prompts", "system");
502
- }
503
-
504
- // Cloud profiler runs live under <workspace>/data/profiler/. These
505
- // helpers enforce a single canonical layout so every runtime caller
506
- // resolves the same paths.
507
-
508
- /**
509
- * Returns the profiler root directory (<workspace>/data/profiler).
510
- * All profiler state (runs directory, global metadata) lives here.
511
- */
512
- export function getProfilerRootDir(): string {
513
- return join(getDataDir(), "profiler");
514
- }
515
-
516
- /**
517
- * Returns the profiler runs directory (<workspace>/data/profiler/runs).
518
- * Each completed or active profiler run gets its own sub-directory here.
519
- */
520
- export function getProfilerRunsDir(): string {
521
- return join(getProfilerRootDir(), "runs");
522
- }
523
-
524
- /**
525
- * Returns the directory for a specific profiler run by ID
526
- * (<workspace>/data/profiler/runs/<runId>).
527
- */
528
- export function getProfilerRunDir(runId: string): string {
529
- return join(getProfilerRunsDir(), runId);
530
- }
531
-
532
- export function ensureDataDir(): void {
533
- const workspace = getWorkspaceDir();
534
- const wsData = join(workspace, "data");
535
- const dirs = [
536
- // Workspace dirs
537
- workspace,
538
- join(workspace, "signals"),
539
- join(workspace, "skills"),
540
- join(workspace, "routes"),
541
- join(workspace, "embedding-models"),
542
- join(workspace, "conversations"),
543
- join(workspace, "logs"),
544
- join(workspace, "external"),
545
- join(workspace, "bin"),
546
- // Data sub-dirs under workspace
547
- wsData,
548
- join(wsData, "db"),
549
- join(wsData, "qdrant"),
550
- join(wsData, "logs"),
551
- join(wsData, "memory"),
552
- join(wsData, "memory", "knowledge"),
553
- join(wsData, "apps"),
554
- join(wsData, "attachments"),
555
- join(wsData, "sounds"),
556
- ];
557
- for (const dir of dirs) {
558
- if (!existsSync(dir)) {
559
- mkdirSync(dir, { recursive: true });
560
- }
561
- }
562
- // Lock down the root directory so only the owner can traverse it.
563
- // Runtime files (socket, session token, PID) live directly under root.
564
- try {
565
- chmodSync(dirname(workspace), 0o700);
566
- } catch {
567
- // Non-fatal: some filesystems don't support Unix permissions
568
- }
569
- }
@@ -83,7 +83,7 @@ export const backfillDefaultProviderMigration: WorkspaceMigration = {
83
83
  return;
84
84
  }
85
85
 
86
- const provider = isPlatform() ? "cadet" : resolveProviderSignal(llm);
86
+ const provider = isCloud() ? "cadet" : resolveProviderSignal(llm);
87
87
  if (provider === undefined) {
88
88
  return;
89
89
  }
@@ -117,7 +117,7 @@ function isValidDefaultProvider(value: unknown): boolean {
117
117
 
118
118
  // Frozen copy of config/env-registry.ts's IS_CLOUD read — migration
119
119
  // modules must not import from config modules (workspace/migrations/AGENTS.md).
120
- function isPlatform(): boolean {
120
+ function isCloud(): boolean {
121
121
  return process.env.IS_CLOUD === "true" || process.env.IS_CLOUD === "1";
122
122
  }
123
123