agent-relay 2.1.0 → 2.1.2
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/index.cjs +193 -63
- package/dist/src/cli/index.d.ts +11 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +112 -110
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +18 -18
- package/packages/api-types/package.json +1 -1
- package/packages/benchmark/package.json +4 -4
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/package.json +1 -1
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +2 -2
- package/packages/daemon/dist/connection.d.ts +5 -0
- package/packages/daemon/dist/connection.d.ts.map +1 -1
- package/packages/daemon/dist/connection.js +19 -1
- package/packages/daemon/dist/connection.js.map +1 -1
- package/packages/daemon/dist/server.js +2 -2
- package/packages/daemon/dist/server.js.map +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/daemon/src/connection.ts +22 -1
- package/packages/daemon/src/router.test.ts +32 -0
- package/packages/daemon/src/server.ts +2 -2
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +3 -3
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/dist/types.d.ts +5 -0
- package/packages/protocol/dist/types.d.ts.map +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/src/types.ts +5 -0
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/dist/client.d.ts +6 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +1 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/client.ts +7 -0
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/dist/cjs/relay-pty-path.js +111 -55
- package/packages/utils/dist/relay-pty-path.d.ts +17 -12
- package/packages/utils/dist/relay-pty-path.d.ts.map +1 -1
- package/packages/utils/dist/relay-pty-path.js +144 -94
- package/packages/utils/dist/relay-pty-path.js.map +1 -1
- package/packages/utils/package.json +2 -2
- package/packages/utils/src/relay-pty-path.test.ts +373 -0
- package/packages/utils/src/relay-pty-path.ts +182 -91
- package/packages/wrapper/dist/base-wrapper.d.ts +5 -0
- package/packages/wrapper/dist/base-wrapper.d.ts.map +1 -1
- package/packages/wrapper/dist/base-wrapper.js +14 -1
- package/packages/wrapper/dist/base-wrapper.js.map +1 -1
- package/packages/wrapper/dist/shared.d.ts +36 -0
- package/packages/wrapper/dist/shared.d.ts.map +1 -1
- package/packages/wrapper/dist/shared.js +123 -2
- package/packages/wrapper/dist/shared.js.map +1 -1
- package/packages/wrapper/dist/tmux-wrapper.js +1 -1
- package/packages/wrapper/dist/tmux-wrapper.js.map +1 -1
- package/packages/wrapper/package.json +6 -6
- package/packages/wrapper/src/base-wrapper.ts +15 -0
- package/packages/wrapper/src/shared.test.ts +156 -11
- package/packages/wrapper/src/shared.ts +154 -2
- package/packages/wrapper/src/tmux-wrapper.ts +1 -1
|
@@ -13,7 +13,7 @@ describe('buildInjectionString', () => {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
describe('sender name display', () => {
|
|
16
|
-
it('uses msg.from when from is not
|
|
16
|
+
it('uses msg.from when from is not Dashboard', () => {
|
|
17
17
|
const msg: QueuedMessage = {
|
|
18
18
|
...baseMessage,
|
|
19
19
|
from: 'RegularAgent',
|
|
@@ -22,40 +22,40 @@ describe('buildInjectionString', () => {
|
|
|
22
22
|
expect(result).toContain('Relay message from RegularAgent');
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it('uses msg.from when from is
|
|
25
|
+
it('uses msg.from when from is Dashboard but no senderName in data', () => {
|
|
26
26
|
const msg: QueuedMessage = {
|
|
27
27
|
...baseMessage,
|
|
28
|
-
from: '
|
|
28
|
+
from: 'Dashboard',
|
|
29
29
|
};
|
|
30
30
|
const result = buildInjectionString(msg);
|
|
31
|
-
expect(result).toContain('Relay message from
|
|
31
|
+
expect(result).toContain('Relay message from Dashboard');
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it('uses senderName when from is
|
|
34
|
+
it('uses senderName when from is Dashboard and senderName exists', () => {
|
|
35
35
|
const msg: QueuedMessage = {
|
|
36
36
|
...baseMessage,
|
|
37
|
-
from: '
|
|
37
|
+
from: 'Dashboard',
|
|
38
38
|
data: { senderName: 'GitHubUser123' },
|
|
39
39
|
};
|
|
40
40
|
const result = buildInjectionString(msg);
|
|
41
41
|
expect(result).toContain('Relay message from GitHubUser123');
|
|
42
|
-
expect(result).not.toContain('
|
|
42
|
+
expect(result).not.toContain('Dashboard');
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
it('uses msg.from when senderName is not a string', () => {
|
|
46
46
|
const msg: QueuedMessage = {
|
|
47
47
|
...baseMessage,
|
|
48
|
-
from: '
|
|
48
|
+
from: 'Dashboard',
|
|
49
49
|
data: { senderName: 12345 }, // not a string
|
|
50
50
|
};
|
|
51
51
|
const result = buildInjectionString(msg);
|
|
52
|
-
expect(result).toContain('Relay message from
|
|
52
|
+
expect(result).toContain('Relay message from Dashboard');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it('uses msg.from when senderName is empty string', () => {
|
|
56
56
|
const msg: QueuedMessage = {
|
|
57
57
|
...baseMessage,
|
|
58
|
-
from: '
|
|
58
|
+
from: 'Dashboard',
|
|
59
59
|
data: { senderName: '' },
|
|
60
60
|
};
|
|
61
61
|
// Empty string is falsy but still a string - our check uses typeof === 'string'
|
|
@@ -65,7 +65,7 @@ describe('buildInjectionString', () => {
|
|
|
65
65
|
expect(result).toContain('Relay message from ['); // empty between 'from' and '['
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it('does not use senderName when from is not
|
|
68
|
+
it('does not use senderName when from is not Dashboard even if senderName exists', () => {
|
|
69
69
|
const msg: QueuedMessage = {
|
|
70
70
|
...baseMessage,
|
|
71
71
|
from: 'OtherAgent',
|
|
@@ -320,3 +320,148 @@ describe('Message Priority System', () => {
|
|
|
320
320
|
});
|
|
321
321
|
});
|
|
322
322
|
});
|
|
323
|
+
|
|
324
|
+
// Import auto-suggestion detection functions for testing
|
|
325
|
+
import { detectAutoSuggest, shouldIgnoreForIdleDetection } from './shared.js';
|
|
326
|
+
|
|
327
|
+
describe('Auto-suggestion Detection', () => {
|
|
328
|
+
describe('detectAutoSuggest', () => {
|
|
329
|
+
it('detects dim text styling (common for ghost text)', () => {
|
|
330
|
+
// \x1B[2m is dim text
|
|
331
|
+
const output = '\x1B[2msuggested completion\x1B[0m';
|
|
332
|
+
const result = detectAutoSuggest(output);
|
|
333
|
+
|
|
334
|
+
expect(result.isAutoSuggest).toBe(true);
|
|
335
|
+
expect(result.patterns).toContain('dim');
|
|
336
|
+
expect(result.confidence).toBeGreaterThanOrEqual(0.4);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('detects bright black (dark gray) styling', () => {
|
|
340
|
+
// \x1B[90m is bright black (dark gray)
|
|
341
|
+
const output = '\x1B[90mauto-suggested text\x1B[0m';
|
|
342
|
+
const result = detectAutoSuggest(output);
|
|
343
|
+
|
|
344
|
+
expect(result.isAutoSuggest).toBe(true);
|
|
345
|
+
expect(result.patterns).toContain('brightBlack');
|
|
346
|
+
expect(result.confidence).toBeGreaterThanOrEqual(0.4);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('detects 256-color gray styling (pattern detected but alone not enough)', () => {
|
|
350
|
+
// \x1B[38;5;8m is 256-color dark gray
|
|
351
|
+
// By itself it only adds 0.3 confidence, below the 0.4 threshold
|
|
352
|
+
const output = '\x1B[38;5;8msuggestion\x1B[0m';
|
|
353
|
+
const result = detectAutoSuggest(output);
|
|
354
|
+
|
|
355
|
+
expect(result.patterns).toContain('gray256');
|
|
356
|
+
expect(result.confidence).toBeGreaterThan(0);
|
|
357
|
+
// 256-color gray alone isn't enough - need additional signals
|
|
358
|
+
expect(result.isAutoSuggest).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('detects 256-color gray combined with other signals', () => {
|
|
362
|
+
// 256-color gray + cursor save/restore = strong signal
|
|
363
|
+
const output = '\x1B[s\x1B[38;5;8msuggestion\x1B[0m\x1B[u';
|
|
364
|
+
const result = detectAutoSuggest(output);
|
|
365
|
+
|
|
366
|
+
expect(result.isAutoSuggest).toBe(true);
|
|
367
|
+
expect(result.patterns).toContain('gray256');
|
|
368
|
+
expect(result.patterns).toContain('cursorSaveRestore');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('detects cursor save/restore pair (strong indicator)', () => {
|
|
372
|
+
// \x1B[s saves cursor, \x1B[u restores cursor
|
|
373
|
+
const output = '\x1B[s\x1B[90msuggestion\x1B[0m\x1B[u';
|
|
374
|
+
const result = detectAutoSuggest(output);
|
|
375
|
+
|
|
376
|
+
expect(result.isAutoSuggest).toBe(true);
|
|
377
|
+
expect(result.patterns).toContain('cursorSaveRestore');
|
|
378
|
+
expect(result.confidence).toBeGreaterThanOrEqual(0.5);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('detects alternative cursor save/restore (ESC 7/8)', () => {
|
|
382
|
+
// \x1B7 saves cursor, \x1B8 restores cursor (alternative format)
|
|
383
|
+
const output = '\x1B7\x1B[2mcompletion\x1B0m\x1B8';
|
|
384
|
+
const result = detectAutoSuggest(output);
|
|
385
|
+
|
|
386
|
+
expect(result.patterns).toContain('cursorSaveRestore');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('returns false for normal text output', () => {
|
|
390
|
+
const output = 'Hello, this is normal output text\n';
|
|
391
|
+
const result = detectAutoSuggest(output);
|
|
392
|
+
|
|
393
|
+
expect(result.isAutoSuggest).toBe(false);
|
|
394
|
+
expect(result.confidence).toBe(0);
|
|
395
|
+
expect(result.patterns).toHaveLength(0);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('returns false for colored output without gray/dim', () => {
|
|
399
|
+
// Regular green text - not an auto-suggestion
|
|
400
|
+
const output = '\x1B[32mSuccess: Tests passed\x1B[0m';
|
|
401
|
+
const result = detectAutoSuggest(output);
|
|
402
|
+
|
|
403
|
+
expect(result.isAutoSuggest).toBe(false);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('reduces confidence for multi-line output', () => {
|
|
407
|
+
// Auto-suggestions are typically single-line
|
|
408
|
+
const multiLine = '\x1B[90mLine 1\nLine 2\nLine 3\nLine 4\x1B[0m';
|
|
409
|
+
const result = detectAutoSuggest(multiLine);
|
|
410
|
+
|
|
411
|
+
// Still detects the pattern but confidence is reduced
|
|
412
|
+
expect(result.patterns).toContain('brightBlack');
|
|
413
|
+
// Multi-line reduces confidence by 50%
|
|
414
|
+
expect(result.confidence).toBeLessThan(0.4);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('combines multiple patterns for higher confidence', () => {
|
|
418
|
+
// Both dim and cursor save/restore
|
|
419
|
+
const output = '\x1B[s\x1B[2m\x1B[90msuggestion\x1B[0m\x1B[u';
|
|
420
|
+
const result = detectAutoSuggest(output);
|
|
421
|
+
|
|
422
|
+
expect(result.patterns).toContain('dim');
|
|
423
|
+
expect(result.patterns).toContain('brightBlack');
|
|
424
|
+
expect(result.patterns).toContain('cursorSaveRestore');
|
|
425
|
+
expect(result.confidence).toBeGreaterThanOrEqual(0.8);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('includes stripped content for debugging', () => {
|
|
429
|
+
const output = '\x1B[90msuggested text\x1B[0m';
|
|
430
|
+
const result = detectAutoSuggest(output);
|
|
431
|
+
|
|
432
|
+
expect(result.strippedContent).toBe('suggested text');
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('shouldIgnoreForIdleDetection', () => {
|
|
437
|
+
it('ignores empty output', () => {
|
|
438
|
+
expect(shouldIgnoreForIdleDetection('')).toBe(true);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('ignores output that is only ANSI control sequences', () => {
|
|
442
|
+
// Just cursor movement, no actual content
|
|
443
|
+
const output = '\x1B[2J\x1B[H'; // Clear screen and home cursor
|
|
444
|
+
expect(shouldIgnoreForIdleDetection(output)).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('ignores auto-suggestion output', () => {
|
|
448
|
+
const autoSuggest = '\x1B[90mtype "exit" to quit\x1B[0m';
|
|
449
|
+
expect(shouldIgnoreForIdleDetection(autoSuggest)).toBe(true);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('does not ignore normal text output', () => {
|
|
453
|
+
const normalOutput = 'Hello world\n';
|
|
454
|
+
expect(shouldIgnoreForIdleDetection(normalOutput)).toBe(false);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('does not ignore colored output (non-gray)', () => {
|
|
458
|
+
const greenOutput = '\x1B[32mTest passed!\x1B[0m\n';
|
|
459
|
+
expect(shouldIgnoreForIdleDetection(greenOutput)).toBe(false);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('does not ignore relay messages', () => {
|
|
463
|
+
const relayMessage = '->relay:Lead Task completed';
|
|
464
|
+
expect(shouldIgnoreForIdleDetection(relayMessage)).toBe(false);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -200,6 +200,158 @@ export function sleep(ms: number): Promise<void> {
|
|
|
200
200
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* ANSI escape patterns for auto-suggestion (ghost text) detection.
|
|
205
|
+
*
|
|
206
|
+
* Claude Code and other CLIs show auto-suggestions using:
|
|
207
|
+
* - Dim text: \x1B[2m
|
|
208
|
+
* - Bright black (gray): \x1B[90m
|
|
209
|
+
* - 256-color gray: \x1B[38;5;8m or \x1B[38;5;240m through \x1B[38;5;250m
|
|
210
|
+
* - Cursor save/restore: \x1B[s / \x1B[u or \x1B7 / \x1B8
|
|
211
|
+
*
|
|
212
|
+
* Auto-suggestions are typically:
|
|
213
|
+
* 1. Styled with dim/gray text
|
|
214
|
+
* 2. Cursor position is saved before, restored after (so cursor doesn't advance)
|
|
215
|
+
* 3. The actual text content is the "ghost" suggestion
|
|
216
|
+
*/
|
|
217
|
+
// eslint-disable-next-line no-control-regex
|
|
218
|
+
const AUTO_SUGGEST_PATTERNS = {
|
|
219
|
+
// Dim text styling - commonly used for ghost text
|
|
220
|
+
dim: /\x1B\[2m/,
|
|
221
|
+
// Bright black (dark gray) - common for suggestions
|
|
222
|
+
brightBlack: /\x1B\[90m/,
|
|
223
|
+
// 256-color grays (8 is dark gray, 240-250 are grays)
|
|
224
|
+
gray256: /\x1B\[38;5;(?:8|24[0-9]|250)m/,
|
|
225
|
+
// Cursor save (CSI s or ESC 7)
|
|
226
|
+
cursorSave: /\x1B\[s|\x1B7/,
|
|
227
|
+
// Cursor restore (CSI u or ESC 8)
|
|
228
|
+
cursorRestore: /\x1B\[u|\x1B8/,
|
|
229
|
+
// Italic text - sometimes used for suggestions
|
|
230
|
+
italic: /\x1B\[3m/,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Result of auto-suggestion detection.
|
|
235
|
+
*/
|
|
236
|
+
export interface AutoSuggestResult {
|
|
237
|
+
/** True if the output looks like an auto-suggestion */
|
|
238
|
+
isAutoSuggest: boolean;
|
|
239
|
+
/** Confidence level (0-1) */
|
|
240
|
+
confidence: number;
|
|
241
|
+
/** Which patterns were detected */
|
|
242
|
+
patterns: string[];
|
|
243
|
+
/** The actual content after stripping ANSI (for debugging) */
|
|
244
|
+
strippedContent?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Detect if terminal output is likely an auto-suggestion (ghost text).
|
|
249
|
+
*
|
|
250
|
+
* Auto-suggestions should NOT reset the idle timer because they represent
|
|
251
|
+
* the CLI showing suggestions to the user, not actual output from the agent.
|
|
252
|
+
*
|
|
253
|
+
* Detection heuristics:
|
|
254
|
+
* 1. Contains dim/gray styling without other foreground colors
|
|
255
|
+
* 2. Has cursor save/restore patterns (suggestion doesn't advance cursor)
|
|
256
|
+
* 3. Stripped content is non-empty but doesn't contain relay commands
|
|
257
|
+
*
|
|
258
|
+
* @param output Raw terminal output including ANSI codes
|
|
259
|
+
* @returns Detection result with confidence and matched patterns
|
|
260
|
+
*/
|
|
261
|
+
export function detectAutoSuggest(output: string): AutoSuggestResult {
|
|
262
|
+
const patterns: string[] = [];
|
|
263
|
+
let confidence = 0;
|
|
264
|
+
|
|
265
|
+
// Check for dim styling (very common for ghost text)
|
|
266
|
+
if (AUTO_SUGGEST_PATTERNS.dim.test(output)) {
|
|
267
|
+
patterns.push('dim');
|
|
268
|
+
confidence += 0.4;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check for bright black (dark gray)
|
|
272
|
+
if (AUTO_SUGGEST_PATTERNS.brightBlack.test(output)) {
|
|
273
|
+
patterns.push('brightBlack');
|
|
274
|
+
confidence += 0.4;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check for 256-color gray
|
|
278
|
+
if (AUTO_SUGGEST_PATTERNS.gray256.test(output)) {
|
|
279
|
+
patterns.push('gray256');
|
|
280
|
+
confidence += 0.3;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check for italic (sometimes used for suggestions)
|
|
284
|
+
if (AUTO_SUGGEST_PATTERNS.italic.test(output)) {
|
|
285
|
+
patterns.push('italic');
|
|
286
|
+
confidence += 0.2;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check for cursor save/restore pair (strong indicator)
|
|
290
|
+
const hasCursorSave = AUTO_SUGGEST_PATTERNS.cursorSave.test(output);
|
|
291
|
+
const hasCursorRestore = AUTO_SUGGEST_PATTERNS.cursorRestore.test(output);
|
|
292
|
+
|
|
293
|
+
if (hasCursorSave && hasCursorRestore) {
|
|
294
|
+
patterns.push('cursorSaveRestore');
|
|
295
|
+
confidence += 0.5;
|
|
296
|
+
} else if (hasCursorSave || hasCursorRestore) {
|
|
297
|
+
patterns.push(hasCursorSave ? 'cursorSave' : 'cursorRestore');
|
|
298
|
+
confidence += 0.2;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Cap confidence at 1.0
|
|
302
|
+
confidence = Math.min(confidence, 1.0);
|
|
303
|
+
|
|
304
|
+
// Strip ANSI to check actual content
|
|
305
|
+
const stripped = stripAnsi(output);
|
|
306
|
+
|
|
307
|
+
// If no patterns detected, it's not an auto-suggest
|
|
308
|
+
if (patterns.length === 0) {
|
|
309
|
+
return { isAutoSuggest: false, confidence: 0, patterns, strippedContent: stripped };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Additional checks to reduce false positives:
|
|
313
|
+
// - Actual content should be relatively short (suggestions are typically one line)
|
|
314
|
+
// - Should not contain newlines (multi-line output is probably real output)
|
|
315
|
+
const lines = stripped.split('\n').filter(l => l.trim().length > 0);
|
|
316
|
+
if (lines.length > 2) {
|
|
317
|
+
// Multi-line content - less likely to be just a suggestion
|
|
318
|
+
confidence *= 0.5;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Consider it an auto-suggest if confidence is above threshold
|
|
322
|
+
const isAutoSuggest = confidence >= 0.4;
|
|
323
|
+
|
|
324
|
+
return { isAutoSuggest, confidence, patterns, strippedContent: stripped };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Check if output should be ignored for idle detection purposes.
|
|
329
|
+
* Returns true if the output is likely an auto-suggestion or control sequence only.
|
|
330
|
+
*
|
|
331
|
+
* @param output Raw terminal output
|
|
332
|
+
* @returns true if output should be ignored for idle detection
|
|
333
|
+
*/
|
|
334
|
+
export function shouldIgnoreForIdleDetection(output: string): boolean {
|
|
335
|
+
// Empty output should be ignored
|
|
336
|
+
if (!output || output.length === 0) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check if it's an auto-suggestion
|
|
341
|
+
const result = detectAutoSuggest(output);
|
|
342
|
+
if (result.isAutoSuggest) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Check if stripped content is empty (only control sequences)
|
|
347
|
+
const stripped = stripAnsi(output).trim();
|
|
348
|
+
if (stripped.length === 0) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
203
355
|
/**
|
|
204
356
|
* Build the injection string for a relay message.
|
|
205
357
|
* Format: Relay message from {from} [{shortId}]{hints}: {body}
|
|
@@ -221,9 +373,9 @@ export function buildInjectionString(msg: QueuedMessage): string {
|
|
|
221
373
|
|
|
222
374
|
const shortId = msg.messageId.substring(0, 8);
|
|
223
375
|
|
|
224
|
-
// Use senderName from data if available (for dashboard messages sent via
|
|
376
|
+
// Use senderName from data if available (for dashboard messages sent via Dashboard)
|
|
225
377
|
// This allows showing the actual GitHub username instead of the system client name
|
|
226
|
-
const displayFrom = (msg.from === '
|
|
378
|
+
const displayFrom = (msg.from === 'Dashboard' && typeof msg.data?.senderName === 'string')
|
|
227
379
|
? msg.data.senderName
|
|
228
380
|
: msg.from;
|
|
229
381
|
|
|
@@ -1291,7 +1291,7 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
1291
1291
|
this.logStderr(`${agentName} is online, sending task...`);
|
|
1292
1292
|
|
|
1293
1293
|
// Send task directly via our relay client (not dashboard API)
|
|
1294
|
-
// This ensures the message comes "from" this agent, not from
|
|
1294
|
+
// This ensures the message comes "from" this agent, not from Dashboard
|
|
1295
1295
|
if (this.client.state === 'READY') {
|
|
1296
1296
|
const sent = this.client.sendMessage(agentName, task, 'message');
|
|
1297
1297
|
if (sent) {
|