@glasstrace/sdk 1.1.1 → 1.1.3

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 (49) hide show
  1. package/README.md +78 -1
  2. package/dist/{chunk-DIM4JRXM.js → chunk-2M57EO6U.js} +2 -2
  3. package/dist/{chunk-Y26HJUPD.js → chunk-FGDS33I2.js} +138 -12
  4. package/dist/chunk-FGDS33I2.js.map +1 -0
  5. package/dist/{chunk-MXDZHFJQ.js → chunk-JKI4OCFV.js} +4 -14
  6. package/dist/chunk-JKI4OCFV.js.map +1 -0
  7. package/dist/{chunk-7SZQN6IU.js → chunk-NB7GJE4S.js} +2 -2
  8. package/dist/chunk-NB7GJE4S.js.map +1 -0
  9. package/dist/{chunk-ZRDQ6ZKI.js → chunk-TWHCJKRS.js} +101 -481
  10. package/dist/chunk-TWHCJKRS.js.map +1 -0
  11. package/dist/{chunk-P22UQ2OJ.js → chunk-TWTWRJ25.js} +233 -9
  12. package/dist/chunk-TWTWRJ25.js.map +1 -0
  13. package/dist/cli/init.cjs +2494 -2332
  14. package/dist/cli/init.cjs.map +1 -1
  15. package/dist/cli/init.js +434 -63
  16. package/dist/cli/init.js.map +1 -1
  17. package/dist/cli/mcp-add.cjs +14 -2
  18. package/dist/cli/mcp-add.cjs.map +1 -1
  19. package/dist/cli/mcp-add.js +17 -5
  20. package/dist/cli/mcp-add.js.map +1 -1
  21. package/dist/cli/status.cjs.map +1 -1
  22. package/dist/cli/status.js +1 -3
  23. package/dist/cli/status.js.map +1 -1
  24. package/dist/cli/uninit.cjs +116 -14
  25. package/dist/cli/uninit.cjs.map +1 -1
  26. package/dist/cli/uninit.js +3 -3
  27. package/dist/cli/validate.cjs +14162 -2
  28. package/dist/cli/validate.cjs.map +1 -1
  29. package/dist/cli/validate.d.cts +7 -3
  30. package/dist/cli/validate.d.ts +7 -3
  31. package/dist/cli/validate.js +25 -2
  32. package/dist/cli/validate.js.map +1 -1
  33. package/dist/index.cjs +339 -28
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +12 -9
  36. package/dist/index.d.ts +12 -9
  37. package/dist/index.js +3 -3
  38. package/dist/{monorepo-GSL6JD3G.js → monorepo-PFVNPQ6X.js} +3 -5
  39. package/dist/node-entry.cjs +339 -28
  40. package/dist/node-entry.cjs.map +1 -1
  41. package/dist/node-entry.js +3 -3
  42. package/package.json +1 -1
  43. package/dist/chunk-7SZQN6IU.js.map +0 -1
  44. package/dist/chunk-MXDZHFJQ.js.map +0 -1
  45. package/dist/chunk-P22UQ2OJ.js.map +0 -1
  46. package/dist/chunk-Y26HJUPD.js.map +0 -1
  47. package/dist/chunk-ZRDQ6ZKI.js.map +0 -1
  48. /package/dist/{chunk-DIM4JRXM.js.map → chunk-2M57EO6U.js.map} +0 -0
  49. /package/dist/{monorepo-GSL6JD3G.js.map → monorepo-PFVNPQ6X.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -88,15 +88,18 @@ declare function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey>;
88
88
  */
89
89
  declare function loadCachedConfig(projectRoot?: string): SdkInitResponse | null;
90
90
  /**
91
- * Persists the init response to `.glasstrace/config` using atomic
92
- * write-temp + rename semantics. Silently skipped when `node:fs` is
93
- * unavailable (non-Node environments). On I/O failure, logs a warning.
94
- *
95
- * Atomicity: the payload is written to `.glasstrace/config.tmp` and then
96
- * renamed into place. `rename` is atomic on POSIX filesystems, so readers
97
- * either see the previous valid config or the new valid config — never a
98
- * truncated or partially-written file (DISC-1247 Scenario 5). If the
99
- * rename fails, the temp file is cleaned up on a best-effort basis.
91
+ * Persists the init response to `.glasstrace/config` using the SDK 2.0
92
+ * atomic-write protocol (`tmp + fsync(tmp) + rename + fsync(parent)`).
93
+ * Silently skipped when `node:fs` is unavailable (non-Node environments).
94
+ * On I/O failure, logs a warning.
95
+ *
96
+ * Atomicity: the payload is written to `.glasstrace/config.tmp`, fsynced
97
+ * to durable storage, then renamed into place; the parent directory is
98
+ * fsynced last so the rename survives an immediate crash. `rename` is
99
+ * atomic on POSIX filesystems, so readers either see the previous valid
100
+ * config or the new valid config — never a truncated or partially-written
101
+ * file (DISC-1247 Scenario 5). If any step fails, the temp file is
102
+ * cleaned up on a best-effort basis.
100
103
  */
101
104
  declare function saveCachedConfig(response: SdkInitResponse, projectRoot?: string): Promise<void>;
102
105
  /**
package/dist/index.d.ts CHANGED
@@ -88,15 +88,18 @@ declare function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey>;
88
88
  */
89
89
  declare function loadCachedConfig(projectRoot?: string): SdkInitResponse | null;
90
90
  /**
91
- * Persists the init response to `.glasstrace/config` using atomic
92
- * write-temp + rename semantics. Silently skipped when `node:fs` is
93
- * unavailable (non-Node environments). On I/O failure, logs a warning.
94
- *
95
- * Atomicity: the payload is written to `.glasstrace/config.tmp` and then
96
- * renamed into place. `rename` is atomic on POSIX filesystems, so readers
97
- * either see the previous valid config or the new valid config — never a
98
- * truncated or partially-written file (DISC-1247 Scenario 5). If the
99
- * rename fails, the temp file is cleaned up on a best-effort basis.
91
+ * Persists the init response to `.glasstrace/config` using the SDK 2.0
92
+ * atomic-write protocol (`tmp + fsync(tmp) + rename + fsync(parent)`).
93
+ * Silently skipped when `node:fs` is unavailable (non-Node environments).
94
+ * On I/O failure, logs a warning.
95
+ *
96
+ * Atomicity: the payload is written to `.glasstrace/config.tmp`, fsynced
97
+ * to durable storage, then renamed into place; the parent directory is
98
+ * fsynced last so the rename survives an immediate crash. `rename` is
99
+ * atomic on POSIX filesystems, so readers either see the previous valid
100
+ * config or the new valid config — never a truncated or partially-written
101
+ * file (DISC-1247 Scenario 5). If any step fails, the temp file is
102
+ * cleaned up on a best-effort basis.
100
103
  */
101
104
  declare function saveCachedConfig(response: SdkInitResponse, projectRoot?: string): Promise<void>;
102
105
  /**
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  registerGlasstrace,
13
13
  waitForReady,
14
14
  withGlasstraceConfig
15
- } from "./chunk-Y26HJUPD.js";
15
+ } from "./chunk-FGDS33I2.js";
16
16
  import {
17
17
  GlasstraceSpanProcessor,
18
18
  SdkError,
@@ -27,7 +27,7 @@ import {
27
27
  performInit,
28
28
  saveCachedConfig,
29
29
  sendInitRequest
30
- } from "./chunk-MXDZHFJQ.js";
30
+ } from "./chunk-JKI4OCFV.js";
31
31
  import {
32
32
  isAnonymousMode,
33
33
  isProductionDisabled,
@@ -37,7 +37,7 @@ import {
37
37
  import {
38
38
  getOrCreateAnonKey,
39
39
  readAnonKey
40
- } from "./chunk-P22UQ2OJ.js";
40
+ } from "./chunk-TWTWRJ25.js";
41
41
  import {
42
42
  deriveSessionId
43
43
  } from "./chunk-X5MAXP5T.js";
@@ -3,10 +3,8 @@ import {
3
3
  isMonorepoRoot,
4
4
  parsePnpmWorkspaceYaml,
5
5
  resolveProjectRoot
6
- } from "./chunk-DIM4JRXM.js";
7
- import "./chunk-7SZQN6IU.js";
8
- import "./chunk-P22UQ2OJ.js";
9
- import "./chunk-X5MAXP5T.js";
6
+ } from "./chunk-2M57EO6U.js";
7
+ import "./chunk-NB7GJE4S.js";
10
8
  import "./chunk-NSBPE2FW.js";
11
9
  export {
12
10
  findNextJsApps,
@@ -14,4 +12,4 @@ export {
14
12
  parsePnpmWorkspaceYaml,
15
13
  resolveProjectRoot
16
14
  };
17
- //# sourceMappingURL=monorepo-GSL6JD3G.js.map
15
+ //# sourceMappingURL=monorepo-PFVNPQ6X.js.map
@@ -17495,6 +17495,208 @@ function sleep(ms, scheduler, signal) {
17495
17495
  // src/mcp-runtime.ts
17496
17496
  var import_node_crypto = require("node:crypto");
17497
17497
  init_dist();
17498
+
17499
+ // src/atomic-write.ts
17500
+ function parentDir(filePath) {
17501
+ const lastSlash = filePath.lastIndexOf("/");
17502
+ const lastBackslash = filePath.lastIndexOf("\\");
17503
+ const lastSep = Math.max(lastSlash, lastBackslash);
17504
+ if (lastSep < 0) return ".";
17505
+ if (lastSep === 0) return filePath.slice(0, 1);
17506
+ return filePath.slice(0, lastSep);
17507
+ }
17508
+ var PARENT_FSYNC_SWALLOWED_CODES = /* @__PURE__ */ new Set([
17509
+ "EISDIR",
17510
+ "EINVAL",
17511
+ "EPERM",
17512
+ "ENOTSUP"
17513
+ ]);
17514
+ function errnoCodeOf(err) {
17515
+ if (err === null || typeof err !== "object") return void 0;
17516
+ const code = err.code;
17517
+ return typeof code === "string" ? code : void 0;
17518
+ }
17519
+ function defaultTmpPath(targetPath) {
17520
+ return `${targetPath}.tmp`;
17521
+ }
17522
+ var fsPromisesCache;
17523
+ var fsSyncCache;
17524
+ async function loadFsPromises() {
17525
+ if (fsPromisesCache !== void 0) {
17526
+ if (fsPromisesCache === null) {
17527
+ throw new Error(
17528
+ "node:fs/promises is unavailable in this environment; atomicWriteFile cannot be used here."
17529
+ );
17530
+ }
17531
+ return fsPromisesCache;
17532
+ }
17533
+ try {
17534
+ fsPromisesCache = await import("node:fs/promises");
17535
+ return fsPromisesCache;
17536
+ } catch {
17537
+ fsPromisesCache = null;
17538
+ throw new Error(
17539
+ "node:fs/promises is unavailable in this environment; atomicWriteFile cannot be used here."
17540
+ );
17541
+ }
17542
+ }
17543
+ function loadFsSync() {
17544
+ if (fsSyncCache !== void 0) {
17545
+ if (fsSyncCache === null) {
17546
+ throw new Error(
17547
+ "node:fs is unavailable in this environment; atomicWriteFileSync cannot be used here."
17548
+ );
17549
+ }
17550
+ return fsSyncCache;
17551
+ }
17552
+ try {
17553
+ fsSyncCache = require("node:fs");
17554
+ return fsSyncCache;
17555
+ } catch {
17556
+ fsSyncCache = null;
17557
+ throw new Error(
17558
+ "node:fs is unavailable in this environment; atomicWriteFileSync cannot be used here."
17559
+ );
17560
+ }
17561
+ }
17562
+ async function atomicWriteFile(targetPath, payload, options = {}) {
17563
+ return atomicWriteFileWithTmp(targetPath, defaultTmpPath(targetPath), payload, options);
17564
+ }
17565
+ async function atomicWriteFileWithTmp(targetPath, tmpPath, payload, options = {}) {
17566
+ const mode = options.mode ?? 384;
17567
+ const encoding = options.encoding ?? "utf-8";
17568
+ const fsp = await loadFsPromises();
17569
+ let handle = null;
17570
+ try {
17571
+ if (typeof payload === "string") {
17572
+ await fsp.writeFile(tmpPath, payload, { encoding, mode });
17573
+ } else {
17574
+ await fsp.writeFile(tmpPath, payload, { mode });
17575
+ }
17576
+ await fsp.chmod(tmpPath, mode);
17577
+ handle = await fsp.open(tmpPath, "r");
17578
+ await handle.sync();
17579
+ await handle.close();
17580
+ handle = null;
17581
+ await fsp.rename(tmpPath, targetPath);
17582
+ } catch (err) {
17583
+ if (handle !== null) {
17584
+ try {
17585
+ await handle.close();
17586
+ } catch {
17587
+ }
17588
+ }
17589
+ await removeTmpResidueAsync(fsp, tmpPath);
17590
+ throw err;
17591
+ }
17592
+ await fsyncParentDirAsync(targetPath, fsp);
17593
+ }
17594
+ async function removeTmpResidueAsync(fsp, tmpPath) {
17595
+ try {
17596
+ await fsp.unlink(tmpPath);
17597
+ return;
17598
+ } catch (err) {
17599
+ const code = errnoCodeOf(err);
17600
+ if (code !== "EISDIR" && code !== "EPERM") {
17601
+ return;
17602
+ }
17603
+ }
17604
+ try {
17605
+ await fsp.rmdir(tmpPath);
17606
+ } catch {
17607
+ }
17608
+ }
17609
+ async function fsyncParentDirAsync(targetPath, fsp) {
17610
+ const parent = parentDir(targetPath);
17611
+ let handle = null;
17612
+ try {
17613
+ handle = await fsp.open(parent, "r");
17614
+ await handle.sync();
17615
+ } catch (err) {
17616
+ const code = errnoCodeOf(err);
17617
+ if (code !== void 0 && PARENT_FSYNC_SWALLOWED_CODES.has(code)) {
17618
+ return;
17619
+ }
17620
+ throw err;
17621
+ } finally {
17622
+ if (handle !== null) {
17623
+ try {
17624
+ await handle.close();
17625
+ } catch {
17626
+ }
17627
+ }
17628
+ }
17629
+ }
17630
+ function atomicWriteFileSync(targetPath, payload, options = {}) {
17631
+ atomicWriteFileSyncWithTmp(targetPath, defaultTmpPath(targetPath), payload, options);
17632
+ }
17633
+ function atomicWriteFileSyncWithTmp(targetPath, tmpPath, payload, options = {}) {
17634
+ const mode = options.mode ?? 384;
17635
+ const encoding = options.encoding ?? "utf-8";
17636
+ const fs4 = loadFsSync();
17637
+ let fd = null;
17638
+ try {
17639
+ if (typeof payload === "string") {
17640
+ fs4.writeFileSync(tmpPath, payload, { encoding, mode });
17641
+ } else {
17642
+ fs4.writeFileSync(tmpPath, payload, { mode });
17643
+ }
17644
+ fs4.chmodSync(tmpPath, mode);
17645
+ fd = fs4.openSync(tmpPath, "r");
17646
+ fs4.fsyncSync(fd);
17647
+ fs4.closeSync(fd);
17648
+ fd = null;
17649
+ fs4.renameSync(tmpPath, targetPath);
17650
+ } catch (err) {
17651
+ if (fd !== null) {
17652
+ try {
17653
+ fs4.closeSync(fd);
17654
+ } catch {
17655
+ }
17656
+ }
17657
+ removeTmpResidueSync(fs4, tmpPath);
17658
+ throw err;
17659
+ }
17660
+ fsyncParentDirSyncWithFs(targetPath, fs4);
17661
+ }
17662
+ function removeTmpResidueSync(fs4, tmpPath) {
17663
+ try {
17664
+ fs4.unlinkSync(tmpPath);
17665
+ return;
17666
+ } catch (err) {
17667
+ const code = errnoCodeOf(err);
17668
+ if (code !== "EISDIR" && code !== "EPERM") {
17669
+ return;
17670
+ }
17671
+ }
17672
+ try {
17673
+ fs4.rmdirSync(tmpPath);
17674
+ } catch {
17675
+ }
17676
+ }
17677
+ function fsyncParentDirSyncWithFs(targetPath, fs4) {
17678
+ const parent = parentDir(targetPath);
17679
+ let fd = null;
17680
+ try {
17681
+ fd = fs4.openSync(parent, "r");
17682
+ fs4.fsyncSync(fd);
17683
+ } catch (err) {
17684
+ const code = errnoCodeOf(err);
17685
+ if (code !== void 0 && PARENT_FSYNC_SWALLOWED_CODES.has(code)) {
17686
+ return;
17687
+ }
17688
+ throw err;
17689
+ } finally {
17690
+ if (fd !== null) {
17691
+ try {
17692
+ fs4.closeSync(fd);
17693
+ } catch {
17694
+ }
17695
+ }
17696
+ }
17697
+ }
17698
+
17699
+ // src/mcp-runtime.ts
17498
17700
  var MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
17499
17701
  var fsPathCache2;
17500
17702
  async function loadFsPath2() {
@@ -17708,7 +17910,6 @@ async function refreshGenericMcpConfigAtRuntime(projectRoot, effective, anonKeyO
17708
17910
  if (!modules) return { action: "absent" };
17709
17911
  const dirPath = modules.path.join(projectRoot, GLASSTRACE_DIR2);
17710
17912
  const configPath = modules.path.join(dirPath, MCP_CONFIG_FILE);
17711
- const tmpPath = configPath + ".tmp";
17712
17913
  let existing;
17713
17914
  try {
17714
17915
  existing = await modules.fs.readFile(configPath, "utf-8");
@@ -17725,18 +17926,12 @@ async function refreshGenericMcpConfigAtRuntime(projectRoot, effective, anonKeyO
17725
17926
  }
17726
17927
  const replacement = genericMcpConfigContent(MCP_ENDPOINT, effective.key);
17727
17928
  try {
17728
- await modules.fs.writeFile(tmpPath, replacement, { mode: 384 });
17729
- await modules.fs.chmod(tmpPath, 384);
17730
- await modules.fs.rename(tmpPath, configPath);
17929
+ await atomicWriteFile(configPath, replacement, { mode: 384 });
17731
17930
  await writeMcpMarker(projectRoot, {
17732
17931
  credentialSource: effective.source,
17733
17932
  credentialHash: identityFingerprint(effective.key)
17734
17933
  });
17735
17934
  } catch {
17736
- try {
17737
- await modules.fs.unlink(tmpPath);
17738
- } catch {
17739
- }
17740
17935
  return { action: "preserved" };
17741
17936
  }
17742
17937
  emitRefreshNudge(effective.source);
@@ -17809,7 +18004,6 @@ async function saveCachedConfig(response, projectRoot) {
17809
18004
  const root = projectRoot ?? process.cwd();
17810
18005
  const dirPath = modules.path.join(root, GLASSTRACE_DIR3);
17811
18006
  const configPath = modules.path.join(dirPath, CONFIG_FILE);
17812
- const tmpPath = `${configPath}.tmp`;
17813
18007
  try {
17814
18008
  await modules.fs.mkdir(dirPath, { recursive: true, mode: 448 });
17815
18009
  await modules.fs.chmod(dirPath, 448);
@@ -17817,20 +18011,10 @@ async function saveCachedConfig(response, projectRoot) {
17817
18011
  response,
17818
18012
  cachedAt: Date.now()
17819
18013
  };
17820
- await modules.fs.writeFile(tmpPath, JSON.stringify(cached2), {
18014
+ await atomicWriteFile(configPath, JSON.stringify(cached2), {
17821
18015
  encoding: "utf-8",
17822
18016
  mode: 384
17823
18017
  });
17824
- try {
17825
- await modules.fs.chmod(tmpPath, 384);
17826
- await modules.fs.rename(tmpPath, configPath);
17827
- } catch (renameErr) {
17828
- try {
17829
- await modules.fs.unlink(tmpPath);
17830
- } catch {
17831
- }
17832
- throw renameErr;
17833
- }
17834
18018
  await modules.fs.chmod(configPath, 384);
17835
18019
  } catch (err) {
17836
18020
  console.warn(
@@ -18109,6 +18293,128 @@ init_esm();
18109
18293
  init_dist();
18110
18294
  init_console_capture();
18111
18295
  init_error_nudge();
18296
+
18297
+ // src/error-response-body.ts
18298
+ var ERROR_RESPONSE_BODY_MAX_BYTES = 4096;
18299
+ var ERROR_RESPONSE_BODY_TRUNCATION_MARKER = "...[truncated]";
18300
+ var REDACTED = "[REDACTED]";
18301
+ var ERROR_STATUS_MIN = 400;
18302
+ var ERROR_STATUS_MAX = 599;
18303
+ function isHttpErrorStatus(status) {
18304
+ let numeric;
18305
+ if (typeof status === "number") {
18306
+ numeric = status;
18307
+ } else if (typeof status === "string" && status.length > 0) {
18308
+ numeric = Number(status);
18309
+ } else {
18310
+ return false;
18311
+ }
18312
+ if (!Number.isFinite(numeric)) return false;
18313
+ return numeric >= ERROR_STATUS_MIN && numeric <= ERROR_STATUS_MAX;
18314
+ }
18315
+ var REDACTION_PATTERNS = [
18316
+ // Order matters: redact specific token shapes BEFORE the generic
18317
+ // key=value catcher so a literal `Bearer eyJ…` collapses into a single
18318
+ // [REDACTED] and the JWT regex does not separately match the suffix.
18319
+ {
18320
+ name: "bearer",
18321
+ // Case-insensitive on the scheme: HTTP frameworks and proxies
18322
+ // round-trip the auth scheme with inconsistent casing
18323
+ // (`Bearer`, `bearer`, `BEARER`), and a real token leaks just as
18324
+ // badly under any of them.
18325
+ pattern: /\bBearer\s+[A-Za-z0-9._\-+/=]+/gi
18326
+ },
18327
+ {
18328
+ name: "jwt",
18329
+ // Three base64url segments separated by dots. Real JWTs encode at
18330
+ // minimum a small JSON header in the first segment, which alone is
18331
+ // typically ≥10 chars after base64url; a 16-char floor avoids false
18332
+ // positives on dotted text like a stack-trace frame
18333
+ // (`react.dom.server`) while still catching every real JWT we have
18334
+ // seen in the wild. Anchored with word boundaries on both sides so
18335
+ // a 3-dot semantic version like "next@15.4.1.2" does not match.
18336
+ pattern: /\b[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
18337
+ },
18338
+ {
18339
+ name: "glasstrace-api-key",
18340
+ // gt_dev_* and gt_anon_* keys are >=24 chars of [A-Za-z0-9].
18341
+ pattern: /\bgt_(?:dev|anon)_[A-Za-z0-9]{16,}\b/g
18342
+ },
18343
+ {
18344
+ name: "aws-access-key",
18345
+ // 20-char prefix-fixed identifier.
18346
+ pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
18347
+ },
18348
+ {
18349
+ name: "key-value-secret-quoted",
18350
+ // Quoted-string variant: (key) [:=] "<value>". The value runs to
18351
+ // the next unescaped closing quote so a multi-word secret like
18352
+ // `password="my secret phrase"` is fully consumed instead of
18353
+ // splitting at the first space and leaving the tail visible.
18354
+ // The leading `(?<![A-Za-z0-9_])` prevents matching inside
18355
+ // identifiers like `passwordless`. The trailing `"?` after the
18356
+ // keyword absorbs the closing quote in JSON-style `"apikey":
18357
+ // "value"` so the colon is still seen as the separator.
18358
+ pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*"(?:[^"\\]|\\.)*"/gi
18359
+ },
18360
+ {
18361
+ name: "key-value-secret-bare",
18362
+ // Unquoted variant: (key) [:=] <bare-value>. The bare value
18363
+ // capture stops at common JSON/text delimiters so we redact only
18364
+ // the value, not surrounding structure. Listed AFTER the quoted
18365
+ // variant so a quoted value's surrounding `"` are consumed by
18366
+ // the first pattern and we never fall through here for a quoted
18367
+ // secret.
18368
+ pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*[^\s,;}\]"]+/gi
18369
+ }
18370
+ ];
18371
+ function sanitizeErrorResponseBody(body) {
18372
+ let out = body;
18373
+ for (const { pattern } of REDACTION_PATTERNS) {
18374
+ out = out.replace(pattern, REDACTED);
18375
+ }
18376
+ return out;
18377
+ }
18378
+ function truncateErrorResponseBody(body) {
18379
+ const encoder = new TextEncoder();
18380
+ const encoded = encoder.encode(body);
18381
+ if (encoded.byteLength <= ERROR_RESPONSE_BODY_MAX_BYTES) {
18382
+ return body;
18383
+ }
18384
+ let cut = ERROR_RESPONSE_BODY_MAX_BYTES;
18385
+ let scan = cut - 1;
18386
+ while (scan >= 0 && (encoded[scan] & 192) === 128) {
18387
+ scan -= 1;
18388
+ }
18389
+ if (scan >= 0) {
18390
+ const leading = encoded[scan];
18391
+ let expected = 1;
18392
+ if ((leading & 128) === 0) {
18393
+ expected = 1;
18394
+ } else if ((leading & 224) === 192) {
18395
+ expected = 2;
18396
+ } else if ((leading & 240) === 224) {
18397
+ expected = 3;
18398
+ } else if ((leading & 248) === 240) {
18399
+ expected = 4;
18400
+ }
18401
+ if (scan + expected > cut) {
18402
+ cut = scan;
18403
+ }
18404
+ }
18405
+ const decoder = new TextDecoder("utf-8", { fatal: false });
18406
+ const sliced = encoded.subarray(0, cut);
18407
+ const decoded = decoder.decode(sliced);
18408
+ return decoded + ERROR_RESPONSE_BODY_TRUNCATION_MARKER;
18409
+ }
18410
+ function prepareErrorResponseBody(body) {
18411
+ if (body.length === 0) return null;
18412
+ if (body.trim().length === 0) return null;
18413
+ const sanitized = sanitizeErrorResponseBody(body);
18414
+ return truncateErrorResponseBody(sanitized);
18415
+ }
18416
+
18417
+ // src/enriching-exporter.ts
18112
18418
  var ATTR2 = GLASSTRACE_ATTRIBUTE_NAMES;
18113
18419
  var API_KEY_PENDING = "pending";
18114
18420
  var MAX_PENDING_SPANS = 1024;
@@ -18336,7 +18642,14 @@ var GlasstraceExporter = class {
18336
18642
  if (this.getConfig().errorResponseBodies) {
18337
18643
  const responseBody = attrs["glasstrace.internal.response_body"];
18338
18644
  if (typeof responseBody === "string") {
18339
- extra[ATTR2.ERROR_RESPONSE_BODY] = responseBody.slice(0, 500);
18645
+ const enrichedStatus = extra[ATTR2.HTTP_STATUS_CODE];
18646
+ const effectiveStatus = typeof enrichedStatus === "number" ? enrichedStatus : statusCode;
18647
+ if (isHttpErrorStatus(effectiveStatus)) {
18648
+ const prepared = prepareErrorResponseBody(responseBody);
18649
+ if (prepared !== null) {
18650
+ extra[ATTR2.ERROR_RESPONSE_BODY] = prepared;
18651
+ }
18652
+ }
18340
18653
  }
18341
18654
  }
18342
18655
  const spanAny = span;
@@ -22158,12 +22471,10 @@ function writeStateNow() {
22158
22471
  };
22159
22472
  const dir = (0, import_node_path.join)(_projectRoot, ".glasstrace");
22160
22473
  const filePath = (0, import_node_path.join)(dir, "runtime-state.json");
22161
- const tmpPath = (0, import_node_path.join)(dir, "runtime-state.json.tmp");
22162
22474
  (0, import_node_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
22163
- (0, import_node_fs.writeFileSync)(tmpPath, JSON.stringify(runtimeState, null, 2) + "\n", {
22475
+ atomicWriteFileSync(filePath, JSON.stringify(runtimeState, null, 2) + "\n", {
22164
22476
  mode: 384
22165
22477
  });
22166
- (0, import_node_fs.renameSync)(tmpPath, filePath);
22167
22478
  } catch (err) {
22168
22479
  sdkLog(
22169
22480
  "warn",
@@ -22202,7 +22513,7 @@ function registerGlasstrace(options) {
22202
22513
  setCoreState(CoreState.REGISTERING);
22203
22514
  startRuntimeStateWriter({
22204
22515
  projectRoot: process.cwd(),
22205
- sdkVersion: "1.1.1"
22516
+ sdkVersion: "1.1.3"
22206
22517
  });
22207
22518
  const config2 = resolveConfig(options);
22208
22519
  if (config2.verbose) {
@@ -22368,8 +22679,8 @@ async function backgroundInit(config2, anonKeyForInit, generation) {
22368
22679
  if (config2.verbose) {
22369
22680
  console.info("[glasstrace] Background init firing.");
22370
22681
  }
22371
- const healthReport = collectHealthReport("1.1.1");
22372
- const initResult = await performInit(config2, anonKeyForInit, "1.1.1", healthReport);
22682
+ const healthReport = collectHealthReport("1.1.3");
22683
+ const initResult = await performInit(config2, anonKeyForInit, "1.1.3", healthReport);
22373
22684
  if (generation !== registrationGeneration) return;
22374
22685
  const currentState = getCoreState();
22375
22686
  if (currentState === CoreState.SHUTTING_DOWN || currentState === CoreState.SHUTDOWN) {
@@ -22392,7 +22703,7 @@ async function backgroundInit(config2, anonKeyForInit, generation) {
22392
22703
  }
22393
22704
  maybeInstallConsoleCapture();
22394
22705
  if (didLastInitSucceed()) {
22395
- startHeartbeat(config2, anonKeyForInit, "1.1.1", generation, (newApiKey, accountId) => {
22706
+ startHeartbeat(config2, anonKeyForInit, "1.1.3", generation, (newApiKey, accountId) => {
22396
22707
  setAuthState(AuthState.CLAIMING);
22397
22708
  emitLifecycleEvent("auth:claim_started", { accountId });
22398
22709
  setResolvedApiKey(newApiKey);