@@ -33,23 +33,47 @@ function parseSdkResponseBody(raw: string): ApiErrorBody {
3333 return { message : raw . trim ( ) || undefined } ;
3434}
3535
36+ /**
37+ * The SDK's session polling deadline surfaces as a plain `UserError` (no
38+ * dedicated timeout class as of SDK 0.3.x), so it is recognized by its stable
39+ * message shape: "Session did not complete within the timeout (N seconds)."
40+ * (session-runtime's assertNotTimedOut — the SDK's only timeout UserError).
41+ * It is a client-side wait limit, not a usage mistake → per bl's error
42+ * boundary it must exit TIMEOUT, not USAGE.
43+ */
44+ function isSdkPollingTimeout ( error : UserError ) : boolean {
45+ return / d i d n o t c o m p l e t e w i t h i n t h e t i m e o u t / i. test ( error . message ) ;
46+ }
47+
3648/**
3749 * Run an SDK-backed operation, translating SDK error types into BailianError so
3850 * bl's error handler produces the right exit code and hint formatting.
39- * SDK `UserError` → USAGE; SDK `ApiError` (server HTTP error) → GENERAL via
40- * `mapApiError` (server message passed through verbatim, with
41- * httpStatus/apiCode/requestId metadata for --output json); fetch transport
42- * failures (`TypeError: fetch failed`) are rethrown untouched so the runtime
43- * error handler maps them to NETWORK with an errno-specific hint, matching the
44- * native client path; any other Error → GENERAL (message passed through, per
45- * bl's "don't translate server errors" boundary).
51+ * SDK `UserError` → USAGE — except the polling-deadline UserError, which is a
52+ * client-side timeout → TIMEOUT with a wait-longer hint; SDK `ApiError`
53+ * (server HTTP error) → GENERAL via `mapApiError` (server message passed
54+ * through verbatim, with httpStatus/apiCode/requestId metadata for
55+ * --output json); fetch transport failures (`TypeError: fetch failed`) are
56+ * rethrown untouched so the runtime error handler maps them to NETWORK with an
57+ * errno-specific hint, matching the native client path; any other Error →
58+ * GENERAL (message passed through, per bl's "don't translate server errors"
59+ * boundary).
4660 */
4761export async function withAgentErrors < T > ( fn : ( ) => Promise < T > ) : Promise < T > {
4862 try {
4963 return await fn ( ) ;
5064 } catch ( error ) {
5165 if ( error instanceof BailianError ) throw error ;
52- if ( error instanceof UserError ) throw new BailianError ( error . message , ExitCode . USAGE ) ;
66+ if ( error instanceof UserError ) {
67+ if ( isSdkPollingTimeout ( error ) ) {
68+ throw new BailianError (
69+ error . message ,
70+ ExitCode . TIMEOUT ,
71+ // `bl` prefix is safe: agent commands ship on `bl` only.
72+ "The session may still be running — check `bl managed-agent session get --session-id <id>` or `session events`." ,
73+ ) ;
74+ }
75+ throw new BailianError ( error . message , ExitCode . USAGE ) ;
76+ }
5377 if ( error instanceof Error && isSdkApiError ( error ) ) {
5478 throw mapApiError ( error . statusCode , parseSdkResponseBody ( error . responseBody ) ) ;
5579 }
0 commit comments