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
33 changes: 33 additions & 0 deletions src/provider/cursor-log-intercept.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,34 @@ function matchesKnownSdkWarning(line: string): boolean {
return SDK_WARNING_PREFIXES.some((prefix) => line.startsWith(prefix));
}

/**
* Slow-cache diagnostic the SDK `console.warn`s when global context rebuild
* exceeds its threshold. Observed shape (colors stripped; the leading
* timestamp token varies in width, e.g. `13:04:05.123` vs `113:26:23.106`):
*
* 113:26:23.106 WARN computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache meta=/totalMs: 1312, cloudRule: 0, codebaseRef: 0, subagents: 416, cursorRules: 1311, ruleCount: 701
*
* The `/` after `meta=` is how the SDK prints its empty context object.
*/
const SLOW_CACHE_WARN_RE =
/^\d{2,3}:\d{2}:\d{2}\.\d{3}\s+WARN\s+(computeGlobalCache: slow .+?)\s+meta=\/?\s*(.+)$/;

export interface ParsedSlowCacheWarn {
message: string;
meta: Record<string, number>;
}

/** Matches one line against the known slow-cache warn shape. */
export function parseSlowCacheWarnLine(
line: string,
): ParsedSlowCacheWarn | undefined {
const match = SLOW_CACHE_WARN_RE.exec(stripAnsi(line));
if (!match) return undefined;
const [, message, meta] = match;
if (!message) return undefined;
return { message: message.trim(), meta: parseCursorLogMeta(meta ?? "") };
}

/** Parses the `meta={key: value, ...}` tail into a plain numeric object. */
export function parseCursorLogMeta(raw: string): Record<string, number> {
const out: Record<string, number> = {};
Expand Down Expand Up @@ -109,6 +137,11 @@ export function installCursorLogInterceptor(): void {
pluginLog("warn", line);
return;
}
const slowCache = parseSlowCacheWarnLine(line);
if (slowCache) {
pluginLog("warn", slowCache.message, slowCache.meta);
return;
}
}
warnPassthrough(...(args as Parameters<typeof console.warn>));
};
Expand Down
17 changes: 17 additions & 0 deletions src/sidecar/agent-host.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ const SDK_WARNING_PREFIXES = [
"shell-parser: tree-sitter natives are unavailable in this artifact",
];

// Slow global-cache rebuild diagnostic (timestamp token varies in width,
// e.g. `13:04:05.123` vs `113:26:23.106`; `/` after `meta=` is the SDK
// printing its empty context object).
const SLOW_CACHE_WARN_RE =
/^\d{2,3}:\d{2}:\d{2}\.\d{3}\s+WARN\s+(computeGlobalCache: slow .+?)\s+meta=\/?\s*(.+)$/;

function parseLogMeta(raw) {
const out = {};
for (const part of raw.split(",")) {
Expand Down Expand Up @@ -101,6 +107,17 @@ console.warn = (...args) => {
write({ ev: "log", level: "warn", message: line });
return;
}
const slowCache = SLOW_CACHE_WARN_RE.exec(line);
if (slowCache) {
const [, message, meta] = slowCache;
write({
ev: "log",
level: "warn",
message: message.trim(),
meta: parseLogMeta(meta ?? ""),
});
return;
}
}
originalConsoleWarn(...args);
};
Expand Down
38 changes: 38 additions & 0 deletions test/cursor-log-intercept.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,44 @@ describe("installCursorLogInterceptor", () => {
passthrough.mockRestore();
});

it("routes the computeGlobalCache slow warn through pluginLog with parsed meta", () => {
const log = vi.fn().mockResolvedValue(undefined);
setLogBridge({ client: { app: { log } } } as never);

const passthrough = vi.spyOn(console, "warn").mockImplementation(() => {});
installCursorLogInterceptor();

console.warn(
"113:26:23.106 WARN computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache meta=/totalMs: 1312, cloudRule: 0, codebaseRef: 0, subagents: 416, cursorRules: 1311, ruleCount: 701",
);
console.warn("unrelated warning");

expect(log).toHaveBeenCalledTimes(1);
expect(log).toHaveBeenCalledWith({
body: {
service: "opencode-cursor",
level: "warn",
message:
"computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache",
extra: {
totalMs: 1312,
cloudRule: 0,
codebaseRef: 0,
subagents: 416,
cursorRules: 1311,
ruleCount: 701,
},
},
});

resetCursorLogInterceptor();
// The spy is the pre-interceptor console.warn; passthrough calls must
// reach it, but the recognized line must not.
expect(passthrough).toHaveBeenCalledTimes(1);
expect(passthrough).toHaveBeenCalledWith("unrelated warning");
passthrough.mockRestore();
});

it("is idempotent across repeated installs", () => {
installCursorLogInterceptor();
const first = console.log;
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/fake-cursor-sdk.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
* `options.emitShellParserWarn` -> Agent.create/resume writes the shell-parser
* "tree-sitter natives unavailable" diagnostic to console.warn, as the real
* @cursor/sdk does on first shell parse, plus one unrelated console.warn.
*
* `options.emitSlowCacheWarn` -> Agent.create/resume writes the
* computeGlobalCache "slow" diagnostic to console.warn, as the real
* @cursor/sdk does on slow global-cache rebuilds, plus one unrelated
* console.warn.
*/

function makeAgent(agentId, options) {
Expand All @@ -36,6 +41,12 @@ function makeAgent(agentId, options) {
);
console.warn("some unrelated cursor sdk warning");
}
if (options?.emitSlowCacheWarn) {
console.warn(
"113:26:23.106 WARN computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache meta=/totalMs: 1312, cloudRule: 0, codebaseRef: 0, subagents: 416, cursorRules: 1311, ruleCount: 701",
);
console.warn("some unrelated slow-cache warning");
}
return {
agentId,
model: options?.model,
Expand Down
19 changes: 18 additions & 1 deletion test/sidecar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,31 @@ describe("SidecarClient", () => {
const client = makeClient((level, message, meta) => {
logs.push({ level, message, meta });
});
await client.createAgent({ ...CREATE_OPTIONS, emitShellParserWarn: true });
await client.createAgent({
...CREATE_OPTIONS,
emitShellParserWarn: true,
emitSlowCacheWarn: true,
});

expect(logs).toEqual([
{
level: "warn",
message:
"shell-parser: tree-sitter natives are unavailable in this artifact; shell command analysis degrades to parsingFailed",
},
{
level: "warn",
message:
"computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache",
meta: {
totalMs: 1312,
cloudRule: 0,
codebaseRef: 0,
subagents: 416,
cursorRules: 1311,
ruleCount: 701,
},
},
]);
});

Expand Down