@link-assistant/agent 0.8.11 → 0.8.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.8.11",
3
+ "version": "0.8.14",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ // Set process title to 'agent' so it appears correctly in process monitoring tools like top/ps
4
+ // Both process.title and process.argv0 need to be set for maximum compatibility
5
+ process.title = 'agent';
6
+ process.argv0 = 'agent';
7
+
3
8
  import { Server } from './server/server.ts';
4
9
  import { Instance } from './project/instance.ts';
5
10
  import { Log } from './util/log.ts';
@@ -58,6 +58,21 @@ export namespace MessageV2 {
58
58
  typeof SocketConnectionError.Schema
59
59
  >;
60
60
 
61
+ /**
62
+ * Timeout error - caused by AbortSignal.timeout() firing when an API request
63
+ * takes too long. These are DOMException with name === 'TimeoutError'.
64
+ * These errors are transient and should be retried with increasing intervals.
65
+ * See: https://github.com/link-assistant/agent/issues/142
66
+ */
67
+ export const TimeoutError = NamedError.create(
68
+ 'TimeoutError',
69
+ z.object({
70
+ message: z.string(),
71
+ isRetryable: z.literal(true),
72
+ })
73
+ );
74
+ export type TimeoutError = z.infer<typeof TimeoutError.Schema>;
75
+
61
76
  const PartBase = z.object({
62
77
  id: z.string(),
63
78
  sessionID: z.string(),
@@ -782,6 +797,11 @@ export namespace MessageV2 {
782
797
  cause: e,
783
798
  }
784
799
  ).toObject();
800
+ case e instanceof DOMException && e.name === 'TimeoutError':
801
+ return new MessageV2.TimeoutError(
802
+ { message: e.message, isRetryable: true },
803
+ { cause: e }
804
+ ).toObject();
785
805
  case MessageV2.OutputLengthError.isInstance(e):
786
806
  return e;
787
807
  case LoadAPIKeyError.isInstance(e):
@@ -816,6 +836,18 @@ export namespace MessageV2 {
816
836
  { cause: e }
817
837
  ).toObject();
818
838
  }
839
+ // Detect timeout errors from various sources
840
+ // See: https://github.com/link-assistant/agent/issues/142
841
+ const isTimeoutError =
842
+ message.includes('The operation timed out') ||
843
+ message.includes('timed out') ||
844
+ e.name === 'TimeoutError';
845
+ if (isTimeoutError) {
846
+ return new MessageV2.TimeoutError(
847
+ { message, isRetryable: true },
848
+ { cause: e }
849
+ ).toObject();
850
+ }
819
851
  return new NamedError.Unknown(
820
852
  { message: e.toString() },
821
853
  { cause: e }
@@ -326,21 +326,31 @@ export namespace SessionProcessor {
326
326
  providerID: input.providerID,
327
327
  });
328
328
 
329
- // Check if error is retryable (APIError or SocketConnectionError)
329
+ // Check if error is retryable (APIError, SocketConnectionError, or TimeoutError)
330
330
  const isRetryableAPIError =
331
331
  error?.name === 'APIError' && error.data.isRetryable;
332
332
  const isRetryableSocketError =
333
333
  error?.name === 'SocketConnectionError' &&
334
334
  error.data.isRetryable &&
335
335
  attempt < SessionRetry.SOCKET_ERROR_MAX_RETRIES;
336
+ const isRetryableTimeoutError =
337
+ error?.name === 'TimeoutError' &&
338
+ error.data.isRetryable &&
339
+ attempt < SessionRetry.TIMEOUT_MAX_RETRIES;
336
340
 
337
- if (isRetryableAPIError || isRetryableSocketError) {
341
+ if (
342
+ isRetryableAPIError ||
343
+ isRetryableSocketError ||
344
+ isRetryableTimeoutError
345
+ ) {
338
346
  attempt++;
339
- // Use socket-specific delay for socket errors
347
+ // Use error-specific delay calculation
340
348
  const delay =
341
349
  error?.name === 'SocketConnectionError'
342
350
  ? SessionRetry.socketErrorDelay(attempt)
343
- : SessionRetry.delay(error, attempt);
351
+ : error?.name === 'TimeoutError'
352
+ ? SessionRetry.timeoutDelay(attempt)
353
+ : SessionRetry.delay(error, attempt);
344
354
  log.info(() => ({
345
355
  message: 'retrying',
346
356
  errorType: error?.name,
@@ -13,6 +13,12 @@ export namespace SessionRetry {
13
13
  export const SOCKET_ERROR_INITIAL_DELAY = 1000; // 1 second
14
14
  export const SOCKET_ERROR_BACKOFF_FACTOR = 2;
15
15
 
16
+ // Timeout error retry configuration
17
+ // When API requests time out (AbortSignal.timeout), retry with increasing intervals
18
+ // See: https://github.com/link-assistant/agent/issues/142
19
+ export const TIMEOUT_MAX_RETRIES = 3;
20
+ export const TIMEOUT_DELAYS = [30_000, 60_000, 120_000]; // 30s, 60s, 120s
21
+
16
22
  export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
17
23
  return new Promise((resolve, reject) => {
18
24
  const timeout = setTimeout(resolve, ms);
@@ -71,4 +77,14 @@ export namespace SessionRetry {
71
77
  Math.pow(SOCKET_ERROR_BACKOFF_FACTOR, attempt - 1)
72
78
  );
73
79
  }
80
+
81
+ /**
82
+ * Calculate delay for timeout error retries.
83
+ * Uses fixed intervals: 30s, 60s, 120s.
84
+ * See: https://github.com/link-assistant/agent/issues/142
85
+ */
86
+ export function timeoutDelay(attempt: number): number {
87
+ const index = Math.min(attempt - 1, TIMEOUT_DELAYS.length - 1);
88
+ return TIMEOUT_DELAYS[index];
89
+ }
74
90
  }