Skip to content

Commit 6acb8a9

Browse files
committed
fix: enrich dev logs with connection retry details and full stack traces
Fixes #137
1 parent 93f6309 commit 6acb8a9

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

src/cli/operations/dev/invoke.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
/** Logger interface for SSE events */
1+
/** Logger interface for SSE events and error logging */
22
export interface SSELogger {
33
logSSEEvent(rawLine: string): void;
4+
/** Optional method to log errors and debug info */
5+
log?(level: 'error' | 'warn' | 'system', message: string): void;
46
}
57

68
/**
@@ -167,23 +169,48 @@ export async function* invokeAgentStreaming(
167169
return;
168170
} catch (err) {
169171
lastError = err instanceof Error ? err : new Error(String(err));
170-
if (lastError.message.includes('fetch') || lastError.message.includes('ECONNREFUSED')) {
171-
await sleep(baseDelay * Math.pow(2, attempt));
172+
const isConnectionError = lastError.message.includes('fetch') || lastError.message.includes('ECONNREFUSED');
173+
174+
if (isConnectionError) {
175+
const delay = baseDelay * Math.pow(2, attempt);
176+
logger?.log?.(
177+
'warn',
178+
`Connection failed (attempt ${attempt + 1}/${maxRetries}): ${lastError.message}. Retrying in ${delay}ms...`
179+
);
180+
await sleep(delay);
172181
continue;
173182
}
183+
184+
// Log non-connection errors with full stack trace before throwing
185+
logger?.log?.('error', `Request failed: ${lastError.stack ?? lastError.message}`);
174186
throw lastError;
175187
}
176188
}
177189

178-
throw lastError ?? new Error('Failed to connect to dev server after retries');
190+
// Log final failure after all retries exhausted with full details
191+
const finalError = lastError ?? new Error('Failed to connect to dev server after retries');
192+
logger?.log?.('error', `Failed to connect after ${maxRetries} attempts: ${finalError.stack ?? finalError.message}`);
193+
throw finalError;
194+
}
195+
196+
export interface InvokeOptions {
197+
port: number;
198+
message: string;
199+
/** Optional logger for error logging */
200+
logger?: SSELogger;
179201
}
180202

181203
/**
182204
* Invokes an agent running on the local dev server.
183205
* Handles both JSON and streaming text responses.
184206
* Includes retry logic for server startup race conditions.
185207
*/
186-
export async function invokeAgent(port: number, message: string): Promise<string> {
208+
export async function invokeAgent(portOrOptions: number | InvokeOptions, message?: string): Promise<string> {
209+
// Support both old signature (port, message) and new signature (options)
210+
const options: InvokeOptions =
211+
typeof portOrOptions === 'number' ? { port: portOrOptions, message: message! } : portOrOptions;
212+
const { port, message: msg, logger } = options;
213+
187214
const maxRetries = 5;
188215
const baseDelay = 500; // ms
189216
let lastError: Error | null = null;
@@ -193,7 +220,7 @@ export async function invokeAgent(port: number, message: string): Promise<string
193220
const res = await fetch(`http://localhost:${port}/invocations`, {
194221
method: 'POST',
195222
headers: { 'Content-Type': 'application/json' },
196-
body: JSON.stringify({ prompt: message }),
223+
body: JSON.stringify({ prompt: msg }),
197224
});
198225

199226
const text = await res.text();
@@ -210,16 +237,26 @@ export async function invokeAgent(port: number, message: string): Promise<string
210237
return extractResult(text);
211238
} catch (err) {
212239
lastError = err instanceof Error ? err : new Error(String(err));
213-
// Only retry on connection errors (server not ready)
214-
if (lastError.message.includes('fetch') || lastError.message.includes('ECONNREFUSED')) {
240+
const isConnectionError = lastError.message.includes('fetch') || lastError.message.includes('ECONNREFUSED');
241+
242+
if (isConnectionError) {
215243
const delay = baseDelay * Math.pow(2, attempt);
244+
logger?.log?.(
245+
'warn',
246+
`Connection failed (attempt ${attempt + 1}/${maxRetries}): ${lastError.message}. Retrying in ${delay}ms...`
247+
);
216248
await sleep(delay);
217249
continue;
218250
}
219-
// For other errors, throw immediately
251+
252+
// Log non-connection errors with full stack trace before throwing
253+
logger?.log?.('error', `Request failed: ${lastError.stack ?? lastError.message}`);
220254
throw lastError;
221255
}
222256
}
223257

224-
throw lastError ?? new Error('Failed to connect to dev server after retries');
258+
// Log final failure after all retries exhausted with full details
259+
const finalError = lastError ?? new Error('Failed to connect to dev server after retries');
260+
logger?.log?.('error', `Failed to connect after ${maxRetries} attempts: ${finalError.stack ?? finalError.message}`);
261+
throw finalError;
225262
}

0 commit comments

Comments
 (0)