Skip to content

Commit c280018

Browse files
committed
fix(mcp): keep cursor start before the recording state, and track waited-for takes
Restore set-recording-state's original order so startRecording cannot return before cursor telemetry has started, document that a session's createdAt is its recordingId, and record a waited-for take's start so setPointerTrack can use it.
1 parent 7d94d17 commit c280018

3 files changed

Lines changed: 37 additions & 4 deletions

File tree

‎electron/ipc/handlers.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,15 +4149,16 @@ export function registerIpcHandlers(
41494149
ipcMain.handle(
41504150
"set-recording-state",
41514151
async (_, recording: boolean, recordingId?: number, cursorCaptureMode?: CursorCaptureMode) => {
4152-
const source = selectedSource || { name: "Screen" };
4153-
setAppRecording(recording, source.name, recordingId);
41544152
const normalizedCursorCaptureMode =
41554153
normalizeCursorCaptureMode(cursorCaptureMode) ?? "editable-overlay";
41564154
if (recording && normalizedCursorCaptureMode === "editable-overlay") {
41574155
await startCursorRecording(recordingId);
41584156
} else {
41594157
await stopCursorRecording();
41604158
}
4159+
4160+
const source = selectedSource || { name: "Screen" };
4161+
setAppRecording(recording, source.name, recordingId);
41614162
},
41624163
);
41634164

@@ -4901,6 +4902,9 @@ export function registerIpcHandlers(
49014902
currentProjectPath = null;
49024903
if (normalizedSession) {
49034904
mcpAppRecorder.takeSaved(normalizedSession.screenVideoPath);
4905+
// Every capture path stamps the session's `createdAt` with its recordingId (the
4906+
// `Date.now()` it started at), so it names the take `recordingCompletion` tracks.
4907+
// Any other session (an opened file) carries a fresh `Date.now()` and matches none.
49044908
recordingCompletion.saved(normalizedSession.createdAt, normalizedSession.screenVideoPath);
49054909
}
49064910
return { success: true, session: currentRecordingSession };

‎electron/mcp/media-tools.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,34 @@ describe("waitForRecording through MCP", () => {
11391139
expect(runners.started).toEqual([]);
11401140
});
11411141

1142+
it("gives setPointerTrack the waited-for take's start, as stopRecording does", async () => {
1143+
const project = await documents.createProject("User take");
1144+
const completion = new RecordingCompletion();
1145+
completion.started(42, 1_000);
1146+
const sent: unknown[] = [];
1147+
const { observer, subscription } = await observing(completion, {
1148+
runTool: async (_name, args) => {
1149+
sent.push(args);
1150+
return { content: [{ type: "text", text: "ok" }] };
1151+
},
1152+
});
1153+
const waiting = observer.callTool({ name: "waitForRecording", arguments: {} });
1154+
await subscription;
1155+
completion.saved(42, videoPath);
1156+
completion.completed(42, {
1157+
projectId: project.project.id,
1158+
videoPath,
1159+
durationSec: 12,
1160+
autoZooms: 0,
1161+
});
1162+
await waiting;
1163+
await observer.callTool({
1164+
name: "setPointerTrack",
1165+
arguments: { projectId: project.project.id, events: [{ at: 1, x: 0.5, y: 0.5 }] },
1166+
});
1167+
expect(sent).toContainEqual(expect.objectContaining({ takes: { [videoPath]: 1_000 } }));
1168+
});
1169+
11421170
it("releases a client take after the user finishes it, so startRecording can record again", async () => {
11431171
const completion = new RecordingCompletion();
11441172
let id = 0;

‎electron/mcp/media-tools.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,22 +631,23 @@ export function createMediaTools(deps: MediaToolDeps): MediaTools {
631631
} catch (error) {
632632
return text(error instanceof Error ? error.message : String(error), true);
633633
}
634-
startedAtByVideo.set(recorded.videoPath, running.startedAt);
635634
return recordingResult(recorded, running.startedAt);
636635
}
637636

637+
/** Also the one place a take's start is kept, so setPointerTrack finds a waited-for take. */
638638
async function recordingResult(
639639
recorded: RecordedTake,
640640
recordingStartedAt: number,
641641
): Promise<CallToolResult> {
642+
startedAtByVideo.set(recorded.videoPath, recordingStartedAt);
642643
const { projectId } = recorded;
643644
return text(
644645
JSON.stringify({
645646
projectId,
646647
checkpointId: await checkpointOf(projectId),
647648
videoPath: recorded.videoPath,
648649
durationSec: recorded.durationSec,
649-
recordingStartedAt: recordingStartedAt,
650+
recordingStartedAt,
650651
autoZooms: recorded.autoZooms,
651652
...(recorded.warnings?.length ? { warnings: recorded.warnings } : {}),
652653
}),

0 commit comments

Comments
 (0)