@agent-relay/wrapper 0.1.0
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/dist/__fixtures__/claude-outputs.d.ts +49 -0
- package/dist/__fixtures__/claude-outputs.d.ts.map +1 -0
- package/dist/__fixtures__/claude-outputs.js +443 -0
- package/dist/__fixtures__/claude-outputs.js.map +1 -0
- package/dist/__fixtures__/codex-outputs.d.ts +9 -0
- package/dist/__fixtures__/codex-outputs.d.ts.map +1 -0
- package/dist/__fixtures__/codex-outputs.js +94 -0
- package/dist/__fixtures__/codex-outputs.js.map +1 -0
- package/dist/__fixtures__/gemini-outputs.d.ts +19 -0
- package/dist/__fixtures__/gemini-outputs.d.ts.map +1 -0
- package/dist/__fixtures__/gemini-outputs.js +144 -0
- package/dist/__fixtures__/gemini-outputs.js.map +1 -0
- package/dist/__fixtures__/index.d.ts +68 -0
- package/dist/__fixtures__/index.d.ts.map +1 -0
- package/dist/__fixtures__/index.js +44 -0
- package/dist/__fixtures__/index.js.map +1 -0
- package/dist/auth-detection.d.ts +49 -0
- package/dist/auth-detection.d.ts.map +1 -0
- package/dist/auth-detection.js +199 -0
- package/dist/auth-detection.js.map +1 -0
- package/dist/base-wrapper.d.ts +225 -0
- package/dist/base-wrapper.d.ts.map +1 -0
- package/dist/base-wrapper.js +572 -0
- package/dist/base-wrapper.js.map +1 -0
- package/dist/client.d.ts +254 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +801 -0
- package/dist/client.js.map +1 -0
- package/dist/id-generator.d.ts +35 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +60 -0
- package/dist/id-generator.js.map +1 -0
- package/dist/idle-detector.d.ts +110 -0
- package/dist/idle-detector.d.ts.map +1 -0
- package/dist/idle-detector.js +304 -0
- package/dist/idle-detector.js.map +1 -0
- package/dist/inbox.d.ts +37 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +73 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +236 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +1238 -0
- package/dist/parser.js.map +1 -0
- package/dist/prompt-composer.d.ts +67 -0
- package/dist/prompt-composer.d.ts.map +1 -0
- package/dist/prompt-composer.js +168 -0
- package/dist/prompt-composer.js.map +1 -0
- package/dist/relay-pty-orchestrator.d.ts +407 -0
- package/dist/relay-pty-orchestrator.d.ts.map +1 -0
- package/dist/relay-pty-orchestrator.js +1885 -0
- package/dist/relay-pty-orchestrator.js.map +1 -0
- package/dist/shared.d.ts +201 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +341 -0
- package/dist/shared.js.map +1 -0
- package/dist/stuck-detector.d.ts +161 -0
- package/dist/stuck-detector.d.ts.map +1 -0
- package/dist/stuck-detector.js +402 -0
- package/dist/stuck-detector.js.map +1 -0
- package/dist/tmux-resolver.d.ts +55 -0
- package/dist/tmux-resolver.d.ts.map +1 -0
- package/dist/tmux-resolver.js +175 -0
- package/dist/tmux-resolver.js.map +1 -0
- package/dist/tmux-wrapper.d.ts +345 -0
- package/dist/tmux-wrapper.d.ts.map +1 -0
- package/dist/tmux-wrapper.js +1747 -0
- package/dist/tmux-wrapper.js.map +1 -0
- package/dist/trajectory-integration.d.ts +292 -0
- package/dist/trajectory-integration.d.ts.map +1 -0
- package/dist/trajectory-integration.js +979 -0
- package/dist/trajectory-integration.js.map +1 -0
- package/dist/wrapper-types.d.ts +41 -0
- package/dist/wrapper-types.d.ts.map +1 -0
- package/dist/wrapper-types.js +7 -0
- package/dist/wrapper-types.js.map +1 -0
- package/package.json +63 -0
- package/src/__fixtures__/claude-outputs.ts +471 -0
- package/src/__fixtures__/codex-outputs.ts +99 -0
- package/src/__fixtures__/gemini-outputs.ts +151 -0
- package/src/__fixtures__/index.ts +47 -0
- package/src/auth-detection.ts +244 -0
- package/src/base-wrapper.test.ts +540 -0
- package/src/base-wrapper.ts +741 -0
- package/src/client.test.ts +262 -0
- package/src/client.ts +984 -0
- package/src/id-generator.test.ts +71 -0
- package/src/id-generator.ts +69 -0
- package/src/idle-detector.test.ts +390 -0
- package/src/idle-detector.ts +370 -0
- package/src/inbox.test.ts +233 -0
- package/src/inbox.ts +89 -0
- package/src/index.ts +170 -0
- package/src/parser.regression.test.ts +251 -0
- package/src/parser.test.ts +1359 -0
- package/src/parser.ts +1477 -0
- package/src/prompt-composer.test.ts +219 -0
- package/src/prompt-composer.ts +231 -0
- package/src/relay-pty-orchestrator.test.ts +1027 -0
- package/src/relay-pty-orchestrator.ts +2270 -0
- package/src/shared.test.ts +221 -0
- package/src/shared.ts +454 -0
- package/src/stuck-detector.test.ts +303 -0
- package/src/stuck-detector.ts +511 -0
- package/src/tmux-resolver.test.ts +104 -0
- package/src/tmux-resolver.ts +207 -0
- package/src/tmux-wrapper.test.ts +316 -0
- package/src/tmux-wrapper.ts +2010 -0
- package/src/trajectory-detection.test.ts +151 -0
- package/src/trajectory-integration.ts +1261 -0
- package/src/wrapper-types.ts +45 -0
package/dist/shared.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types and utilities for TmuxWrapper and PtyWrapper
|
|
3
|
+
*
|
|
4
|
+
* This module contains common code to prevent drift between the two
|
|
5
|
+
* wrapper implementations and reduce duplication.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Injection timing constants
|
|
9
|
+
*
|
|
10
|
+
* Performance tuning (2024-01):
|
|
11
|
+
* - QUEUE_PROCESS_DELAY_MS: 500ms → 100ms (5x faster message throughput)
|
|
12
|
+
* - STABILITY_POLL_MS: 200ms → 100ms (faster idle detection)
|
|
13
|
+
* - ENTER_DELAY_MS: 100ms → 50ms (faster message completion)
|
|
14
|
+
* - RETRY_BACKOFF_MS: 300ms → 200ms (faster recovery)
|
|
15
|
+
*
|
|
16
|
+
* Use AdaptiveThrottle class for dynamic backpressure handling.
|
|
17
|
+
*/
|
|
18
|
+
export const INJECTION_CONSTANTS = {
|
|
19
|
+
/** Maximum retry attempts for injection */
|
|
20
|
+
MAX_RETRIES: 3,
|
|
21
|
+
/** Timeout for output stability check (ms) */
|
|
22
|
+
STABILITY_TIMEOUT_MS: 3000,
|
|
23
|
+
/** Polling interval for stability check (ms) - reduced from 200ms */
|
|
24
|
+
STABILITY_POLL_MS: 100,
|
|
25
|
+
/** Required consecutive stable polls before injection */
|
|
26
|
+
REQUIRED_STABLE_POLLS: 2,
|
|
27
|
+
/** Timeout for injection verification (ms) */
|
|
28
|
+
VERIFICATION_TIMEOUT_MS: 2000,
|
|
29
|
+
/** Delay between message and Enter key (ms) - reduced from 100ms */
|
|
30
|
+
ENTER_DELAY_MS: 50,
|
|
31
|
+
/** Backoff multiplier for retries (ms per attempt) - reduced from 300ms */
|
|
32
|
+
RETRY_BACKOFF_MS: 200,
|
|
33
|
+
/** Base delay between processing queued messages (ms) - reduced from 500ms */
|
|
34
|
+
QUEUE_PROCESS_DELAY_MS: 100,
|
|
35
|
+
/** Maximum delay when under backpressure (ms) */
|
|
36
|
+
QUEUE_PROCESS_DELAY_MAX_MS: 500,
|
|
37
|
+
/** Threshold for increasing delay (consecutive failures) */
|
|
38
|
+
BACKPRESSURE_THRESHOLD: 2,
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Adaptive throttle for message queue processing.
|
|
42
|
+
* Increases delay when failures occur, decreases on success.
|
|
43
|
+
*
|
|
44
|
+
* This allows fast messaging under normal conditions (~100ms between messages)
|
|
45
|
+
* while automatically backing off when the system is under stress.
|
|
46
|
+
*/
|
|
47
|
+
export class AdaptiveThrottle {
|
|
48
|
+
consecutiveFailures = 0;
|
|
49
|
+
currentDelay = INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS;
|
|
50
|
+
/** Get current delay in milliseconds */
|
|
51
|
+
getDelay() {
|
|
52
|
+
return this.currentDelay;
|
|
53
|
+
}
|
|
54
|
+
/** Record a successful injection - decrease delay */
|
|
55
|
+
recordSuccess() {
|
|
56
|
+
this.consecutiveFailures = 0;
|
|
57
|
+
// Gradually decrease delay on success (exponential decay toward minimum)
|
|
58
|
+
this.currentDelay = Math.max(INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS, Math.floor(this.currentDelay * 0.7));
|
|
59
|
+
}
|
|
60
|
+
/** Record a failed injection - increase delay if threshold exceeded */
|
|
61
|
+
recordFailure() {
|
|
62
|
+
this.consecutiveFailures++;
|
|
63
|
+
if (this.consecutiveFailures >= INJECTION_CONSTANTS.BACKPRESSURE_THRESHOLD) {
|
|
64
|
+
// Increase delay when under backpressure (exponential backoff)
|
|
65
|
+
this.currentDelay = Math.min(INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MAX_MS, Math.floor(this.currentDelay * 1.5));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Reset to default state */
|
|
69
|
+
reset() {
|
|
70
|
+
this.consecutiveFailures = 0;
|
|
71
|
+
this.currentDelay = INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Strip ANSI escape codes from a string.
|
|
76
|
+
* Converts cursor movements to spaces to preserve visual layout.
|
|
77
|
+
*/
|
|
78
|
+
export function stripAnsi(str) {
|
|
79
|
+
// Convert cursor forward movements to spaces (CSI n C)
|
|
80
|
+
// eslint-disable-next-line no-control-regex
|
|
81
|
+
let result = str.replace(/\x1B\[(\d+)C/g, (_m, n) => ' '.repeat(parseInt(n, 10) || 1));
|
|
82
|
+
// Convert single cursor right (CSI C) to space
|
|
83
|
+
// eslint-disable-next-line no-control-regex
|
|
84
|
+
result = result.replace(/\x1B\[C/g, ' ');
|
|
85
|
+
// Remove carriage returns (causes text overwriting issues)
|
|
86
|
+
result = result.replace(/\r(?!\n)/g, '');
|
|
87
|
+
// Strip ANSI escape sequences (with \x1B prefix)
|
|
88
|
+
// eslint-disable-next-line no-control-regex
|
|
89
|
+
result = result.replace(/\x1B(?:\[[0-9;?]*[A-Za-z]|\].*?(?:\x07|\x1B\\)|[@-Z\\-_])/g, '');
|
|
90
|
+
// Strip orphaned CSI sequences that lost their escape byte
|
|
91
|
+
// Requires at least one digit or question mark to avoid stripping legitimate text like [Agent
|
|
92
|
+
result = result.replace(/^\s*(\[(?:\?|\d)\d*[A-Za-z])+\s*/g, '');
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Sleep for a given number of milliseconds
|
|
97
|
+
*/
|
|
98
|
+
export function sleep(ms) {
|
|
99
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build the injection string for a relay message.
|
|
103
|
+
* Format: Relay message from {from} [{shortId}]{hints}: {body}
|
|
104
|
+
*
|
|
105
|
+
* If the body is already formatted (starts with "Relay message from"),
|
|
106
|
+
* returns it as-is to prevent double-wrapping.
|
|
107
|
+
*/
|
|
108
|
+
export function buildInjectionString(msg) {
|
|
109
|
+
// Check if body is already formatted (prevents double-wrapping)
|
|
110
|
+
// This can happen when:
|
|
111
|
+
// - Delivering queued/pending messages that were already formatted
|
|
112
|
+
// - Agent output includes quoted relay messages that get re-processed
|
|
113
|
+
// Strip ANSI first so escape codes don't interfere with detection
|
|
114
|
+
const sanitizedBody = stripAnsi(msg.body || '').replace(/[\r\n]+/g, ' ').trim();
|
|
115
|
+
if (sanitizedBody.startsWith('Relay message from ')) {
|
|
116
|
+
// Already formatted - return as-is
|
|
117
|
+
return sanitizedBody;
|
|
118
|
+
}
|
|
119
|
+
const shortId = msg.messageId.substring(0, 8);
|
|
120
|
+
// Use senderName from data if available (for dashboard messages sent via _DashboardUI)
|
|
121
|
+
// This allows showing the actual GitHub username instead of the system client name
|
|
122
|
+
const displayFrom = (msg.from === '_DashboardUI' && typeof msg.data?.senderName === 'string')
|
|
123
|
+
? msg.data.senderName
|
|
124
|
+
: msg.from;
|
|
125
|
+
// Thread hint
|
|
126
|
+
const threadHint = msg.thread ? ` [thread:${msg.thread}]` : '';
|
|
127
|
+
// Importance indicator: [!!] for high (>75), [!] for medium (>50)
|
|
128
|
+
const importanceHint = msg.importance !== undefined && msg.importance > 75
|
|
129
|
+
? ' [!!]'
|
|
130
|
+
: msg.importance !== undefined && msg.importance > 50
|
|
131
|
+
? ' [!]'
|
|
132
|
+
: '';
|
|
133
|
+
// Channel indicator for channel messages and broadcasts
|
|
134
|
+
// originalTo will be '*' for broadcasts or the channel name (e.g., '#general') for channel messages
|
|
135
|
+
// Make it clear that replies should go to the channel, not the sender
|
|
136
|
+
const channelHint = msg.originalTo === '*'
|
|
137
|
+
? ' [#general] (reply to #general, not sender)'
|
|
138
|
+
: msg.originalTo?.startsWith('#')
|
|
139
|
+
? ` [${msg.originalTo}] (reply to ${msg.originalTo}, not sender)`
|
|
140
|
+
: '';
|
|
141
|
+
// Extract attachment file paths if present
|
|
142
|
+
let attachmentHint = '';
|
|
143
|
+
if (msg.data?.attachments && Array.isArray(msg.data.attachments)) {
|
|
144
|
+
const filePaths = msg.data.attachments
|
|
145
|
+
.map((att) => att.filePath)
|
|
146
|
+
.filter((p) => typeof p === 'string');
|
|
147
|
+
if (filePaths.length > 0) {
|
|
148
|
+
attachmentHint = ` [Attachments: ${filePaths.join(', ')}]`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return `Relay message from ${displayFrom} [${shortId}]${threadHint}${importanceHint}${channelHint}${attachmentHint}: ${sanitizedBody}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Calculate injection success rate from metrics
|
|
155
|
+
*/
|
|
156
|
+
export function calculateSuccessRate(metrics) {
|
|
157
|
+
if (metrics.total === 0)
|
|
158
|
+
return 100;
|
|
159
|
+
const successful = metrics.successFirstTry + metrics.successWithRetry;
|
|
160
|
+
return Math.round((successful / metrics.total) * 10000) / 100;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create a fresh injection metrics object
|
|
164
|
+
*/
|
|
165
|
+
export function createInjectionMetrics() {
|
|
166
|
+
return {
|
|
167
|
+
total: 0,
|
|
168
|
+
successFirstTry: 0,
|
|
169
|
+
successWithRetry: 0,
|
|
170
|
+
failed: 0,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Detect CLI type from command string
|
|
175
|
+
*/
|
|
176
|
+
export function detectCliType(command) {
|
|
177
|
+
const cmdLower = command.toLowerCase();
|
|
178
|
+
if (cmdLower.includes('gemini'))
|
|
179
|
+
return 'gemini';
|
|
180
|
+
if (cmdLower.includes('codex'))
|
|
181
|
+
return 'codex';
|
|
182
|
+
if (cmdLower.includes('claude'))
|
|
183
|
+
return 'claude';
|
|
184
|
+
if (cmdLower.includes('droid'))
|
|
185
|
+
return 'droid';
|
|
186
|
+
if (cmdLower.includes('opencode'))
|
|
187
|
+
return 'opencode';
|
|
188
|
+
if (cmdLower.includes('cursor'))
|
|
189
|
+
return 'cursor';
|
|
190
|
+
return 'other';
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get the default relay prefix (unified for all agent types)
|
|
194
|
+
*/
|
|
195
|
+
export function getDefaultRelayPrefix() {
|
|
196
|
+
return '->relay:';
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* CLI-specific quirks and handling
|
|
200
|
+
*/
|
|
201
|
+
export const CLI_QUIRKS = {
|
|
202
|
+
/**
|
|
203
|
+
* CLIs that support bracketed paste mode.
|
|
204
|
+
* Others may interpret the escape sequences literally.
|
|
205
|
+
*/
|
|
206
|
+
supportsBracketedPaste: (cli) => {
|
|
207
|
+
return cli === 'claude' || cli === 'codex' || cli === 'gemini' || cli === 'opencode' || cli === 'cursor';
|
|
208
|
+
},
|
|
209
|
+
/**
|
|
210
|
+
* Gemini interprets certain keywords (While, For, If, etc.) as shell commands.
|
|
211
|
+
* Wrap message in backticks to prevent shell keyword interpretation.
|
|
212
|
+
*/
|
|
213
|
+
wrapForGemini: (body) => {
|
|
214
|
+
return `\`${body.replace(/`/g, "'")}\``;
|
|
215
|
+
},
|
|
216
|
+
/**
|
|
217
|
+
* Get prompt pattern regex for a CLI type.
|
|
218
|
+
* Used to detect when input line is clear.
|
|
219
|
+
*/
|
|
220
|
+
getPromptPattern: (cli) => {
|
|
221
|
+
const patterns = {
|
|
222
|
+
claude: /^[>›»]\s*$/,
|
|
223
|
+
gemini: /^[>›»]\s*$/,
|
|
224
|
+
codex: /^[>›»]\s*$/,
|
|
225
|
+
droid: /^[>›»]\s*$/,
|
|
226
|
+
opencode: /^[>›»]\s*$/,
|
|
227
|
+
cursor: /^[>›»]\s*$/,
|
|
228
|
+
spawned: /^[>›»]\s*$/,
|
|
229
|
+
other: /^[>$%#➜›»]\s*$/,
|
|
230
|
+
};
|
|
231
|
+
return patterns[cli] || patterns.other;
|
|
232
|
+
},
|
|
233
|
+
/**
|
|
234
|
+
* Check if a line looks like a shell prompt (for Gemini safety check).
|
|
235
|
+
* Gemini can drop into shell mode - we skip injection to avoid executing commands.
|
|
236
|
+
*/
|
|
237
|
+
isShellPrompt: (line) => {
|
|
238
|
+
const clean = stripAnsi(line).trim();
|
|
239
|
+
return /^\$\s*$/.test(clean) || /^\s*\$\s*$/.test(clean);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Verify that an injected message appeared in the output.
|
|
244
|
+
* Uses a callback to get output content, allowing different backends
|
|
245
|
+
* (tmux capture-pane, PTY buffer) to be used.
|
|
246
|
+
*
|
|
247
|
+
* @param shortId - First 8 chars of message ID
|
|
248
|
+
* @param from - Sender name
|
|
249
|
+
* @param getOutput - Callback to retrieve current output
|
|
250
|
+
* @returns true if message pattern found in output
|
|
251
|
+
*/
|
|
252
|
+
export async function verifyInjection(shortId, from, getOutput) {
|
|
253
|
+
const expectedPattern = `Relay message from ${from} [${shortId}]`;
|
|
254
|
+
const startTime = Date.now();
|
|
255
|
+
while (Date.now() - startTime < INJECTION_CONSTANTS.VERIFICATION_TIMEOUT_MS) {
|
|
256
|
+
try {
|
|
257
|
+
const output = await getOutput();
|
|
258
|
+
if (output.includes(expectedPattern)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Output retrieval failed, verification fails
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
await sleep(100);
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Inject a message with retry logic and verification.
|
|
272
|
+
* Includes dedup check to prevent double-injection race condition.
|
|
273
|
+
*
|
|
274
|
+
* This consolidates the retry/verification logic that was duplicated
|
|
275
|
+
* in TmuxWrapper and PtyWrapper.
|
|
276
|
+
*
|
|
277
|
+
* @param injection - The formatted injection string
|
|
278
|
+
* @param shortId - First 8 chars of message ID for verification
|
|
279
|
+
* @param from - Sender name for verification pattern
|
|
280
|
+
* @param callbacks - Wrapper-specific callbacks for injection operations
|
|
281
|
+
* @returns Result indicating success/failure and attempt count
|
|
282
|
+
*/
|
|
283
|
+
export async function injectWithRetry(injection, shortId, from, callbacks) {
|
|
284
|
+
const metrics = callbacks.getMetrics();
|
|
285
|
+
metrics.total++;
|
|
286
|
+
// Skip verification mode: trust that write() succeeds without checking output
|
|
287
|
+
// Used for PTY-based injection where CLIs don't echo input back
|
|
288
|
+
if (callbacks.skipVerification) {
|
|
289
|
+
try {
|
|
290
|
+
await callbacks.performInjection(injection);
|
|
291
|
+
metrics.successFirstTry++;
|
|
292
|
+
return { success: true, attempts: 1 };
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
callbacks.logError(`Injection error: ${err?.message || err}`);
|
|
296
|
+
metrics.failed++;
|
|
297
|
+
return { success: false, attempts: 1 };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (let attempt = 0; attempt < INJECTION_CONSTANTS.MAX_RETRIES; attempt++) {
|
|
301
|
+
try {
|
|
302
|
+
// On retry attempts, first check if message already exists (race condition fix)
|
|
303
|
+
// Previous injection may have succeeded but verification timed out
|
|
304
|
+
if (attempt > 0) {
|
|
305
|
+
const alreadyExists = await verifyInjection(shortId, from, callbacks.getOutput);
|
|
306
|
+
if (alreadyExists) {
|
|
307
|
+
metrics.successWithRetry++;
|
|
308
|
+
callbacks.log(`Message already present (late verification), skipping re-injection`);
|
|
309
|
+
return { success: true, attempts: attempt + 1 };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Perform the injection
|
|
313
|
+
await callbacks.performInjection(injection);
|
|
314
|
+
// Verify it appeared in output
|
|
315
|
+
const verified = await verifyInjection(shortId, from, callbacks.getOutput);
|
|
316
|
+
if (verified) {
|
|
317
|
+
if (attempt === 0) {
|
|
318
|
+
metrics.successFirstTry++;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
metrics.successWithRetry++;
|
|
322
|
+
callbacks.log(`Injection succeeded on attempt ${attempt + 1}`);
|
|
323
|
+
}
|
|
324
|
+
return { success: true, attempts: attempt + 1 };
|
|
325
|
+
}
|
|
326
|
+
// Not verified - log and retry
|
|
327
|
+
callbacks.log(`Injection not verified, attempt ${attempt + 1}/${INJECTION_CONSTANTS.MAX_RETRIES}`);
|
|
328
|
+
// Backoff before retry
|
|
329
|
+
if (attempt < INJECTION_CONSTANTS.MAX_RETRIES - 1) {
|
|
330
|
+
await sleep(INJECTION_CONSTANTS.RETRY_BACKOFF_MS * (attempt + 1));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
callbacks.logError(`Injection error on attempt ${attempt + 1}: ${err?.message || err}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// All retries failed
|
|
338
|
+
metrics.failed++;
|
|
339
|
+
return { success: false, attempts: INJECTION_CONSTANTS.MAX_RETRIES };
|
|
340
|
+
}
|
|
341
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StuckDetector - Detect when an agent is stuck
|
|
3
|
+
*
|
|
4
|
+
* Implements agent-relay-501: Stuck detection heuristics
|
|
5
|
+
*
|
|
6
|
+
* Detects five stuck conditions:
|
|
7
|
+
* 1. Extended idle (no output for 10+ minutes)
|
|
8
|
+
* 2. Error loop (same error message repeated 3+ times)
|
|
9
|
+
* 3. Output loop (same output pattern repeated 3+ times)
|
|
10
|
+
* 4. Tool loop (same file operated on 10+ times in 5 minutes)
|
|
11
|
+
* 5. Output flood (abnormally high output rate suggesting infinite loop)
|
|
12
|
+
*
|
|
13
|
+
* NOTE: Message intent detection (agent says "I'll send" but doesn't) was removed
|
|
14
|
+
* because pattern-based NLP detection is unreliable. A protocol-level approach
|
|
15
|
+
* (detecting stale outbox files) should be implemented in relay-pty instead.
|
|
16
|
+
*
|
|
17
|
+
* Emits 'stuck' event when detected, with reason and details.
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from 'node:events';
|
|
20
|
+
export type StuckReason = 'extended_idle' | 'error_loop' | 'output_loop' | 'tool_loop' | 'output_flood';
|
|
21
|
+
/**
|
|
22
|
+
* Tracked tool invocation for loop detection
|
|
23
|
+
*/
|
|
24
|
+
interface ToolInvocation {
|
|
25
|
+
tool: string;
|
|
26
|
+
target: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}
|
|
29
|
+
export interface StuckEvent {
|
|
30
|
+
reason: StuckReason;
|
|
31
|
+
details: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
/** Time since last output in ms (for extended_idle) */
|
|
34
|
+
idleDurationMs?: number;
|
|
35
|
+
/** Repeated content (for loops) */
|
|
36
|
+
repeatedContent?: string;
|
|
37
|
+
/** Number of repetitions (for loops) */
|
|
38
|
+
repetitions?: number;
|
|
39
|
+
/** Target file/command (for tool_loop) */
|
|
40
|
+
targetFile?: string;
|
|
41
|
+
/** Tool name (for tool_loop) */
|
|
42
|
+
toolName?: string;
|
|
43
|
+
/** Output rate in lines per minute (for output_flood) */
|
|
44
|
+
linesPerMinute?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface StuckDetectorConfig {
|
|
47
|
+
/** Duration of inactivity before considered stuck (ms, default: 10 minutes) */
|
|
48
|
+
extendedIdleMs?: number;
|
|
49
|
+
/** Number of repeated outputs before considered stuck (default: 3) */
|
|
50
|
+
loopThreshold?: number;
|
|
51
|
+
/** Check interval (ms, default: 30 seconds) */
|
|
52
|
+
checkIntervalMs?: number;
|
|
53
|
+
/** Minimum output length to consider for loop detection */
|
|
54
|
+
minLoopLength?: number;
|
|
55
|
+
/** Error patterns to detect (regexes) */
|
|
56
|
+
errorPatterns?: RegExp[];
|
|
57
|
+
/** Threshold for same file operations before considered stuck (default: 10) */
|
|
58
|
+
toolLoopThreshold?: number;
|
|
59
|
+
/** Time window for tool loop detection (ms, default: 5 minutes) */
|
|
60
|
+
toolLoopWindowMs?: number;
|
|
61
|
+
/** Output lines per minute threshold for flood detection (default: 5000) */
|
|
62
|
+
outputFloodLinesPerMinute?: number;
|
|
63
|
+
/** Minimum duration before flood detection activates (ms, default: 2 minutes) */
|
|
64
|
+
outputFloodMinDurationMs?: number;
|
|
65
|
+
}
|
|
66
|
+
export declare class StuckDetector extends EventEmitter {
|
|
67
|
+
private config;
|
|
68
|
+
private lastOutputTime;
|
|
69
|
+
private recentOutputs;
|
|
70
|
+
private checkInterval;
|
|
71
|
+
private isStuck;
|
|
72
|
+
private stuckReason;
|
|
73
|
+
private toolInvocations;
|
|
74
|
+
private outputLineCount;
|
|
75
|
+
private outputStartTime;
|
|
76
|
+
constructor(config?: StuckDetectorConfig);
|
|
77
|
+
/**
|
|
78
|
+
* Start monitoring for stuck conditions.
|
|
79
|
+
* Call this after the agent process starts.
|
|
80
|
+
*/
|
|
81
|
+
start(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Stop monitoring.
|
|
84
|
+
*/
|
|
85
|
+
stop(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Feed output to the detector.
|
|
88
|
+
* Call this for every output chunk from the agent.
|
|
89
|
+
*/
|
|
90
|
+
onOutput(chunk: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* Extract tool invocations from output and track them.
|
|
93
|
+
*/
|
|
94
|
+
private extractToolInvocations;
|
|
95
|
+
/**
|
|
96
|
+
* Normalize output for comparison (strip ANSI, trim, lowercase).
|
|
97
|
+
*/
|
|
98
|
+
private normalizeOutput;
|
|
99
|
+
/**
|
|
100
|
+
* Check for stuck conditions.
|
|
101
|
+
*/
|
|
102
|
+
private checkStuck;
|
|
103
|
+
/**
|
|
104
|
+
* Detect repeated error messages.
|
|
105
|
+
*/
|
|
106
|
+
private detectErrorLoop;
|
|
107
|
+
/**
|
|
108
|
+
* Detect repeated output patterns (not necessarily errors).
|
|
109
|
+
*/
|
|
110
|
+
private detectOutputLoop;
|
|
111
|
+
/**
|
|
112
|
+
* Detect when the same file is being operated on repeatedly.
|
|
113
|
+
* This catches cases like an agent repeatedly reading/writing the same file
|
|
114
|
+
* in a loop, even if the output content differs each time.
|
|
115
|
+
*/
|
|
116
|
+
private detectToolLoop;
|
|
117
|
+
/**
|
|
118
|
+
* Detect abnormally high output rates that suggest an infinite loop.
|
|
119
|
+
* Only triggers after minimum duration to avoid false positives during
|
|
120
|
+
* normal high-output operations (like builds or tests).
|
|
121
|
+
*/
|
|
122
|
+
private detectOutputFlood;
|
|
123
|
+
/**
|
|
124
|
+
* Emit stuck event.
|
|
125
|
+
*/
|
|
126
|
+
private emitStuck;
|
|
127
|
+
/**
|
|
128
|
+
* Check if currently detected as stuck.
|
|
129
|
+
*/
|
|
130
|
+
getIsStuck(): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Get the reason for being stuck (if stuck).
|
|
133
|
+
*/
|
|
134
|
+
getStuckReason(): StuckReason | null;
|
|
135
|
+
/**
|
|
136
|
+
* Get time since last output in milliseconds.
|
|
137
|
+
*/
|
|
138
|
+
getIdleDuration(): number;
|
|
139
|
+
/**
|
|
140
|
+
* Reset state.
|
|
141
|
+
*/
|
|
142
|
+
reset(): void;
|
|
143
|
+
/**
|
|
144
|
+
* Get current output statistics (useful for debugging).
|
|
145
|
+
*/
|
|
146
|
+
getOutputStats(): {
|
|
147
|
+
lineCount: number;
|
|
148
|
+
durationMs: number;
|
|
149
|
+
linesPerMinute: number;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Get recent tool invocations (useful for debugging).
|
|
153
|
+
*/
|
|
154
|
+
getToolInvocations(): ToolInvocation[];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Create a stuck detector with default configuration.
|
|
158
|
+
*/
|
|
159
|
+
export declare function createStuckDetector(config?: StuckDetectorConfig): StuckDetector;
|
|
160
|
+
export {};
|
|
161
|
+
//# sourceMappingURL=stuck-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stuck-detector.d.ts","sourceRoot":"","sources":["../src/stuck-detector.ts"],"names":[],"mappings":"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"}
|