tracegist-mcp-bridge 0.2.7 → 0.2.8
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 +114 -30
- package/package.json +1 -1
|
@@ -957,9 +957,12 @@ async function transcribeLiveVoice(voiceBlobDataUrl) {
|
|
|
957
957
|
return { error: "No transcription available (set OPENROUTER_API_KEY or install Whisper)" };
|
|
958
958
|
}
|
|
959
959
|
|
|
960
|
-
/** Push a live event into the ring buffer with auto-incrementing seq.
|
|
960
|
+
/** Push a live event into the ring buffer with auto-incrementing seq.
|
|
961
|
+
* Seq is always higher than any existing event (including extension-originated ones). */
|
|
961
962
|
function pushLiveEvent(eventType, data) {
|
|
962
|
-
|
|
963
|
+
// Ensure our seq is always higher than the max seq in the buffer
|
|
964
|
+
const maxExistingSeq = liveEvents.length > 0 ? liveEvents[liveEvents.length - 1].seq : 0;
|
|
965
|
+
liveEventSeq = Math.max(liveEventSeq, maxExistingSeq) + 1;
|
|
963
966
|
liveEvents.push({ seq: liveEventSeq, ts: Date.now(), eventType, data });
|
|
964
967
|
if (liveEvents.length > LIVE_SHADOW_EVENT_BUFFER_SIZE) {
|
|
965
968
|
const evictCount = Math.floor(LIVE_SHADOW_EVENT_BUFFER_SIZE * 0.25);
|
|
@@ -1045,6 +1048,7 @@ function handleExtensionMessage(msg) {
|
|
|
1045
1048
|
startedAt: Date.now(),
|
|
1046
1049
|
};
|
|
1047
1050
|
liveEvents.length = 0;
|
|
1051
|
+
liveEventSeq = 0;
|
|
1048
1052
|
pendingQuestions.length = 0;
|
|
1049
1053
|
questionResponses.clear();
|
|
1050
1054
|
liveInteractions.length = 0;
|
|
@@ -1089,6 +1093,54 @@ function handleExtensionMessage(msg) {
|
|
|
1089
1093
|
highlightCount: msg.highlightCaptures?.length || 0,
|
|
1090
1094
|
hint: "Use get_tester_response to retrieve the full response with transcription and images.",
|
|
1091
1095
|
});
|
|
1096
|
+
// Eagerly transcribe the voice response and log the interaction immediately
|
|
1097
|
+
// (don't wait for get_tester_response to be called)
|
|
1098
|
+
if (msg.voiceBlobDataUrl) {
|
|
1099
|
+
transcribeLiveVoice(msg.voiceBlobDataUrl)
|
|
1100
|
+
.then((result) => {
|
|
1101
|
+
// Store transcription with the response for later retrieval
|
|
1102
|
+
const stored = questionResponses.get(msg.questionId);
|
|
1103
|
+
if (stored && !stored.dismissed) {
|
|
1104
|
+
stored.transcription = result.transcription || null;
|
|
1105
|
+
}
|
|
1106
|
+
// Log the interaction with transcription
|
|
1107
|
+
liveInteractions.push({
|
|
1108
|
+
type: "tester-response",
|
|
1109
|
+
questionId: msg.questionId,
|
|
1110
|
+
timestamp: msg.timestamp || Date.now(),
|
|
1111
|
+
transcription: result.transcription || null,
|
|
1112
|
+
screenshot: !!msg.screenshot,
|
|
1113
|
+
highlightCount: msg.highlightCaptures?.length || 0,
|
|
1114
|
+
});
|
|
1115
|
+
// Push transcription as a live event so polling agents see it
|
|
1116
|
+
pushLiveEvent("question-response-transcribed", {
|
|
1117
|
+
questionId: msg.questionId,
|
|
1118
|
+
transcription: result.transcription || null,
|
|
1119
|
+
error: result.error || undefined,
|
|
1120
|
+
});
|
|
1121
|
+
})
|
|
1122
|
+
.catch((err) => {
|
|
1123
|
+
console.error(`[${BRIDGE_NAME}] Question response transcription failed:`, err);
|
|
1124
|
+
liveInteractions.push({
|
|
1125
|
+
type: "tester-response",
|
|
1126
|
+
questionId: msg.questionId,
|
|
1127
|
+
timestamp: msg.timestamp || Date.now(),
|
|
1128
|
+
transcription: null,
|
|
1129
|
+
screenshot: !!msg.screenshot,
|
|
1130
|
+
highlightCount: msg.highlightCaptures?.length || 0,
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
} else {
|
|
1134
|
+
// No voice — log interaction immediately
|
|
1135
|
+
liveInteractions.push({
|
|
1136
|
+
type: "tester-response",
|
|
1137
|
+
questionId: msg.questionId,
|
|
1138
|
+
timestamp: msg.timestamp || Date.now(),
|
|
1139
|
+
transcription: null,
|
|
1140
|
+
screenshot: !!msg.screenshot,
|
|
1141
|
+
highlightCount: msg.highlightCaptures?.length || 0,
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1092
1144
|
break;
|
|
1093
1145
|
}
|
|
1094
1146
|
|
|
@@ -1188,8 +1240,11 @@ server.tool(
|
|
|
1188
1240
|
"- You MUST keep calling this tool every 2-4 seconds in a loop until sessionActive becomes false.\n" +
|
|
1189
1241
|
"- Do NOT stop polling just because there are no new events — the tester is still active.\n" +
|
|
1190
1242
|
"- 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" +
|
|
1243
|
+
"- When you see 'question-response-received' events, use get_tester_response to retrieve the full answer with images.\n" +
|
|
1244
|
+
"- When you see 'question-response-transcribed' events, the tester's voice response has been transcribed — read it for context.\n" +
|
|
1245
|
+
"- Marker events with hasPendingVoice=true will have their voiceTranscription field populated once transcription completes.\n" +
|
|
1192
1246
|
"- The tester may set voice markers at any time to communicate with you — keep watching for them.\n" +
|
|
1247
|
+
"- If you need clarification, use ask_tester_question to show a toast notification to the tester.\n" +
|
|
1193
1248
|
"- Only stop polling when sessionActive is false (the recording ended).",
|
|
1194
1249
|
{
|
|
1195
1250
|
since_seq: z
|
|
@@ -1209,12 +1264,33 @@ server.tool(
|
|
|
1209
1264
|
|
|
1210
1265
|
const latestSeq = liveEvents.length > 0 ? liveEvents[liveEvents.length - 1].seq : 0;
|
|
1211
1266
|
|
|
1267
|
+
// Enrich marker events with their transcriptions (if available in the buffer)
|
|
1268
|
+
// so agents don't have to correlate separate marker + voice-transcription events
|
|
1269
|
+
const transcriptionsByMarkerId = new Map();
|
|
1270
|
+
for (const e of liveEvents) {
|
|
1271
|
+
if (e.eventType === "voice-transcription" && e.data?.markerId) {
|
|
1272
|
+
transcriptionsByMarkerId.set(e.data.markerId, e.data.transcription);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
const enrichedEvents = events.map((e) => {
|
|
1276
|
+
if (e.eventType === "marker" && e.data?.markerId && transcriptionsByMarkerId.has(e.data.markerId)) {
|
|
1277
|
+
return {
|
|
1278
|
+
...e,
|
|
1279
|
+
data: {
|
|
1280
|
+
...e.data,
|
|
1281
|
+
voiceTranscription: transcriptionsByMarkerId.get(e.data.markerId),
|
|
1282
|
+
},
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
return e;
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1212
1288
|
const responseData = {
|
|
1213
1289
|
sessionActive: liveSessionActive,
|
|
1214
1290
|
session: liveSessionMeta,
|
|
1215
|
-
events,
|
|
1291
|
+
events: enrichedEvents,
|
|
1216
1292
|
latestSeq,
|
|
1217
|
-
eventCount:
|
|
1293
|
+
eventCount: enrichedEvents.length,
|
|
1218
1294
|
};
|
|
1219
1295
|
|
|
1220
1296
|
if (liveSessionActive) {
|
|
@@ -1241,12 +1317,23 @@ server.tool(
|
|
|
1241
1317
|
waitingSince: Math.round((Date.now() - q.sentAt) / 1000) + "s ago",
|
|
1242
1318
|
}));
|
|
1243
1319
|
}
|
|
1244
|
-
} else
|
|
1245
|
-
// Session ended — include full interaction log
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1320
|
+
} else {
|
|
1321
|
+
// Session ended — include full interaction log and voice transcriptions
|
|
1322
|
+
if (liveInteractions.length > 0 || liveEvents.some((e) => e.eventType === "voice-transcription")) {
|
|
1323
|
+
responseData.sessionEndedNote =
|
|
1324
|
+
"Session has ended. The interactionLog and voiceMarkerTranscriptions below contain all agent-tester exchanges from this session. " +
|
|
1325
|
+
"Include these in your analysis — they represent collaborative context between agent and tester. " +
|
|
1326
|
+
"You can also call get_live_session_summary for the complete summary.";
|
|
1327
|
+
responseData.interactionLog = liveInteractions;
|
|
1328
|
+
// Include all voice marker transcriptions
|
|
1329
|
+
responseData.voiceMarkerTranscriptions = liveEvents
|
|
1330
|
+
.filter((e) => e.eventType === "voice-transcription" && e.data?.transcription)
|
|
1331
|
+
.map((e) => ({
|
|
1332
|
+
timestamp: e.ts,
|
|
1333
|
+
markerId: e.data.markerId,
|
|
1334
|
+
transcription: e.data.transcription,
|
|
1335
|
+
}));
|
|
1336
|
+
}
|
|
1250
1337
|
}
|
|
1251
1338
|
|
|
1252
1339
|
// Always include interaction count so the agent knows exchanges happened
|
|
@@ -1428,33 +1515,30 @@ server.tool(
|
|
|
1428
1515
|
}
|
|
1429
1516
|
}
|
|
1430
1517
|
|
|
1431
|
-
let transcriptionText = null;
|
|
1432
1518
|
if (response.voiceBlobDataUrl) {
|
|
1433
|
-
|
|
1434
|
-
if (
|
|
1435
|
-
transcriptionText = voiceResult.transcription;
|
|
1519
|
+
// Use pre-transcribed result if available (from eager transcription on response arrival)
|
|
1520
|
+
if (response.transcription) {
|
|
1436
1521
|
content.push({
|
|
1437
1522
|
type: "text",
|
|
1438
|
-
text: `Voice transcription:\n${
|
|
1523
|
+
text: `Voice transcription:\n${response.transcription}`,
|
|
1439
1524
|
});
|
|
1440
1525
|
} else {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1526
|
+
// Fallback: transcribe now if eager transcription hasn't completed yet
|
|
1527
|
+
const voiceResult = await transcribeLiveVoice(response.voiceBlobDataUrl);
|
|
1528
|
+
if (voiceResult.transcription) {
|
|
1529
|
+
content.push({
|
|
1530
|
+
type: "text",
|
|
1531
|
+
text: `Voice transcription:\n${voiceResult.transcription}`,
|
|
1532
|
+
});
|
|
1533
|
+
} else {
|
|
1534
|
+
content.push({
|
|
1535
|
+
type: "text",
|
|
1536
|
+
text: `Voice note is attached but could not be transcribed${voiceResult.error ? ` (${voiceResult.error})` : ""}. Set OPENROUTER_API_KEY or install local Whisper for transcription.`,
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1445
1539
|
}
|
|
1446
1540
|
}
|
|
1447
1541
|
|
|
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
1542
|
content.push({
|
|
1459
1543
|
type: "text",
|
|
1460
1544
|
text: "\nIMPORTANT: Resume polling watch_live_session to continue watching the session.",
|