tracegist-mcp-bridge 0.2.11 → 0.2.14
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 +13 -10
- package/bin/lib.mjs +53 -9
- package/bin/tracegist-mcp-bridge.mjs +168 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,16 +37,19 @@ Set `TRACEGIST_DIR` to change where the bridge looks for packages (defaults to `
|
|
|
37
37
|
|
|
38
38
|
## MCP tools
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
| Tool | Description |
|
|
41
|
+
| ------------------------------------------ | --------------------------------------------------------------- |
|
|
42
|
+
| `list_tracegist_packages` | List package ZIPs in Downloads |
|
|
43
|
+
| `get_tracegist_package_overview` | Manifest + truncated handoff preview |
|
|
44
|
+
| `get_tracegist_handoff_markdown` | Full handoff markdown |
|
|
45
|
+
| `read_tracegist_package_file` | Read any file from the ZIP (e.g. `network/api-requests.jsonl`) |
|
|
46
|
+
| `extract_tracegist_package_file` | Extract a file to disk |
|
|
47
|
+
| `transcribe_tracegist_package_voice_notes` | Local Whisper transcription |
|
|
48
|
+
| `watch_live_session` | Poll real-time session events while recording is active |
|
|
49
|
+
| `get_live_screenshot` | Request and retrieve a screenshot of the current tab |
|
|
50
|
+
| `ask_tester_question` | Send a question to the tester (shown as a browser toast) |
|
|
51
|
+
| `get_tester_response` | Retrieve the tester's voice + screenshot answer |
|
|
52
|
+
| `get_live_session_summary` | Complete interaction log and voice transcriptions after session |
|
|
50
53
|
|
|
51
54
|
## Package contents
|
|
52
55
|
|
package/bin/lib.mjs
CHANGED
|
@@ -11,14 +11,24 @@ export function normalizeZipBaseName(entryName) {
|
|
|
11
11
|
return path.posix.basename(entryName).toLowerCase();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function renderPackagesText(
|
|
14
|
+
export function renderPackagesText(
|
|
15
|
+
packages,
|
|
16
|
+
directory,
|
|
17
|
+
totalCount = null,
|
|
18
|
+
hasMore = false,
|
|
19
|
+
offset = 0,
|
|
20
|
+
) {
|
|
15
21
|
if (packages.length === 0) {
|
|
16
22
|
return [
|
|
17
23
|
`No TraceGist package zips found in ${directory}.`,
|
|
18
24
|
"Expected naming pattern: tracegist-...-package.zip",
|
|
19
25
|
].join("\n");
|
|
20
26
|
}
|
|
21
|
-
const
|
|
27
|
+
const countLabel =
|
|
28
|
+
totalCount != null
|
|
29
|
+
? `Showing ${packages.length} of ${totalCount} TraceGist package(s) in ${directory}:`
|
|
30
|
+
: `Found ${packages.length} TraceGist package(s) in ${directory}:`;
|
|
31
|
+
const lines = [countLabel, ""];
|
|
22
32
|
for (const pkg of packages) {
|
|
23
33
|
lines.push(
|
|
24
34
|
`- ${pkg.name}`,
|
|
@@ -28,6 +38,16 @@ export function renderPackagesText(packages, directory) {
|
|
|
28
38
|
"",
|
|
29
39
|
);
|
|
30
40
|
}
|
|
41
|
+
if (hasMore) {
|
|
42
|
+
lines.push(
|
|
43
|
+
`More packages available. Use offset: ${offset + packages.length} to see the next page.`,
|
|
44
|
+
"",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
lines.push(
|
|
48
|
+
"Each package includes a ready-to-run Playwright repro script.",
|
|
49
|
+
"Call get_tracegist_handoff_markdown (no section param) on a package to see the TOC with repro script paths.",
|
|
50
|
+
);
|
|
31
51
|
return lines.join("\n");
|
|
32
52
|
}
|
|
33
53
|
|
|
@@ -74,7 +94,8 @@ const SECTION_HINTS = {
|
|
|
74
94
|
"user interaction timeline": "Full click/fill/navigation sequence — key for reproduction",
|
|
75
95
|
"session context": "Page URL, title, session metadata",
|
|
76
96
|
"session environment": "Browser, viewport, OS — match for reproduction",
|
|
77
|
-
"package files":
|
|
97
|
+
"package files":
|
|
98
|
+
"ACTION: Read the Python repro script path here — use read_tracegist_package_file before writing any automation",
|
|
78
99
|
"marker-to-file mapping": "Which screenshots/voice files belong to each marker",
|
|
79
100
|
"backend log correlation": "Absolute timestamps for server-side log alignment",
|
|
80
101
|
"full session console timeline": "All console output (deep exports only)",
|
|
@@ -90,7 +111,7 @@ const SECTION_HINTS = {
|
|
|
90
111
|
"environment at marker time": "Environment snapshot at a specific marker",
|
|
91
112
|
"marker timeline logs (context window)": "Logs within the ±5 s marker window",
|
|
92
113
|
"webapp testing reproduction":
|
|
93
|
-
"
|
|
114
|
+
"ACTION: Ready-to-run Python repro script path + key selectors — read before writing any automation",
|
|
94
115
|
"tester intent summary": "Classified intent: specifications, issues found, observations",
|
|
95
116
|
"iterative context": "Previous session reference, verification checklist for build-test cycle",
|
|
96
117
|
"tier 0: quick summary": "Session overview, intent, reproduction command (~200 tokens)",
|
|
@@ -103,14 +124,40 @@ function getSectionHint(sectionName) {
|
|
|
103
124
|
return SECTION_HINTS[sectionName.toLowerCase()] || "Additional section";
|
|
104
125
|
}
|
|
105
126
|
|
|
106
|
-
export function renderSectionToc(sections, zipPath) {
|
|
127
|
+
export function renderSectionToc(sections, zipPath, manifest = null) {
|
|
107
128
|
const totalChars = sections.reduce((sum, s) => sum + s.content.length, 0);
|
|
108
129
|
const lines = [
|
|
109
130
|
`Handoff document for ${path.basename(zipPath)} — ${sections.length} sections, ${totalChars.toLocaleString()} chars total.`,
|
|
110
131
|
"",
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
// Prominent callout BEFORE the table — shown before any section is read
|
|
135
|
+
const pythonScript = manifest?.pythonPlaywrightScriptPath;
|
|
136
|
+
const tsScript = manifest?.playwrightScriptPath;
|
|
137
|
+
if (pythonScript || tsScript) {
|
|
138
|
+
lines.push(
|
|
139
|
+
"**Playwright repro script is ready — read it before writing any automation code:**",
|
|
140
|
+
);
|
|
141
|
+
if (pythonScript) {
|
|
142
|
+
lines.push(
|
|
143
|
+
`- Python (webapp-testing skill): \`read_tracegist_package_file(zipPath, "${pythonScript}")\``,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (tsScript) {
|
|
147
|
+
lines.push(
|
|
148
|
+
`- TypeScript (Playwright Test): \`read_tracegist_package_file(zipPath, "${tsScript}")\``,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
lines.push(
|
|
155
|
+
"**Reading guide:** For analysis, start with Session Triage → Marker Timeline → Notable Anomalies.",
|
|
156
|
+
"For reproduction, start with the Playwright repro script above → Session Environment → User Interaction Timeline.",
|
|
157
|
+
"",
|
|
111
158
|
"| # | Section | Size | Purpose |",
|
|
112
159
|
"|---|---------|------|---------|",
|
|
113
|
-
|
|
160
|
+
);
|
|
114
161
|
for (let i = 0; i < sections.length; i++) {
|
|
115
162
|
const hint = getSectionHint(sections[i].name);
|
|
116
163
|
lines.push(
|
|
@@ -118,9 +165,6 @@ export function renderSectionToc(sections, zipPath) {
|
|
|
118
165
|
);
|
|
119
166
|
}
|
|
120
167
|
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
168
|
"",
|
|
125
169
|
'Pass `section` with a section name (e.g. "Marker Timeline") to retrieve its full content.',
|
|
126
170
|
);
|
|
@@ -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) {
|
|
@@ -414,6 +416,7 @@ server.registerPrompt(
|
|
|
414
416
|
server.registerTool(
|
|
415
417
|
"transcribe_tracegist_package_voice_notes",
|
|
416
418
|
{
|
|
419
|
+
title: "Transcribe Voice Notes",
|
|
417
420
|
description:
|
|
418
421
|
"Transcribe voice-note files inside a TraceGist package zip using local Python Whisper (no external API).",
|
|
419
422
|
annotations: {
|
|
@@ -435,6 +438,16 @@ server.registerTool(
|
|
|
435
438
|
.optional()
|
|
436
439
|
.describe("ISO 639-1 language code (e.g. en, de, ja). Omit for auto-detection."),
|
|
437
440
|
}),
|
|
441
|
+
outputSchema: {
|
|
442
|
+
zipPath: z.string(),
|
|
443
|
+
model: z.string(),
|
|
444
|
+
language: z.string().nullable(),
|
|
445
|
+
voiceFileCount: z.number().optional(),
|
|
446
|
+
transcribedCount: z.number().optional(),
|
|
447
|
+
transcriptions: z.array(z.object({ entryName: z.string(), transcript: z.string() })),
|
|
448
|
+
failures: z.array(z.object({ entryName: z.string(), error: z.string() })).optional(),
|
|
449
|
+
warning: z.string().optional(),
|
|
450
|
+
},
|
|
438
451
|
},
|
|
439
452
|
async ({ zipPath, model = "base", language }) => {
|
|
440
453
|
try {
|
|
@@ -536,9 +549,10 @@ server.registerTool(
|
|
|
536
549
|
failures,
|
|
537
550
|
};
|
|
538
551
|
|
|
552
|
+
const hasError = transcriptions.length === 0 && failures.length > 0;
|
|
539
553
|
return {
|
|
540
554
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
541
|
-
...(
|
|
555
|
+
...(hasError ? { isError: true } : { structuredContent: result }),
|
|
542
556
|
};
|
|
543
557
|
} catch (err) {
|
|
544
558
|
return toolError(err);
|
|
@@ -549,6 +563,7 @@ server.registerTool(
|
|
|
549
563
|
server.registerTool(
|
|
550
564
|
"list_tracegist_packages",
|
|
551
565
|
{
|
|
566
|
+
title: "List Packages",
|
|
552
567
|
description:
|
|
553
568
|
"List TraceGist marker package zip files. Searches TRACEGIST_DIR (env var), falls back to ~/Downloads, or uses the given directory.",
|
|
554
569
|
annotations: {
|
|
@@ -569,14 +584,51 @@ server.registerTool(
|
|
|
569
584
|
.max(200)
|
|
570
585
|
.optional()
|
|
571
586
|
.describe("Maximum number of packages to return (default: 20)."),
|
|
587
|
+
offset: z
|
|
588
|
+
.number()
|
|
589
|
+
.int()
|
|
590
|
+
.min(0)
|
|
591
|
+
.optional()
|
|
592
|
+
.describe("Number of packages to skip for pagination (default: 0)."),
|
|
572
593
|
}),
|
|
594
|
+
outputSchema: {
|
|
595
|
+
totalCount: z.number(),
|
|
596
|
+
count: z.number(),
|
|
597
|
+
offset: z.number(),
|
|
598
|
+
hasMore: z.boolean(),
|
|
599
|
+
packages: z.array(
|
|
600
|
+
z.object({
|
|
601
|
+
name: z.string(),
|
|
602
|
+
path: z.string(),
|
|
603
|
+
sizeBytes: z.number(),
|
|
604
|
+
modifiedAt: z.string(),
|
|
605
|
+
}),
|
|
606
|
+
),
|
|
607
|
+
},
|
|
573
608
|
},
|
|
574
|
-
async ({ directory, limit = 20 }) => {
|
|
609
|
+
async ({ directory, limit = 20, offset = 0 }) => {
|
|
575
610
|
try {
|
|
576
611
|
const searchDir = directory || DEFAULT_DOWNLOADS_DIR;
|
|
577
|
-
const packages = await listTraceGistPackages(
|
|
612
|
+
const { packages, totalCount, hasMore } = await listTraceGistPackages(
|
|
613
|
+
searchDir,
|
|
614
|
+
limit,
|
|
615
|
+
offset,
|
|
616
|
+
);
|
|
617
|
+
const structured = {
|
|
618
|
+
totalCount,
|
|
619
|
+
count: packages.length,
|
|
620
|
+
offset,
|
|
621
|
+
hasMore,
|
|
622
|
+
packages,
|
|
623
|
+
};
|
|
578
624
|
return {
|
|
579
|
-
content: [
|
|
625
|
+
content: [
|
|
626
|
+
{
|
|
627
|
+
type: "text",
|
|
628
|
+
text: renderPackagesText(packages, searchDir, totalCount, hasMore, offset),
|
|
629
|
+
},
|
|
630
|
+
],
|
|
631
|
+
structuredContent: structured,
|
|
580
632
|
};
|
|
581
633
|
} catch (err) {
|
|
582
634
|
return toolError(err);
|
|
@@ -587,6 +639,7 @@ server.registerTool(
|
|
|
587
639
|
server.registerTool(
|
|
588
640
|
"get_tracegist_package_overview",
|
|
589
641
|
{
|
|
642
|
+
title: "Package Overview",
|
|
590
643
|
description:
|
|
591
644
|
"Quick metadata overview of a TraceGist package: manifest and file list. " +
|
|
592
645
|
"Does NOT include handoff content — call get_tracegist_handoff_markdown (no section param → table of contents, then load sections by name) for the actual handoff. " +
|
|
@@ -600,6 +653,15 @@ server.registerTool(
|
|
|
600
653
|
inputSchema: z.object({
|
|
601
654
|
zipPath: z.string().describe("Absolute path to the TraceGist package zip file."),
|
|
602
655
|
}),
|
|
656
|
+
outputSchema: {
|
|
657
|
+
zipPath: z.string(),
|
|
658
|
+
entryCount: z.number(),
|
|
659
|
+
manifestEntryName: z.string().nullable(),
|
|
660
|
+
handoffEntryName: z.string().nullable(),
|
|
661
|
+
manifest: z.any().nullable(),
|
|
662
|
+
entries: z.array(z.string()),
|
|
663
|
+
hint: z.string(),
|
|
664
|
+
},
|
|
603
665
|
},
|
|
604
666
|
async ({ zipPath }) => {
|
|
605
667
|
try {
|
|
@@ -619,6 +681,7 @@ server.registerTool(
|
|
|
619
681
|
|
|
620
682
|
return {
|
|
621
683
|
content: [{ type: "text", text: JSON.stringify(overview, null, 2) }],
|
|
684
|
+
structuredContent: overview,
|
|
622
685
|
};
|
|
623
686
|
} catch (err) {
|
|
624
687
|
return toolError(err);
|
|
@@ -629,6 +692,7 @@ server.registerTool(
|
|
|
629
692
|
server.registerTool(
|
|
630
693
|
"get_tracegist_handoff_markdown",
|
|
631
694
|
{
|
|
695
|
+
title: "Handoff Markdown",
|
|
632
696
|
description:
|
|
633
697
|
"Extract coding agent handoff markdown from a TraceGist package zip. " +
|
|
634
698
|
"Voice note transcripts are automatically generated (via local Whisper) and injected inline " +
|
|
@@ -715,7 +779,7 @@ server.registerTool(
|
|
|
715
779
|
content: [
|
|
716
780
|
{
|
|
717
781
|
type: "text",
|
|
718
|
-
text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath)}`,
|
|
782
|
+
text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath, manifest)}`,
|
|
719
783
|
},
|
|
720
784
|
],
|
|
721
785
|
};
|
|
@@ -753,6 +817,7 @@ server.registerTool(
|
|
|
753
817
|
server.registerTool(
|
|
754
818
|
"read_tracegist_package_file",
|
|
755
819
|
{
|
|
820
|
+
title: "Read Package File",
|
|
756
821
|
description:
|
|
757
822
|
"Read a text file directly from inside a TraceGist package zip and return its content without writing to disk. " +
|
|
758
823
|
"Common uses: `network/api-requests.jsonl` for request/response bodies, or the Playwright repro script " +
|
|
@@ -793,6 +858,7 @@ server.registerTool(
|
|
|
793
858
|
server.registerTool(
|
|
794
859
|
"extract_tracegist_package_file",
|
|
795
860
|
{
|
|
861
|
+
title: "Extract Package File",
|
|
796
862
|
description: "Extract one file from a TraceGist package zip to a local directory.",
|
|
797
863
|
annotations: {
|
|
798
864
|
readOnlyHint: false,
|
|
@@ -1237,24 +1303,34 @@ server.resource(
|
|
|
1237
1303
|
);
|
|
1238
1304
|
|
|
1239
1305
|
// Tool: watch_live_session
|
|
1240
|
-
server.
|
|
1306
|
+
server.registerTool(
|
|
1241
1307
|
"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
1308
|
{
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
.
|
|
1257
|
-
|
|
1309
|
+
title: "Watch Live Session",
|
|
1310
|
+
description:
|
|
1311
|
+
"Watch a live TraceGist shadowing session in real-time. Returns buffered events since the given sequence number.\n\n" +
|
|
1312
|
+
"IMPORTANT POLLING BEHAVIOR:\n" +
|
|
1313
|
+
"- You MUST keep calling this tool every 2-4 seconds in a loop until sessionActive becomes false.\n" +
|
|
1314
|
+
"- Do NOT stop polling just because there are no new events — the tester is still active.\n" +
|
|
1315
|
+
"- When you see 'voice-transcription' events, the tester recorded a voice marker — read the transcription and react to it.\n" +
|
|
1316
|
+
"- When you see 'question-response-received' events, use get_tester_response to retrieve the full answer with images.\n" +
|
|
1317
|
+
"- When you see 'question-response-transcribed' events, the tester's voice response has been transcribed — read it for context.\n" +
|
|
1318
|
+
"- Marker events with hasPendingVoice=true will have their voiceTranscription field populated once transcription completes.\n" +
|
|
1319
|
+
"- The tester may set voice markers at any time to communicate with you — keep watching for them.\n" +
|
|
1320
|
+
"- If you need clarification, use ask_tester_question to show a toast notification to the tester.\n" +
|
|
1321
|
+
"- Only stop polling when sessionActive is false (the recording ended).",
|
|
1322
|
+
annotations: {
|
|
1323
|
+
readOnlyHint: true,
|
|
1324
|
+
destructiveHint: false,
|
|
1325
|
+
idempotentHint: true,
|
|
1326
|
+
openWorldHint: false,
|
|
1327
|
+
},
|
|
1328
|
+
inputSchema: z.object({
|
|
1329
|
+
since_seq: z
|
|
1330
|
+
.number()
|
|
1331
|
+
.optional()
|
|
1332
|
+
.describe("Return events after this sequence number. Omit for all buffered events."),
|
|
1333
|
+
}),
|
|
1258
1334
|
},
|
|
1259
1335
|
async ({ since_seq }) => {
|
|
1260
1336
|
if (!liveSessionActive && liveEvents.length === 0) {
|
|
@@ -1363,20 +1439,30 @@ server.tool(
|
|
|
1363
1439
|
);
|
|
1364
1440
|
|
|
1365
1441
|
// Tool: ask_tester_question
|
|
1366
|
-
server.
|
|
1442
|
+
server.registerTool(
|
|
1367
1443
|
"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
1444
|
{
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
.
|
|
1376
|
-
.
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1445
|
+
title: "Ask Tester Question",
|
|
1446
|
+
description:
|
|
1447
|
+
"Ask the tester a short, concise question during a live shadowing session. " +
|
|
1448
|
+
"The question appears as a toast notification in their browser. " +
|
|
1449
|
+
"The tester responds with a voice marker (Alt+Shift+M) and optional highlight captures (Alt+Shift+S). " +
|
|
1450
|
+
"Use get_tester_response to check for their answer.",
|
|
1451
|
+
annotations: {
|
|
1452
|
+
readOnlyHint: false,
|
|
1453
|
+
destructiveHint: false,
|
|
1454
|
+
idempotentHint: false,
|
|
1455
|
+
openWorldHint: true,
|
|
1456
|
+
},
|
|
1457
|
+
inputSchema: z.object({
|
|
1458
|
+
question: z
|
|
1459
|
+
.string()
|
|
1460
|
+
.max(200)
|
|
1461
|
+
.describe(
|
|
1462
|
+
"Short, concise question for the tester (max 200 chars). " +
|
|
1463
|
+
"Example: 'Can you click the Save button again?' or 'Does the error appear with a different email?'",
|
|
1464
|
+
),
|
|
1465
|
+
}),
|
|
1380
1466
|
},
|
|
1381
1467
|
async ({ question }) => {
|
|
1382
1468
|
if (!liveSessionActive || !activeConnection) {
|
|
@@ -1435,16 +1521,26 @@ server.tool(
|
|
|
1435
1521
|
);
|
|
1436
1522
|
|
|
1437
1523
|
// Tool: get_tester_response
|
|
1438
|
-
server.
|
|
1524
|
+
server.registerTool(
|
|
1439
1525
|
"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
1526
|
{
|
|
1447
|
-
|
|
1527
|
+
title: "Get Tester Response",
|
|
1528
|
+
description:
|
|
1529
|
+
"Check for the tester's response to a question asked during live shadowing. " +
|
|
1530
|
+
"Returns the tester's screenshot, highlight captures, and transcribed voice note.\n\n" +
|
|
1531
|
+
"IMPORTANT: The tester needs time to record their answer (10-60 seconds). " +
|
|
1532
|
+
"Instead of calling this tool repeatedly, prefer polling watch_live_session — " +
|
|
1533
|
+
"you will see a 'question-response-received' event when the response arrives. " +
|
|
1534
|
+
"Then call this tool once to get the full response with transcription and images.",
|
|
1535
|
+
annotations: {
|
|
1536
|
+
readOnlyHint: true,
|
|
1537
|
+
destructiveHint: false,
|
|
1538
|
+
idempotentHint: true,
|
|
1539
|
+
openWorldHint: false,
|
|
1540
|
+
},
|
|
1541
|
+
inputSchema: z.object({
|
|
1542
|
+
question_id: z.string().describe("The question ID returned by ask_tester_question."),
|
|
1543
|
+
}),
|
|
1448
1544
|
},
|
|
1449
1545
|
async ({ question_id }) => {
|
|
1450
1546
|
const response = questionResponses.get(question_id);
|
|
@@ -1561,10 +1657,20 @@ server.tool(
|
|
|
1561
1657
|
);
|
|
1562
1658
|
|
|
1563
1659
|
// Tool: get_live_screenshot
|
|
1564
|
-
server.
|
|
1660
|
+
server.registerTool(
|
|
1565
1661
|
"get_live_screenshot",
|
|
1566
|
-
|
|
1567
|
-
|
|
1662
|
+
{
|
|
1663
|
+
title: "Live Screenshot",
|
|
1664
|
+
description:
|
|
1665
|
+
"Capture a screenshot of the tester's current browser tab during a live shadowing session.",
|
|
1666
|
+
annotations: {
|
|
1667
|
+
readOnlyHint: true,
|
|
1668
|
+
destructiveHint: false,
|
|
1669
|
+
idempotentHint: true,
|
|
1670
|
+
openWorldHint: false,
|
|
1671
|
+
},
|
|
1672
|
+
inputSchema: z.object({}),
|
|
1673
|
+
},
|
|
1568
1674
|
async () => {
|
|
1569
1675
|
if (!liveSessionActive || !activeConnection) {
|
|
1570
1676
|
return toolError("No active live shadowing session or extension not connected.");
|
|
@@ -1615,13 +1721,23 @@ server.tool(
|
|
|
1615
1721
|
);
|
|
1616
1722
|
|
|
1617
1723
|
// Tool: get_live_session_summary
|
|
1618
|
-
server.
|
|
1724
|
+
server.registerTool(
|
|
1619
1725
|
"get_live_session_summary",
|
|
1620
|
-
|
|
1621
|
-
"
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1726
|
+
{
|
|
1727
|
+
title: "Live Session Summary",
|
|
1728
|
+
description:
|
|
1729
|
+
"Get a summary of the live shadowing session including all agent-tester interactions. " +
|
|
1730
|
+
"Call this after the session ends (sessionActive becomes false) to get the complete interaction log " +
|
|
1731
|
+
"for incorporating into your analysis. This includes all questions asked, tester responses, " +
|
|
1732
|
+
"voice transcriptions, and voice marker transcriptions from the session.",
|
|
1733
|
+
annotations: {
|
|
1734
|
+
readOnlyHint: true,
|
|
1735
|
+
destructiveHint: false,
|
|
1736
|
+
idempotentHint: true,
|
|
1737
|
+
openWorldHint: false,
|
|
1738
|
+
},
|
|
1739
|
+
inputSchema: z.object({}),
|
|
1740
|
+
},
|
|
1625
1741
|
async () => {
|
|
1626
1742
|
if (!liveSessionMeta && liveInteractions.length === 0) {
|
|
1627
1743
|
return toolError("No live session data available. Start a live shadowing session first.");
|