@glasstrace/sdk 1.4.0 → 1.5.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.
- package/README.md +56 -0
- package/dist/{chunk-JZ475QRH.js → chunk-D3QXU2VM.js} +22 -191
- package/dist/chunk-D3QXU2VM.js.map +1 -0
- package/dist/{chunk-VQDYXXVS.js → chunk-N3XIVM2U.js} +154 -8
- package/dist/chunk-N3XIVM2U.js.map +1 -0
- package/dist/{chunk-VJQIFY33.js → chunk-YLY7AGLC.js} +7 -4
- package/dist/chunk-YLY7AGLC.js.map +1 -0
- package/dist/chunk-ZBQQXVHD.js +208 -0
- package/dist/chunk-ZBQQXVHD.js.map +1 -0
- package/dist/cli/init.cjs +206 -34
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +65 -8
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs +45 -25
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +10 -7
- package/dist/cli/mcp-add.js.map +1 -1
- package/dist/cli/status.cjs +33 -3
- package/dist/cli/status.cjs.map +1 -1
- package/dist/cli/status.js +12 -3
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/uninit.cjs +27 -3
- package/dist/cli/uninit.cjs.map +1 -1
- package/dist/cli/uninit.d.cts +10 -2
- package/dist/cli/uninit.d.ts +10 -2
- package/dist/cli/uninit.js +2 -1
- package/dist/cli/upgrade-instructions.cjs +440 -0
- package/dist/cli/upgrade-instructions.cjs.map +1 -0
- package/dist/cli/upgrade-instructions.d.cts +48 -0
- package/dist/cli/upgrade-instructions.d.ts +48 -0
- package/dist/cli/upgrade-instructions.js +80 -0
- package/dist/cli/upgrade-instructions.js.map +1 -0
- package/dist/index.cjs +229 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/node-entry.cjs +237 -68
- package/dist/node-entry.cjs.map +1 -1
- package/dist/node-entry.js +2 -1
- package/package.json +1 -1
- package/dist/chunk-JZ475QRH.js.map +0 -1
- package/dist/chunk-VJQIFY33.js.map +0 -1
- package/dist/chunk-VQDYXXVS.js.map +0 -1
package/README.md
CHANGED
|
@@ -98,6 +98,62 @@ total cap). HTTP 4xx/5xx and malformed responses are surfaced
|
|
|
98
98
|
immediately. Set `GLASSTRACE_SKIP_INIT_VERIFY=1` to skip verification
|
|
99
99
|
for offline installs.
|
|
100
100
|
|
|
101
|
+
## Refreshing agent instruction guidance
|
|
102
|
+
|
|
103
|
+
`glasstrace init` and `glasstrace mcp add` write a managed Glasstrace
|
|
104
|
+
MCP section into your project's agent instruction file (CLAUDE.md /
|
|
105
|
+
codex.md / .cursorrules). The section opens with a cost-aware
|
|
106
|
+
cross-tool decision paragraph telling your AI agent **when** Glasstrace
|
|
107
|
+
MCP is worth calling and **which** tool is the cheapest first call for
|
|
108
|
+
each symptom class.
|
|
109
|
+
|
|
110
|
+
The managed section's start marker carries an SDK version stamp, e.g.
|
|
111
|
+
`<!-- glasstrace:mcp:start v=1.5.0 -->`. When you upgrade
|
|
112
|
+
`@glasstrace/sdk`, run:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx glasstrace upgrade-instructions
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The command refreshes every detected agent instruction file in one
|
|
119
|
+
run. Files outside the markers are untouched; files without a
|
|
120
|
+
Glasstrace managed section are left alone. The command is idempotent
|
|
121
|
+
— re-running produces byte-for-byte identical output.
|
|
122
|
+
|
|
123
|
+
`npx glasstrace mcp add` performs the same managed-section refresh
|
|
124
|
+
when run with `--force` (or against a project whose marker file has
|
|
125
|
+
shifted credentials), so either command is a valid upgrade path.
|
|
126
|
+
|
|
127
|
+
### Stale-section warning at SDK init
|
|
128
|
+
|
|
129
|
+
When the running SDK detects that an agent instruction file's stamp
|
|
130
|
+
is strictly older than the running version, it writes a single
|
|
131
|
+
stderr line at `registerGlasstrace()` time pointing at the upgrade
|
|
132
|
+
command. Constraints:
|
|
133
|
+
|
|
134
|
+
- Stderr only, never stdout. Tracing behaviour is unaffected.
|
|
135
|
+
- At most one warning per process boot, even when multiple
|
|
136
|
+
`registerGlasstrace()` calls happen (test runners, hot reload).
|
|
137
|
+
- Node-only — no-op on Edge / browser runtimes. Never throws.
|
|
138
|
+
- Does not mutate any file at runtime; the user opts in by running
|
|
139
|
+
the upgrade command.
|
|
140
|
+
- Suppressed by setting one of:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
GLASSTRACE_DISABLE_UPGRADE_NOTICE=1
|
|
144
|
+
GLASSTRACE_DISABLE_UPGRADE_NOTICE=true
|
|
145
|
+
GLASSTRACE_DISABLE_UPGRADE_NOTICE=yes
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
(case-insensitive). Any other value, including unset, leaves the
|
|
149
|
+
warning enabled.
|
|
150
|
+
|
|
151
|
+
Legacy unstamped managed sections (written by `@glasstrace/sdk`
|
|
152
|
+
versions before 1.5.0) trigger no warning — those projects receive
|
|
153
|
+
the refreshed text on their next `mcp add` or
|
|
154
|
+
`upgrade-instructions` run, and the upgrade replaces the legacy
|
|
155
|
+
block in place rather than appending a duplicate.
|
|
156
|
+
|
|
101
157
|
## Server Action detection (Next.js)
|
|
102
158
|
|
|
103
159
|
Next.js does not emit a dedicated OTel span for Server Actions. The SDK
|
|
@@ -252,28 +252,39 @@ function generateMcpConfig(agent, endpoint, bearer) {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
-
|
|
255
|
+
var SDK_VERSION_STAMP_PATTERN = /^[A-Za-z0-9.+-]+$/;
|
|
256
|
+
function htmlMarkers(sdkVersion) {
|
|
256
257
|
return {
|
|
257
|
-
start:
|
|
258
|
+
start: `<!-- glasstrace:mcp:start v=${sdkVersion} -->`,
|
|
258
259
|
end: "<!-- glasstrace:mcp:end -->"
|
|
259
260
|
};
|
|
260
261
|
}
|
|
261
|
-
function hashMarkers() {
|
|
262
|
+
function hashMarkers(sdkVersion) {
|
|
262
263
|
return {
|
|
263
|
-
start:
|
|
264
|
+
start: `# glasstrace:mcp:start v=${sdkVersion}`,
|
|
264
265
|
end: "# glasstrace:mcp:end"
|
|
265
266
|
};
|
|
266
267
|
}
|
|
267
|
-
function generateInfoSection(agent, endpoint) {
|
|
268
|
+
function generateInfoSection(agent, endpoint, sdkVersion) {
|
|
268
269
|
if (!endpoint || endpoint.trim() === "") {
|
|
269
270
|
throw new Error("endpoint must not be empty");
|
|
270
271
|
}
|
|
272
|
+
if (!sdkVersion || sdkVersion.trim() === "") {
|
|
273
|
+
throw new Error("sdkVersion must not be empty");
|
|
274
|
+
}
|
|
275
|
+
if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
"sdkVersion must match [A-Za-z0-9.+\\-]+ (semver-shaped, no whitespace, no angle brackets)"
|
|
278
|
+
);
|
|
279
|
+
}
|
|
271
280
|
const content = [
|
|
272
281
|
"",
|
|
273
282
|
"## Glasstrace MCP Integration",
|
|
274
283
|
"",
|
|
275
284
|
`Glasstrace is configured as an MCP server at: ${endpoint}`,
|
|
276
285
|
"",
|
|
286
|
+
"Glasstrace MCP is available when runtime evidence would materially reduce uncertainty. Use it when there is a failing request, stack trace, unclear runtime behavior, race/data-flow symptom, side effect, or performance issue that source inspection alone does not explain. For a current error, `get_latest_error` or `get_error_list` is usually the cheapest orientation call. For a known route/procedure with no exact error, use `find_trace_candidates` and follow returned exact `get_trace` or `get_root_cause` arguments only if the candidates look relevant. Do not call trace tools for trivial source-local fixes. Treat **no candidates** or **no_traces_found** as a scoped retrieval result, not proof the bug is absent.",
|
|
287
|
+
"",
|
|
277
288
|
"Available tools:",
|
|
278
289
|
"- `get_latest_error` - Get the most recent error trace from the current session",
|
|
279
290
|
"- `find_trace_candidates` - First-contact route/procedure/URL candidate selection when you have a route fragment, tRPC procedure, method, status, or rough recent activity window but not the exact trace ID. Returns candidate traces plus suggested `get_trace` / `get_root_cause` follow-up call arguments. Candidate discovery, not root-cause proof.",
|
|
@@ -283,24 +294,24 @@ function generateInfoSection(agent, endpoint) {
|
|
|
283
294
|
"- `get_test_suggestions` - Get test suggestions based on recent errors",
|
|
284
295
|
"- `get_session_timeline` - Get the timeline of all traces in the current session",
|
|
285
296
|
"",
|
|
286
|
-
"To reconfigure, run: `npx glasstrace mcp add
|
|
297
|
+
"To refresh this managed section after a `@glasstrace/sdk` upgrade, run: `npx glasstrace upgrade-instructions`. To reconfigure MCP credentials, run: `npx glasstrace mcp add`.",
|
|
287
298
|
""
|
|
288
299
|
].join("\n");
|
|
289
300
|
switch (agent.name) {
|
|
290
301
|
case "claude": {
|
|
291
|
-
const m = htmlMarkers();
|
|
302
|
+
const m = htmlMarkers(sdkVersion);
|
|
292
303
|
return `${m.start}
|
|
293
304
|
${content}${m.end}
|
|
294
305
|
`;
|
|
295
306
|
}
|
|
296
307
|
case "codex": {
|
|
297
|
-
const m = htmlMarkers();
|
|
308
|
+
const m = htmlMarkers(sdkVersion);
|
|
298
309
|
return `${m.start}
|
|
299
310
|
${content}${m.end}
|
|
300
311
|
`;
|
|
301
312
|
}
|
|
302
313
|
case "cursor": {
|
|
303
|
-
const m = hashMarkers();
|
|
314
|
+
const m = hashMarkers(sdkVersion);
|
|
304
315
|
return `${m.start}
|
|
305
316
|
${content}${m.end}
|
|
306
317
|
`;
|
|
@@ -316,189 +327,9 @@ ${content}${m.end}
|
|
|
316
327
|
}
|
|
317
328
|
}
|
|
318
329
|
|
|
319
|
-
// src/agent-detection/inject.ts
|
|
320
|
-
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
321
|
-
import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
|
|
322
|
-
var HTML_START = "<!-- glasstrace:mcp:start -->";
|
|
323
|
-
var HTML_END = "<!-- glasstrace:mcp:end -->";
|
|
324
|
-
var HASH_START = "# glasstrace:mcp:start";
|
|
325
|
-
var HASH_END = "# glasstrace:mcp:end";
|
|
326
|
-
function isPermissionError(err) {
|
|
327
|
-
const code = err.code;
|
|
328
|
-
return code === "EACCES" || code === "EPERM" || code === "EROFS";
|
|
329
|
-
}
|
|
330
|
-
async function writeMcpConfig(agent, content, projectRoot) {
|
|
331
|
-
if (agent.mcpConfigPath === null) {
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
const configPath = agent.mcpConfigPath;
|
|
335
|
-
const parentDir = dirname2(configPath);
|
|
336
|
-
try {
|
|
337
|
-
await mkdir(parentDir, { recursive: true });
|
|
338
|
-
} catch (err) {
|
|
339
|
-
if (isPermissionError(err)) {
|
|
340
|
-
process.stderr.write(
|
|
341
|
-
`Warning: cannot create directory ${parentDir}: permission denied
|
|
342
|
-
`
|
|
343
|
-
);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
throw err;
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
await writeFile(configPath, content, { mode: 384 });
|
|
350
|
-
} catch (err) {
|
|
351
|
-
if (isPermissionError(err)) {
|
|
352
|
-
process.stderr.write(
|
|
353
|
-
`Warning: cannot write config file ${configPath}: permission denied
|
|
354
|
-
`
|
|
355
|
-
);
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
throw err;
|
|
359
|
-
}
|
|
360
|
-
try {
|
|
361
|
-
await chmod(configPath, 384);
|
|
362
|
-
} catch {
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
function findMarkerBoundaries(lines) {
|
|
366
|
-
let startIdx = -1;
|
|
367
|
-
let endIdx = -1;
|
|
368
|
-
for (let i = 0; i < lines.length; i++) {
|
|
369
|
-
const trimmed = lines[i].trim();
|
|
370
|
-
if (trimmed === HTML_START || trimmed === HASH_START) {
|
|
371
|
-
startIdx = i;
|
|
372
|
-
} else if (trimmed === HTML_END || trimmed === HASH_END) {
|
|
373
|
-
if (startIdx !== -1) {
|
|
374
|
-
endIdx = i;
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (startIdx === -1 || endIdx === -1) {
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
return { startIdx, endIdx };
|
|
383
|
-
}
|
|
384
|
-
async function injectInfoSection(agent, content, projectRoot) {
|
|
385
|
-
if (agent.infoFilePath === null) {
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
if (content === "") {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
const filePath = agent.infoFilePath;
|
|
392
|
-
let existingContent = null;
|
|
393
|
-
try {
|
|
394
|
-
existingContent = await readFile(filePath, "utf-8");
|
|
395
|
-
} catch (err) {
|
|
396
|
-
const code = err.code;
|
|
397
|
-
if (code !== "ENOENT") {
|
|
398
|
-
if (isPermissionError(err)) {
|
|
399
|
-
process.stderr.write(
|
|
400
|
-
`Warning: cannot read info file ${filePath}: permission denied
|
|
401
|
-
`
|
|
402
|
-
);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
throw err;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (existingContent === null) {
|
|
409
|
-
try {
|
|
410
|
-
await mkdir(dirname2(filePath), { recursive: true });
|
|
411
|
-
await writeFile(filePath, content, "utf-8");
|
|
412
|
-
} catch (err) {
|
|
413
|
-
if (isPermissionError(err)) {
|
|
414
|
-
process.stderr.write(
|
|
415
|
-
`Warning: cannot write info file ${filePath}: permission denied
|
|
416
|
-
`
|
|
417
|
-
);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
throw err;
|
|
421
|
-
}
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const lines = existingContent.split("\n");
|
|
425
|
-
const boundaries = findMarkerBoundaries(lines);
|
|
426
|
-
let newContent;
|
|
427
|
-
if (boundaries !== null) {
|
|
428
|
-
const before = lines.slice(0, boundaries.startIdx);
|
|
429
|
-
const after = lines.slice(boundaries.endIdx + 1);
|
|
430
|
-
const contentWithoutTrailingNewline = content.endsWith("\n") ? content.slice(0, -1) : content;
|
|
431
|
-
newContent = [...before, contentWithoutTrailingNewline, ...after].join("\n");
|
|
432
|
-
} else {
|
|
433
|
-
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
434
|
-
newContent = existingContent + separator + content;
|
|
435
|
-
}
|
|
436
|
-
try {
|
|
437
|
-
await writeFile(filePath, newContent, "utf-8");
|
|
438
|
-
} catch (err) {
|
|
439
|
-
if (isPermissionError(err)) {
|
|
440
|
-
process.stderr.write(
|
|
441
|
-
`Warning: cannot write info file ${filePath}: permission denied
|
|
442
|
-
`
|
|
443
|
-
);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
throw err;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
async function updateGitignore(paths, projectRoot) {
|
|
450
|
-
const gitignorePath = join2(projectRoot, ".gitignore");
|
|
451
|
-
const relativePaths = paths.filter((p) => !isAbsolute(p));
|
|
452
|
-
if (relativePaths.length === 0) {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
let existingContent = "";
|
|
456
|
-
try {
|
|
457
|
-
existingContent = await readFile(gitignorePath, "utf-8");
|
|
458
|
-
} catch (err) {
|
|
459
|
-
const code = err.code;
|
|
460
|
-
if (code !== "ENOENT") {
|
|
461
|
-
if (isPermissionError(err)) {
|
|
462
|
-
process.stderr.write(
|
|
463
|
-
`Warning: cannot read .gitignore: permission denied
|
|
464
|
-
`
|
|
465
|
-
);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
throw err;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const existingLines = existingContent.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
472
|
-
const existingSet = new Set(existingLines);
|
|
473
|
-
const toAdd = relativePaths.map((p) => p.trim().replace(/\\/g, "/")).filter((p) => p !== "" && !existingSet.has(p));
|
|
474
|
-
if (toAdd.length === 0) {
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
let updatedContent = existingContent;
|
|
478
|
-
if (updatedContent.length > 0 && !updatedContent.endsWith("\n")) {
|
|
479
|
-
updatedContent += "\n";
|
|
480
|
-
}
|
|
481
|
-
updatedContent += toAdd.join("\n") + "\n";
|
|
482
|
-
try {
|
|
483
|
-
await writeFile(gitignorePath, updatedContent, "utf-8");
|
|
484
|
-
} catch (err) {
|
|
485
|
-
if (isPermissionError(err)) {
|
|
486
|
-
process.stderr.write(
|
|
487
|
-
`Warning: cannot write .gitignore: permission denied
|
|
488
|
-
`
|
|
489
|
-
);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
throw err;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
330
|
export {
|
|
497
331
|
detectAgents,
|
|
498
332
|
generateMcpConfig,
|
|
499
|
-
generateInfoSection
|
|
500
|
-
writeMcpConfig,
|
|
501
|
-
injectInfoSection,
|
|
502
|
-
updateGitignore
|
|
333
|
+
generateInfoSection
|
|
503
334
|
};
|
|
504
|
-
//# sourceMappingURL=chunk-
|
|
335
|
+
//# sourceMappingURL=chunk-D3QXU2VM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agent-detection/detect.ts","../src/agent-detection/configs.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { access, stat } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { constants } from \"node:fs\";\n\n/**\n * Describes an AI coding agent detected in a project.\n */\nexport interface DetectedAgent {\n name: \"claude\" | \"codex\" | \"gemini\" | \"cursor\" | \"windsurf\" | \"generic\";\n mcpConfigPath: string | null;\n infoFilePath: string | null;\n cliAvailable: boolean;\n registrationCommand: string | null;\n}\n\ntype AgentName = DetectedAgent[\"name\"];\n\ninterface AgentRule {\n name: AgentName;\n /** Paths relative to a search directory that indicate this agent is present. */\n markers: string[];\n /** Function to compute the MCP config path given the directory where markers were found. */\n mcpConfigPath: (markerDir: string) => string;\n /** Function to compute the info file path, or null. */\n infoFilePath: (markerDir: string) => string | null;\n /** CLI binary name to check in PATH, or null if no CLI exists. */\n cliBinary: string | null;\n /** Registration command template, or null. */\n registrationCommand: string | null;\n}\n\nconst AGENT_RULES: AgentRule[] = [\n {\n name: \"claude\",\n markers: [\".claude\", \"CLAUDE.md\"],\n mcpConfigPath: (dir) => join(dir, \".mcp.json\"),\n infoFilePath: (dir) => join(dir, \"CLAUDE.md\"),\n cliBinary: \"claude\",\n registrationCommand: \"npx glasstrace mcp add --agent claude\",\n },\n {\n name: \"codex\",\n markers: [\"codex.md\", \".codex\"],\n mcpConfigPath: (dir) => join(dir, \".codex\", \"config.toml\"),\n infoFilePath: (dir) => join(dir, \"codex.md\"),\n cliBinary: \"codex\",\n registrationCommand: \"npx glasstrace mcp add --agent codex\",\n },\n {\n name: \"gemini\",\n markers: [\".gemini\"],\n mcpConfigPath: (dir) => join(dir, \".gemini\", \"settings.json\"),\n infoFilePath: () => null,\n cliBinary: \"gemini\",\n registrationCommand: \"npx glasstrace mcp add --agent gemini\",\n },\n {\n name: \"cursor\",\n markers: [\".cursor\", \".cursorrules\"],\n mcpConfigPath: (dir) => join(dir, \".cursor\", \"mcp.json\"),\n infoFilePath: (dir) => join(dir, \".cursorrules\"),\n cliBinary: null,\n registrationCommand: \"npx glasstrace mcp add --agent cursor\",\n },\n {\n name: \"windsurf\",\n markers: [\".windsurfrules\", \".windsurf\"],\n mcpConfigPath: () =>\n join(homedir(), \".codeium\", \"windsurf\", \"mcp_config.json\"),\n infoFilePath: (dir) => join(dir, \".windsurfrules\"),\n cliBinary: null,\n registrationCommand: \"npx glasstrace mcp add --agent windsurf\",\n },\n];\n\n/**\n * Checks whether a path exists and is accessible, following symlinks.\n * Returns false on permission errors or missing paths.\n *\n * @param mode - The access mode to check (defaults to R_OK for marker detection).\n */\nasync function pathExists(\n path: string,\n mode: number = constants.R_OK,\n): Promise<boolean> {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Finds the git root directory by walking up from the given path.\n * Returns the starting directory if no `.git` is found.\n */\nasync function findGitRoot(startDir: string): Promise<string> {\n let current = resolve(startDir);\n\n while (true) {\n if (await pathExists(join(current, \".git\"), constants.F_OK)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root without finding .git\n break;\n }\n current = parent;\n }\n\n return resolve(startDir);\n}\n\n/**\n * Returns true if a CLI binary is available on PATH.\n * Uses `which` on Unix and `where` on Windows, via execFile (no shell injection).\n */\nfunction isCliAvailable(binary: string): Promise<boolean> {\n return new Promise((resolve) => {\n const command = process.platform === \"win32\" ? \"where\" : \"which\";\n execFile(command, [binary], (error) => {\n resolve(error === null);\n });\n });\n}\n\n/**\n * Detects AI coding agents present in a project by scanning for marker\n * files and directories. Walks up from projectRoot to the git root to\n * support monorepo layouts.\n *\n * Always includes a \"generic\" fallback entry.\n *\n * @param projectRoot - Absolute or relative path to the project directory.\n * @returns Array of detected agents, with generic always last.\n * @throws If projectRoot does not exist or is not a directory.\n */\nexport async function detectAgents(\n projectRoot: string,\n): Promise<DetectedAgent[]> {\n const resolvedRoot = resolve(projectRoot);\n\n // Validate projectRoot exists and is a directory\n let rootStat;\n try {\n rootStat = await stat(resolvedRoot);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n throw new Error(\n `projectRoot does not exist: ${resolvedRoot}` +\n (code ? ` (${code})` : \"\"),\n );\n }\n\n if (!rootStat.isDirectory()) {\n throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);\n }\n\n const gitRoot = await findGitRoot(resolvedRoot);\n\n // Collect unique directories to search: projectRoot and every ancestor up to gitRoot\n const searchDirs: string[] = [];\n let current = resolvedRoot;\n while (true) {\n searchDirs.push(current);\n if (current === gitRoot) {\n break;\n }\n const parent = dirname(current);\n if (parent === current) {\n break;\n }\n current = parent;\n }\n\n const detected: DetectedAgent[] = [];\n const seenAgents = new Set<AgentName>();\n\n for (const rule of AGENT_RULES) {\n let foundDir: string | null = null;\n\n // Check each search directory for markers\n for (const dir of searchDirs) {\n let markerFound = false;\n for (const marker of rule.markers) {\n if (await pathExists(join(dir, marker))) {\n markerFound = true;\n break;\n }\n }\n if (markerFound) {\n foundDir = dir;\n break;\n }\n }\n\n if (foundDir === null) {\n continue;\n }\n\n if (seenAgents.has(rule.name)) {\n continue;\n }\n seenAgents.add(rule.name);\n\n // Determine info file path — only include if the file actually exists\n let infoFilePath = rule.infoFilePath(foundDir);\n if (infoFilePath !== null && !(await pathExists(infoFilePath))) {\n infoFilePath = null;\n }\n\n const cliAvailable = rule.cliBinary\n ? await isCliAvailable(rule.cliBinary)\n : false;\n\n detected.push({\n name: rule.name,\n mcpConfigPath: rule.mcpConfigPath(foundDir),\n infoFilePath,\n cliAvailable,\n registrationCommand: rule.registrationCommand,\n });\n }\n\n // Always include generic fallback\n detected.push({\n name: \"generic\",\n mcpConfigPath: join(resolvedRoot, \".glasstrace\", \"mcp.json\"),\n infoFilePath: null,\n cliAvailable: false,\n registrationCommand: null,\n });\n\n return detected;\n}\n","import type { DetectedAgent } from \"./detect.js\";\n\n/**\n * Generates the MCP server configuration content for a given agent.\n *\n * The output is the full file content suitable for writing to the agent's\n * MCP config file. The bearer token is intentionally embedded here for\n * agents whose schemas inline the Authorization header — Codex is the\n * exception and uses `bearer_token_env_var` so the actual token never\n * appears in TOML.\n *\n * @param agent - The detected agent to generate config for.\n * @param endpoint - The Glasstrace MCP endpoint URL.\n * @param bearer - The credential to embed in the Authorization header\n * (anon key or dev key, depending on the project's resolved\n * credential source). Empty values throw.\n * @returns The formatted configuration string.\n * @throws If endpoint or bearer is empty.\n */\nexport function generateMcpConfig(\n agent: DetectedAgent,\n endpoint: string,\n bearer: string,\n): string {\n if (!endpoint || endpoint.trim() === \"\") {\n throw new Error(\"endpoint must not be empty\");\n }\n if (!bearer || bearer.trim() === \"\") {\n throw new Error(\"bearer must not be empty\");\n }\n\n switch (agent.name) {\n case \"claude\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n type: \"http\",\n url: endpoint,\n headers: {\n Authorization: `Bearer ${bearer}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"codex\": {\n // Escape TOML basic string special characters in the endpoint value.\n // TOML requires backslashes, quotes, and control characters to be escaped.\n const safeEndpoint = endpoint\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\");\n return [\n \"[mcp_servers.glasstrace]\",\n `url = \"${safeEndpoint}\"`,\n `bearer_token_env_var = \"GLASSTRACE_API_KEY\"`,\n \"\",\n ].join(\"\\n\");\n }\n\n case \"gemini\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n httpUrl: endpoint,\n headers: {\n Authorization: `Bearer ${bearer}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"cursor\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n url: endpoint,\n headers: {\n Authorization: `Bearer ${bearer}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"windsurf\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n serverUrl: endpoint,\n headers: {\n Authorization: `Bearer ${bearer}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"generic\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n type: \"http\",\n url: endpoint,\n headers: {\n Authorization: `Bearer ${bearer}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n default: {\n const _exhaustive: never = agent.name;\n throw new Error(`Unknown agent: ${_exhaustive}`);\n }\n }\n}\n\n/**\n * Strict pattern accepted as the value substituted into a `v=<sdkVersion>`\n * marker stamp. Covers the SDK's own published versions\n * (e.g. `1.4.0`, `0.0.0-canary-20260508120000`, `1.4.0+build.42`) without\n * admitting whitespace, angle-brackets, or terminal control sequences\n * that could be smuggled into the agent instruction file via a\n * malformed callsite.\n *\n * The stamp is the SDK semver string and nothing else (DISC-1592 / SDK-050\n * Required Semantics Item 1: \"the stamp encodes only the SDK semver\n * string ... it must not embed user-controlled or environment-derived\n * content\"). Reject anything outside this charset at the render site\n * rather than relying on the upstream `__SDK_VERSION__` define being\n * well-formed.\n */\nconst SDK_VERSION_STAMP_PATTERN = /^[A-Za-z0-9.+-]+$/;\n\n/**\n * Marker pair used to delimit the Glasstrace section in agent info files.\n */\ninterface MarkerPair {\n start: string;\n end: string;\n}\n\nfunction htmlMarkers(sdkVersion: string): MarkerPair {\n return {\n start: `<!-- glasstrace:mcp:start v=${sdkVersion} -->`,\n end: \"<!-- glasstrace:mcp:end -->\",\n };\n}\n\nfunction hashMarkers(sdkVersion: string): MarkerPair {\n return {\n start: `# glasstrace:mcp:start v=${sdkVersion}`,\n end: \"# glasstrace:mcp:end\",\n };\n}\n\n/**\n * Generates informational content for an agent's instruction file.\n *\n * This content is designed to be appended to or inserted into agent-specific\n * instruction files (CLAUDE.md, .cursorrules, codex.md). It contains ONLY\n * the endpoint URL, tool descriptions, and setup instructions. Auth tokens\n * are NEVER included in this output.\n *\n * The rendered block opens with a cost-aware cross-tool decision paragraph\n * (DISC-1593 / SDK-050) telling the user's AI agent **when** Glasstrace\n * MCP is worth calling at all and **which** tool is the cheapest first\n * call for each symptom class. The start marker carries a `v=<sdkVersion>`\n * stamp (DISC-1592 / SDK-050) so a later `glasstrace upgrade-instructions`\n * run — and the SDK's stale-section warning at init — can detect that\n * the file was rendered by an older SDK and refresh the block.\n *\n * @param agent - The detected agent to generate info for.\n * @param endpoint - The Glasstrace MCP endpoint URL.\n * @param sdkVersion - The SDK semver string to embed in the start marker\n * (e.g. `1.4.0`, `0.0.0-canary-20260508120000`). Must match\n * `[A-Za-z0-9.+\\-]+`; arbitrary or empty values throw.\n * @returns The formatted info section string, or empty string for agents without a supported info file format.\n * @throws If endpoint is empty, or if sdkVersion is empty or contains\n * characters outside the accepted stamp charset.\n */\nexport function generateInfoSection(\n agent: DetectedAgent,\n endpoint: string,\n sdkVersion: string,\n): string {\n if (!endpoint || endpoint.trim() === \"\") {\n throw new Error(\"endpoint must not be empty\");\n }\n if (!sdkVersion || sdkVersion.trim() === \"\") {\n throw new Error(\"sdkVersion must not be empty\");\n }\n if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {\n throw new Error(\n \"sdkVersion must match [A-Za-z0-9.+\\\\-]+ (semver-shaped, no whitespace, no angle brackets)\",\n );\n }\n\n // Cost-aware cross-tool decision paragraph (DISC-1593 / SDK-050\n // Required Semantics §1). Load-bearing semantics:\n // 1. Frame Glasstrace MCP as conditionally worth calling.\n // 2. Name cheapest-orientation routing per symptom class.\n // 3. Restate the no-candidates / no_traces_found \"scoped retrieval\n // result, not absence of the bug\" contract.\n // 4. List the conditions that justify calling Glasstrace MCP at all.\n // Wording aligned with MCP-025's planned `recoveryActions` so the two\n // surfaces do not contradict each other.\n const content = [\n \"\",\n \"## Glasstrace MCP Integration\",\n \"\",\n `Glasstrace is configured as an MCP server at: ${endpoint}`,\n \"\",\n \"Glasstrace MCP is available when runtime evidence would materially reduce uncertainty. Use it when there is a failing request, stack trace, unclear runtime behavior, race/data-flow symptom, side effect, or performance issue that source inspection alone does not explain. For a current error, `get_latest_error` or `get_error_list` is usually the cheapest orientation call. For a known route/procedure with no exact error, use `find_trace_candidates` and follow returned exact `get_trace` or `get_root_cause` arguments only if the candidates look relevant. Do not call trace tools for trivial source-local fixes. Treat **no candidates** or **no_traces_found** as a scoped retrieval result, not proof the bug is absent.\",\n \"\",\n \"Available tools:\",\n \"- `get_latest_error` - Get the most recent error trace from the current session\",\n \"- `find_trace_candidates` - First-contact route/procedure/URL candidate selection when you have a route fragment, tRPC procedure, method, status, or rough recent activity window but not the exact trace ID. Returns candidate traces plus suggested `get_trace` / `get_root_cause` follow-up call arguments. Candidate discovery, not root-cause proof.\",\n \"- `get_error_list` - List recent errors with filtering and pagination\",\n \"- `get_trace` - Get a specific trace by ID or URL pattern\",\n \"- `get_root_cause` - Get the root cause analysis for a specific error trace (requires a `traceId` from `get_latest_error`, `get_error_list`, or `get_trace`)\",\n \"- `get_test_suggestions` - Get test suggestions based on recent errors\",\n \"- `get_session_timeline` - Get the timeline of all traces in the current session\",\n \"\",\n \"To refresh this managed section after a `@glasstrace/sdk` upgrade, run: `npx glasstrace upgrade-instructions`. To reconfigure MCP credentials, run: `npx glasstrace mcp add`.\",\n \"\",\n ].join(\"\\n\");\n\n switch (agent.name) {\n case \"claude\": {\n const m = htmlMarkers(sdkVersion);\n return `${m.start}\\n${content}${m.end}\\n`;\n }\n\n case \"codex\": {\n const m = htmlMarkers(sdkVersion);\n return `${m.start}\\n${content}${m.end}\\n`;\n }\n\n case \"cursor\": {\n const m = hashMarkers(sdkVersion);\n return `${m.start}\\n${content}${m.end}\\n`;\n }\n\n case \"gemini\":\n case \"windsurf\":\n case \"generic\":\n return \"\";\n\n default: {\n const _exhaustive: never = agent.name;\n throw new Error(`Unknown agent: ${_exhaustive}`);\n }\n }\n}\n"],"mappings":"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","names":["resolve"]}
|
|
@@ -49,6 +49,10 @@ import {
|
|
|
49
49
|
GLASSTRACE_ATTRIBUTE_NAMES,
|
|
50
50
|
deriveSessionId
|
|
51
51
|
} from "./chunk-4WI7B5FQ.js";
|
|
52
|
+
import {
|
|
53
|
+
isEndMarkerLine,
|
|
54
|
+
parseStartMarkerLine
|
|
55
|
+
} from "./chunk-ZBQQXVHD.js";
|
|
52
56
|
import {
|
|
53
57
|
__require
|
|
54
58
|
} from "./chunk-NSBPE2FW.js";
|
|
@@ -3553,7 +3557,7 @@ function appendRootPathToUrlIfNeeded(url) {
|
|
|
3553
3557
|
return void 0;
|
|
3554
3558
|
}
|
|
3555
3559
|
}
|
|
3556
|
-
function appendResourcePathToUrl(url,
|
|
3560
|
+
function appendResourcePathToUrl(url, path3) {
|
|
3557
3561
|
try {
|
|
3558
3562
|
new URL(url);
|
|
3559
3563
|
} catch {
|
|
@@ -3563,11 +3567,11 @@ function appendResourcePathToUrl(url, path2) {
|
|
|
3563
3567
|
if (!url.endsWith("/")) {
|
|
3564
3568
|
url = url + "/";
|
|
3565
3569
|
}
|
|
3566
|
-
url +=
|
|
3570
|
+
url += path3;
|
|
3567
3571
|
try {
|
|
3568
3572
|
new URL(url);
|
|
3569
3573
|
} catch {
|
|
3570
|
-
diag.warn(`Configuration: Provided URL appended with '${
|
|
3574
|
+
diag.warn(`Configuration: Provided URL appended with '${path3}' is not a valid URL, using 'undefined' instead of '${url}'`);
|
|
3571
3575
|
return void 0;
|
|
3572
3576
|
}
|
|
3573
3577
|
return url;
|
|
@@ -4267,6 +4271,144 @@ function writeStateNow() {
|
|
|
4267
4271
|
}
|
|
4268
4272
|
}
|
|
4269
4273
|
|
|
4274
|
+
// src/agent-detection/upgrade-notice.ts
|
|
4275
|
+
import * as fs2 from "node:fs";
|
|
4276
|
+
import * as path2 from "node:path";
|
|
4277
|
+
var warningEmitted = false;
|
|
4278
|
+
var AGENT_INSTRUCTION_FILES = [
|
|
4279
|
+
"CLAUDE.md",
|
|
4280
|
+
"codex.md",
|
|
4281
|
+
".cursorrules"
|
|
4282
|
+
];
|
|
4283
|
+
function parseSemver(input) {
|
|
4284
|
+
const plusIdx = input.indexOf("+");
|
|
4285
|
+
const core = plusIdx === -1 ? input : input.slice(0, plusIdx);
|
|
4286
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(core);
|
|
4287
|
+
if (m === null) return null;
|
|
4288
|
+
return {
|
|
4289
|
+
major: Number(m[1]),
|
|
4290
|
+
minor: Number(m[2]),
|
|
4291
|
+
patch: Number(m[3]),
|
|
4292
|
+
prerelease: m[4] ?? null
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
function comparePrerelease(a, b) {
|
|
4296
|
+
const ap = a.split(".");
|
|
4297
|
+
const bp = b.split(".");
|
|
4298
|
+
const len = Math.min(ap.length, bp.length);
|
|
4299
|
+
for (let i = 0; i < len; i++) {
|
|
4300
|
+
const x = ap[i];
|
|
4301
|
+
const y = bp[i];
|
|
4302
|
+
const xNumeric = /^\d+$/.test(x);
|
|
4303
|
+
const yNumeric = /^\d+$/.test(y);
|
|
4304
|
+
if (xNumeric && yNumeric) {
|
|
4305
|
+
const xv = Number(x);
|
|
4306
|
+
const yv = Number(y);
|
|
4307
|
+
if (xv !== yv) return xv < yv ? -1 : 1;
|
|
4308
|
+
} else if (xNumeric) {
|
|
4309
|
+
return -1;
|
|
4310
|
+
} else if (yNumeric) {
|
|
4311
|
+
return 1;
|
|
4312
|
+
} else if (x !== y) {
|
|
4313
|
+
return x < y ? -1 : 1;
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
return ap.length - bp.length;
|
|
4317
|
+
}
|
|
4318
|
+
function compareSemver(a, b) {
|
|
4319
|
+
const pa = parseSemver(a);
|
|
4320
|
+
const pb = parseSemver(b);
|
|
4321
|
+
if (pa === null || pb === null) return null;
|
|
4322
|
+
if (pa.major !== pb.major) return pa.major - pb.major;
|
|
4323
|
+
if (pa.minor !== pb.minor) return pa.minor - pb.minor;
|
|
4324
|
+
if (pa.patch !== pb.patch) return pa.patch - pb.patch;
|
|
4325
|
+
if (pa.prerelease === null && pb.prerelease === null) return 0;
|
|
4326
|
+
if (pa.prerelease === null) return 1;
|
|
4327
|
+
if (pb.prerelease === null) return -1;
|
|
4328
|
+
return comparePrerelease(pa.prerelease, pb.prerelease);
|
|
4329
|
+
}
|
|
4330
|
+
function isOptedOut() {
|
|
4331
|
+
const raw = process.env.GLASSTRACE_DISABLE_UPGRADE_NOTICE;
|
|
4332
|
+
if (typeof raw !== "string") return false;
|
|
4333
|
+
const trimmed = raw.trim().toLowerCase();
|
|
4334
|
+
return trimmed === "1" || trimmed === "true" || trimmed === "yes";
|
|
4335
|
+
}
|
|
4336
|
+
function isQuietCiContext() {
|
|
4337
|
+
const stderrIsTty = process.stderr.isTTY === true;
|
|
4338
|
+
if (stderrIsTty) return false;
|
|
4339
|
+
return process.env.CI === "true";
|
|
4340
|
+
}
|
|
4341
|
+
var MAX_AGENT_FILE_BYTES = 5 * 1024 * 1024;
|
|
4342
|
+
function inspectFile(filePath, runningSdkVersion) {
|
|
4343
|
+
let content;
|
|
4344
|
+
try {
|
|
4345
|
+
const stat = fs2.statSync(filePath);
|
|
4346
|
+
if (!stat.isFile()) return "absent";
|
|
4347
|
+
if (stat.size > MAX_AGENT_FILE_BYTES) return "absent";
|
|
4348
|
+
content = fs2.readFileSync(filePath, "utf-8");
|
|
4349
|
+
} catch {
|
|
4350
|
+
return "absent";
|
|
4351
|
+
}
|
|
4352
|
+
const lines = content.split("\n");
|
|
4353
|
+
let lastStart = null;
|
|
4354
|
+
let foundEnd = false;
|
|
4355
|
+
for (const line of lines) {
|
|
4356
|
+
const parsed = parseStartMarkerLine(line);
|
|
4357
|
+
if (parsed !== null) {
|
|
4358
|
+
lastStart = parsed;
|
|
4359
|
+
continue;
|
|
4360
|
+
}
|
|
4361
|
+
if (lastStart !== null && isEndMarkerLine(line)) {
|
|
4362
|
+
foundEnd = true;
|
|
4363
|
+
break;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
if (lastStart === null || !foundEnd) {
|
|
4367
|
+
return "no-section";
|
|
4368
|
+
}
|
|
4369
|
+
if (lastStart.stamp === null) {
|
|
4370
|
+
return "no-stamp";
|
|
4371
|
+
}
|
|
4372
|
+
const cmp = compareSemver(lastStart.stamp, runningSdkVersion);
|
|
4373
|
+
if (cmp === null) {
|
|
4374
|
+
return "unknown-stamp";
|
|
4375
|
+
}
|
|
4376
|
+
return cmp < 0 ? "stale" : "current";
|
|
4377
|
+
}
|
|
4378
|
+
function maybeWarnStaleAgentInstructions(options) {
|
|
4379
|
+
try {
|
|
4380
|
+
if (warningEmitted) return;
|
|
4381
|
+
if (typeof process === "undefined" || typeof process.versions?.node !== "string" || typeof process.env !== "object" || process.env === null) {
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
if (isOptedOut()) return;
|
|
4385
|
+
if (isQuietCiContext()) return;
|
|
4386
|
+
if (parseSemver(options.sdkVersion) === null) return;
|
|
4387
|
+
const staleFiles = [];
|
|
4388
|
+
for (const fileName of AGENT_INSTRUCTION_FILES) {
|
|
4389
|
+
const fullPath = path2.join(options.projectRoot, fileName);
|
|
4390
|
+
const state = inspectFile(fullPath, options.sdkVersion);
|
|
4391
|
+
if (state === "stale") {
|
|
4392
|
+
staleFiles.push(fileName);
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
if (staleFiles.length === 0) return;
|
|
4396
|
+
const fileList = staleFiles.join(", ");
|
|
4397
|
+
const message = `[glasstrace] Glasstrace managed MCP section in ${fileList} was rendered by an older @glasstrace/sdk; run \`npx glasstrace upgrade-instructions\` to refresh (silence with GLASSTRACE_DISABLE_UPGRADE_NOTICE=1).
|
|
4398
|
+
`;
|
|
4399
|
+
warningEmitted = true;
|
|
4400
|
+
if (options.stderrWrite !== void 0) {
|
|
4401
|
+
options.stderrWrite(message);
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4404
|
+
try {
|
|
4405
|
+
process.stderr.write(message);
|
|
4406
|
+
} catch {
|
|
4407
|
+
}
|
|
4408
|
+
} catch {
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4270
4412
|
// src/register.ts
|
|
4271
4413
|
function maskKey(key) {
|
|
4272
4414
|
if (key.length <= 12) return key.slice(0, 4) + "...";
|
|
@@ -4295,9 +4437,13 @@ function registerGlasstrace(options) {
|
|
|
4295
4437
|
return;
|
|
4296
4438
|
}
|
|
4297
4439
|
setCoreState(CoreState.REGISTERING);
|
|
4440
|
+
maybeWarnStaleAgentInstructions({
|
|
4441
|
+
projectRoot: process.cwd(),
|
|
4442
|
+
sdkVersion: "1.5.0"
|
|
4443
|
+
});
|
|
4298
4444
|
startRuntimeStateWriter({
|
|
4299
4445
|
projectRoot: process.cwd(),
|
|
4300
|
-
sdkVersion: "1.
|
|
4446
|
+
sdkVersion: "1.5.0"
|
|
4301
4447
|
});
|
|
4302
4448
|
const config = resolveConfig(options);
|
|
4303
4449
|
if (config.verbose) {
|
|
@@ -4464,8 +4610,8 @@ async function backgroundInit(config, anonKeyForInit, generation) {
|
|
|
4464
4610
|
if (config.verbose) {
|
|
4465
4611
|
console.info("[glasstrace] Background init firing.");
|
|
4466
4612
|
}
|
|
4467
|
-
const healthReport = collectHealthReport("1.
|
|
4468
|
-
const initResult = await performInit(config, anonKeyForInit, "1.
|
|
4613
|
+
const healthReport = collectHealthReport("1.5.0");
|
|
4614
|
+
const initResult = await performInit(config, anonKeyForInit, "1.5.0", healthReport);
|
|
4469
4615
|
if (generation !== registrationGeneration) return;
|
|
4470
4616
|
const currentState = getCoreState();
|
|
4471
4617
|
if (currentState === CoreState.SHUTTING_DOWN || currentState === CoreState.SHUTDOWN) {
|
|
@@ -4488,7 +4634,7 @@ async function backgroundInit(config, anonKeyForInit, generation) {
|
|
|
4488
4634
|
}
|
|
4489
4635
|
maybeInstallConsoleCapture();
|
|
4490
4636
|
if (didLastInitSucceed()) {
|
|
4491
|
-
startHeartbeat(config, anonKeyForInit, "1.
|
|
4637
|
+
startHeartbeat(config, anonKeyForInit, "1.5.0", generation, (newApiKey, accountId) => {
|
|
4492
4638
|
setAuthState(AuthState.CLAIMING);
|
|
4493
4639
|
emitLifecycleEvent("auth:claim_started", { accountId });
|
|
4494
4640
|
setResolvedApiKey(newApiKey);
|
|
@@ -4888,4 +5034,4 @@ export {
|
|
|
4888
5034
|
withGlasstraceConfig,
|
|
4889
5035
|
captureError
|
|
4890
5036
|
};
|
|
4891
|
-
//# sourceMappingURL=chunk-
|
|
5037
|
+
//# sourceMappingURL=chunk-N3XIVM2U.js.map
|