tracegist-mcp-bridge 0.2.6 → 0.2.7
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/tracegist-mcp-bridge.mjs +178 -33
- package/package.json +1 -1
|
@@ -866,6 +866,14 @@ const pendingQuestions = [];
|
|
|
866
866
|
/** @type {Map<string, object>} */
|
|
867
867
|
const questionResponses = new Map();
|
|
868
868
|
|
|
869
|
+
/**
|
|
870
|
+
* Persistent log of all agent-tester interactions during the live session.
|
|
871
|
+
* Survives until the next session-start. Used to build the complete interaction
|
|
872
|
+
* history for the final artifact.
|
|
873
|
+
* @type {Array<{type: string, questionId: string, question?: string, timestamp: number, transcription?: string, screenshot?: boolean, highlightCount?: number, dismissed?: boolean}>}
|
|
874
|
+
*/
|
|
875
|
+
const liveInteractions = [];
|
|
876
|
+
|
|
869
877
|
/** @type {Array<{resolve: Function, reject: Function, timeout: ReturnType<typeof setTimeout>}>} */
|
|
870
878
|
const screenshotWaiters = [];
|
|
871
879
|
|
|
@@ -1039,6 +1047,7 @@ function handleExtensionMessage(msg) {
|
|
|
1039
1047
|
liveEvents.length = 0;
|
|
1040
1048
|
pendingQuestions.length = 0;
|
|
1041
1049
|
questionResponses.clear();
|
|
1050
|
+
liveInteractions.length = 0;
|
|
1042
1051
|
console.error(`[${BRIDGE_NAME}] Live shadow: session started (${msg.sessionId})`);
|
|
1043
1052
|
// Notify MCP clients that resources changed
|
|
1044
1053
|
try {
|
|
@@ -1072,6 +1081,14 @@ function handleExtensionMessage(msg) {
|
|
|
1072
1081
|
});
|
|
1073
1082
|
const rIdx = pendingQuestions.findIndex((q) => q.questionId === msg.questionId);
|
|
1074
1083
|
if (rIdx >= 0) pendingQuestions.splice(rIdx, 1);
|
|
1084
|
+
// Push a live event so polling agents see the response immediately
|
|
1085
|
+
pushLiveEvent("question-response-received", {
|
|
1086
|
+
questionId: msg.questionId,
|
|
1087
|
+
hasVoice: !!msg.voiceBlobDataUrl,
|
|
1088
|
+
hasScreenshot: !!msg.screenshot,
|
|
1089
|
+
highlightCount: msg.highlightCaptures?.length || 0,
|
|
1090
|
+
hint: "Use get_tester_response to retrieve the full response with transcription and images.",
|
|
1091
|
+
});
|
|
1075
1092
|
break;
|
|
1076
1093
|
}
|
|
1077
1094
|
|
|
@@ -1079,6 +1096,14 @@ function handleExtensionMessage(msg) {
|
|
|
1079
1096
|
questionResponses.set(msg.questionId, { dismissed: true });
|
|
1080
1097
|
const dIdx = pendingQuestions.findIndex((q) => q.questionId === msg.questionId);
|
|
1081
1098
|
if (dIdx >= 0) pendingQuestions.splice(dIdx, 1);
|
|
1099
|
+
// Log dismissal in interactions
|
|
1100
|
+
liveInteractions.push({
|
|
1101
|
+
type: "question-dismissed",
|
|
1102
|
+
questionId: msg.questionId,
|
|
1103
|
+
timestamp: Date.now(),
|
|
1104
|
+
dismissed: true,
|
|
1105
|
+
});
|
|
1106
|
+
pushLiveEvent("question-dismissed", { questionId: msg.questionId });
|
|
1082
1107
|
break;
|
|
1083
1108
|
}
|
|
1084
1109
|
|
|
@@ -1099,6 +1124,13 @@ function handleExtensionMessage(msg) {
|
|
|
1099
1124
|
transcription: result.transcription || null,
|
|
1100
1125
|
error: result.error || undefined,
|
|
1101
1126
|
});
|
|
1127
|
+
// Log voice marker in interactions for the final artifact
|
|
1128
|
+
liveInteractions.push({
|
|
1129
|
+
type: "voice-marker",
|
|
1130
|
+
questionId: markerId,
|
|
1131
|
+
timestamp: Date.now(),
|
|
1132
|
+
transcription: result.transcription || null,
|
|
1133
|
+
});
|
|
1102
1134
|
})
|
|
1103
1135
|
.catch((err) => {
|
|
1104
1136
|
console.error(`[${BRIDGE_NAME}] Live voice transcription failed:`, err);
|
|
@@ -1151,9 +1183,14 @@ server.resource(
|
|
|
1151
1183
|
// Tool: watch_live_session
|
|
1152
1184
|
server.tool(
|
|
1153
1185
|
"watch_live_session",
|
|
1154
|
-
"Watch a live TraceGist shadowing session. Returns buffered events since the given sequence number
|
|
1155
|
-
"
|
|
1156
|
-
"
|
|
1186
|
+
"Watch a live TraceGist shadowing session in real-time. Returns buffered events since the given sequence number.\n\n" +
|
|
1187
|
+
"IMPORTANT POLLING BEHAVIOR:\n" +
|
|
1188
|
+
"- You MUST keep calling this tool every 2-4 seconds in a loop until sessionActive becomes false.\n" +
|
|
1189
|
+
"- Do NOT stop polling just because there are no new events — the tester is still active.\n" +
|
|
1190
|
+
"- When you see 'voice-transcription' events, the tester recorded a voice marker — read the transcription and react to it.\n" +
|
|
1191
|
+
"- When you see 'question-response-received' events, use get_tester_response to retrieve the full answer.\n" +
|
|
1192
|
+
"- The tester may set voice markers at any time to communicate with you — keep watching for them.\n" +
|
|
1193
|
+
"- Only stop polling when sessionActive is false (the recording ended).",
|
|
1157
1194
|
{
|
|
1158
1195
|
since_seq: z
|
|
1159
1196
|
.number()
|
|
@@ -1172,37 +1209,56 @@ server.tool(
|
|
|
1172
1209
|
|
|
1173
1210
|
const latestSeq = liveEvents.length > 0 ? liveEvents[liveEvents.length - 1].seq : 0;
|
|
1174
1211
|
|
|
1212
|
+
const responseData = {
|
|
1213
|
+
sessionActive: liveSessionActive,
|
|
1214
|
+
session: liveSessionMeta,
|
|
1215
|
+
events,
|
|
1216
|
+
latestSeq,
|
|
1217
|
+
eventCount: events.length,
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
if (liveSessionActive) {
|
|
1221
|
+
responseData.pollingGuidance =
|
|
1222
|
+
"KEEP POLLING every 2-4 seconds. Do NOT stop — the tester is still active. " +
|
|
1223
|
+
"Watch for 'voice-transcription' events (tester voice markers) and 'question-response-received' events. " +
|
|
1224
|
+
"React to voice markers by acknowledging them or asking follow-up questions.";
|
|
1225
|
+
responseData.availableActions = [
|
|
1226
|
+
{
|
|
1227
|
+
tool: "ask_tester_question",
|
|
1228
|
+
description:
|
|
1229
|
+
"Ask the tester a short question (max 200 chars). They respond with voice + highlights.",
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
tool: "get_live_screenshot",
|
|
1233
|
+
description:
|
|
1234
|
+
"Capture a screenshot of the tester's current browser tab.",
|
|
1235
|
+
},
|
|
1236
|
+
];
|
|
1237
|
+
if (pendingQuestions.length > 0) {
|
|
1238
|
+
responseData.pendingQuestions = pendingQuestions.map((q) => ({
|
|
1239
|
+
questionId: q.questionId,
|
|
1240
|
+
question: q.question,
|
|
1241
|
+
waitingSince: Math.round((Date.now() - q.sentAt) / 1000) + "s ago",
|
|
1242
|
+
}));
|
|
1243
|
+
}
|
|
1244
|
+
} else if (liveInteractions.length > 0) {
|
|
1245
|
+
// Session ended — include full interaction log for the agent to incorporate
|
|
1246
|
+
responseData.sessionEndedNote =
|
|
1247
|
+
"Session has ended. The interactionLog below contains all agent-tester exchanges from this session. " +
|
|
1248
|
+
"Include these in your analysis — they represent collaborative context between agent and tester.";
|
|
1249
|
+
responseData.interactionLog = liveInteractions;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Always include interaction count so the agent knows exchanges happened
|
|
1253
|
+
if (liveInteractions.length > 0) {
|
|
1254
|
+
responseData.interactionCount = liveInteractions.length;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1175
1257
|
return {
|
|
1176
1258
|
content: [
|
|
1177
1259
|
{
|
|
1178
1260
|
type: "text",
|
|
1179
|
-
text: JSON.stringify(
|
|
1180
|
-
{
|
|
1181
|
-
sessionActive: liveSessionActive,
|
|
1182
|
-
session: liveSessionMeta,
|
|
1183
|
-
events,
|
|
1184
|
-
latestSeq,
|
|
1185
|
-
eventCount: events.length,
|
|
1186
|
-
...(liveSessionActive
|
|
1187
|
-
? {
|
|
1188
|
-
availableActions: [
|
|
1189
|
-
{
|
|
1190
|
-
tool: "ask_tester_question",
|
|
1191
|
-
description:
|
|
1192
|
-
"Ask the tester a short question (max 200 chars). They respond with voice + highlights.",
|
|
1193
|
-
},
|
|
1194
|
-
{
|
|
1195
|
-
tool: "get_live_screenshot",
|
|
1196
|
-
description:
|
|
1197
|
-
"Capture a screenshot of the tester's current browser tab.",
|
|
1198
|
-
},
|
|
1199
|
-
],
|
|
1200
|
-
}
|
|
1201
|
-
: {}),
|
|
1202
|
-
},
|
|
1203
|
-
null,
|
|
1204
|
-
2,
|
|
1205
|
-
),
|
|
1261
|
+
text: JSON.stringify(responseData, null, 2),
|
|
1206
1262
|
},
|
|
1207
1263
|
],
|
|
1208
1264
|
};
|
|
@@ -1248,6 +1304,17 @@ server.tool(
|
|
|
1248
1304
|
pendingQuestions.shift();
|
|
1249
1305
|
}
|
|
1250
1306
|
|
|
1307
|
+
// Log the interaction
|
|
1308
|
+
liveInteractions.push({
|
|
1309
|
+
type: "agent-question",
|
|
1310
|
+
questionId,
|
|
1311
|
+
question,
|
|
1312
|
+
timestamp: Date.now(),
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
// Push a live event so the polling loop sees it
|
|
1316
|
+
pushLiveEvent("agent-question-sent", { questionId, question });
|
|
1317
|
+
|
|
1251
1318
|
return {
|
|
1252
1319
|
content: [
|
|
1253
1320
|
{
|
|
@@ -1260,7 +1327,9 @@ server.tool(
|
|
|
1260
1327
|
"- Voice marker: Alt/Option + Shift + M (start/stop recording)",
|
|
1261
1328
|
"- Highlight captures: Alt/Option + Shift + S (draw on screen)",
|
|
1262
1329
|
"",
|
|
1263
|
-
"
|
|
1330
|
+
"IMPORTANT: Keep polling watch_live_session — you will see a 'question-response-received' event when they respond.",
|
|
1331
|
+
"Then use `get_tester_response` with this question ID to retrieve their full answer.",
|
|
1332
|
+
"The tester may take 10-30 seconds to record their voice response. Be patient.",
|
|
1264
1333
|
].join("\n"),
|
|
1265
1334
|
},
|
|
1266
1335
|
],
|
|
@@ -1272,7 +1341,11 @@ server.tool(
|
|
|
1272
1341
|
server.tool(
|
|
1273
1342
|
"get_tester_response",
|
|
1274
1343
|
"Check for the tester's response to a question asked during live shadowing. " +
|
|
1275
|
-
"Returns the tester's screenshot, highlight captures, and voice note
|
|
1344
|
+
"Returns the tester's screenshot, highlight captures, and transcribed voice note.\n\n" +
|
|
1345
|
+
"IMPORTANT: The tester needs time to record their answer (10-60 seconds). " +
|
|
1346
|
+
"Instead of calling this tool repeatedly, prefer polling watch_live_session — " +
|
|
1347
|
+
"you will see a 'question-response-received' event when the response arrives. " +
|
|
1348
|
+
"Then call this tool once to get the full response with transcription and images.",
|
|
1276
1349
|
{
|
|
1277
1350
|
question_id: z.string().describe("The question ID returned by ask_tester_question."),
|
|
1278
1351
|
},
|
|
@@ -1280,11 +1353,22 @@ server.tool(
|
|
|
1280
1353
|
const response = questionResponses.get(question_id);
|
|
1281
1354
|
|
|
1282
1355
|
if (!response) {
|
|
1356
|
+
// Check how long we've been waiting
|
|
1357
|
+
const pending = pendingQuestions.find((q) => q.questionId === question_id);
|
|
1358
|
+
const waitingSec = pending ? Math.round((Date.now() - pending.sentAt) / 1000) : null;
|
|
1283
1359
|
return {
|
|
1284
1360
|
content: [
|
|
1285
1361
|
{
|
|
1286
1362
|
type: "text",
|
|
1287
|
-
text:
|
|
1363
|
+
text: [
|
|
1364
|
+
"No response yet." +
|
|
1365
|
+
(waitingSec !== null ? ` Waiting for ${waitingSec}s.` : ""),
|
|
1366
|
+
"The tester may still be recording their voice answer.",
|
|
1367
|
+
"",
|
|
1368
|
+
"TIP: Instead of polling this tool, go back to polling watch_live_session — " +
|
|
1369
|
+
"a 'question-response-received' event will appear when the tester responds. " +
|
|
1370
|
+
"Then call this tool once to retrieve the full answer.",
|
|
1371
|
+
].join("\n"),
|
|
1288
1372
|
},
|
|
1289
1373
|
],
|
|
1290
1374
|
};
|
|
@@ -1344,9 +1428,11 @@ server.tool(
|
|
|
1344
1428
|
}
|
|
1345
1429
|
}
|
|
1346
1430
|
|
|
1431
|
+
let transcriptionText = null;
|
|
1347
1432
|
if (response.voiceBlobDataUrl) {
|
|
1348
1433
|
const voiceResult = await transcribeLiveVoice(response.voiceBlobDataUrl);
|
|
1349
1434
|
if (voiceResult.transcription) {
|
|
1435
|
+
transcriptionText = voiceResult.transcription;
|
|
1350
1436
|
content.push({
|
|
1351
1437
|
type: "text",
|
|
1352
1438
|
text: `Voice transcription:\n${voiceResult.transcription}`,
|
|
@@ -1359,6 +1445,21 @@ server.tool(
|
|
|
1359
1445
|
}
|
|
1360
1446
|
}
|
|
1361
1447
|
|
|
1448
|
+
// Log the interaction for the session history
|
|
1449
|
+
liveInteractions.push({
|
|
1450
|
+
type: "tester-response",
|
|
1451
|
+
questionId: question_id,
|
|
1452
|
+
timestamp: Date.now(),
|
|
1453
|
+
transcription: transcriptionText,
|
|
1454
|
+
screenshot: !!response.screenshot,
|
|
1455
|
+
highlightCount: response.highlightCaptures?.length || 0,
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
content.push({
|
|
1459
|
+
type: "text",
|
|
1460
|
+
text: "\nIMPORTANT: Resume polling watch_live_session to continue watching the session.",
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1362
1463
|
// Clean up after retrieval
|
|
1363
1464
|
questionResponses.delete(question_id);
|
|
1364
1465
|
|
|
@@ -1420,6 +1521,50 @@ server.tool(
|
|
|
1420
1521
|
},
|
|
1421
1522
|
);
|
|
1422
1523
|
|
|
1524
|
+
// Tool: get_live_session_summary
|
|
1525
|
+
server.tool(
|
|
1526
|
+
"get_live_session_summary",
|
|
1527
|
+
"Get a summary of the live shadowing session including all agent-tester interactions. " +
|
|
1528
|
+
"Call this after the session ends (sessionActive becomes false) to get the complete interaction log " +
|
|
1529
|
+
"for incorporating into your analysis. This includes all questions asked, tester responses, " +
|
|
1530
|
+
"voice transcriptions, and voice marker transcriptions from the session.",
|
|
1531
|
+
{},
|
|
1532
|
+
async () => {
|
|
1533
|
+
if (!liveSessionMeta && liveInteractions.length === 0) {
|
|
1534
|
+
return toolError("No live session data available. Start a live shadowing session first.");
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// Collect voice-transcription events from the event buffer
|
|
1538
|
+
const voiceTranscriptions = liveEvents
|
|
1539
|
+
.filter((e) => e.eventType === "voice-transcription" && e.data?.transcription)
|
|
1540
|
+
.map((e) => ({
|
|
1541
|
+
timestamp: e.ts,
|
|
1542
|
+
markerId: e.data.markerId,
|
|
1543
|
+
transcription: e.data.transcription,
|
|
1544
|
+
}));
|
|
1545
|
+
|
|
1546
|
+
const summary = {
|
|
1547
|
+
session: liveSessionMeta,
|
|
1548
|
+
sessionActive: liveSessionActive,
|
|
1549
|
+
interactionLog: liveInteractions,
|
|
1550
|
+
voiceMarkerTranscriptions: voiceTranscriptions,
|
|
1551
|
+
totalEvents: liveEvents.length,
|
|
1552
|
+
guidance: liveSessionActive
|
|
1553
|
+
? "Session is still active. Resume polling watch_live_session."
|
|
1554
|
+
: "Session has ended. Use this interaction log and voice transcriptions to enrich your analysis of the exported session package.",
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
return {
|
|
1558
|
+
content: [
|
|
1559
|
+
{
|
|
1560
|
+
type: "text",
|
|
1561
|
+
text: JSON.stringify(summary, null, 2),
|
|
1562
|
+
},
|
|
1563
|
+
],
|
|
1564
|
+
};
|
|
1565
|
+
},
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1423
1568
|
// ---------------------------------------------------------------------------
|
|
1424
1569
|
// Startup
|
|
1425
1570
|
// ---------------------------------------------------------------------------
|