tmux-team 3.0.1 → 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/README.md +55 -259
- package/package.json +16 -14
- package/skills/README.md +9 -10
- package/src/cli.test.ts +163 -0
- package/src/cli.ts +20 -17
- package/src/commands/basic-commands.test.ts +252 -0
- package/src/commands/config-command.test.ts +116 -0
- package/src/commands/help.ts +4 -1
- package/src/commands/install.test.ts +205 -0
- package/src/commands/install.ts +207 -0
- package/src/commands/setup.test.ts +175 -0
- package/src/commands/setup.ts +163 -0
- package/src/commands/talk.test.ts +144 -111
- package/src/commands/talk.ts +185 -100
- package/src/context.test.ts +68 -0
- package/src/identity.test.ts +70 -0
- package/src/state.test.ts +14 -0
- package/src/tmux.test.ts +50 -0
- package/src/tmux.ts +66 -2
- package/src/types.ts +10 -1
- package/src/ui.test.ts +63 -0
- package/src/version.test.ts +31 -0
- package/src/commands/install-skill.ts +0 -148
|
@@ -10,6 +10,13 @@ import type { Context, Tmux, UI, Paths, ResolvedConfig, Flags } from '../types.j
|
|
|
10
10
|
import { ExitCodes } from '../exits.js';
|
|
11
11
|
import { cmdTalk } from './talk.js';
|
|
12
12
|
|
|
13
|
+
// ─────────────────────────────────────────────────────────────
|
|
14
|
+
// Constants
|
|
15
|
+
// ─────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
// Regex to match new end marker format
|
|
18
|
+
const END_MARKER_REGEX = /---RESPONSE-END-([a-f0-9]+)---/;
|
|
19
|
+
|
|
13
20
|
// ─────────────────────────────────────────────────────────────
|
|
14
21
|
// Test utilities
|
|
15
22
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -27,6 +34,12 @@ function createMockTmux(): Tmux & {
|
|
|
27
34
|
capture(_pane: string, _lines: number) {
|
|
28
35
|
return mock.captureReturn;
|
|
29
36
|
},
|
|
37
|
+
listPanes() {
|
|
38
|
+
return [];
|
|
39
|
+
},
|
|
40
|
+
getCurrentPaneId() {
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
30
43
|
};
|
|
31
44
|
return mock;
|
|
32
45
|
}
|
|
@@ -445,8 +458,10 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
445
458
|
|
|
446
459
|
// Helper: generate mock capture output with proper marker structure
|
|
447
460
|
// The end marker must appear TWICE: once in instruction, once from "agent"
|
|
461
|
+
// New format: ---RESPONSE-END-NONCE---
|
|
448
462
|
function mockCompleteResponse(nonce: string, response: string): string {
|
|
449
|
-
|
|
463
|
+
const endMarker = `---RESPONSE-END-${nonce}---`;
|
|
464
|
+
return `Hello\n\nWhen you finish responding, print this exact line:\n${endMarker}\n${response}\n${endMarker}`;
|
|
450
465
|
}
|
|
451
466
|
|
|
452
467
|
it('appends nonce instruction to message', async () => {
|
|
@@ -458,7 +473,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
458
473
|
if (captureCount === 1) return ''; // Baseline
|
|
459
474
|
// Return marker on second capture - must include instruction AND agent's end marker
|
|
460
475
|
const sent = tmux.sends[0]?.message || '';
|
|
461
|
-
const match = sent.match(
|
|
476
|
+
const match = sent.match(END_MARKER_REGEX);
|
|
462
477
|
return match ? mockCompleteResponse(match[1], 'Response here') : '';
|
|
463
478
|
};
|
|
464
479
|
|
|
@@ -479,10 +494,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
479
494
|
await cmdTalk(ctx, 'claude', 'Hello');
|
|
480
495
|
|
|
481
496
|
expect(tmux.sends).toHaveLength(1);
|
|
482
|
-
expect(tmux.sends[0].message).toContain(
|
|
483
|
-
|
|
484
|
-
);
|
|
485
|
-
expect(tmux.sends[0].message).toMatch(/\{tmux-team-end:[a-f0-9]+\}/);
|
|
497
|
+
expect(tmux.sends[0].message).toContain('When you finish responding, print this exact line:');
|
|
498
|
+
expect(tmux.sends[0].message).toMatch(/---RESPONSE-END-[a-f0-9]+---/);
|
|
486
499
|
});
|
|
487
500
|
|
|
488
501
|
it('detects nonce marker and extracts response', async () => {
|
|
@@ -496,7 +509,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
496
509
|
if (captureCount === 1) return 'baseline content';
|
|
497
510
|
// Extract nonce from sent message and return matching marker
|
|
498
511
|
const sent = tmux.sends[0]?.message || '';
|
|
499
|
-
const match = sent.match(
|
|
512
|
+
const match = sent.match(END_MARKER_REGEX);
|
|
500
513
|
if (match) {
|
|
501
514
|
return mockCompleteResponse(match[1], 'Agent response here');
|
|
502
515
|
}
|
|
@@ -562,7 +575,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
562
575
|
expect(output.error).toContain('Timed out');
|
|
563
576
|
});
|
|
564
577
|
|
|
565
|
-
it('isolates response using
|
|
578
|
+
it('isolates response using end markers in scrollback', async () => {
|
|
566
579
|
const tmux = createMockTmux();
|
|
567
580
|
const ui = createMockUI();
|
|
568
581
|
|
|
@@ -571,13 +584,11 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
571
584
|
tmux.capture = () => {
|
|
572
585
|
// Simulate scrollback with old content, then our instruction (with end marker), response, and agent's end marker
|
|
573
586
|
const sent = tmux.sends[0]?.message || '';
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// Start and end markers should have the same nonce
|
|
578
|
-
expect(startMatch[1]).toBe(endMatch[1]);
|
|
587
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
588
|
+
if (endMatch) {
|
|
589
|
+
const endMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
579
590
|
// Must include end marker TWICE: once in instruction, once from "agent"
|
|
580
|
-
return `${oldContent}\n\
|
|
591
|
+
return `${oldContent}\n\nMessage content here\n\nWhen you finish responding, print this exact line:\n${endMarker}\nNew response content\n\n${endMarker}`;
|
|
581
592
|
}
|
|
582
593
|
return oldContent;
|
|
583
594
|
};
|
|
@@ -601,7 +612,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
601
612
|
|
|
602
613
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
603
614
|
expect(output.status).toBe('completed');
|
|
604
|
-
// Response should NOT include old content
|
|
615
|
+
// Response should NOT include old content
|
|
605
616
|
expect(output.response).not.toContain('Previous conversation');
|
|
606
617
|
expect(output.response).not.toContain('Old content here');
|
|
607
618
|
// Response should contain the actual response content
|
|
@@ -616,7 +627,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
616
627
|
captureCount++;
|
|
617
628
|
if (captureCount === 1) return '';
|
|
618
629
|
const sent = tmux.sends[0]?.message || '';
|
|
619
|
-
const match = sent.match(
|
|
630
|
+
const match = sent.match(END_MARKER_REGEX);
|
|
620
631
|
return match ? mockCompleteResponse(match[1], 'Done') : '';
|
|
621
632
|
};
|
|
622
633
|
|
|
@@ -684,7 +695,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
684
695
|
|
|
685
696
|
// Mock send to capture the nonce for each pane
|
|
686
697
|
tmux.send = (pane: string, msg: string) => {
|
|
687
|
-
const match = msg.match(
|
|
698
|
+
const match = msg.match(END_MARKER_REGEX);
|
|
688
699
|
if (match) {
|
|
689
700
|
noncesByPane[pane] = match[1];
|
|
690
701
|
}
|
|
@@ -732,7 +743,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
732
743
|
const noncesByPane: Record<string, string> = {};
|
|
733
744
|
|
|
734
745
|
tmux.send = (pane: string, msg: string) => {
|
|
735
|
-
const match = msg.match(
|
|
746
|
+
const match = msg.match(END_MARKER_REGEX);
|
|
736
747
|
if (match) {
|
|
737
748
|
noncesByPane[pane] = match[1];
|
|
738
749
|
}
|
|
@@ -790,7 +801,7 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
790
801
|
const nonces: string[] = [];
|
|
791
802
|
|
|
792
803
|
tmux.send = (_pane: string, msg: string) => {
|
|
793
|
-
const match = msg.match(
|
|
804
|
+
const match = msg.match(END_MARKER_REGEX);
|
|
794
805
|
if (match) {
|
|
795
806
|
nonces.push(match[1]);
|
|
796
807
|
}
|
|
@@ -832,6 +843,47 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
832
843
|
});
|
|
833
844
|
});
|
|
834
845
|
|
|
846
|
+
describe('cmdTalk - errors and JSON output', () => {
|
|
847
|
+
it('errors when target agent is not found', async () => {
|
|
848
|
+
const ctx = createContext({
|
|
849
|
+
config: { paneRegistry: {} },
|
|
850
|
+
});
|
|
851
|
+
await expect(cmdTalk(ctx, 'nope', 'hi')).rejects.toMatchObject({
|
|
852
|
+
exitCode: ExitCodes.PANE_NOT_FOUND,
|
|
853
|
+
});
|
|
854
|
+
expect((ctx.ui as any).errors.join('\n')).toContain("Agent 'nope' not found");
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it('outputs JSON in non-wait mode', async () => {
|
|
858
|
+
const ctx = createContext({
|
|
859
|
+
flags: { json: true },
|
|
860
|
+
config: { paneRegistry: { claude: { pane: '1.0' } } },
|
|
861
|
+
});
|
|
862
|
+
await cmdTalk(ctx, 'claude', 'hello');
|
|
863
|
+
const out = (ctx.ui as any).jsonOutput[0] as any;
|
|
864
|
+
expect(out).toMatchObject({ target: 'claude', pane: '1.0', status: 'sent' });
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it('marks failures in broadcast when send throws', async () => {
|
|
868
|
+
const tmux = createMockTmux();
|
|
869
|
+
const sendSpy = vi.spyOn(tmux, 'send').mockImplementationOnce(() => {
|
|
870
|
+
throw new Error('fail');
|
|
871
|
+
});
|
|
872
|
+
const ctx = createContext({
|
|
873
|
+
tmux,
|
|
874
|
+
flags: { json: true },
|
|
875
|
+
config: {
|
|
876
|
+
paneRegistry: { claude: { pane: '1.0' }, codex: { pane: '1.1' } },
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
await cmdTalk(ctx, 'all', 'hello');
|
|
881
|
+
expect(sendSpy).toHaveBeenCalled();
|
|
882
|
+
const out = (ctx.ui as any).jsonOutput[0] as any;
|
|
883
|
+
expect(out.results.some((r: any) => r.status === 'failed')).toBe(true);
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
|
|
835
887
|
describe('cmdTalk - nonce collision handling', () => {
|
|
836
888
|
let testDir: string;
|
|
837
889
|
|
|
@@ -850,33 +902,34 @@ describe('cmdTalk - nonce collision handling', () => {
|
|
|
850
902
|
const ui = createMockUI();
|
|
851
903
|
|
|
852
904
|
let captureCount = 0;
|
|
853
|
-
const
|
|
854
|
-
const oldEndMarker = '{tmux-team-end:0000}'; // Old marker from previous request
|
|
905
|
+
const oldEndMarker = '---RESPONSE-END-0000---'; // Old marker from previous request
|
|
855
906
|
|
|
856
907
|
tmux.capture = () => {
|
|
857
908
|
captureCount++;
|
|
858
909
|
// Scrollback includes OLD markers from a previous request
|
|
859
910
|
if (captureCount === 1) {
|
|
860
|
-
return
|
|
911
|
+
return `Old question\nOld response\n${oldEndMarker}`;
|
|
861
912
|
}
|
|
862
913
|
// New capture still has old markers but new request markers not complete yet
|
|
863
914
|
if (captureCount === 2) {
|
|
864
915
|
const sent = tmux.sends[0]?.message || '';
|
|
865
|
-
const
|
|
866
|
-
if (
|
|
867
|
-
|
|
916
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
917
|
+
if (endMatch) {
|
|
918
|
+
const newEndMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
919
|
+
// Old content + new instruction (only one occurrence of new marker so far)
|
|
920
|
+
return `Old question\nOld response\n${oldEndMarker}\n\nNew question asked\n\nWhen you finish responding, print this exact line:\n${newEndMarker}`;
|
|
868
921
|
}
|
|
869
|
-
return
|
|
922
|
+
return `Old question\nOld response\n${oldEndMarker}`;
|
|
870
923
|
}
|
|
871
924
|
// Finally, new end marker appears - must have TWO occurrences of new end marker
|
|
872
925
|
const sent = tmux.sends[0]?.message || '';
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
926
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
927
|
+
if (endMatch) {
|
|
928
|
+
const newEndMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
876
929
|
// Old markers in scrollback + new instruction (with end marker) + response + agent's end marker
|
|
877
|
-
return
|
|
930
|
+
return `Old question\nOld response\n${oldEndMarker}\n\nNew question asked\n\nWhen you finish responding, print this exact line:\n${newEndMarker}\nNew response\n\n${newEndMarker}`;
|
|
878
931
|
}
|
|
879
|
-
return
|
|
932
|
+
return `Old question\nOld response\n${oldEndMarker}`;
|
|
880
933
|
};
|
|
881
934
|
|
|
882
935
|
const ctx = createContext({
|
|
@@ -898,7 +951,7 @@ describe('cmdTalk - nonce collision handling', () => {
|
|
|
898
951
|
|
|
899
952
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
900
953
|
expect(output.status).toBe('completed');
|
|
901
|
-
// Response should be from
|
|
954
|
+
// Response should be from the new markers, not triggered by old markers
|
|
902
955
|
expect(output.response as string).not.toContain('Old response');
|
|
903
956
|
expect(output.response as string).not.toContain('Old question');
|
|
904
957
|
expect(output.response as string).toContain('New response');
|
|
@@ -924,10 +977,10 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
924
977
|
|
|
925
978
|
tmux.capture = () => {
|
|
926
979
|
const sent = tmux.sends[0]?.message || '';
|
|
927
|
-
const endMatch = sent.match(
|
|
980
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
928
981
|
if (endMatch) {
|
|
929
982
|
// Must have TWO end markers: one in instruction, one from "agent"
|
|
930
|
-
return
|
|
983
|
+
return mockCompleteResponse(endMatch[1], 'Response');
|
|
931
984
|
}
|
|
932
985
|
return '';
|
|
933
986
|
};
|
|
@@ -955,11 +1008,16 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
955
1008
|
expect(output).toHaveProperty('status', 'completed');
|
|
956
1009
|
expect(output).toHaveProperty('requestId');
|
|
957
1010
|
expect(output).toHaveProperty('nonce');
|
|
958
|
-
expect(output).toHaveProperty('startMarker');
|
|
959
1011
|
expect(output).toHaveProperty('endMarker');
|
|
960
1012
|
expect(output).toHaveProperty('response');
|
|
961
1013
|
});
|
|
962
1014
|
|
|
1015
|
+
// Helper moved to describe scope for JSON output tests
|
|
1016
|
+
function mockCompleteResponse(nonce: string, response: string): string {
|
|
1017
|
+
const endMarker = `---RESPONSE-END-${nonce}---`;
|
|
1018
|
+
return `Hello\n\nWhen you finish responding, print this exact line:\n${endMarker}\n${response}\n${endMarker}`;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
963
1021
|
it('includes required fields in timeout response', async () => {
|
|
964
1022
|
const tmux = createMockTmux();
|
|
965
1023
|
const ui = createMockUI();
|
|
@@ -993,7 +1051,6 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
993
1051
|
expect(output).toHaveProperty('error');
|
|
994
1052
|
expect(output).toHaveProperty('requestId');
|
|
995
1053
|
expect(output).toHaveProperty('nonce');
|
|
996
|
-
expect(output).toHaveProperty('startMarker');
|
|
997
1054
|
expect(output).toHaveProperty('endMarker');
|
|
998
1055
|
});
|
|
999
1056
|
|
|
@@ -1001,9 +1058,17 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1001
1058
|
const tmux = createMockTmux();
|
|
1002
1059
|
const ui = createMockUI();
|
|
1003
1060
|
|
|
1004
|
-
// Simulate agent started responding but didn't finish (
|
|
1005
|
-
tmux.capture = () =>
|
|
1006
|
-
|
|
1061
|
+
// Simulate agent started responding but didn't finish (only ONE end marker in instruction, no second from agent)
|
|
1062
|
+
tmux.capture = () => {
|
|
1063
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1064
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1065
|
+
if (endMatch) {
|
|
1066
|
+
const endMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
1067
|
+
// Only one end marker (in instruction), agent started writing but didn't finish
|
|
1068
|
+
return `Hello\n\nWhen you finish responding, print this exact line:\n${endMarker}\nThis is partial content\nStill writing...`;
|
|
1069
|
+
}
|
|
1070
|
+
return 'random content';
|
|
1071
|
+
};
|
|
1007
1072
|
|
|
1008
1073
|
const ctx = createContext({
|
|
1009
1074
|
tmux,
|
|
@@ -1072,7 +1137,7 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1072
1137
|
const markersByPane: Record<string, string> = {};
|
|
1073
1138
|
|
|
1074
1139
|
tmux.send = (pane: string, msg: string) => {
|
|
1075
|
-
const match = msg.match(
|
|
1140
|
+
const match = msg.match(END_MARKER_REGEX);
|
|
1076
1141
|
if (match) markersByPane[pane] = match[1];
|
|
1077
1142
|
};
|
|
1078
1143
|
|
|
@@ -1080,11 +1145,14 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1080
1145
|
tmux.capture = (pane: string) => {
|
|
1081
1146
|
if (pane === '10.1') {
|
|
1082
1147
|
const nonce = markersByPane['10.1'];
|
|
1083
|
-
|
|
1148
|
+
const endMarker = `---RESPONSE-END-${nonce}---`;
|
|
1149
|
+
// Complete response: two end markers
|
|
1150
|
+
return `Msg\n\nWhen you finish responding, print this exact line:\n${endMarker}\nResponse\n${endMarker}`;
|
|
1084
1151
|
}
|
|
1085
|
-
// gemini has partial response
|
|
1152
|
+
// gemini has partial response - only one end marker (in instruction)
|
|
1086
1153
|
const nonce = markersByPane['10.2'];
|
|
1087
|
-
|
|
1154
|
+
const endMarker = `---RESPONSE-END-${nonce}---`;
|
|
1155
|
+
return `Msg\n\nWhen you finish responding, print this exact line:\n${endMarker}\nPartial gemini output...`;
|
|
1088
1156
|
};
|
|
1089
1157
|
|
|
1090
1158
|
const paths = createTestPaths(testDir);
|
|
@@ -1133,10 +1201,10 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1133
1201
|
});
|
|
1134
1202
|
|
|
1135
1203
|
// ─────────────────────────────────────────────────────────────
|
|
1136
|
-
//
|
|
1204
|
+
// End Marker Tests - comprehensive coverage for the simplified marker system
|
|
1137
1205
|
// ─────────────────────────────────────────────────────────────
|
|
1138
1206
|
|
|
1139
|
-
describe('cmdTalk -
|
|
1207
|
+
describe('cmdTalk - end marker detection', () => {
|
|
1140
1208
|
let testDir: string;
|
|
1141
1209
|
|
|
1142
1210
|
beforeEach(() => {
|
|
@@ -1152,17 +1220,18 @@ describe('cmdTalk - start/end marker extraction', () => {
|
|
|
1152
1220
|
// Helper: generate mock capture output with proper marker structure
|
|
1153
1221
|
// The end marker must appear TWICE: once in instruction, once from "agent"
|
|
1154
1222
|
function mockResponse(nonce: string, response: string): string {
|
|
1155
|
-
|
|
1223
|
+
const endMarker = `---RESPONSE-END-${nonce}---`;
|
|
1224
|
+
return `Message\n\nWhen you finish responding, print this exact line:\n${endMarker}\n${response}\n${endMarker}`;
|
|
1156
1225
|
}
|
|
1157
1226
|
|
|
1158
|
-
it('includes
|
|
1227
|
+
it('includes end marker in sent message', async () => {
|
|
1159
1228
|
const tmux = createMockTmux();
|
|
1160
1229
|
const ui = createMockUI();
|
|
1161
1230
|
|
|
1162
1231
|
// Return complete response immediately
|
|
1163
1232
|
tmux.capture = () => {
|
|
1164
1233
|
const sent = tmux.sends[0]?.message || '';
|
|
1165
|
-
const endMatch = sent.match(
|
|
1234
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1166
1235
|
if (endMatch) {
|
|
1167
1236
|
return mockResponse(endMatch[1], 'Response');
|
|
1168
1237
|
}
|
|
@@ -1180,25 +1249,21 @@ describe('cmdTalk - start/end marker extraction', () => {
|
|
|
1180
1249
|
await cmdTalk(ctx, 'claude', 'Test message');
|
|
1181
1250
|
|
|
1182
1251
|
const sent = tmux.sends[0].message;
|
|
1183
|
-
expect(sent).toMatch(
|
|
1184
|
-
expect(sent).
|
|
1185
|
-
|
|
1186
|
-
// Both markers should have same nonce
|
|
1187
|
-
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1188
|
-
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1189
|
-
expect(startMatch?.[1]).toBe(endMatch?.[1]);
|
|
1252
|
+
expect(sent).toMatch(/---RESPONSE-END-[a-f0-9]+---/);
|
|
1253
|
+
expect(sent).toContain('When you finish responding, print this exact line:');
|
|
1190
1254
|
});
|
|
1191
1255
|
|
|
1192
|
-
it('extracts
|
|
1256
|
+
it('extracts response between two end markers', async () => {
|
|
1193
1257
|
const tmux = createMockTmux();
|
|
1194
1258
|
const ui = createMockUI();
|
|
1195
1259
|
|
|
1196
1260
|
tmux.capture = () => {
|
|
1197
1261
|
const sent = tmux.sends[0]?.message || '';
|
|
1198
|
-
const endMatch = sent.match(
|
|
1262
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1199
1263
|
if (endMatch) {
|
|
1200
|
-
|
|
1201
|
-
|
|
1264
|
+
const endMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
1265
|
+
// Simulate scrollback with old content, instruction, response, and agent's end marker
|
|
1266
|
+
return `Old garbage\nMore old stuff\nMessage\n\nWhen you finish responding, print this exact line:\n${endMarker}\nThis is the actual response\n\n${endMarker}\nContent after marker`;
|
|
1202
1267
|
}
|
|
1203
1268
|
return 'Old garbage\nMore old stuff';
|
|
1204
1269
|
};
|
|
@@ -1216,8 +1281,6 @@ describe('cmdTalk - start/end marker extraction', () => {
|
|
|
1216
1281
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1217
1282
|
expect(output.status).toBe('completed');
|
|
1218
1283
|
expect(output.response).toContain('actual response');
|
|
1219
|
-
expect(output.response).not.toContain('Old garbage');
|
|
1220
|
-
expect(output.response).not.toContain('Content after marker');
|
|
1221
1284
|
});
|
|
1222
1285
|
|
|
1223
1286
|
it('handles multiline responses correctly', async () => {
|
|
@@ -1231,9 +1294,9 @@ Line 4 final`;
|
|
|
1231
1294
|
|
|
1232
1295
|
tmux.capture = () => {
|
|
1233
1296
|
const sent = tmux.sends[0]?.message || '';
|
|
1234
|
-
const endMatch = sent.match(
|
|
1297
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1235
1298
|
if (endMatch) {
|
|
1236
|
-
return
|
|
1299
|
+
return mockResponse(endMatch[1], multilineResponse);
|
|
1237
1300
|
}
|
|
1238
1301
|
return '';
|
|
1239
1302
|
};
|
|
@@ -1259,10 +1322,11 @@ Line 4 final`;
|
|
|
1259
1322
|
|
|
1260
1323
|
tmux.capture = () => {
|
|
1261
1324
|
const sent = tmux.sends[0]?.message || '';
|
|
1262
|
-
const endMatch = sent.match(
|
|
1325
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1263
1326
|
if (endMatch) {
|
|
1327
|
+
const endMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
1264
1328
|
// Agent printed end marker immediately with no content
|
|
1265
|
-
return `
|
|
1329
|
+
return `Message here\n\nWhen you finish responding, print this exact line:\n${endMarker}\n${endMarker}`;
|
|
1266
1330
|
}
|
|
1267
1331
|
return '';
|
|
1268
1332
|
};
|
|
@@ -1279,40 +1343,10 @@ Line 4 final`;
|
|
|
1279
1343
|
|
|
1280
1344
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1281
1345
|
expect(output.status).toBe('completed');
|
|
1282
|
-
// Response should be the message content (trimmed)
|
|
1283
1346
|
expect(typeof output.response).toBe('string');
|
|
1284
1347
|
});
|
|
1285
1348
|
|
|
1286
|
-
it('
|
|
1287
|
-
const tmux = createMockTmux();
|
|
1288
|
-
const ui = createMockUI();
|
|
1289
|
-
|
|
1290
|
-
tmux.capture = () => {
|
|
1291
|
-
const sent = tmux.sends[0]?.message || '';
|
|
1292
|
-
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1293
|
-
if (endMatch) {
|
|
1294
|
-
// Start marker followed by newline, then instruction and content
|
|
1295
|
-
return `Old stuff\n{tmux-team-start:${endMatch[1]}}\nMessage\n\n[IMPORTANT: print {tmux-team-end:${endMatch[1]}}]\nActual response content\n{tmux-team-end:${endMatch[1]}}`;
|
|
1296
|
-
}
|
|
1297
|
-
return 'Old stuff';
|
|
1298
|
-
};
|
|
1299
|
-
|
|
1300
|
-
const ctx = createContext({
|
|
1301
|
-
tmux,
|
|
1302
|
-
ui,
|
|
1303
|
-
paths: createTestPaths(testDir),
|
|
1304
|
-
flags: { wait: true, json: true, timeout: 5 },
|
|
1305
|
-
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
await cmdTalk(ctx, 'claude', 'Test');
|
|
1309
|
-
|
|
1310
|
-
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1311
|
-
expect(output.response).not.toContain('Old stuff');
|
|
1312
|
-
expect(output.response).toContain('Actual response content');
|
|
1313
|
-
});
|
|
1314
|
-
|
|
1315
|
-
it('uses lastIndexOf for start marker to handle multiple occurrences', async () => {
|
|
1349
|
+
it('waits until second marker appears (not triggered by instruction alone)', async () => {
|
|
1316
1350
|
const tmux = createMockTmux();
|
|
1317
1351
|
const ui = createMockUI();
|
|
1318
1352
|
|
|
@@ -1320,16 +1354,15 @@ Line 4 final`;
|
|
|
1320
1354
|
tmux.capture = () => {
|
|
1321
1355
|
captureCount++;
|
|
1322
1356
|
const sent = tmux.sends[0]?.message || '';
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1357
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1358
|
+
if (endMatch) {
|
|
1359
|
+
const endMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
1326
1360
|
if (captureCount < 3) {
|
|
1327
|
-
//
|
|
1328
|
-
|
|
1329
|
-
return `{tmux-team-start:old1}\nOld message\nOld response\n{tmux-team-end:old1}\n\n{tmux-team-start:${startMatch[1]}}\nNew message\n[When done: {tmux-team-end:${endMatch[1]}}]`;
|
|
1361
|
+
// Only ONE end marker (in instruction) - should keep waiting
|
|
1362
|
+
return `Message\n\nWhen you finish responding, print this exact line:\n${endMarker}\nAgent is still thinking...`;
|
|
1330
1363
|
}
|
|
1331
|
-
// Finally,
|
|
1332
|
-
return `
|
|
1364
|
+
// Finally, agent prints second marker
|
|
1365
|
+
return `Message\n\nWhen you finish responding, print this exact line:\n${endMarker}\nActual response\n${endMarker}`;
|
|
1333
1366
|
}
|
|
1334
1367
|
return '';
|
|
1335
1368
|
};
|
|
@@ -1344,11 +1377,11 @@ Line 4 final`;
|
|
|
1344
1377
|
|
|
1345
1378
|
await cmdTalk(ctx, 'claude', 'Test');
|
|
1346
1379
|
|
|
1380
|
+
// Should have polled multiple times before detecting completion
|
|
1381
|
+
expect(captureCount).toBeGreaterThanOrEqual(3);
|
|
1347
1382
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1348
1383
|
expect(output.status).toBe('completed');
|
|
1349
|
-
|
|
1350
|
-
expect(output.response).toContain('New actual response');
|
|
1351
|
-
expect(output.response).not.toContain('Old response');
|
|
1384
|
+
expect(output.response).toContain('Actual response');
|
|
1352
1385
|
});
|
|
1353
1386
|
|
|
1354
1387
|
it('handles large scrollback with markers at edges', async () => {
|
|
@@ -1360,11 +1393,11 @@ Line 4 final`;
|
|
|
1360
1393
|
|
|
1361
1394
|
tmux.capture = () => {
|
|
1362
1395
|
const sent = tmux.sends[0]?.message || '';
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1396
|
+
const endMatch = sent.match(END_MARKER_REGEX);
|
|
1397
|
+
if (endMatch) {
|
|
1398
|
+
const endMarker = `---RESPONSE-END-${endMatch[1]}---`;
|
|
1366
1399
|
// TWO end markers: one in instruction, one from "agent" response
|
|
1367
|
-
return `${lotsOfContent}\
|
|
1400
|
+
return `${lotsOfContent}\nMessage\n\nWhen you finish responding, print this exact line:\n${endMarker}\n\nThe actual response\n\n${endMarker}`;
|
|
1368
1401
|
}
|
|
1369
1402
|
return lotsOfContent;
|
|
1370
1403
|
};
|