From c8c99445eacfcd0e201aedc24eb1ee5544ab9e1a Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 5 Mar 2026 11:11:53 +0900 Subject: [PATCH] fix(look-at): add catch block to prevent TUI crash on unexpected errors --- src/tools/look-at/tools.test.ts | 90 +++++++++++++++++++++++++++++++++ src/tools/look-at/tools.ts | 4 ++ 2 files changed, 94 insertions(+) diff --git a/src/tools/look-at/tools.test.ts b/src/tools/look-at/tools.test.ts index bb71703b..3312fbbd 100644 --- a/src/tools/look-at/tools.test.ts +++ b/src/tools/look-at/tools.test.ts @@ -456,6 +456,96 @@ describe("look-at tool", () => { }) }) + describe("createLookAt unhandled error resilience", () => { + const createToolContext = (): ToolContext => ({ + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + directory: "/project", + worktree: "/project", + abort: new AbortController().signal, + metadata: () => {}, + ask: async () => {}, + }) + + // given session.create throws (network error, not error response) + // when LookAt tool executed + // then returns error string instead of crashing + test("catches session.create throw and returns error string", async () => { + const mockClient = { + session: { + get: async () => ({ data: { directory: "/project" } }), + create: async () => { throw new Error("ECONNREFUSED: connection refused") }, + }, + } + + const tool = createLookAt({ + client: mockClient, + directory: "/project", + } as any) + + const result = await tool.execute( + { file_path: "/test/file.png", goal: "analyze" }, + createToolContext(), + ) + expect(result).toContain("Error") + expect(result).toContain("ECONNREFUSED") + }) + + // given session.messages throws unexpectedly + // when LookAt tool executed + // then returns error string instead of crashing + test("catches session.messages throw and returns error string", async () => { + const mockClient = { + app: { + agents: async () => ({ data: [] }), + }, + session: { + get: async () => ({ data: { directory: "/project" } }), + create: async () => ({ data: { id: "ses_msg_throw" } }), + prompt: async () => ({}), + messages: async () => { throw new Error("Unexpected server error") }, + }, + } + + const tool = createLookAt({ + client: mockClient, + directory: "/project", + } as any) + + const result = await tool.execute( + { file_path: "/test/file.png", goal: "analyze" }, + createToolContext(), + ) + expect(result).toContain("Error") + expect(result).toContain("Unexpected server error") + }) + + // given a non-Error object is thrown + // when LookAt tool executed + // then still returns error string + test("handles non-Error thrown objects gracefully", async () => { + const mockClient = { + session: { + get: async () => ({ data: { directory: "/project" } }), + create: async () => { throw "string error thrown" }, + }, + } + + const tool = createLookAt({ + client: mockClient, + directory: "/project", + } as any) + + const result = await tool.execute( + { file_path: "/test/file.png", goal: "analyze" }, + createToolContext(), + ) + expect(result).toContain("Error") + expect(result).toContain("string error thrown") + }) + }) + describe("createLookAt with image_data", () => { // given base64 image data is provided // when LookAt tool executed diff --git a/src/tools/look-at/tools.ts b/src/tools/look-at/tools.ts index 423b1f91..c363fe77 100644 --- a/src/tools/look-at/tools.ts +++ b/src/tools/look-at/tools.ts @@ -217,6 +217,10 @@ Original error: ${createResult.error}` log(`[look_at] Got response, length: ${responseText.length}`) return responseText + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + log(`[look_at] Unexpected error analyzing ${sourceDescription}:`, error) + return `Error: Failed to analyze ${sourceDescription}: ${errorMessage}` } finally { if (tempConversionPath) { cleanupConvertedImage(tempConversionPath)