tmux-team 3.1.0 → 3.2.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/package.json +1 -1
- package/src/commands/talk.test.ts +0 -35
- package/src/commands/talk.ts +31 -34
package/package.json
CHANGED
|
@@ -1224,12 +1224,6 @@ describe('cmdTalk - end marker detection', () => {
|
|
|
1224
1224
|
return `Message\n\nWhen you finish responding, print this exact line:\n${endMarker}\n${response}\n${endMarker}`;
|
|
1225
1225
|
}
|
|
1226
1226
|
|
|
1227
|
-
// Helper: generate mock capture where instruction scrolled off, detected via UI
|
|
1228
|
-
function mockResponseWithUI(nonce: string, response: string): string {
|
|
1229
|
-
const endMarker = `---RESPONSE-END-${nonce}---`;
|
|
1230
|
-
return `${response}\n${endMarker}\n\n╭───────────────────╮\n│ > Type message │\n╰───────────────────╯`;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
1227
|
it('includes end marker in sent message', async () => {
|
|
1234
1228
|
const tmux = createMockTmux();
|
|
1235
1229
|
const ui = createMockUI();
|
|
@@ -1289,35 +1283,6 @@ describe('cmdTalk - end marker detection', () => {
|
|
|
1289
1283
|
expect(output.response).toContain('actual response');
|
|
1290
1284
|
});
|
|
1291
1285
|
|
|
1292
|
-
it('detects completion via UI elements when instruction scrolled off', async () => {
|
|
1293
|
-
const tmux = createMockTmux();
|
|
1294
|
-
const ui = createMockUI();
|
|
1295
|
-
|
|
1296
|
-
tmux.capture = () => {
|
|
1297
|
-
const sent = tmux.sends[0]?.message || '';
|
|
1298
|
-
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1299
|
-
if (endMatch) {
|
|
1300
|
-
// Only ONE end marker visible (agent's), followed by CLI UI
|
|
1301
|
-
return mockResponseWithUI(endMatch[1], 'Response from agent');
|
|
1302
|
-
}
|
|
1303
|
-
return '';
|
|
1304
|
-
};
|
|
1305
|
-
|
|
1306
|
-
const ctx = createContext({
|
|
1307
|
-
tmux,
|
|
1308
|
-
ui,
|
|
1309
|
-
paths: createTestPaths(testDir),
|
|
1310
|
-
flags: { wait: true, json: true, timeout: 5 },
|
|
1311
|
-
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1312
|
-
});
|
|
1313
|
-
|
|
1314
|
-
await cmdTalk(ctx, 'claude', 'Test');
|
|
1315
|
-
|
|
1316
|
-
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1317
|
-
expect(output.status).toBe('completed');
|
|
1318
|
-
expect(output.response).toContain('Response from agent');
|
|
1319
|
-
});
|
|
1320
|
-
|
|
1321
1286
|
it('handles multiline responses correctly', async () => {
|
|
1322
1287
|
const tmux = createMockTmux();
|
|
1323
1288
|
const ui = createMockUI();
|
package/src/commands/talk.ts
CHANGED
|
@@ -278,9 +278,10 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
278
278
|
let lastNonTtyLogAt = 0;
|
|
279
279
|
const isTTY = process.stdout.isTTY && !flags.json;
|
|
280
280
|
|
|
281
|
-
//
|
|
282
|
-
const
|
|
283
|
-
|
|
281
|
+
// Debounce detection: wait for output to stabilize
|
|
282
|
+
const MIN_WAIT_MS = 3000; // Wait at least 3 seconds before detecting completion
|
|
283
|
+
const IDLE_THRESHOLD_MS = 3000; // Content unchanged for 3 seconds = complete
|
|
284
|
+
let lastOutput = '';
|
|
284
285
|
let lastOutputChangeAt = Date.now();
|
|
285
286
|
|
|
286
287
|
const onSigint = (): void => {
|
|
@@ -402,40 +403,40 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
402
403
|
console.error(`[DEBUG ${elapsedSec}s] Output tail:\n${output.slice(-300)}`);
|
|
403
404
|
}
|
|
404
405
|
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const firstEndMarkerIndex = output.indexOf(endMarker);
|
|
410
|
-
const lastEndMarkerIndex = output.lastIndexOf(endMarker);
|
|
411
|
-
|
|
412
|
-
if (firstEndMarkerIndex === -1) {
|
|
413
|
-
// No marker at all - still waiting
|
|
414
|
-
continue;
|
|
406
|
+
// Track output changes for debounce detection
|
|
407
|
+
if (output !== lastOutput) {
|
|
408
|
+
lastOutput = output;
|
|
409
|
+
lastOutputChangeAt = Date.now();
|
|
415
410
|
}
|
|
416
411
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
const twoMarkers = firstEndMarkerIndex !== lastEndMarkerIndex;
|
|
412
|
+
const elapsedMs = Date.now() - startedAt;
|
|
413
|
+
const idleMs = Date.now() - lastOutputChangeAt;
|
|
414
|
+
|
|
415
|
+
// Find end marker
|
|
416
|
+
const hasEndMarker = output.includes(endMarker);
|
|
423
417
|
|
|
424
|
-
|
|
425
|
-
|
|
418
|
+
// Completion conditions:
|
|
419
|
+
// 1. Must wait at least MIN_WAIT_MS
|
|
420
|
+
// 2. Must have end marker in output
|
|
421
|
+
// 3. Output must be stable for IDLE_THRESHOLD_MS (debounce)
|
|
422
|
+
if (elapsedMs < MIN_WAIT_MS || !hasEndMarker || idleMs < IDLE_THRESHOLD_MS) {
|
|
423
|
+
if (flags.debug && hasEndMarker) {
|
|
424
|
+
console.error(
|
|
425
|
+
`[DEBUG] Marker found, waiting for debounce (elapsed: ${elapsedMs}ms, idle: ${idleMs}ms)`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
426
428
|
continue;
|
|
427
429
|
}
|
|
428
430
|
|
|
429
|
-
if (flags.debug)
|
|
430
|
-
console.error(
|
|
431
|
-
|
|
432
|
-
);
|
|
431
|
+
if (flags.debug) {
|
|
432
|
+
console.error(`[DEBUG] Agent completed (elapsed: ${elapsedMs}ms, idle: ${idleMs}ms)`);
|
|
433
|
+
}
|
|
433
434
|
|
|
434
|
-
// Extract response: get N lines before the
|
|
435
|
+
// Extract response: get N lines before the end marker
|
|
435
436
|
const responseLines = flags.lines ?? 100;
|
|
436
437
|
const lines = output.split('\n');
|
|
437
438
|
|
|
438
|
-
// Find the line with the
|
|
439
|
+
// Find the line with the end marker (last occurrence = agent's marker)
|
|
439
440
|
let endMarkerLineIndex = -1;
|
|
440
441
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
441
442
|
if (lines[i].includes(endMarker)) {
|
|
@@ -446,13 +447,9 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
446
447
|
|
|
447
448
|
if (endMarkerLineIndex === -1) continue;
|
|
448
449
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// Two markers - find line after first marker (instruction)
|
|
453
|
-
const firstMarkerLineIndex = lines.findIndex((line) => line.includes(endMarker));
|
|
454
|
-
startLine = firstMarkerLineIndex + 1;
|
|
455
|
-
}
|
|
450
|
+
// Find where response starts (after instruction's end marker, if visible)
|
|
451
|
+
const firstMarkerLineIndex = lines.findIndex((line) => line.includes(endMarker));
|
|
452
|
+
let startLine = firstMarkerLineIndex + 1;
|
|
456
453
|
// Limit to N lines before end marker
|
|
457
454
|
startLine = Math.max(startLine, endMarkerLineIndex - responseLines);
|
|
458
455
|
|