Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions src/adapters/cursor-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export class CursorAgentAdapter extends BaseAdapter implements RunnerAdapter {
const observedReads: string[] = [];
const explicitSkillNames = new Set<string>();
const seenToolCalls = new Set<string>();
const seenCommands = new Set<string>();
const seenReadPaths = new Set<string>();
const seenSkillSignals = new Set<string>();
const callBaseDirs = new Map<string, string>();

let sessionCwd = input.cwd;
Expand Down Expand Up @@ -110,15 +113,15 @@ export class CursorAgentAdapter extends BaseAdapter implements RunnerAdapter {
continue;
}

const callId = readString(record, "call_id");
const callId = readCursorToolCallId(record);
const isCompleted = readString(record, "subtype") === "completed";
const hasEmittedCall = callId !== undefined && seenToolCalls.has(callId);
const baseDir =
callId === undefined
? resolveToolBaseDir(toolCall.args, sessionCwd)
: resolveCursorCallBaseDir(callId, toolCall.args, sessionCwd, callBaseDirs);

if (!hasEmittedCall) {
if (!hasEmittedCall && !isCompleted) {
events.push({
type: "toolCall",
tool: toolCall.tool,
Expand All @@ -132,11 +135,14 @@ export class CursorAgentAdapter extends BaseAdapter implements RunnerAdapter {
}

const command = extractCommand(toolCall.tool, toolCall.args);
if (command !== undefined) {
if (command !== undefined && shouldEmitCursorCallDetail(seenCommands, callId, command)) {
events.push({ type: "command", command, at });
for (const filePath of extractFilePathsFromCommand(command)) {
const resolvedPath = resolveReportedPath(filePath, baseDir);
if (resolvedPath === undefined) {
if (
resolvedPath === undefined ||
!shouldEmitCursorCallDetail(seenReadPaths, callId, resolvedPath)
) {
continue;
}

Expand All @@ -148,14 +154,20 @@ export class CursorAgentAdapter extends BaseAdapter implements RunnerAdapter {
const readPath = extractReadPath(toolCall.tool, toolCall.args);
if (readPath !== undefined) {
const resolvedPath = resolveReportedPath(readPath, baseDir);
if (resolvedPath !== undefined) {
if (
resolvedPath !== undefined &&
shouldEmitCursorCallDetail(seenReadPaths, callId, resolvedPath)
) {
observedReads.push(resolvedPath);
events.push({ type: "fileRead", path: resolvedPath, at });
}
}

const skillName = extractSkillName(toolCall.tool, toolCall.args);
if (skillName !== undefined) {
if (
skillName !== undefined &&
shouldEmitCursorCallDetail(seenSkillSignals, callId, skillName)
) {
explicitSkillNames.add(skillName);
events.push({ type: "skillSignal", skill: skillName, signal: "tool:skill", at });
}
Expand Down Expand Up @@ -297,6 +309,10 @@ function readTimestamp(record: Record<string, unknown>): string | undefined {
return typeof timestamp === "number" ? new Date(timestamp).toISOString() : undefined;
}

function readCursorToolCallId(record: Record<string, unknown>): string | undefined {
return readString(record, "call_id") ?? readString(record, "model_call_id");
}

function extractMessageText(message: Record<string, unknown>): string {
const content = message.content;
if (!Array.isArray(content)) {
Expand Down Expand Up @@ -396,6 +412,24 @@ function readToolBaseDir(args: unknown): string | undefined {
return readString(args, "workingDirectory") ?? readString(args, "cwd");
}

function shouldEmitCursorCallDetail(
seenValues: Set<string>,
callId: string | undefined,
value: string,
): boolean {
if (callId === undefined) {
return true;
}

const key = `${callId}\u0000${value}`;
if (seenValues.has(key)) {
return false;
}

seenValues.add(key);
return true;
}

function stringifyUnknown(value: unknown): string {
if (typeof value === "string") {
return value;
Expand Down
4 changes: 4 additions & 0 deletions test/adapters/cursor-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ test("CursorAgentAdapter normalize extracts commands, file reads, tool results,
expect(report.detectedSkills).toEqual(
expect.arrayContaining([expect.objectContaining({ skill: "find-skills" })]),
);
expect(report.events.filter((event) => event.type === "toolCall")).toHaveLength(1);
expect(report.events.filter((event) => event.type === "command")).toHaveLength(1);
expect(report.events).toEqual(
expect.arrayContaining([
expect.objectContaining({
Expand Down Expand Up @@ -242,6 +244,7 @@ test("CursorAgentAdapter normalize resolves shell reads against stored call work
expect(report.files.observedReads).toEqual([
"/tmp/isolated-workspace/skills/find-skills/SKILL.md",
]);
expect(report.events.filter((event) => event.type === "fileRead")).toHaveLength(1);
expect(report.events).toEqual(
expect.arrayContaining([
expect.objectContaining({
Expand Down Expand Up @@ -297,6 +300,7 @@ test("CursorAgentAdapter normalize resolves read tool calls against stored call
expect(report.detectedSkills).toEqual(
expect.arrayContaining([expect.objectContaining({ skill: "find-skills" })]),
);
expect(report.events.filter((event) => event.type === "fileRead")).toHaveLength(1);
expect(report.events).toEqual(
expect.arrayContaining([
expect.objectContaining({
Expand Down
Loading