tracegist-mcp-bridge 0.2.11 → 0.2.12
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/bin/lib.mjs +46 -9
- package/bin/tracegist-mcp-bridge.mjs +110 -51
- package/package.json +1 -1
package/bin/lib.mjs
CHANGED
|
@@ -11,14 +11,17 @@ export function normalizeZipBaseName(entryName) {
|
|
|
11
11
|
return path.posix.basename(entryName).toLowerCase();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function renderPackagesText(packages, directory) {
|
|
14
|
+
export function renderPackagesText(packages, directory, totalCount = null, hasMore = false, offset = 0) {
|
|
15
15
|
if (packages.length === 0) {
|
|
16
16
|
return [
|
|
17
17
|
`No TraceGist package zips found in ${directory}.`,
|
|
18
18
|
"Expected naming pattern: tracegist-...-package.zip",
|
|
19
19
|
].join("\n");
|
|
20
20
|
}
|
|
21
|
-
const
|
|
21
|
+
const countLabel = totalCount != null
|
|
22
|
+
? `Showing ${packages.length} of ${totalCount} TraceGist package(s) in ${directory}:`
|
|
23
|
+
: `Found ${packages.length} TraceGist package(s) in ${directory}:`;
|
|
24
|
+
const lines = [countLabel, ""];
|
|
22
25
|
for (const pkg of packages) {
|
|
23
26
|
lines.push(
|
|
24
27
|
`- ${pkg.name}`,
|
|
@@ -28,6 +31,16 @@ export function renderPackagesText(packages, directory) {
|
|
|
28
31
|
"",
|
|
29
32
|
);
|
|
30
33
|
}
|
|
34
|
+
if (hasMore) {
|
|
35
|
+
lines.push(
|
|
36
|
+
`More packages available. Use offset: ${offset + packages.length} to see the next page.`,
|
|
37
|
+
"",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
lines.push(
|
|
41
|
+
"Each package includes a ready-to-run Playwright repro script.",
|
|
42
|
+
"Call get_tracegist_handoff_markdown (no section param) on a package to see the TOC with repro script paths.",
|
|
43
|
+
);
|
|
31
44
|
return lines.join("\n");
|
|
32
45
|
}
|
|
33
46
|
|
|
@@ -74,7 +87,8 @@ const SECTION_HINTS = {
|
|
|
74
87
|
"user interaction timeline": "Full click/fill/navigation sequence — key for reproduction",
|
|
75
88
|
"session context": "Page URL, title, session metadata",
|
|
76
89
|
"session environment": "Browser, viewport, OS — match for reproduction",
|
|
77
|
-
"package files":
|
|
90
|
+
"package files":
|
|
91
|
+
"ACTION: Read the Python repro script path here — use read_tracegist_package_file before writing any automation",
|
|
78
92
|
"marker-to-file mapping": "Which screenshots/voice files belong to each marker",
|
|
79
93
|
"backend log correlation": "Absolute timestamps for server-side log alignment",
|
|
80
94
|
"full session console timeline": "All console output (deep exports only)",
|
|
@@ -90,7 +104,7 @@ const SECTION_HINTS = {
|
|
|
90
104
|
"environment at marker time": "Environment snapshot at a specific marker",
|
|
91
105
|
"marker timeline logs (context window)": "Logs within the ±5 s marker window",
|
|
92
106
|
"webapp testing reproduction":
|
|
93
|
-
"
|
|
107
|
+
"ACTION: Ready-to-run Python repro script path + key selectors — read before writing any automation",
|
|
94
108
|
"tester intent summary": "Classified intent: specifications, issues found, observations",
|
|
95
109
|
"iterative context": "Previous session reference, verification checklist for build-test cycle",
|
|
96
110
|
"tier 0: quick summary": "Session overview, intent, reproduction command (~200 tokens)",
|
|
@@ -103,14 +117,40 @@ function getSectionHint(sectionName) {
|
|
|
103
117
|
return SECTION_HINTS[sectionName.toLowerCase()] || "Additional section";
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
export function renderSectionToc(sections, zipPath) {
|
|
120
|
+
export function renderSectionToc(sections, zipPath, manifest = null) {
|
|
107
121
|
const totalChars = sections.reduce((sum, s) => sum + s.content.length, 0);
|
|
108
122
|
const lines = [
|
|
109
123
|
`Handoff document for ${path.basename(zipPath)} — ${sections.length} sections, ${totalChars.toLocaleString()} chars total.`,
|
|
110
124
|
"",
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// Prominent callout BEFORE the table — shown before any section is read
|
|
128
|
+
const pythonScript = manifest?.pythonPlaywrightScriptPath;
|
|
129
|
+
const tsScript = manifest?.playwrightScriptPath;
|
|
130
|
+
if (pythonScript || tsScript) {
|
|
131
|
+
lines.push(
|
|
132
|
+
"**Playwright repro script is ready — read it before writing any automation code:**",
|
|
133
|
+
);
|
|
134
|
+
if (pythonScript) {
|
|
135
|
+
lines.push(
|
|
136
|
+
`- Python (webapp-testing skill): \`read_tracegist_package_file(zipPath, "${pythonScript}")\``,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (tsScript) {
|
|
140
|
+
lines.push(
|
|
141
|
+
`- TypeScript (Playwright Test): \`read_tracegist_package_file(zipPath, "${tsScript}")\``,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
lines.push(
|
|
148
|
+
"**Reading guide:** For analysis, start with Session Triage → Marker Timeline → Notable Anomalies.",
|
|
149
|
+
"For reproduction, start with the Playwright repro script above → Session Environment → User Interaction Timeline.",
|
|
150
|
+
"",
|
|
111
151
|
"| # | Section | Size | Purpose |",
|
|
112
152
|
"|---|---------|------|---------|",
|
|
113
|
-
|
|
153
|
+
);
|
|
114
154
|
for (let i = 0; i < sections.length; i++) {
|
|
115
155
|
const hint = getSectionHint(sections[i].name);
|
|
116
156
|
lines.push(
|
|
@@ -118,9 +158,6 @@ export function renderSectionToc(sections, zipPath) {
|
|
|
118
158
|
);
|
|
119
159
|
}
|
|
120
160
|
lines.push(
|
|
121
|
-
"",
|
|
122
|
-
"**Reading guide:** For analysis, start with Session Triage → Marker Timeline → Notable Anomalies.",
|
|
123
|
-
"For reproduction, start with Session Environment → User Interaction Timeline → Package Files (Playwright script path).",
|
|
124
161
|
"",
|
|
125
162
|
'Pass `section` with a section name (e.g. "Marker Timeline") to retrieve its full content.',
|
|
126
163
|
);
|
|
@@ -85,7 +85,7 @@ async function checkWhisperDependencies() {
|
|
|
85
85
|
return warnings;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
async function listTraceGistPackages(directory, limit) {
|
|
88
|
+
async function listTraceGistPackages(directory, limit, offset = 0) {
|
|
89
89
|
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
90
90
|
const zipNames = entries
|
|
91
91
|
.filter((entry) => entry.isFile() && isTraceGistPackageFile(entry.name))
|
|
@@ -105,7 +105,9 @@ async function listTraceGistPackages(directory, limit) {
|
|
|
105
105
|
);
|
|
106
106
|
|
|
107
107
|
packages.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
|
|
108
|
-
|
|
108
|
+
const totalCount = packages.length;
|
|
109
|
+
const sliced = packages.slice(offset, offset + limit);
|
|
110
|
+
return { packages: sliced, totalCount, hasMore: offset + limit < totalCount };
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
async function readZipEntries(zipPath) {
|
|
@@ -569,14 +571,26 @@ server.registerTool(
|
|
|
569
571
|
.max(200)
|
|
570
572
|
.optional()
|
|
571
573
|
.describe("Maximum number of packages to return (default: 20)."),
|
|
574
|
+
offset: z
|
|
575
|
+
.number()
|
|
576
|
+
.int()
|
|
577
|
+
.min(0)
|
|
578
|
+
.optional()
|
|
579
|
+
.describe("Number of packages to skip for pagination (default: 0)."),
|
|
572
580
|
}),
|
|
573
581
|
},
|
|
574
|
-
async ({ directory, limit = 20 }) => {
|
|
582
|
+
async ({ directory, limit = 20, offset = 0 }) => {
|
|
575
583
|
try {
|
|
576
584
|
const searchDir = directory || DEFAULT_DOWNLOADS_DIR;
|
|
577
|
-
const packages = await listTraceGistPackages(
|
|
585
|
+
const { packages, totalCount, hasMore } = await listTraceGistPackages(
|
|
586
|
+
searchDir,
|
|
587
|
+
limit,
|
|
588
|
+
offset,
|
|
589
|
+
);
|
|
578
590
|
return {
|
|
579
|
-
content: [
|
|
591
|
+
content: [
|
|
592
|
+
{ type: "text", text: renderPackagesText(packages, searchDir, totalCount, hasMore, offset) },
|
|
593
|
+
],
|
|
580
594
|
};
|
|
581
595
|
} catch (err) {
|
|
582
596
|
return toolError(err);
|
|
@@ -715,7 +729,7 @@ server.registerTool(
|
|
|
715
729
|
content: [
|
|
716
730
|
{
|
|
717
731
|
type: "text",
|
|
718
|
-
text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath)}`,
|
|
732
|
+
text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath, manifest)}`,
|
|
719
733
|
},
|
|
720
734
|
],
|
|
721
735
|
};
|
|
@@ -1237,24 +1251,33 @@ server.resource(
|
|
|
1237
1251
|
);
|
|
1238
1252
|
|
|
1239
1253
|
// Tool: watch_live_session
|
|
1240
|
-
server.
|
|
1254
|
+
server.registerTool(
|
|
1241
1255
|
"watch_live_session",
|
|
1242
|
-
"Watch a live TraceGist shadowing session in real-time. Returns buffered events since the given sequence number.\n\n" +
|
|
1243
|
-
"IMPORTANT POLLING BEHAVIOR:\n" +
|
|
1244
|
-
"- You MUST keep calling this tool every 2-4 seconds in a loop until sessionActive becomes false.\n" +
|
|
1245
|
-
"- Do NOT stop polling just because there are no new events — the tester is still active.\n" +
|
|
1246
|
-
"- When you see 'voice-transcription' events, the tester recorded a voice marker — read the transcription and react to it.\n" +
|
|
1247
|
-
"- When you see 'question-response-received' events, use get_tester_response to retrieve the full answer with images.\n" +
|
|
1248
|
-
"- When you see 'question-response-transcribed' events, the tester's voice response has been transcribed — read it for context.\n" +
|
|
1249
|
-
"- Marker events with hasPendingVoice=true will have their voiceTranscription field populated once transcription completes.\n" +
|
|
1250
|
-
"- The tester may set voice markers at any time to communicate with you — keep watching for them.\n" +
|
|
1251
|
-
"- If you need clarification, use ask_tester_question to show a toast notification to the tester.\n" +
|
|
1252
|
-
"- Only stop polling when sessionActive is false (the recording ended).",
|
|
1253
1256
|
{
|
|
1254
|
-
|
|
1255
|
-
.number
|
|
1256
|
-
|
|
1257
|
-
|
|
1257
|
+
description:
|
|
1258
|
+
"Watch a live TraceGist shadowing session in real-time. Returns buffered events since the given sequence number.\n\n" +
|
|
1259
|
+
"IMPORTANT POLLING BEHAVIOR:\n" +
|
|
1260
|
+
"- You MUST keep calling this tool every 2-4 seconds in a loop until sessionActive becomes false.\n" +
|
|
1261
|
+
"- Do NOT stop polling just because there are no new events — the tester is still active.\n" +
|
|
1262
|
+
"- When you see 'voice-transcription' events, the tester recorded a voice marker — read the transcription and react to it.\n" +
|
|
1263
|
+
"- When you see 'question-response-received' events, use get_tester_response to retrieve the full answer with images.\n" +
|
|
1264
|
+
"- When you see 'question-response-transcribed' events, the tester's voice response has been transcribed — read it for context.\n" +
|
|
1265
|
+
"- Marker events with hasPendingVoice=true will have their voiceTranscription field populated once transcription completes.\n" +
|
|
1266
|
+
"- The tester may set voice markers at any time to communicate with you — keep watching for them.\n" +
|
|
1267
|
+
"- If you need clarification, use ask_tester_question to show a toast notification to the tester.\n" +
|
|
1268
|
+
"- Only stop polling when sessionActive is false (the recording ended).",
|
|
1269
|
+
annotations: {
|
|
1270
|
+
readOnlyHint: true,
|
|
1271
|
+
destructiveHint: false,
|
|
1272
|
+
idempotentHint: true,
|
|
1273
|
+
openWorldHint: false,
|
|
1274
|
+
},
|
|
1275
|
+
inputSchema: z.object({
|
|
1276
|
+
since_seq: z
|
|
1277
|
+
.number()
|
|
1278
|
+
.optional()
|
|
1279
|
+
.describe("Return events after this sequence number. Omit for all buffered events."),
|
|
1280
|
+
}),
|
|
1258
1281
|
},
|
|
1259
1282
|
async ({ since_seq }) => {
|
|
1260
1283
|
if (!liveSessionActive && liveEvents.length === 0) {
|
|
@@ -1363,20 +1386,29 @@ server.tool(
|
|
|
1363
1386
|
);
|
|
1364
1387
|
|
|
1365
1388
|
// Tool: ask_tester_question
|
|
1366
|
-
server.
|
|
1389
|
+
server.registerTool(
|
|
1367
1390
|
"ask_tester_question",
|
|
1368
|
-
"Ask the tester a short, concise question during a live shadowing session. " +
|
|
1369
|
-
"The question appears as a toast notification in their browser. " +
|
|
1370
|
-
"The tester responds with a voice marker (Alt+Shift+M) and optional highlight captures (Alt+Shift+S). " +
|
|
1371
|
-
"Use get_tester_response to check for their answer.",
|
|
1372
1391
|
{
|
|
1373
|
-
|
|
1374
|
-
.
|
|
1375
|
-
.
|
|
1376
|
-
.
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1392
|
+
description:
|
|
1393
|
+
"Ask the tester a short, concise question during a live shadowing session. " +
|
|
1394
|
+
"The question appears as a toast notification in their browser. " +
|
|
1395
|
+
"The tester responds with a voice marker (Alt+Shift+M) and optional highlight captures (Alt+Shift+S). " +
|
|
1396
|
+
"Use get_tester_response to check for their answer.",
|
|
1397
|
+
annotations: {
|
|
1398
|
+
readOnlyHint: false,
|
|
1399
|
+
destructiveHint: false,
|
|
1400
|
+
idempotentHint: false,
|
|
1401
|
+
openWorldHint: true,
|
|
1402
|
+
},
|
|
1403
|
+
inputSchema: z.object({
|
|
1404
|
+
question: z
|
|
1405
|
+
.string()
|
|
1406
|
+
.max(200)
|
|
1407
|
+
.describe(
|
|
1408
|
+
"Short, concise question for the tester (max 200 chars). " +
|
|
1409
|
+
"Example: 'Can you click the Save button again?' or 'Does the error appear with a different email?'",
|
|
1410
|
+
),
|
|
1411
|
+
}),
|
|
1380
1412
|
},
|
|
1381
1413
|
async ({ question }) => {
|
|
1382
1414
|
if (!liveSessionActive || !activeConnection) {
|
|
@@ -1435,16 +1467,25 @@ server.tool(
|
|
|
1435
1467
|
);
|
|
1436
1468
|
|
|
1437
1469
|
// Tool: get_tester_response
|
|
1438
|
-
server.
|
|
1470
|
+
server.registerTool(
|
|
1439
1471
|
"get_tester_response",
|
|
1440
|
-
"Check for the tester's response to a question asked during live shadowing. " +
|
|
1441
|
-
"Returns the tester's screenshot, highlight captures, and transcribed voice note.\n\n" +
|
|
1442
|
-
"IMPORTANT: The tester needs time to record their answer (10-60 seconds). " +
|
|
1443
|
-
"Instead of calling this tool repeatedly, prefer polling watch_live_session — " +
|
|
1444
|
-
"you will see a 'question-response-received' event when the response arrives. " +
|
|
1445
|
-
"Then call this tool once to get the full response with transcription and images.",
|
|
1446
1472
|
{
|
|
1447
|
-
|
|
1473
|
+
description:
|
|
1474
|
+
"Check for the tester's response to a question asked during live shadowing. " +
|
|
1475
|
+
"Returns the tester's screenshot, highlight captures, and transcribed voice note.\n\n" +
|
|
1476
|
+
"IMPORTANT: The tester needs time to record their answer (10-60 seconds). " +
|
|
1477
|
+
"Instead of calling this tool repeatedly, prefer polling watch_live_session — " +
|
|
1478
|
+
"you will see a 'question-response-received' event when the response arrives. " +
|
|
1479
|
+
"Then call this tool once to get the full response with transcription and images.",
|
|
1480
|
+
annotations: {
|
|
1481
|
+
readOnlyHint: true,
|
|
1482
|
+
destructiveHint: false,
|
|
1483
|
+
idempotentHint: true,
|
|
1484
|
+
openWorldHint: false,
|
|
1485
|
+
},
|
|
1486
|
+
inputSchema: z.object({
|
|
1487
|
+
question_id: z.string().describe("The question ID returned by ask_tester_question."),
|
|
1488
|
+
}),
|
|
1448
1489
|
},
|
|
1449
1490
|
async ({ question_id }) => {
|
|
1450
1491
|
const response = questionResponses.get(question_id);
|
|
@@ -1561,10 +1602,19 @@ server.tool(
|
|
|
1561
1602
|
);
|
|
1562
1603
|
|
|
1563
1604
|
// Tool: get_live_screenshot
|
|
1564
|
-
server.
|
|
1605
|
+
server.registerTool(
|
|
1565
1606
|
"get_live_screenshot",
|
|
1566
|
-
|
|
1567
|
-
|
|
1607
|
+
{
|
|
1608
|
+
description:
|
|
1609
|
+
"Capture a screenshot of the tester's current browser tab during a live shadowing session.",
|
|
1610
|
+
annotations: {
|
|
1611
|
+
readOnlyHint: true,
|
|
1612
|
+
destructiveHint: false,
|
|
1613
|
+
idempotentHint: true,
|
|
1614
|
+
openWorldHint: false,
|
|
1615
|
+
},
|
|
1616
|
+
inputSchema: z.object({}),
|
|
1617
|
+
},
|
|
1568
1618
|
async () => {
|
|
1569
1619
|
if (!liveSessionActive || !activeConnection) {
|
|
1570
1620
|
return toolError("No active live shadowing session or extension not connected.");
|
|
@@ -1615,13 +1665,22 @@ server.tool(
|
|
|
1615
1665
|
);
|
|
1616
1666
|
|
|
1617
1667
|
// Tool: get_live_session_summary
|
|
1618
|
-
server.
|
|
1668
|
+
server.registerTool(
|
|
1619
1669
|
"get_live_session_summary",
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1670
|
+
{
|
|
1671
|
+
description:
|
|
1672
|
+
"Get a summary of the live shadowing session including all agent-tester interactions. " +
|
|
1673
|
+
"Call this after the session ends (sessionActive becomes false) to get the complete interaction log " +
|
|
1674
|
+
"for incorporating into your analysis. This includes all questions asked, tester responses, " +
|
|
1675
|
+
"voice transcriptions, and voice marker transcriptions from the session.",
|
|
1676
|
+
annotations: {
|
|
1677
|
+
readOnlyHint: true,
|
|
1678
|
+
destructiveHint: false,
|
|
1679
|
+
idempotentHint: true,
|
|
1680
|
+
openWorldHint: false,
|
|
1681
|
+
},
|
|
1682
|
+
inputSchema: z.object({}),
|
|
1683
|
+
},
|
|
1625
1684
|
async () => {
|
|
1626
1685
|
if (!liveSessionMeta && liveInteractions.length === 0) {
|
|
1627
1686
|
return toolError("No live session data available. Start a live shadowing session first.");
|