From a03ee0c72ce8d57c969811328732f97a5052cc8a Mon Sep 17 00:00:00 2001 From: chenanran555 Date: Mon, 27 Jul 2026 20:23:21 +0800 Subject: [PATCH] fix(agent): timeout error --- .../commands/managed-agent/_engine/errors.ts | 40 +++++++++++++++---- .../tests/managed-agent-errors.test.ts | 16 ++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/commands/src/commands/managed-agent/_engine/errors.ts b/packages/commands/src/commands/managed-agent/_engine/errors.ts index 32c5c9c..f22571c 100644 --- a/packages/commands/src/commands/managed-agent/_engine/errors.ts +++ b/packages/commands/src/commands/managed-agent/_engine/errors.ts @@ -33,23 +33,47 @@ function parseSdkResponseBody(raw: string): ApiErrorBody { return { message: raw.trim() || undefined }; } +/** + * The SDK's session polling deadline surfaces as a plain `UserError` (no + * dedicated timeout class as of SDK 0.3.x), so it is recognized by its stable + * message shape: "Session did not complete within the timeout (N seconds)." + * (session-runtime's assertNotTimedOut — the SDK's only timeout UserError). + * It is a client-side wait limit, not a usage mistake → per bl's error + * boundary it must exit TIMEOUT, not USAGE. + */ +function isSdkPollingTimeout(error: UserError): boolean { + return /did not complete within the timeout/i.test(error.message); +} + /** * Run an SDK-backed operation, translating SDK error types into BailianError so * bl's error handler produces the right exit code and hint formatting. - * SDK `UserError` → USAGE; SDK `ApiError` (server HTTP error) → GENERAL via - * `mapApiError` (server message passed through verbatim, with - * httpStatus/apiCode/requestId metadata for --output json); fetch transport - * failures (`TypeError: fetch failed`) are rethrown untouched so the runtime - * error handler maps them to NETWORK with an errno-specific hint, matching the - * native client path; any other Error → GENERAL (message passed through, per - * bl's "don't translate server errors" boundary). + * SDK `UserError` → USAGE — except the polling-deadline UserError, which is a + * client-side timeout → TIMEOUT with a wait-longer hint; SDK `ApiError` + * (server HTTP error) → GENERAL via `mapApiError` (server message passed + * through verbatim, with httpStatus/apiCode/requestId metadata for + * --output json); fetch transport failures (`TypeError: fetch failed`) are + * rethrown untouched so the runtime error handler maps them to NETWORK with an + * errno-specific hint, matching the native client path; any other Error → + * GENERAL (message passed through, per bl's "don't translate server errors" + * boundary). */ export async function withAgentErrors(fn: () => Promise): Promise { try { return await fn(); } catch (error) { if (error instanceof BailianError) throw error; - if (error instanceof UserError) throw new BailianError(error.message, ExitCode.USAGE); + if (error instanceof UserError) { + if (isSdkPollingTimeout(error)) { + throw new BailianError( + error.message, + ExitCode.TIMEOUT, + // `bl` prefix is safe: agent commands ship on `bl` only. + "The session may still be running — check `bl managed-agent session get --session-id ` or `session events`.", + ); + } + throw new BailianError(error.message, ExitCode.USAGE); + } if (error instanceof Error && isSdkApiError(error)) { throw mapApiError(error.statusCode, parseSdkResponseBody(error.responseBody)); } diff --git a/packages/commands/tests/managed-agent-errors.test.ts b/packages/commands/tests/managed-agent-errors.test.ts index 238cb7e..8af71b7 100644 --- a/packages/commands/tests/managed-agent-errors.test.ts +++ b/packages/commands/tests/managed-agent-errors.test.ts @@ -39,6 +39,22 @@ test("SDK UserError maps to USAGE", async () => { expect(mapped.message).toBe("bad agents.yaml"); }); +test("SDK polling-timeout UserError maps to TIMEOUT (5), not USAGE", async () => { + // 消息形状来自 SDK session-runtime 的 assertNotTimedOut —— 客户端等待超时, + // 按 bl 错误边界必须归 TIMEOUT,不能告诉自动化调用方“参数错误”。 + const mapped = await catchMapped( + new UserError("Session did not complete within the timeout (600 seconds)."), + ); + expect(mapped.exitCode).toBe(ExitCode.TIMEOUT); + expect(mapped.message).toBe("Session did not complete within the timeout (600 seconds)."); + expect(mapped.hint).toMatch(/session get/); +}); + +test("提及 timeout 但非轮询超时句式的 UserError 仍归 USAGE", async () => { + const mapped = await catchMapped(new UserError("Invalid timeout value in agents.yaml")); + expect(mapped.exitCode).toBe(ExitCode.USAGE); +}); + test("SDK ApiError with DashScope-style JSON body surfaces clean message and api metadata", async () => { const body = JSON.stringify({ code: "InvalidParameter",