From 3adfe9be379c46a3a726675fc4605d50065b1a9f Mon Sep 17 00:00:00 2001 From: Jason Carter Date: Thu, 21 May 2026 22:09:39 +0800 Subject: [PATCH] fix: add request expiration to extension queue to prevent stale request blocking Bridge now sends _deadline with each request. Extension skips expired requests that sat too long in the serial queue, preventing cascading timeouts when prior test/agent operations leave orphaned work. Also accept 'file' as parameter alias in file read/write/edit tools. Co-authored-by: Cursor --- packages/chrome-extension/src/protocol.ts | 10 ++++++++++ packages/core/src/tools/file.ts | 6 +++--- packages/core/src/tools/markus-browser-bridge.ts | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/chrome-extension/src/protocol.ts b/packages/chrome-extension/src/protocol.ts index 0120c8b8..635adb93 100644 --- a/packages/chrome-extension/src/protocol.ts +++ b/packages/chrome-extension/src/protocol.ts @@ -7,6 +7,7 @@ export interface BridgeRequest { id: number; method: string; params: Record; + _deadline?: number; } export interface BridgeResponse { @@ -89,12 +90,21 @@ export class BridgeClient { * when multiple agents issue concurrent tool calls. */ private enqueueRequest(req: BridgeRequest): void { + if (!req._deadline) { + req._deadline = Date.now() + 110_000; + } this.requestQueue = this.requestQueue .then(() => this.handleRequest(req)) .catch((err) => console.error('[Markus] Request queue error:', err)); } private async handleRequest(req: BridgeRequest): Promise { + if (req._deadline && Date.now() > req._deadline) { + console.warn(`[Markus] Skipping expired request ${req.method} (id=${req.id})`); + this.send({ id: req.id, error: `Request expired while queued (bridge timeout likely already fired)` }); + return; + } + const handler = this.handlers.get(req.method); if (!handler) { this.send({ id: req.id, error: `Unknown method: ${req.method}` }); diff --git a/packages/core/src/tools/file.ts b/packages/core/src/tools/file.ts index 419a9f07..fdb27e92 100644 --- a/packages/core/src/tools/file.ts +++ b/packages/core/src/tools/file.ts @@ -52,7 +52,7 @@ export function createFileReadTool(security?: SecurityGuard, workspacePath?: str }, async execute(args: Record): Promise { - const rawPath = (args['path'] ?? args['file_path'] ?? args['filePath']) as string | undefined; + const rawPath = (args['path'] ?? args['file'] ?? args['file_path'] ?? args['filePath']) as string | undefined; if (!rawPath || typeof rawPath !== 'string') { return JSON.stringify({ status: 'error', error: 'path is required and must be a non-empty string' }); } @@ -145,7 +145,7 @@ export function createFileWriteTool(security?: SecurityGuard, workspacePath?: st }, async execute(args: Record): Promise { - const rawPath = (args['path'] ?? args['file_path'] ?? args['filePath']) as string; + const rawPath = (args['path'] ?? args['file'] ?? args['file_path'] ?? args['filePath']) as string; const { resolved: path, access } = resolveAndCheckAccess(rawPath, workspacePath, policy); if (access === 'denied') { @@ -187,7 +187,7 @@ export function createFileEditTool(security?: SecurityGuard, workspacePath?: str }, async execute(args: Record): Promise { - const rawPath = (args['path'] ?? args['file_path'] ?? args['filePath']) as string; + const rawPath = (args['path'] ?? args['file'] ?? args['file_path'] ?? args['filePath']) as string; const { resolved: path, access } = resolveAndCheckAccess(rawPath, workspacePath, policy); if (access === 'denied') { diff --git a/packages/core/src/tools/markus-browser-bridge.ts b/packages/core/src/tools/markus-browser-bridge.ts index ad4eba2a..88cd43ac 100644 --- a/packages/core/src/tools/markus-browser-bridge.ts +++ b/packages/core/src/tools/markus-browser-bridge.ts @@ -124,7 +124,8 @@ export class MarkusBrowserBridge { } const id = ++this.requestId; - const message = JSON.stringify({ id, method: name, params: args }); + const _deadline = Date.now() + TOOL_CALL_TIMEOUT_MS; + const message = JSON.stringify({ id, method: name, params: args, _deadline }); return new Promise((resolve, reject) => { const timer = setTimeout(() => {