tmux-team 2.2.0 → 3.0.0-alpha.1
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 +21 -191
- package/package.json +1 -1
- package/src/cli.ts +0 -5
- package/src/commands/config.ts +2 -44
- package/src/commands/help.ts +0 -2
- package/src/commands/talk.test.ts +296 -46
- package/src/commands/talk.ts +69 -63
- package/src/config.test.ts +0 -1
- package/src/config.ts +0 -1
- package/src/identity.ts +89 -0
- package/src/types.ts +2 -2
- package/src/version.ts +1 -1
- package/src/pm/commands.test.ts +0 -1462
- package/src/pm/commands.ts +0 -1011
- package/src/pm/manager.test.ts +0 -377
- package/src/pm/manager.ts +0 -146
- package/src/pm/permissions.test.ts +0 -444
- package/src/pm/permissions.ts +0 -293
- package/src/pm/storage/adapter.ts +0 -57
- package/src/pm/storage/fs.test.ts +0 -512
- package/src/pm/storage/fs.ts +0 -290
- package/src/pm/storage/github.ts +0 -842
- package/src/pm/types.ts +0 -91
|
@@ -64,7 +64,6 @@ function createDefaultConfig(): ResolvedConfig {
|
|
|
64
64
|
pollInterval: 0.1, // Fast polling for tests
|
|
65
65
|
captureLines: 100,
|
|
66
66
|
preambleEvery: 3,
|
|
67
|
-
hideOrphanTasks: false,
|
|
68
67
|
},
|
|
69
68
|
agents: {},
|
|
70
69
|
paneRegistry: {
|
|
@@ -222,7 +221,6 @@ describe('buildMessage (via cmdTalk)', () => {
|
|
|
222
221
|
pollInterval: 0.1,
|
|
223
222
|
captureLines: 100,
|
|
224
223
|
preambleEvery: 3,
|
|
225
|
-
hideOrphanTasks: false,
|
|
226
224
|
},
|
|
227
225
|
};
|
|
228
226
|
|
|
@@ -259,7 +257,6 @@ describe('buildMessage (via cmdTalk)', () => {
|
|
|
259
257
|
pollInterval: 0.1,
|
|
260
258
|
captureLines: 100,
|
|
261
259
|
preambleEvery: 1,
|
|
262
|
-
hideOrphanTasks: false,
|
|
263
260
|
},
|
|
264
261
|
};
|
|
265
262
|
|
|
@@ -283,7 +280,6 @@ describe('buildMessage (via cmdTalk)', () => {
|
|
|
283
280
|
pollInterval: 0.1,
|
|
284
281
|
captureLines: 100,
|
|
285
282
|
preambleEvery: 0,
|
|
286
|
-
hideOrphanTasks: false,
|
|
287
283
|
},
|
|
288
284
|
};
|
|
289
285
|
|
|
@@ -470,7 +466,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
470
466
|
pollInterval: 0.01,
|
|
471
467
|
captureLines: 100,
|
|
472
468
|
preambleEvery: 3,
|
|
473
|
-
hideOrphanTasks: false,
|
|
474
469
|
},
|
|
475
470
|
},
|
|
476
471
|
});
|
|
@@ -513,7 +508,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
513
508
|
pollInterval: 0.01,
|
|
514
509
|
captureLines: 100,
|
|
515
510
|
preambleEvery: 3,
|
|
516
|
-
hideOrphanTasks: false,
|
|
517
511
|
},
|
|
518
512
|
},
|
|
519
513
|
});
|
|
@@ -544,7 +538,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
544
538
|
pollInterval: 0.02,
|
|
545
539
|
captureLines: 100,
|
|
546
540
|
preambleEvery: 3,
|
|
547
|
-
hideOrphanTasks: false,
|
|
548
541
|
},
|
|
549
542
|
},
|
|
550
543
|
});
|
|
@@ -563,23 +556,23 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
563
556
|
expect(output.error).toContain('Timed out');
|
|
564
557
|
});
|
|
565
558
|
|
|
566
|
-
it('isolates response
|
|
559
|
+
it('isolates response using start/end markers in scrollback', async () => {
|
|
567
560
|
const tmux = createMockTmux();
|
|
568
561
|
const ui = createMockUI();
|
|
569
562
|
|
|
570
|
-
|
|
571
|
-
const baseline = 'Previous conversation\nOld content here';
|
|
563
|
+
const oldContent = 'Previous conversation\nOld content here';
|
|
572
564
|
|
|
573
565
|
tmux.capture = () => {
|
|
574
|
-
|
|
575
|
-
if (captureCount === 1) return baseline;
|
|
576
|
-
// Second capture includes baseline + new content + marker
|
|
566
|
+
// Simulate scrollback with old content, start marker, response, and end marker
|
|
577
567
|
const sent = tmux.sends[0]?.message || '';
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
568
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
569
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
570
|
+
if (startMatch && endMatch) {
|
|
571
|
+
// Start and end markers should have the same nonce
|
|
572
|
+
expect(startMatch[1]).toBe(endMatch[1]);
|
|
573
|
+
return `${oldContent}\n\n{tmux-team-start:${startMatch[1]}}\nMessage content here\n\nNew response content\n\n{tmux-team-end:${endMatch[1]}}`;
|
|
581
574
|
}
|
|
582
|
-
return
|
|
575
|
+
return oldContent;
|
|
583
576
|
};
|
|
584
577
|
|
|
585
578
|
const ctx = createContext({
|
|
@@ -593,7 +586,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
593
586
|
pollInterval: 0.01,
|
|
594
587
|
captureLines: 100,
|
|
595
588
|
preambleEvery: 3,
|
|
596
|
-
hideOrphanTasks: false,
|
|
597
589
|
},
|
|
598
590
|
},
|
|
599
591
|
});
|
|
@@ -602,8 +594,11 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
602
594
|
|
|
603
595
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
604
596
|
expect(output.status).toBe('completed');
|
|
605
|
-
// Response should NOT include
|
|
606
|
-
expect(output.response).
|
|
597
|
+
// Response should NOT include old content before start marker
|
|
598
|
+
expect(output.response).not.toContain('Previous conversation');
|
|
599
|
+
expect(output.response).not.toContain('Old content here');
|
|
600
|
+
// Response should contain the actual response content
|
|
601
|
+
expect(output.response).toContain('New response content');
|
|
607
602
|
});
|
|
608
603
|
|
|
609
604
|
it('clears active request on completion', async () => {
|
|
@@ -629,7 +624,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
629
624
|
pollInterval: 0.01,
|
|
630
625
|
captureLines: 100,
|
|
631
626
|
preambleEvery: 3,
|
|
632
|
-
hideOrphanTasks: false,
|
|
633
627
|
},
|
|
634
628
|
},
|
|
635
629
|
});
|
|
@@ -658,7 +652,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
658
652
|
pollInterval: 0.01,
|
|
659
653
|
captureLines: 100,
|
|
660
654
|
preambleEvery: 3,
|
|
661
|
-
hideOrphanTasks: false,
|
|
662
655
|
},
|
|
663
656
|
},
|
|
664
657
|
});
|
|
@@ -713,7 +706,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
713
706
|
pollInterval: 0.05,
|
|
714
707
|
captureLines: 100,
|
|
715
708
|
preambleEvery: 3,
|
|
716
|
-
hideOrphanTasks: false,
|
|
717
709
|
},
|
|
718
710
|
paneRegistry: {
|
|
719
711
|
codex: { pane: '10.1' },
|
|
@@ -760,7 +752,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
760
752
|
pollInterval: 0.02,
|
|
761
753
|
captureLines: 100,
|
|
762
754
|
preambleEvery: 3,
|
|
763
|
-
hideOrphanTasks: false,
|
|
764
755
|
},
|
|
765
756
|
paneRegistry: {
|
|
766
757
|
codex: { pane: '10.1' },
|
|
@@ -818,7 +809,6 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
818
809
|
pollInterval: 0.02,
|
|
819
810
|
captureLines: 100,
|
|
820
811
|
preambleEvery: 3,
|
|
821
|
-
hideOrphanTasks: false,
|
|
822
812
|
},
|
|
823
813
|
paneRegistry: {
|
|
824
814
|
codex: { pane: '10.1' },
|
|
@@ -853,25 +843,32 @@ describe('cmdTalk - nonce collision handling', () => {
|
|
|
853
843
|
const ui = createMockUI();
|
|
854
844
|
|
|
855
845
|
let captureCount = 0;
|
|
856
|
-
const
|
|
846
|
+
const oldStartMarker = '{tmux-team-start:0000}';
|
|
847
|
+
const oldEndMarker = '{tmux-team-end:0000}'; // Old marker from previous request
|
|
857
848
|
|
|
858
849
|
tmux.capture = () => {
|
|
859
850
|
captureCount++;
|
|
851
|
+
// Scrollback includes OLD markers from a previous request
|
|
860
852
|
if (captureCount === 1) {
|
|
861
|
-
|
|
862
|
-
return `Old response ${oldMarker}`;
|
|
853
|
+
return `${oldStartMarker}\nOld question\nOld response\n${oldEndMarker}`;
|
|
863
854
|
}
|
|
864
|
-
// New capture still has old
|
|
855
|
+
// New capture still has old markers but new request markers not complete yet
|
|
865
856
|
if (captureCount === 2) {
|
|
866
|
-
|
|
857
|
+
const sent = tmux.sends[0]?.message || '';
|
|
858
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
859
|
+
if (startMatch) {
|
|
860
|
+
return `${oldStartMarker}\nOld question\nOld response\n${oldEndMarker}\n\n{tmux-team-start:${startMatch[1]}}\nNew question asked`;
|
|
861
|
+
}
|
|
862
|
+
return `${oldStartMarker}\nOld question\nOld response\n${oldEndMarker}`;
|
|
867
863
|
}
|
|
868
|
-
// Finally, new marker appears
|
|
864
|
+
// Finally, new end marker appears
|
|
869
865
|
const sent = tmux.sends[0]?.message || '';
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
866
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
867
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
868
|
+
if (startMatch && endMatch) {
|
|
869
|
+
return `${oldStartMarker}\nOld question\nOld response\n${oldEndMarker}\n\n{tmux-team-start:${startMatch[1]}}\nNew question asked\n\nNew response\n\n{tmux-team-end:${endMatch[1]}}`;
|
|
873
870
|
}
|
|
874
|
-
return
|
|
871
|
+
return `${oldStartMarker}\nOld question\nOld response\n${oldEndMarker}`;
|
|
875
872
|
};
|
|
876
873
|
|
|
877
874
|
const ctx = createContext({
|
|
@@ -885,7 +882,6 @@ describe('cmdTalk - nonce collision handling', () => {
|
|
|
885
882
|
pollInterval: 0.01,
|
|
886
883
|
captureLines: 100,
|
|
887
884
|
preambleEvery: 3,
|
|
888
|
-
hideOrphanTasks: false,
|
|
889
885
|
},
|
|
890
886
|
},
|
|
891
887
|
});
|
|
@@ -894,8 +890,10 @@ describe('cmdTalk - nonce collision handling', () => {
|
|
|
894
890
|
|
|
895
891
|
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
896
892
|
expect(output.status).toBe('completed');
|
|
897
|
-
// Response should be from after the new
|
|
893
|
+
// Response should be from after the new start marker, not triggered by old markers
|
|
898
894
|
expect(output.response as string).not.toContain('Old response');
|
|
895
|
+
expect(output.response as string).not.toContain('Old question');
|
|
896
|
+
expect(output.response as string).toContain('New response');
|
|
899
897
|
});
|
|
900
898
|
});
|
|
901
899
|
|
|
@@ -916,13 +914,14 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
916
914
|
const tmux = createMockTmux();
|
|
917
915
|
const ui = createMockUI();
|
|
918
916
|
|
|
919
|
-
let captureCount = 0;
|
|
920
917
|
tmux.capture = () => {
|
|
921
|
-
captureCount++;
|
|
922
|
-
if (captureCount === 1) return '';
|
|
923
918
|
const sent = tmux.sends[0]?.message || '';
|
|
924
|
-
const
|
|
925
|
-
|
|
919
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
920
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
921
|
+
if (startMatch && endMatch) {
|
|
922
|
+
return `{tmux-team-start:${startMatch[1]}}\nMessage\n\nResponse\n\n{tmux-team-end:${endMatch[1]}}`;
|
|
923
|
+
}
|
|
924
|
+
return '';
|
|
926
925
|
};
|
|
927
926
|
|
|
928
927
|
const ctx = createContext({
|
|
@@ -936,7 +935,6 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
936
935
|
pollInterval: 0.01,
|
|
937
936
|
captureLines: 100,
|
|
938
937
|
preambleEvery: 3,
|
|
939
|
-
hideOrphanTasks: false,
|
|
940
938
|
},
|
|
941
939
|
},
|
|
942
940
|
});
|
|
@@ -949,7 +947,8 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
949
947
|
expect(output).toHaveProperty('status', 'completed');
|
|
950
948
|
expect(output).toHaveProperty('requestId');
|
|
951
949
|
expect(output).toHaveProperty('nonce');
|
|
952
|
-
expect(output).toHaveProperty('
|
|
950
|
+
expect(output).toHaveProperty('startMarker');
|
|
951
|
+
expect(output).toHaveProperty('endMarker');
|
|
953
952
|
expect(output).toHaveProperty('response');
|
|
954
953
|
});
|
|
955
954
|
|
|
@@ -969,7 +968,6 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
969
968
|
pollInterval: 0.01,
|
|
970
969
|
captureLines: 100,
|
|
971
970
|
preambleEvery: 3,
|
|
972
|
-
hideOrphanTasks: false,
|
|
973
971
|
},
|
|
974
972
|
},
|
|
975
973
|
});
|
|
@@ -987,6 +985,258 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
987
985
|
expect(output).toHaveProperty('error');
|
|
988
986
|
expect(output).toHaveProperty('requestId');
|
|
989
987
|
expect(output).toHaveProperty('nonce');
|
|
990
|
-
expect(output).toHaveProperty('
|
|
988
|
+
expect(output).toHaveProperty('startMarker');
|
|
989
|
+
expect(output).toHaveProperty('endMarker');
|
|
990
|
+
});
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
// ─────────────────────────────────────────────────────────────
|
|
994
|
+
// Start/End Marker Tests - comprehensive coverage for the new marker system
|
|
995
|
+
// ─────────────────────────────────────────────────────────────
|
|
996
|
+
|
|
997
|
+
describe('cmdTalk - start/end marker extraction', () => {
|
|
998
|
+
let testDir: string;
|
|
999
|
+
|
|
1000
|
+
beforeEach(() => {
|
|
1001
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'talk-test-'));
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
afterEach(() => {
|
|
1005
|
+
if (fs.existsSync(testDir)) {
|
|
1006
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it('includes both start and end markers in sent message', async () => {
|
|
1011
|
+
const tmux = createMockTmux();
|
|
1012
|
+
const ui = createMockUI();
|
|
1013
|
+
|
|
1014
|
+
// Return markers immediately to complete
|
|
1015
|
+
tmux.capture = () => {
|
|
1016
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1017
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1018
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1019
|
+
if (startMatch && endMatch) {
|
|
1020
|
+
return `${startMatch[0]}\nContent\nResponse\n${endMatch[0]}`;
|
|
1021
|
+
}
|
|
1022
|
+
return '';
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const ctx = createContext({
|
|
1026
|
+
tmux,
|
|
1027
|
+
ui,
|
|
1028
|
+
paths: createTestPaths(testDir),
|
|
1029
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1030
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
await cmdTalk(ctx, 'claude', 'Test message');
|
|
1034
|
+
|
|
1035
|
+
const sent = tmux.sends[0].message;
|
|
1036
|
+
expect(sent).toMatch(/\{tmux-team-start:[a-f0-9]+\}/);
|
|
1037
|
+
expect(sent).toMatch(/\{tmux-team-end:[a-f0-9]+\}/);
|
|
1038
|
+
|
|
1039
|
+
// Both markers should have same nonce
|
|
1040
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1041
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1042
|
+
expect(startMatch?.[1]).toBe(endMatch?.[1]);
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
it('extracts only content between start and end markers', async () => {
|
|
1046
|
+
const tmux = createMockTmux();
|
|
1047
|
+
const ui = createMockUI();
|
|
1048
|
+
|
|
1049
|
+
tmux.capture = () => {
|
|
1050
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1051
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1052
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1053
|
+
if (startMatch && endMatch) {
|
|
1054
|
+
// Simulate scrollback with content before start marker, message, and response
|
|
1055
|
+
return `Old garbage\nMore old stuff\n{tmux-team-start:${startMatch[1]}}\nThe original message\n\nThis is the actual response\n\n{tmux-team-end:${endMatch[1]}}\nContent after marker`;
|
|
1056
|
+
}
|
|
1057
|
+
return 'Old garbage\nMore old stuff';
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
const ctx = createContext({
|
|
1061
|
+
tmux,
|
|
1062
|
+
ui,
|
|
1063
|
+
paths: createTestPaths(testDir),
|
|
1064
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1065
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
await cmdTalk(ctx, 'claude', 'Test');
|
|
1069
|
+
|
|
1070
|
+
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1071
|
+
expect(output.status).toBe('completed');
|
|
1072
|
+
expect(output.response).toContain('actual response');
|
|
1073
|
+
expect(output.response).not.toContain('Old garbage');
|
|
1074
|
+
expect(output.response).not.toContain('Content after marker');
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
it('handles multiline responses correctly', async () => {
|
|
1078
|
+
const tmux = createMockTmux();
|
|
1079
|
+
const ui = createMockUI();
|
|
1080
|
+
|
|
1081
|
+
const multilineResponse = `Line 1 of response
|
|
1082
|
+
Line 2 of response
|
|
1083
|
+
Line 3 with special chars: <>&"'
|
|
1084
|
+
Line 4 final`;
|
|
1085
|
+
|
|
1086
|
+
tmux.capture = () => {
|
|
1087
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1088
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1089
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1090
|
+
if (startMatch && endMatch) {
|
|
1091
|
+
return `{tmux-team-start:${startMatch[1]}}\nMessage\n\n${multilineResponse}\n\n{tmux-team-end:${endMatch[1]}}`;
|
|
1092
|
+
}
|
|
1093
|
+
return '';
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
const ctx = createContext({
|
|
1097
|
+
tmux,
|
|
1098
|
+
ui,
|
|
1099
|
+
paths: createTestPaths(testDir),
|
|
1100
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1101
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
await cmdTalk(ctx, 'claude', 'Test');
|
|
1105
|
+
|
|
1106
|
+
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1107
|
+
expect(output.response).toContain('Line 1 of response');
|
|
1108
|
+
expect(output.response).toContain('Line 4 final');
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it('handles empty response between markers', async () => {
|
|
1112
|
+
const tmux = createMockTmux();
|
|
1113
|
+
const ui = createMockUI();
|
|
1114
|
+
|
|
1115
|
+
tmux.capture = () => {
|
|
1116
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1117
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1118
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1119
|
+
if (startMatch && endMatch) {
|
|
1120
|
+
// Just markers with message, agent printed end marker immediately
|
|
1121
|
+
return `{tmux-team-start:${startMatch[1]}}\nMessage here\n{tmux-team-end:${endMatch[1]}}`;
|
|
1122
|
+
}
|
|
1123
|
+
return '';
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
const ctx = createContext({
|
|
1127
|
+
tmux,
|
|
1128
|
+
ui,
|
|
1129
|
+
paths: createTestPaths(testDir),
|
|
1130
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1131
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
await cmdTalk(ctx, 'claude', 'Test');
|
|
1135
|
+
|
|
1136
|
+
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1137
|
+
expect(output.status).toBe('completed');
|
|
1138
|
+
// Response should be the message content (trimmed)
|
|
1139
|
+
expect(typeof output.response).toBe('string');
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
it('correctly handles start marker on same line as content', async () => {
|
|
1143
|
+
const tmux = createMockTmux();
|
|
1144
|
+
const ui = createMockUI();
|
|
1145
|
+
|
|
1146
|
+
tmux.capture = () => {
|
|
1147
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1148
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1149
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1150
|
+
if (startMatch && endMatch) {
|
|
1151
|
+
// Start marker followed by newline, then content
|
|
1152
|
+
return `Old stuff\n{tmux-team-start:${startMatch[1]}}\nActual response content\n{tmux-team-end:${endMatch[1]}}`;
|
|
1153
|
+
}
|
|
1154
|
+
return 'Old stuff';
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
const ctx = createContext({
|
|
1158
|
+
tmux,
|
|
1159
|
+
ui,
|
|
1160
|
+
paths: createTestPaths(testDir),
|
|
1161
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1162
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
await cmdTalk(ctx, 'claude', 'Test');
|
|
1166
|
+
|
|
1167
|
+
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1168
|
+
expect(output.response).not.toContain('Old stuff');
|
|
1169
|
+
expect(output.response).toContain('Actual response content');
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
it('uses lastIndexOf for start marker to handle multiple occurrences', async () => {
|
|
1173
|
+
const tmux = createMockTmux();
|
|
1174
|
+
const ui = createMockUI();
|
|
1175
|
+
|
|
1176
|
+
let captureCount = 0;
|
|
1177
|
+
tmux.capture = () => {
|
|
1178
|
+
captureCount++;
|
|
1179
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1180
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1181
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1182
|
+
if (startMatch && endMatch) {
|
|
1183
|
+
if (captureCount < 3) {
|
|
1184
|
+
// First few captures: old markers in history, new start marker sent but not end yet
|
|
1185
|
+
return `{tmux-team-start:old1}\nOld message\nOld response\n{tmux-team-end:old1}\n\n{tmux-team-start:${startMatch[1]}}\nNew message pending`;
|
|
1186
|
+
}
|
|
1187
|
+
// Finally, new end marker appears
|
|
1188
|
+
return `{tmux-team-start:old1}\nOld message\nOld response\n{tmux-team-end:old1}\n\n{tmux-team-start:${startMatch[1]}}\nNew message\n\nNew actual response\n\n{tmux-team-end:${endMatch[1]}}`;
|
|
1189
|
+
}
|
|
1190
|
+
return '';
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
const ctx = createContext({
|
|
1194
|
+
tmux,
|
|
1195
|
+
ui,
|
|
1196
|
+
paths: createTestPaths(testDir),
|
|
1197
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1198
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
await cmdTalk(ctx, 'claude', 'Test');
|
|
1202
|
+
|
|
1203
|
+
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1204
|
+
expect(output.status).toBe('completed');
|
|
1205
|
+
// Should get response from the NEW start marker, not the old one
|
|
1206
|
+
expect(output.response).toContain('New actual response');
|
|
1207
|
+
expect(output.response).not.toContain('Old response');
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
it('handles large scrollback with markers at edges', async () => {
|
|
1211
|
+
const tmux = createMockTmux();
|
|
1212
|
+
const ui = createMockUI();
|
|
1213
|
+
|
|
1214
|
+
// Simulate 100+ lines of scrollback
|
|
1215
|
+
const lotsOfContent = Array.from({ length: 150 }, (_, i) => `Line ${i}`).join('\n');
|
|
1216
|
+
|
|
1217
|
+
tmux.capture = () => {
|
|
1218
|
+
const sent = tmux.sends[0]?.message || '';
|
|
1219
|
+
const startMatch = sent.match(/\{tmux-team-start:([a-f0-9]+)\}/);
|
|
1220
|
+
const endMatch = sent.match(/\{tmux-team-end:([a-f0-9]+)\}/);
|
|
1221
|
+
if (startMatch && endMatch) {
|
|
1222
|
+
return `${lotsOfContent}\n{tmux-team-start:${startMatch[1]}}\nMessage\n\nThe actual response\n\n{tmux-team-end:${endMatch[1]}}`;
|
|
1223
|
+
}
|
|
1224
|
+
return lotsOfContent;
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
const ctx = createContext({
|
|
1228
|
+
tmux,
|
|
1229
|
+
ui,
|
|
1230
|
+
paths: createTestPaths(testDir),
|
|
1231
|
+
flags: { wait: true, json: true, timeout: 5 },
|
|
1232
|
+
config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 200, preambleEvery: 3 } },
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
await cmdTalk(ctx, 'claude', 'Test');
|
|
1236
|
+
|
|
1237
|
+
const output = ui.jsonOutput[0] as Record<string, unknown>;
|
|
1238
|
+
expect(output.status).toBe('completed');
|
|
1239
|
+
expect(output.response).toContain('actual response');
|
|
1240
|
+
expect(output.response).not.toContain('Line 0');
|
|
991
1241
|
});
|
|
992
1242
|
});
|