@superblocksteam/sdk 2.0.137-next.0 → 2.0.138-next.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 (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/cli-replacement/automatic-upgrades.d.ts +1 -1
  3. package/dist/cli-replacement/automatic-upgrades.d.ts.map +1 -1
  4. package/dist/cli-replacement/automatic-upgrades.js +64 -59
  5. package/dist/cli-replacement/automatic-upgrades.js.map +1 -1
  6. package/dist/cli-replacement/automatic-upgrades.test.js +54 -33
  7. package/dist/cli-replacement/automatic-upgrades.test.js.map +1 -1
  8. package/dist/cli-replacement/dependency-install-classifier.d.mts +26 -1
  9. package/dist/cli-replacement/dependency-install-classifier.d.mts.map +1 -1
  10. package/dist/cli-replacement/dependency-install-classifier.mjs +58 -0
  11. package/dist/cli-replacement/dependency-install-classifier.mjs.map +1 -1
  12. package/dist/cli-replacement/dependency-install-classifier.test.mjs +33 -1
  13. package/dist/cli-replacement/dependency-install-classifier.test.mjs.map +1 -1
  14. package/dist/cli-replacement/dev.d.mts +4 -2
  15. package/dist/cli-replacement/dev.d.mts.map +1 -1
  16. package/dist/cli-replacement/dev.interception.test.mjs +32 -5
  17. package/dist/cli-replacement/dev.interception.test.mjs.map +1 -1
  18. package/dist/cli-replacement/dev.mjs +142 -65
  19. package/dist/cli-replacement/dev.mjs.map +1 -1
  20. package/dist/cli-replacement/home-npmrc.d.mts.map +1 -1
  21. package/dist/cli-replacement/home-npmrc.mjs +13 -5
  22. package/dist/cli-replacement/home-npmrc.mjs.map +1 -1
  23. package/dist/cli-replacement/home-npmrc.test.mjs +49 -17
  24. package/dist/cli-replacement/home-npmrc.test.mjs.map +1 -1
  25. package/dist/cli-replacement/package-json-snapshot.d.mts +1 -0
  26. package/dist/cli-replacement/package-json-snapshot.d.mts.map +1 -1
  27. package/dist/cli-replacement/package-json-snapshot.mjs +35 -0
  28. package/dist/cli-replacement/package-json-snapshot.mjs.map +1 -1
  29. package/dist/cli-replacement/package-json-snapshot.test.mjs +104 -0
  30. package/dist/cli-replacement/package-json-snapshot.test.mjs.map +1 -1
  31. package/dist/dev-utils/dev-server-metrics.d.mts +17 -7
  32. package/dist/dev-utils/dev-server-metrics.d.mts.map +1 -1
  33. package/dist/dev-utils/dev-server-metrics.mjs +18 -12
  34. package/dist/dev-utils/dev-server-metrics.mjs.map +1 -1
  35. package/dist/dev-utils/dev-server.d.mts.map +1 -1
  36. package/dist/dev-utils/dev-server.mjs +15 -6
  37. package/dist/dev-utils/dev-server.mjs.map +1 -1
  38. package/dist/dev-utils/prepare-in-place-restart.d.mts +31 -0
  39. package/dist/dev-utils/prepare-in-place-restart.d.mts.map +1 -0
  40. package/dist/dev-utils/prepare-in-place-restart.mjs +64 -0
  41. package/dist/dev-utils/prepare-in-place-restart.mjs.map +1 -0
  42. package/dist/dev-utils/prepare-in-place-restart.test.d.mts +2 -0
  43. package/dist/dev-utils/prepare-in-place-restart.test.d.mts.map +1 -0
  44. package/dist/dev-utils/prepare-in-place-restart.test.mjs +46 -0
  45. package/dist/dev-utils/prepare-in-place-restart.test.mjs.map +1 -0
  46. package/dist/telemetry/index.d.ts +12 -0
  47. package/dist/telemetry/index.d.ts.map +1 -1
  48. package/dist/telemetry/index.js +26 -12
  49. package/dist/telemetry/index.js.map +1 -1
  50. package/package.json +6 -6
  51. package/src/cli-replacement/automatic-upgrades.test.ts +68 -58
  52. package/src/cli-replacement/automatic-upgrades.ts +81 -59
  53. package/src/cli-replacement/dependency-install-classifier.mts +74 -1
  54. package/src/cli-replacement/dependency-install-classifier.test.mts +49 -0
  55. package/src/cli-replacement/dev.interception.test.mts +39 -7
  56. package/src/cli-replacement/dev.mts +154 -62
  57. package/src/cli-replacement/home-npmrc.mts +12 -4
  58. package/src/cli-replacement/home-npmrc.test.mts +57 -17
  59. package/src/cli-replacement/package-json-snapshot.mts +50 -0
  60. package/src/cli-replacement/package-json-snapshot.test.mts +121 -0
  61. package/src/dev-utils/dev-server-metrics.mts +48 -21
  62. package/src/dev-utils/dev-server.mts +22 -5
  63. package/src/dev-utils/prepare-in-place-restart.mts +99 -0
  64. package/src/dev-utils/prepare-in-place-restart.test.mts +69 -0
  65. package/src/telemetry/index.ts +24 -10
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -11,6 +11,21 @@ import {
11
11
  restoreManagedPackageDependencies,
12
12
  } from "./package-json-snapshot.mjs";
13
13
 
14
+ async function writeTempPackageJson(packageJson: unknown): Promise<string> {
15
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "pkg-json-"));
16
+ await fs.writeFile(
17
+ path.join(tmpDir, "package.json"),
18
+ JSON.stringify(packageJson, null, 2) + "\n",
19
+ );
20
+ return tmpDir;
21
+ }
22
+
23
+ async function readTempPackageJson(tmpDir: string): Promise<any> {
24
+ return JSON.parse(
25
+ await fs.readFile(path.join(tmpDir, "package.json"), "utf-8"),
26
+ );
27
+ }
28
+
14
29
  describe("packageJsonSnapshot", () => {
15
30
  it("is stable for object key order changes", () => {
16
31
  const before = packageJsonSnapshot({
@@ -248,3 +263,109 @@ describe("restoreManagedPackageDependencies", () => {
248
263
  }
249
264
  });
250
265
  });
266
+
267
+ describe("restoreManagedPackageDependencies zod override", () => {
268
+ it("injects the warm template zod override into an app that has none", async () => {
269
+ const tmpDir = await writeTempPackageJson({
270
+ name: "restored-app",
271
+ dependencies: {
272
+ "@superblocksteam/sdk-api": "0.0.7",
273
+ ai: "6.0.97",
274
+ },
275
+ });
276
+
277
+ try {
278
+ const changed = await restoreManagedPackageDependencies(tmpDir, {
279
+ overrides: { zod: "3.25.76" },
280
+ });
281
+
282
+ expect(changed).toBe(true);
283
+ const restored = await readTempPackageJson(tmpDir);
284
+ expect(restored.overrides?.zod).toBe("3.25.76");
285
+ } finally {
286
+ await fs.rm(tmpDir, { recursive: true, force: true });
287
+ }
288
+ });
289
+
290
+ it("does not inject a zod override when the warm template omits it", async () => {
291
+ const tmpDir = await writeTempPackageJson({
292
+ name: "restored-app",
293
+ dependencies: {
294
+ "@superblocksteam/sdk-api": "0.0.7",
295
+ ai: "6.0.97",
296
+ },
297
+ });
298
+
299
+ try {
300
+ const changed = await restoreManagedPackageDependencies(tmpDir, {
301
+ dependencies: { "@superblocksteam/sdk-api": "0.0.7" },
302
+ });
303
+
304
+ expect(changed).toBe(false);
305
+ const restored = await readTempPackageJson(tmpDir);
306
+ expect(restored.overrides).toBeUndefined();
307
+ } finally {
308
+ await fs.rm(tmpDir, { recursive: true, force: true });
309
+ }
310
+ });
311
+
312
+ it("preserves unrelated overrides while enforcing the zod override", async () => {
313
+ const tmpDir = await writeTempPackageJson({
314
+ name: "restored-app",
315
+ dependencies: { ai: "6.0.97" },
316
+ overrides: { lodash: "4.17.21" },
317
+ });
318
+
319
+ try {
320
+ await restoreManagedPackageDependencies(tmpDir, {
321
+ overrides: { zod: "3.25.76" },
322
+ });
323
+
324
+ const restored = await readTempPackageJson(tmpDir);
325
+ expect(restored.overrides).toEqual({
326
+ lodash: "4.17.21",
327
+ zod: "3.25.76",
328
+ });
329
+ } finally {
330
+ await fs.rm(tmpDir, { recursive: true, force: true });
331
+ }
332
+ });
333
+
334
+ it("overwrites a stale app zod override with the managed version", async () => {
335
+ const tmpDir = await writeTempPackageJson({
336
+ name: "restored-app",
337
+ dependencies: { ai: "6.0.97" },
338
+ overrides: { zod: "3.23.8" },
339
+ });
340
+
341
+ try {
342
+ const changed = await restoreManagedPackageDependencies(tmpDir, {
343
+ overrides: { zod: "3.25.76" },
344
+ });
345
+
346
+ expect(changed).toBe(true);
347
+ const restored = await readTempPackageJson(tmpDir);
348
+ expect(restored.overrides?.zod).toBe("3.25.76");
349
+ } finally {
350
+ await fs.rm(tmpDir, { recursive: true, force: true });
351
+ }
352
+ });
353
+
354
+ it("does not rewrite when the zod override already matches", async () => {
355
+ const tmpDir = await writeTempPackageJson({
356
+ name: "restored-app",
357
+ dependencies: { ai: "6.0.97" },
358
+ overrides: { zod: "3.25.76" },
359
+ });
360
+
361
+ try {
362
+ const changed = await restoreManagedPackageDependencies(tmpDir, {
363
+ overrides: { zod: "3.25.76" },
364
+ });
365
+
366
+ expect(changed).toBe(false);
367
+ } finally {
368
+ await fs.rm(tmpDir, { recursive: true, force: true });
369
+ }
370
+ });
371
+ });
@@ -403,27 +403,32 @@ class DevServerMetrics {
403
403
  }
404
404
 
405
405
  /**
406
- * Records a degraded startup caused by a failed initial dependency install.
406
+ * Emits a dependency-failure counter with the standard low-cardinality tags.
407
+ * Shared by the initial-install and auto-upgrade degrade paths, which differ
408
+ * only in counter name + description.
407
409
  *
408
410
  * All tags are low-cardinality:
409
- * - `category` is a closed enum from DependencyInstallError (safe).
411
+ * - `category` is a closed enum from the matching error type.
410
412
  * - `npm_error_code` is a bounded npm error code string (e.g. "E401",
411
413
  * "ERESOLVE") or `"none"` when absent.
412
- * - `has_any_registry_configured` is a boolean string (`"true"` /
413
- * `"false"`) or `"unknown"` when the tri-state is undefined.
414
+ * - `has_any_registry_configured` is `"true"` / `"false"` / `"unknown"`.
414
415
  *
415
- * Do NOT pass raw package names, registry hostnames, or rawError — those
416
- * are high-cardinality or contain customer-infra identifiers.
416
+ * Do NOT pass raw package names, registry hostnames, or rawError — those are
417
+ * high-cardinality or contain customer-infra identifiers.
417
418
  */
418
- recordInitialInstallFailure({
419
- category,
420
- npmErrorCode,
421
- hasAnyRegistryConfigured,
422
- }: {
423
- category: string;
424
- npmErrorCode?: string;
425
- hasAnyRegistryConfigured?: boolean;
426
- }): void {
419
+ private recordDependencyFailure(
420
+ counterName: string,
421
+ description: string,
422
+ {
423
+ category,
424
+ npmErrorCode,
425
+ hasAnyRegistryConfigured,
426
+ }: {
427
+ category: string;
428
+ npmErrorCode?: string;
429
+ hasAnyRegistryConfigured?: boolean;
430
+ },
431
+ ): void {
427
432
  const labels = {
428
433
  category,
429
434
  npm_error_code: normalizeNpmErrorCodeTag(npmErrorCode),
@@ -433,15 +438,37 @@ class DevServerMetrics {
433
438
  : String(hasAnyRegistryConfigured),
434
439
  };
435
440
  this.record(() => {
436
- getMeter()
437
- .createCounter("dev_server_initial_install_failure_total", {
438
- description:
439
- "Count of degraded dev-server startups caused by a failed initial dependency install.",
440
- })
441
- .add(1, labels);
441
+ getMeter().createCounter(counterName, { description }).add(1, labels);
442
442
  });
443
443
  }
444
444
 
445
+ /** Records a degraded startup caused by a failed initial dependency install. */
446
+ recordInitialInstallFailure(labels: {
447
+ category: string;
448
+ npmErrorCode?: string;
449
+ hasAnyRegistryConfigured?: boolean;
450
+ }): void {
451
+ this.recordDependencyFailure(
452
+ "dev_server_initial_install_failure_total",
453
+ "Count of degraded dev-server startups caused by a failed initial dependency install.",
454
+ labels,
455
+ );
456
+ }
457
+
458
+ /** Records a degraded startup where a best-effort CLI/library auto-upgrade
459
+ * failed and the pod kept serving the currently installed version. */
460
+ recordUpgradeFailure(labels: {
461
+ category: string;
462
+ npmErrorCode?: string;
463
+ hasAnyRegistryConfigured?: boolean;
464
+ }): void {
465
+ this.recordDependencyFailure(
466
+ "dev_server_auto_upgrade_failure_total",
467
+ "Count of degraded dev-server startups where a best-effort CLI/library auto-upgrade failed and the pod kept the current version.",
468
+ labels,
469
+ );
470
+ }
471
+
445
472
  /**
446
473
  * Records a recoverable shutdown error that was suppressed (logged at warn,
447
474
  * process left alive) instead of triggering a fatal exit. This is the
@@ -70,6 +70,7 @@ import {
70
70
  isRecoverableShutdownError,
71
71
  parseIsWarm,
72
72
  } from "./fatal-exit.mjs";
73
+ import { prepareInPlaceRestart } from "./prepare-in-place-restart.mjs";
73
74
  import {
74
75
  formatViteDevServerStartedLog,
75
76
  logViteBuildError,
@@ -727,6 +728,22 @@ export async function createDevServer({
727
728
  }
728
729
  }
729
730
 
731
+ const runPrepareInPlaceRestart = async (options?: { flipRestart?: true }) => {
732
+ logger.info(
733
+ options?.flipRestart
734
+ ? "Pausing dev server while restarting after template flip"
735
+ : "Pausing dev server while restarting",
736
+ );
737
+ await prepareInPlaceRestart(
738
+ {
739
+ aiService,
740
+ closeViteServer,
741
+ lockService,
742
+ },
743
+ { flipRestart: options?.flipRestart, logger },
744
+ );
745
+ };
746
+
730
747
  async function runGracefulShutdown({
731
748
  logger,
732
749
  serverInitiated,
@@ -1018,11 +1035,7 @@ export async function createDevServer({
1018
1035
 
1019
1036
  try {
1020
1037
  if (isRestart) {
1021
- logger.info("Pausing dev server while restarting");
1022
- lockService?.setIsRestarting();
1023
- // Invalidate chat session cache to reload fresh chat messages after checkpoint restore
1024
- aiService?.chatSessionStore.invalidateCache();
1025
- await closeViteServer();
1038
+ await runPrepareInPlaceRestart();
1026
1039
  res.send("ok");
1027
1040
  // RESTART_EXIT_CODE tells the supervisor to respawn, and this runs
1028
1041
  // synchronously below (no await), so the next /_sb_connect is served by
@@ -1347,6 +1360,7 @@ export async function createDevServer({
1347
1360
  logger: loggerOverride,
1348
1361
  httpServer,
1349
1362
  superblocksBaseUrl: explicitBaseUrl || localToken?.superblocksBaseUrl,
1363
+ prepareInPlaceRestart: runPrepareInPlaceRestart,
1350
1364
  features: {
1351
1365
  enableSessionRecording: featureFlags?.enableSessionRecording() ?? false,
1352
1366
  // Gate the in-place restart on the LD flag OR the env escape hatch
@@ -1420,6 +1434,7 @@ async function startVite({
1420
1434
  logger: loggerOverride,
1421
1435
  superblocksBaseUrl,
1422
1436
  features,
1437
+ prepareInPlaceRestart,
1423
1438
  }: CreateDevServerOptions & {
1424
1439
  app: express.Express;
1425
1440
  httpServer: http.Server;
@@ -1428,6 +1443,7 @@ async function startVite({
1428
1443
  enableSessionRecording: boolean;
1429
1444
  inPlaceTemplateRestartEnabled: boolean;
1430
1445
  };
1446
+ prepareInPlaceRestart: (options?: { flipRestart?: true }) => Promise<void>;
1431
1447
  }): Promise<{
1432
1448
  viteServer: ViteDevServer;
1433
1449
  dispose: () => void;
@@ -1509,6 +1525,7 @@ async function startVite({
1509
1525
  superblocksBaseUrl,
1510
1526
  appRootDir: root,
1511
1527
  features,
1528
+ prepareInPlaceRestart,
1512
1529
  } as FileSyncPluginParams,
1513
1530
  { isCustomBuildEnabled },
1514
1531
  );
@@ -0,0 +1,99 @@
1
+ import { RESTART_REASON_FLIP_RESTART } from "@superblocksteam/library-shared/restart-contract";
2
+
3
+ /** Upper bound on the pre-exit IPC handshake to the parent supervisor. */
4
+ const RESTART_REASON_IPC_TIMEOUT_MS = 1000;
5
+
6
+ export interface PrepareInPlaceRestartLogger {
7
+ warn(message: string, meta?: Record<string, unknown>): void;
8
+ }
9
+
10
+ export interface PrepareInPlaceRestartServices {
11
+ aiService?: {
12
+ chatSessionStore: { invalidateCache(): void };
13
+ };
14
+ closeViteServer?: () => Promise<void>;
15
+ lockService?: {
16
+ setIsRestarting(): void | Promise<void>;
17
+ };
18
+ }
19
+
20
+ export interface PrepareInPlaceRestartOptions {
21
+ flipRestart?: true;
22
+ logger?: PrepareInPlaceRestartLogger;
23
+ }
24
+
25
+ /**
26
+ * Tell the CLI parent supervisor that the imminent `RESTART_EXIT_CODE` exit is a
27
+ * template flip — not the editor-driven `/_sb_disconnect` restart, which shares
28
+ * the same exit code.
29
+ */
30
+ export async function announceFlipRestartToParent(
31
+ logger?: PrepareInPlaceRestartLogger,
32
+ ): Promise<void> {
33
+ const send = process.send;
34
+ if (!send) {
35
+ return;
36
+ }
37
+ await new Promise<void>((resolve) => {
38
+ let settled = false;
39
+ const finish = (): void => {
40
+ if (settled) {
41
+ return;
42
+ }
43
+ settled = true;
44
+ resolve();
45
+ };
46
+ const timer = setTimeout(finish, RESTART_REASON_IPC_TIMEOUT_MS);
47
+ timer.unref?.();
48
+ const done = (): void => {
49
+ clearTimeout(timer);
50
+ finish();
51
+ };
52
+ try {
53
+ send.call(
54
+ process,
55
+ { type: "restartReason", reason: RESTART_REASON_FLIP_RESTART },
56
+ (error: Error | null) => {
57
+ if (error) {
58
+ logger?.warn(
59
+ "Failed to notify parent of flip restart reason; restart will surface as cold_start",
60
+ { error: error.message },
61
+ );
62
+ }
63
+ done();
64
+ },
65
+ );
66
+ } catch (error) {
67
+ logger?.warn(
68
+ "Failed to notify parent of flip restart reason; restart will surface as cold_start",
69
+ {
70
+ error: error instanceof Error ? error.message : String(error),
71
+ },
72
+ );
73
+ done();
74
+ }
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Shared teardown before an in-place dev-server restart (`process.exit(98)`).
80
+ * Used by `/_sb_disconnect` with `isRestart: true` and by the migration
81
+ * self-restart seam after a template flip.
82
+ */
83
+ export async function prepareInPlaceRestart(
84
+ services: PrepareInPlaceRestartServices,
85
+ options?: PrepareInPlaceRestartOptions,
86
+ ): Promise<void> {
87
+ if (services.lockService) {
88
+ await services.lockService.setIsRestarting();
89
+ }
90
+ if (services.aiService?.chatSessionStore) {
91
+ services.aiService.chatSessionStore.invalidateCache();
92
+ }
93
+ if (options?.flipRestart) {
94
+ await announceFlipRestartToParent(options.logger);
95
+ }
96
+ if (services.closeViteServer) {
97
+ await services.closeViteServer();
98
+ }
99
+ }
@@ -0,0 +1,69 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import {
4
+ announceFlipRestartToParent,
5
+ prepareInPlaceRestart,
6
+ } from "./prepare-in-place-restart.mjs";
7
+
8
+ describe("prepareInPlaceRestart", () => {
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ });
12
+
13
+ it("sets restarting, invalidates AI cache, and closes Vite when services are present", async () => {
14
+ const setIsRestarting = vi.fn();
15
+ const invalidateCache = vi.fn();
16
+ const closeViteServer = vi.fn(async () => {});
17
+
18
+ await prepareInPlaceRestart({
19
+ lockService: { setIsRestarting },
20
+ aiService: { chatSessionStore: { invalidateCache } },
21
+ closeViteServer,
22
+ });
23
+
24
+ expect(setIsRestarting).toHaveBeenCalledTimes(1);
25
+ expect(invalidateCache).toHaveBeenCalledTimes(1);
26
+ expect(closeViteServer).toHaveBeenCalledTimes(1);
27
+ });
28
+
29
+ it("announces flip restart to parent when flipRestart is set", async () => {
30
+ const send = vi.fn(
31
+ (_msg: unknown, cb: (err: Error | null) => void): boolean => {
32
+ cb(null);
33
+ return true;
34
+ },
35
+ );
36
+ process.send = send as unknown as typeof process.send;
37
+
38
+ await prepareInPlaceRestart(
39
+ {},
40
+ { flipRestart: true, logger: { warn: vi.fn() } },
41
+ );
42
+
43
+ expect(send).toHaveBeenCalledWith(
44
+ { type: "restartReason", reason: "flip_restart" },
45
+ expect.any(Function),
46
+ );
47
+ });
48
+
49
+ it("does not announce flip restart without flipRestart option", async () => {
50
+ const send = vi.fn(() => true);
51
+ process.send = send as unknown as typeof process.send;
52
+
53
+ await prepareInPlaceRestart({});
54
+
55
+ expect(send).not.toHaveBeenCalled();
56
+ });
57
+ });
58
+
59
+ describe("announceFlipRestartToParent", () => {
60
+ afterEach(() => {
61
+ vi.restoreAllMocks();
62
+ process.send = undefined;
63
+ });
64
+
65
+ it("no-ops when process.send is absent", async () => {
66
+ process.send = undefined;
67
+ await announceFlipRestartToParent();
68
+ });
69
+ });
@@ -79,16 +79,30 @@ export const deploymentType = (() => {
79
79
  */
80
80
  export const isCloudPrem = deploymentType === DeploymentTypeEnum.CLOUD_PREM;
81
81
 
82
- async function shutdownTelemetry(): Promise<void> {
83
- if (isTelemetryInitialized()) {
84
- try {
85
- await getTelemetryInstance().shutdown();
86
- } catch (error) {
87
- console.error("Error during telemetry shutdown:", error);
88
- } finally {
89
- resetTelemetry();
90
- configured = false;
91
- }
82
+ /**
83
+ * Shut down telemetry, forcing a final flush of all buffered traces, metrics,
84
+ * and logs (via `sdk.shutdown()` → the metric reader's force-flush).
85
+ *
86
+ * Never rejects: shutdown errors are caught and logged internally. Returns
87
+ * `true` only when a flush actually ran to completion; `false` when telemetry
88
+ * was never initialized (nothing to flush) or `sdk.shutdown()` threw (the
89
+ * export may not have completed). Callers can use the return value to report
90
+ * whether buffered samples were really flushed — any `try/catch` around it is
91
+ * purely defensive.
92
+ */
93
+ export async function shutdownTelemetry(): Promise<boolean> {
94
+ if (!isTelemetryInitialized()) {
95
+ return false;
96
+ }
97
+ try {
98
+ await getTelemetryInstance().shutdown();
99
+ return true;
100
+ } catch (error) {
101
+ console.error("Error during telemetry shutdown:", error);
102
+ return false;
103
+ } finally {
104
+ resetTelemetry();
105
+ configured = false;
92
106
  }
93
107
  }
94
108