fix(agent): restore MCP wait timeout and fix direct URL navigation

- Restore 50-second self-imposed timeout for agent wait operations in MCP
  server. Returns friendly message instead of letting SDK timeout trigger
  generic "tool failed" error that confuses orchestrator agents.

- Fix direct URL navigation to agent screens by waiting for WebSocket
  connection before initializing. Previously, init would fire before WS
  connected, fail silently, and never retry due to guard ref.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-01-05 23:47:58 +07:00
parent 348242541c
commit 33a3031f74
2 changed files with 88 additions and 9 deletions

View File

@@ -438,6 +438,11 @@ function AgentScreenContent({
return;
}
// Skip if not connected - will re-run when connection is established
if (!ws?.isConnected) {
return;
}
// Skip if already initializing
if (isInitializingFromMap === true) {
return;
@@ -459,7 +464,7 @@ function AgentScreenContent({
});
initializeAgent({ agentId: resolvedAgentId });
}, [resolvedAgentId, initializeAgent, isInitializingFromMap]);
}, [resolvedAgentId, initializeAgent, isInitializingFromMap, ws?.isConnected]);
useEffect(() => {
if (Platform.OS !== "web") {

View File

@@ -92,6 +92,9 @@ const AgentStatusEnum = z.enum([
"closed",
]);
// 50 seconds - surface friendly message before SDK tool timeout (~60s)
const AGENT_WAIT_TIMEOUT_MS = 50000;
function expandPath(path: string): string {
if (path.startsWith("~/") || path === "~") {
return resolve(homedir(), path.slice(2));
@@ -99,6 +102,74 @@ function expandPath(path: string): string {
return resolve(path);
}
/**
* Wraps agentManager.waitForAgentEvent with a self-imposed timeout.
* Returns a friendly message when timeout occurs, rather than letting
* the SDK tool timeout trigger a generic "tool failed" error.
*/
async function waitForAgentWithTimeout(
agentManager: AgentManager,
agentId: string,
options?: {
signal?: AbortSignal;
waitForActive?: boolean;
}
): Promise<WaitForAgentResult> {
const timeoutController = new AbortController();
const combinedController = new AbortController();
const timeoutId = setTimeout(() => {
timeoutController.abort(new Error("wait timeout"));
}, AGENT_WAIT_TIMEOUT_MS);
const forwardAbort = (reason: unknown) => {
if (!combinedController.signal.aborted) {
combinedController.abort(reason);
}
};
// Forward external signal abort
if (options?.signal) {
if (options.signal.aborted) {
forwardAbort(options.signal.reason);
} else {
options.signal.addEventListener(
"abort",
() => forwardAbort(options.signal!.reason),
{ once: true }
);
}
}
// Forward timeout abort
timeoutController.signal.addEventListener(
"abort",
() => forwardAbort(timeoutController.signal.reason),
{ once: true }
);
try {
const result = await agentManager.waitForAgentEvent(agentId, {
signal: combinedController.signal,
waitForActive: options?.waitForActive,
});
return result;
} catch (error) {
if (error instanceof Error && error.message === "wait timeout") {
const snapshot = agentManager.getAgent(agentId);
return {
status: snapshot?.lifecycle ?? "idle",
permission: null,
lastMessage:
"Awaiting the agent timed out. This does not mean the agent failed - call wait_for_agent again to continue waiting.",
};
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
function startAgentRun(
agentManager: AgentManager,
agentId: string,
@@ -373,9 +444,11 @@ export async function createAgentMcpServer(
// If not running in background, wait for completion
if (!background) {
const result = await agentManager.waitForAgentEvent(snapshot.id, {
waitForActive: true,
});
const result = await waitForAgentWithTimeout(
agentManager,
snapshot.id,
{ waitForActive: true }
);
const responseData = {
agentId: snapshot.id,
@@ -481,10 +554,11 @@ export async function createAgentMcpServer(
cleanupFns.push(unregister);
try {
const result: WaitForAgentResult =
await agentManager.waitForAgentEvent(agentId, {
signal: abortController.signal,
});
const result: WaitForAgentResult = await waitForAgentWithTimeout(
agentManager,
agentId,
{ signal: abortController.signal }
);
const validJson = ensureValidJson({
agentId,
@@ -596,7 +670,7 @@ export async function createAgentMcpServer(
// If not running in background, wait for completion
if (!background) {
const result = await agentManager.waitForAgentEvent(agentId, {
const result = await waitForAgentWithTimeout(agentManager, agentId, {
waitForActive: true,
});