@agent-relay/wrapper 2.0.12 → 2.0.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.
@@ -16,7 +16,8 @@
16
16
  * @see docs/RUST_WRAPPER_DESIGN.md for protocol details
17
17
  */
18
18
  import { BaseWrapper, type BaseWrapperConfig } from './base-wrapper.js';
19
- import type { SendPayload, SendMeta } from '@agent-relay/protocol/types';
19
+ import type { SendPayload, SendMeta, Envelope } from '@agent-relay/protocol/types';
20
+ import type { ChannelMessagePayload } from '@agent-relay/protocol/channels';
20
21
  interface StatusResponse {
21
22
  type: 'status';
22
23
  agent_idle: boolean;
@@ -92,6 +93,7 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
92
93
  private lastParsedLength;
93
94
  private isInteractive;
94
95
  private pendingInjections;
96
+ private pendingSendEnter;
95
97
  private backpressureActive;
96
98
  private readyForMessages;
97
99
  private throttle;
@@ -116,10 +118,12 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
116
118
  constructor(config: RelayPtyOrchestratorConfig);
117
119
  /**
118
120
  * Debug log - only outputs when debug is enabled
121
+ * Writes to log file to avoid polluting TUI output
119
122
  */
120
123
  private log;
121
124
  /**
122
125
  * Error log - always outputs (errors are important)
126
+ * Writes to log file to avoid polluting TUI output
123
127
  */
124
128
  private logError;
125
129
  /**
@@ -210,6 +214,14 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
210
214
  * Disconnect from socket
211
215
  */
212
216
  private disconnectSocket;
217
+ /** Timer for socket reconnection */
218
+ private socketReconnectTimer?;
219
+ /** Current reconnection attempt count */
220
+ private socketReconnectAttempt;
221
+ /**
222
+ * Schedule a socket reconnection attempt with exponential backoff
223
+ */
224
+ private scheduleSocketReconnect;
213
225
  /**
214
226
  * Send a request to the socket and optionally wait for response
215
227
  */
@@ -224,6 +236,15 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
224
236
  * If verification fails, retries up to MAX_RETRIES times.
225
237
  */
226
238
  private handleInjectResult;
239
+ /**
240
+ * Handle SendEnter result (stuck input recovery)
241
+ * Called when relay-pty responds to a SendEnter request
242
+ */
243
+ private handleSendEnterResult;
244
+ /**
245
+ * Do a full retry with message content (used when SendEnter fails or for subsequent retries)
246
+ */
247
+ private doFullRetry;
227
248
  /**
228
249
  * Handle backpressure notification
229
250
  */
@@ -240,6 +261,12 @@ export declare class RelayPtyOrchestrator extends BaseWrapper {
240
261
  * Override handleIncomingMessage to trigger queue processing
241
262
  */
242
263
  protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
264
+ /**
265
+ * Override handleIncomingChannelMessage to trigger queue processing.
266
+ * Without this override, channel messages would be queued but processMessageQueue()
267
+ * would never be called, causing messages to get stuck until the queue monitor runs.
268
+ */
269
+ protected handleIncomingChannelMessage(from: string, channel: string, body: string, envelope: Envelope<ChannelMessagePayload>): void;
243
270
  /**
244
271
  * Start the queue monitor to periodically check for stuck messages.
245
272
  * This ensures messages don't get orphaned in the queue when the agent is idle.
@@ -1 +1 @@
1
- {"version":3,"file":"relay-pty-orchestrator.d.ts","sourceRoot":"","sources":["../src/relay-pty-orchestrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-pty-orchestrator.d.ts","sourceRoot":"","sources":["../src/relay-pty-orchestrator.ts"],"names":[],"mappings":"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"}
@@ -19,7 +19,7 @@ import { spawn } from 'node:child_process';
19
19
  import { createConnection } from 'node:net';
20
20
  import { createHash } from 'node:crypto';
21
21
  import { join, dirname } from 'node:path';
22
- import { existsSync, unlinkSync, mkdirSync, symlinkSync, lstatSync, rmSync, watch, readdirSync } from 'node:fs';
22
+ import { existsSync, unlinkSync, mkdirSync, symlinkSync, lstatSync, rmSync, watch, readdirSync, readlinkSync, writeFileSync, appendFileSync } from 'node:fs';
23
23
  import { getProjectPaths } from '@agent-relay/config/project-namespace';
24
24
  import { fileURLToPath } from 'node:url';
25
25
  // Get the directory where this module is located
@@ -63,6 +63,8 @@ export class RelayPtyOrchestrator extends BaseWrapper {
63
63
  isInteractive = false;
64
64
  // Injection state
65
65
  pendingInjections = new Map();
66
+ // Pending SendEnter requests (for stuck input recovery)
67
+ pendingSendEnter = new Map();
66
68
  backpressureActive = false;
67
69
  readyForMessages = false;
68
70
  // Adaptive throttle for message queue - adjusts delay based on success/failure
@@ -168,17 +170,39 @@ export class RelayPtyOrchestrator extends BaseWrapper {
168
170
  }
169
171
  /**
170
172
  * Debug log - only outputs when debug is enabled
173
+ * Writes to log file to avoid polluting TUI output
171
174
  */
172
175
  log(message) {
173
176
  if (this.config.debug) {
174
- console.log(`[relay-pty-orchestrator:${this.config.name}] ${message}`);
177
+ const logLine = `${new Date().toISOString()} [relay-pty-orchestrator:${this.config.name}] ${message}\n`;
178
+ try {
179
+ const logDir = dirname(this._logPath);
180
+ if (!existsSync(logDir)) {
181
+ mkdirSync(logDir, { recursive: true });
182
+ }
183
+ appendFileSync(this._logPath, logLine);
184
+ }
185
+ catch {
186
+ // Fallback to stderr if file write fails (only during init before _logPath is set)
187
+ }
175
188
  }
176
189
  }
177
190
  /**
178
191
  * Error log - always outputs (errors are important)
192
+ * Writes to log file to avoid polluting TUI output
179
193
  */
180
194
  logError(message) {
181
- console.error(`[relay-pty-orchestrator:${this.config.name}] ERROR: ${message}`);
195
+ const logLine = `${new Date().toISOString()} [relay-pty-orchestrator:${this.config.name}] ERROR: ${message}\n`;
196
+ try {
197
+ const logDir = dirname(this._logPath);
198
+ if (!existsSync(logDir)) {
199
+ mkdirSync(logDir, { recursive: true });
200
+ }
201
+ appendFileSync(this._logPath, logLine);
202
+ }
203
+ catch {
204
+ // Fallback to stderr if file write fails (only during init before _logPath is set)
205
+ }
182
206
  }
183
207
  /**
184
208
  * Get the outbox path for this agent (for documentation purposes)
@@ -239,22 +263,82 @@ export class RelayPtyOrchestrator extends BaseWrapper {
239
263
  if (!existsSync(linkParent)) {
240
264
  mkdirSync(linkParent, { recursive: true });
241
265
  }
242
- if (existsSync(linkPath)) {
266
+ // Remove existing path if it exists (file, symlink, or directory)
267
+ // Use lstatSync instead of existsSync to detect broken symlinks
268
+ // (existsSync returns false for broken symlinks, but the symlink itself still exists)
269
+ let pathExists = false;
270
+ try {
271
+ lstatSync(linkPath);
272
+ pathExists = true;
273
+ }
274
+ catch {
275
+ // Path doesn't exist at all - proceed to create symlink
276
+ }
277
+ if (pathExists) {
243
278
  try {
244
279
  const stats = lstatSync(linkPath);
245
- if (stats.isSymbolicLink() || stats.isFile()) {
280
+ if (stats.isSymbolicLink()) {
281
+ // Handle both valid and broken symlinks
282
+ try {
283
+ const currentTarget = readlinkSync(linkPath);
284
+ if (currentTarget === targetPath) {
285
+ // Symlink already points to correct target, no need to recreate
286
+ this.log(` Symlink already exists and is correct: ${linkPath} -> ${targetPath}`);
287
+ return;
288
+ }
289
+ }
290
+ catch {
291
+ // Broken symlink (target doesn't exist) - remove it
292
+ this.log(` Removing broken symlink: ${linkPath}`);
293
+ }
294
+ unlinkSync(linkPath);
295
+ }
296
+ else if (stats.isFile()) {
246
297
  unlinkSync(linkPath);
247
298
  }
248
299
  else if (stats.isDirectory()) {
300
+ // Force remove directory - this is critical for fixing existing directories
249
301
  rmSync(linkPath, { recursive: true, force: true });
302
+ // Verify removal succeeded using lstatSync to catch broken symlinks
303
+ try {
304
+ lstatSync(linkPath);
305
+ throw new Error(`Failed to remove existing directory: ${linkPath}`);
306
+ }
307
+ catch (err) {
308
+ if (err.code !== 'ENOENT') {
309
+ throw err; // Re-throw if it's not a "doesn't exist" error
310
+ }
311
+ // Path successfully removed
312
+ }
250
313
  }
251
314
  }
252
- catch {
253
- // Ignore cleanup errors
315
+ catch (err) {
316
+ // Log cleanup errors instead of silently ignoring them
317
+ this.logError(` Failed to clean up existing path ${linkPath}: ${err.message}`);
318
+ throw err; // Re-throw to prevent symlink creation on failed cleanup
319
+ }
320
+ }
321
+ // Create the symlink
322
+ try {
323
+ symlinkSync(targetPath, linkPath);
324
+ // Verify symlink was created correctly
325
+ if (!existsSync(linkPath)) {
326
+ throw new Error(`Symlink creation failed: ${linkPath}`);
327
+ }
328
+ const verifyStats = lstatSync(linkPath);
329
+ if (!verifyStats.isSymbolicLink()) {
330
+ throw new Error(`Created path is not a symlink: ${linkPath}`);
254
331
  }
332
+ const verifyTarget = readlinkSync(linkPath);
333
+ if (verifyTarget !== targetPath) {
334
+ throw new Error(`Symlink points to wrong target: expected ${targetPath}, got ${verifyTarget}`);
335
+ }
336
+ this.log(` Created symlink: ${linkPath} -> ${targetPath}`);
337
+ }
338
+ catch (err) {
339
+ this.logError(` Failed to create symlink ${linkPath} -> ${targetPath}: ${err.message}`);
340
+ throw err;
255
341
  }
256
- symlinkSync(targetPath, linkPath);
257
- this.log(` Created symlink: ${linkPath} -> ${targetPath}`);
258
342
  };
259
343
  // In workspace mode, create symlinks so agents can use canonical path
260
344
  if (this._workspaceId) {
@@ -276,6 +360,25 @@ export class RelayPtyOrchestrator extends BaseWrapper {
276
360
  catch (err) {
277
361
  this.logError(` Failed to set up outbox: ${err.message}`);
278
362
  }
363
+ // Write MCP identity file so MCP servers can discover their agent name
364
+ // This is needed because Claude Code may not pass through env vars to MCP server processes
365
+ try {
366
+ const projectPaths = getProjectPaths(this.config.cwd);
367
+ const identityDir = join(projectPaths.dataDir);
368
+ if (!existsSync(identityDir)) {
369
+ mkdirSync(identityDir, { recursive: true });
370
+ }
371
+ // Write a per-process identity file (using PPID so MCP server finds parent's identity)
372
+ const identityPath = join(identityDir, `mcp-identity-${process.pid}`);
373
+ writeFileSync(identityPath, this.config.name, 'utf-8');
374
+ this.log(` Wrote MCP identity file: ${identityPath}`);
375
+ // Also write a simple identity file (for single-agent scenarios)
376
+ const simpleIdentityPath = join(identityDir, 'mcp-identity');
377
+ writeFileSync(simpleIdentityPath, this.config.name, 'utf-8');
378
+ }
379
+ catch (err) {
380
+ this.logError(` Failed to write MCP identity file: ${err.message}`);
381
+ }
279
382
  // Find relay-pty binary
280
383
  const binaryPath = this.findRelayPtyBinary();
281
384
  if (!binaryPath) {
@@ -318,6 +421,11 @@ export class RelayPtyOrchestrator extends BaseWrapper {
318
421
  this.stopQueueMonitor();
319
422
  this.stopProtocolMonitor();
320
423
  this.stopPeriodicReminder();
424
+ // Clear socket reconnect timer
425
+ if (this.socketReconnectTimer) {
426
+ clearTimeout(this.socketReconnectTimer);
427
+ this.socketReconnectTimer = undefined;
428
+ }
321
429
  // Unregister from memory monitor
322
430
  this.memoryMonitor.unregister(this.config.name);
323
431
  if (this.memoryAlertHandler) {
@@ -432,6 +540,7 @@ export class RelayPtyOrchestrator extends BaseWrapper {
432
540
  ...process.env,
433
541
  ...this.config.env,
434
542
  AGENT_RELAY_NAME: this.config.name,
543
+ RELAY_AGENT_NAME: this.config.name, // MCP server uses this env var
435
544
  AGENT_RELAY_OUTBOX: this._canonicalOutboxPath, // Agents use this for outbox path
436
545
  TERM: 'xterm-256color',
437
546
  },
@@ -847,6 +956,15 @@ export class RelayPtyOrchestrator extends BaseWrapper {
847
956
  */
848
957
  attemptSocketConnection(timeout) {
849
958
  return new Promise((resolve, reject) => {
959
+ // Clean up any existing socket before creating new one
960
+ // This prevents orphaned sockets with stale event handlers
961
+ if (this.socket) {
962
+ // Remove all listeners to prevent the old socket's 'close' event
963
+ // from triggering another reconnect cycle
964
+ this.socket.removeAllListeners();
965
+ this.socket.destroy();
966
+ this.socket = undefined;
967
+ }
850
968
  const timer = setTimeout(() => {
851
969
  reject(new Error('Socket connection timeout'));
852
970
  }, timeout);
@@ -860,9 +978,18 @@ export class RelayPtyOrchestrator extends BaseWrapper {
860
978
  this.socketConnected = false;
861
979
  reject(err);
862
980
  });
981
+ // Handle 'end' event - server closed its write side (half-close)
982
+ this.socket.on('end', () => {
983
+ this.socketConnected = false;
984
+ this.log(` Socket received end (server closed write side)`);
985
+ });
863
986
  this.socket.on('close', () => {
864
987
  this.socketConnected = false;
865
988
  this.log(` Socket closed`);
989
+ // Auto-reconnect if not intentionally stopped
990
+ if (this.running && !this.isGracefulStop) {
991
+ this.scheduleSocketReconnect();
992
+ }
866
993
  });
867
994
  // Handle incoming data (responses)
868
995
  let buffer = '';
@@ -895,6 +1022,55 @@ export class RelayPtyOrchestrator extends BaseWrapper {
895
1022
  }
896
1023
  this.pendingInjections.clear();
897
1024
  }
1025
+ /** Timer for socket reconnection */
1026
+ socketReconnectTimer;
1027
+ /** Current reconnection attempt count */
1028
+ socketReconnectAttempt = 0;
1029
+ /**
1030
+ * Schedule a socket reconnection attempt with exponential backoff
1031
+ */
1032
+ scheduleSocketReconnect() {
1033
+ const maxAttempts = this.config.socketReconnectAttempts ?? 3;
1034
+ // Clear any existing timer
1035
+ if (this.socketReconnectTimer) {
1036
+ clearTimeout(this.socketReconnectTimer);
1037
+ this.socketReconnectTimer = undefined;
1038
+ }
1039
+ if (this.socketReconnectAttempt >= maxAttempts) {
1040
+ this.logError(` Socket reconnect failed after ${maxAttempts} attempts`);
1041
+ // Reset counter for future reconnects (processMessageQueue can trigger new cycle)
1042
+ this.socketReconnectAttempt = 0;
1043
+ // Note: socketReconnectTimer is already undefined, allowing processMessageQueue
1044
+ // to trigger a new reconnection cycle when new messages arrive
1045
+ return;
1046
+ }
1047
+ this.socketReconnectAttempt++;
1048
+ const delay = Math.min(1000 * Math.pow(2, this.socketReconnectAttempt - 1), 10000); // Max 10s
1049
+ this.log(` Scheduling socket reconnect in ${delay}ms (attempt ${this.socketReconnectAttempt}/${maxAttempts})`);
1050
+ this.socketReconnectTimer = setTimeout(async () => {
1051
+ // Clear timer reference now that callback is executing
1052
+ this.socketReconnectTimer = undefined;
1053
+ if (!this.running || this.isGracefulStop) {
1054
+ return;
1055
+ }
1056
+ try {
1057
+ const timeout = this.config.socketConnectTimeoutMs ?? 5000;
1058
+ await this.attemptSocketConnection(timeout);
1059
+ this.log(` Socket reconnected successfully`);
1060
+ this.socketReconnectAttempt = 0; // Reset on success
1061
+ // Process any queued messages that were waiting
1062
+ if (this.messageQueue.length > 0 && !this.isInjecting) {
1063
+ this.log(` Processing ${this.messageQueue.length} queued messages after reconnect`);
1064
+ this.processMessageQueue();
1065
+ }
1066
+ }
1067
+ catch (err) {
1068
+ this.logError(` Socket reconnect attempt ${this.socketReconnectAttempt} failed: ${err.message}`);
1069
+ // Schedule another attempt
1070
+ this.scheduleSocketReconnect();
1071
+ }
1072
+ }, delay);
1073
+ }
898
1074
  /**
899
1075
  * Send a request to the socket and optionally wait for response
900
1076
  */
@@ -942,6 +1118,12 @@ export class RelayPtyOrchestrator extends BaseWrapper {
942
1118
  case 'shutdown_ack':
943
1119
  this.log(` Shutdown acknowledged`);
944
1120
  break;
1121
+ case 'send_enter_result':
1122
+ // Handle SendEnter result (stuck input recovery)
1123
+ this.handleSendEnterResult(response).catch((err) => {
1124
+ this.logError(` Error handling send_enter result: ${err.message}`);
1125
+ });
1126
+ break;
945
1127
  }
946
1128
  }
947
1129
  catch (err) {
@@ -1024,7 +1206,6 @@ export class RelayPtyOrchestrator extends BaseWrapper {
1024
1206
  this.log(` Message ${pending.shortId} NOT found in output after delivery`);
1025
1207
  // Check if we should retry
1026
1208
  if (pending.retryCount < INJECTION_CONSTANTS.MAX_RETRIES - 1) {
1027
- this.log(` Retrying injection (attempt ${pending.retryCount + 2}/${INJECTION_CONSTANTS.MAX_RETRIES})`);
1028
1209
  clearTimeout(pending.timeout);
1029
1210
  this.pendingInjections.delete(response.id);
1030
1211
  // Wait before retry with backoff
@@ -1044,37 +1225,54 @@ export class RelayPtyOrchestrator extends BaseWrapper {
1044
1225
  pending.resolve(true);
1045
1226
  return;
1046
1227
  }
1047
- // Re-inject by sending another socket request
1048
- // The original promise will be resolved when this retry completes
1049
- // Prepend [RETRY] to help agent notice this is a retry
1050
- const retryBody = pending.originalBody.startsWith('[RETRY]')
1051
- ? pending.originalBody
1052
- : `[RETRY] ${pending.originalBody}`;
1053
- const retryRequest = {
1054
- type: 'inject',
1055
- id: response.id,
1056
- from: pending.from,
1057
- body: retryBody,
1058
- priority: 1, // Higher priority for retries
1059
- };
1060
- // Create new pending entry with incremented retry count
1061
- const newTimeout = setTimeout(() => {
1062
- this.logError(` Retry timeout for ${pending.shortId}`);
1063
- this.pendingInjections.delete(response.id);
1064
- pending.resolve(false);
1065
- }, 30000);
1066
- this.pendingInjections.set(response.id, {
1067
- ...pending,
1068
- timeout: newTimeout,
1069
- retryCount: pending.retryCount + 1,
1070
- originalBody: retryBody, // Use retry body for subsequent retries
1071
- });
1072
- this.sendSocketRequest(retryRequest).catch((err) => {
1073
- this.logError(` Retry request failed: ${err.message}`);
1074
- clearTimeout(newTimeout);
1075
- this.pendingInjections.delete(response.id);
1076
- pending.resolve(false);
1077
- });
1228
+ // On first retry attempt (retryCount === 0), try SendEnter first
1229
+ // This handles the case where message content was written but Enter wasn't processed
1230
+ if (pending.retryCount === 0) {
1231
+ this.log(` Trying SendEnter first for ${pending.shortId} (stuck input recovery)`);
1232
+ // Send just the Enter key
1233
+ const sendEnterRequest = {
1234
+ type: 'send_enter',
1235
+ id: response.id,
1236
+ };
1237
+ // Track this SendEnter request for verification
1238
+ const sendEnterTimeout = setTimeout(() => {
1239
+ this.logError(` SendEnter timeout for ${pending.shortId}`);
1240
+ this.pendingSendEnter.delete(response.id);
1241
+ // Fall back to full retry after SendEnter timeout
1242
+ this.doFullRetry(response.id, pending);
1243
+ }, 5000); // 5 second timeout for SendEnter
1244
+ this.pendingSendEnter.set(response.id, {
1245
+ resolve: (verified) => {
1246
+ if (verified) {
1247
+ // SendEnter worked!
1248
+ this.injectionMetrics.successWithRetry++;
1249
+ this.injectionMetrics.total++;
1250
+ pending.resolve(true);
1251
+ }
1252
+ else {
1253
+ // SendEnter didn't work, do full retry
1254
+ this.doFullRetry(response.id, pending);
1255
+ }
1256
+ },
1257
+ timeout: sendEnterTimeout,
1258
+ from: pending.from,
1259
+ shortId: pending.shortId,
1260
+ retryCount: pending.retryCount,
1261
+ originalBody: pending.originalBody,
1262
+ originalResolve: pending.resolve,
1263
+ });
1264
+ this.sendSocketRequest(sendEnterRequest).catch((err) => {
1265
+ this.logError(` SendEnter request failed: ${err.message}`);
1266
+ clearTimeout(sendEnterTimeout);
1267
+ this.pendingSendEnter.delete(response.id);
1268
+ // Fall back to full retry
1269
+ this.doFullRetry(response.id, pending);
1270
+ });
1271
+ }
1272
+ else {
1273
+ // On subsequent retries (retryCount > 0), do full retry directly
1274
+ this.doFullRetry(response.id, pending);
1275
+ }
1078
1276
  }
1079
1277
  else {
1080
1278
  // Max retries exceeded
@@ -1107,6 +1305,77 @@ export class RelayPtyOrchestrator extends BaseWrapper {
1107
1305
  }
1108
1306
  // queued/injecting are intermediate states - wait for final status
1109
1307
  }
1308
+ /**
1309
+ * Handle SendEnter result (stuck input recovery)
1310
+ * Called when relay-pty responds to a SendEnter request
1311
+ */
1312
+ async handleSendEnterResult(response) {
1313
+ this.log(` handleSendEnterResult: id=${response.id.substring(0, 8)} success=${response.success}`);
1314
+ const pendingEnter = this.pendingSendEnter.get(response.id);
1315
+ if (!pendingEnter) {
1316
+ this.log(` No pending SendEnter found for ${response.id.substring(0, 8)}`);
1317
+ return;
1318
+ }
1319
+ clearTimeout(pendingEnter.timeout);
1320
+ this.pendingSendEnter.delete(response.id);
1321
+ if (!response.success) {
1322
+ this.log(` SendEnter failed for ${pendingEnter.shortId}, will try full retry`);
1323
+ pendingEnter.resolve(false);
1324
+ return;
1325
+ }
1326
+ // SendEnter succeeded - wait and verify
1327
+ this.log(` SendEnter sent for ${pendingEnter.shortId}, waiting to verify...`);
1328
+ await sleep(150); // Give time for Enter to be processed
1329
+ // Verify the message appeared in output
1330
+ const verified = await verifyInjection(pendingEnter.shortId, pendingEnter.from, async () => this.getCleanOutput());
1331
+ if (verified) {
1332
+ this.log(` Message ${pendingEnter.shortId} verified after SendEnter ✓`);
1333
+ pendingEnter.resolve(true);
1334
+ }
1335
+ else {
1336
+ this.log(` Message ${pendingEnter.shortId} still not verified after SendEnter, will try full retry`);
1337
+ pendingEnter.resolve(false);
1338
+ }
1339
+ }
1340
+ /**
1341
+ * Do a full retry with message content (used when SendEnter fails or for subsequent retries)
1342
+ */
1343
+ doFullRetry(messageId, pending) {
1344
+ this.log(` Doing full retry for ${pending.shortId} (attempt ${pending.retryCount + 2}/${INJECTION_CONSTANTS.MAX_RETRIES})`);
1345
+ // Re-inject by sending another socket request
1346
+ // Prepend [RETRY] to help agent notice this is a retry
1347
+ const retryBody = pending.originalBody.startsWith('[RETRY]')
1348
+ ? pending.originalBody
1349
+ : `[RETRY] ${pending.originalBody}`;
1350
+ const retryRequest = {
1351
+ type: 'inject',
1352
+ id: messageId,
1353
+ from: pending.from,
1354
+ body: retryBody,
1355
+ priority: 1, // Higher priority for retries
1356
+ };
1357
+ // Create new pending entry with incremented retry count
1358
+ const newTimeout = setTimeout(() => {
1359
+ this.logError(` Retry timeout for ${pending.shortId}`);
1360
+ this.pendingInjections.delete(messageId);
1361
+ pending.resolve(false);
1362
+ }, 30000);
1363
+ this.pendingInjections.set(messageId, {
1364
+ resolve: pending.resolve,
1365
+ reject: pending.reject,
1366
+ timeout: newTimeout,
1367
+ from: pending.from,
1368
+ shortId: pending.shortId,
1369
+ retryCount: pending.retryCount + 1,
1370
+ originalBody: retryBody,
1371
+ });
1372
+ this.sendSocketRequest(retryRequest).catch((err) => {
1373
+ this.logError(` Full retry request failed: ${err.message}`);
1374
+ clearTimeout(newTimeout);
1375
+ this.pendingInjections.delete(messageId);
1376
+ pending.resolve(false);
1377
+ });
1378
+ }
1110
1379
  /**
1111
1380
  * Handle backpressure notification
1112
1381
  */
@@ -1180,12 +1449,42 @@ export class RelayPtyOrchestrator extends BaseWrapper {
1180
1449
  * Process queued messages
1181
1450
  */
1182
1451
  async processMessageQueue() {
1183
- if (!this.readyForMessages || this.backpressureActive || this.isInjecting) {
1184
- return;
1452
+ // Debug: Log blocking conditions when queue has messages
1453
+ if (this.messageQueue.length > 0) {
1454
+ if (!this.readyForMessages) {
1455
+ this.log(` Queue blocked: readyForMessages=false (queue=${this.messageQueue.length})`);
1456
+ return;
1457
+ }
1458
+ if (this.backpressureActive) {
1459
+ this.log(` Queue blocked: backpressure active (queue=${this.messageQueue.length})`);
1460
+ return;
1461
+ }
1462
+ if (this.isInjecting) {
1463
+ // Already injecting - the finally block will process next message
1464
+ // But add a safety timeout in case injection gets stuck
1465
+ const elapsed = this.injectionStartTime > 0 ? Date.now() - this.injectionStartTime : 0;
1466
+ if (elapsed > 35000) {
1467
+ this.logError(` Injection stuck for ${elapsed}ms, forcing reset`);
1468
+ this.isInjecting = false;
1469
+ this.injectionStartTime = 0;
1470
+ }
1471
+ return;
1472
+ }
1185
1473
  }
1186
1474
  if (this.messageQueue.length === 0) {
1187
1475
  return;
1188
1476
  }
1477
+ // Proactively reconnect socket if disconnected and we have messages to send
1478
+ if (!this.socketConnected && !this.socketReconnectTimer) {
1479
+ this.log(` Socket disconnected, triggering reconnect before processing queue`);
1480
+ this.scheduleSocketReconnect();
1481
+ return; // Wait for reconnection to complete
1482
+ }
1483
+ if (!this.socketConnected) {
1484
+ // Reconnection in progress, wait for it
1485
+ this.log(` Queue waiting: socket reconnecting (queue=${this.messageQueue.length})`);
1486
+ return;
1487
+ }
1189
1488
  // Check if agent is in editor mode - delay injection if so
1190
1489
  const idleResult = this.idleDetector.checkIdle();
1191
1490
  if (idleResult.inEditorMode) {
@@ -1244,6 +1543,18 @@ export class RelayPtyOrchestrator extends BaseWrapper {
1244
1543
  this.log(` Queue length after add: ${this.messageQueue.length}`);
1245
1544
  this.processMessageQueue();
1246
1545
  }
1546
+ /**
1547
+ * Override handleIncomingChannelMessage to trigger queue processing.
1548
+ * Without this override, channel messages would be queued but processMessageQueue()
1549
+ * would never be called, causing messages to get stuck until the queue monitor runs.
1550
+ */
1551
+ handleIncomingChannelMessage(from, channel, body, envelope) {
1552
+ this.log(` === CHANNEL MESSAGE RECEIVED: ${envelope.id.substring(0, 8)} from ${from} on ${channel} ===`);
1553
+ this.log(` Body preview: ${body?.substring(0, 100) ?? '(no body)'}...`);
1554
+ super.handleIncomingChannelMessage(from, channel, body, envelope);
1555
+ this.log(` Queue length after add: ${this.messageQueue.length}`);
1556
+ this.processMessageQueue();
1557
+ }
1247
1558
  // =========================================================================
1248
1559
  // Queue monitor - Detect and process stuck messages
1249
1560
  // =========================================================================
@@ -1841,6 +2152,10 @@ Then output: \`->relay-file:spawn\`
1841
2152
  */
1842
2153
  async kill() {
1843
2154
  this.isGracefulStop = true; // Mark as intentional to prevent crash broadcast
2155
+ if (this.socketReconnectTimer) {
2156
+ clearTimeout(this.socketReconnectTimer);
2157
+ this.socketReconnectTimer = undefined;
2158
+ }
1844
2159
  if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
1845
2160
  this.relayPtyProcess.kill('SIGKILL');
1846
2161
  }