tmux-team 3.2.2 → 3.2.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-team",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
4
4
  "description": "CLI tool for AI agent collaboration in tmux - manage cross-pane communication",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.test.ts CHANGED
@@ -16,7 +16,7 @@ function makeStubContext(): Context {
16
16
  config: {
17
17
  mode: 'polling',
18
18
  preambleMode: 'always',
19
- defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
19
+ defaults: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 },
20
20
  agents: {},
21
21
  paneRegistry: {},
22
22
  },
@@ -53,7 +53,7 @@ function createCtx(
53
53
  const baseConfig: ResolvedConfig = {
54
54
  mode: 'polling',
55
55
  preambleMode: 'always',
56
- defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
56
+ defaults: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 },
57
57
  agents: {},
58
58
  paneRegistry: {},
59
59
  ...overrides?.config,
@@ -34,7 +34,7 @@ function createCtx(
34
34
  const config: ResolvedConfig = {
35
35
  mode: 'polling',
36
36
  preambleMode: 'always',
37
- defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
37
+ defaults: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 },
38
38
  agents: {},
39
39
  paneRegistry: {},
40
40
  ...configOverrides,
@@ -155,6 +155,7 @@ function setConfig(ctx: Context, key: string, value: string, global: boolean): v
155
155
  timeout: 180,
156
156
  pollInterval: 1,
157
157
  captureLines: 100,
158
+ maxCaptureLines: 2000,
158
159
  preambleEvery: parseInt(value, 10),
159
160
  };
160
161
  } else {
@@ -26,7 +26,7 @@ function createCtx(testDir: string, overrides?: Partial<{ flags: Partial<Flags>
26
26
  const config: ResolvedConfig = {
27
27
  mode: 'polling',
28
28
  preambleMode: 'always',
29
- defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
29
+ defaults: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 },
30
30
  agents: {},
31
31
  paneRegistry: {},
32
32
  };
@@ -26,7 +26,7 @@ function createCtx(testDir: string, tmux: Tmux, flags?: Partial<Flags>): Context
26
26
  const config: ResolvedConfig = {
27
27
  mode: 'polling',
28
28
  preambleMode: 'always',
29
- defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
29
+ defaults: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 },
30
30
  agents: {},
31
31
  paneRegistry: {},
32
32
  };
@@ -79,6 +79,7 @@ function createDefaultConfig(): ResolvedConfig {
79
79
  timeout: 60,
80
80
  pollInterval: 0.1, // Fast polling for tests
81
81
  captureLines: 100,
82
+ maxCaptureLines: 2000,
82
83
  preambleEvery: 3,
83
84
  },
84
85
  agents: {},
@@ -236,6 +237,7 @@ describe('buildMessage (via cmdTalk)', () => {
236
237
  timeout: 60,
237
238
  pollInterval: 0.1,
238
239
  captureLines: 100,
240
+ maxCaptureLines: 2000,
239
241
  preambleEvery: 3,
240
242
  },
241
243
  };
@@ -272,6 +274,7 @@ describe('buildMessage (via cmdTalk)', () => {
272
274
  timeout: 60,
273
275
  pollInterval: 0.1,
274
276
  captureLines: 100,
277
+ maxCaptureLines: 2000,
275
278
  preambleEvery: 1,
276
279
  },
277
280
  };
@@ -295,6 +298,7 @@ describe('buildMessage (via cmdTalk)', () => {
295
298
  timeout: 60,
296
299
  pollInterval: 0.1,
297
300
  captureLines: 100,
301
+ maxCaptureLines: 2000,
298
302
  preambleEvery: 0,
299
303
  },
300
304
  };
@@ -491,6 +495,7 @@ describe('cmdTalk - --wait mode', () => {
491
495
  timeout: 5,
492
496
  pollInterval: 0.01,
493
497
  captureLines: 100,
498
+ maxCaptureLines: 2000,
494
499
  preambleEvery: 3,
495
500
  },
496
501
  },
@@ -534,6 +539,7 @@ describe('cmdTalk - --wait mode', () => {
534
539
  timeout: 5,
535
540
  pollInterval: 0.01,
536
541
  captureLines: 100,
542
+ maxCaptureLines: 2000,
537
543
  preambleEvery: 3,
538
544
  },
539
545
  },
@@ -564,6 +570,7 @@ describe('cmdTalk - --wait mode', () => {
564
570
  timeout: 0.1,
565
571
  pollInterval: 0.02,
566
572
  captureLines: 100,
573
+ maxCaptureLines: 2000,
567
574
  preambleEvery: 3,
568
575
  },
569
576
  },
@@ -611,6 +618,7 @@ describe('cmdTalk - --wait mode', () => {
611
618
  timeout: 5,
612
619
  pollInterval: 0.01,
613
620
  captureLines: 100,
621
+ maxCaptureLines: 2000,
614
622
  preambleEvery: 3,
615
623
  },
616
624
  },
@@ -646,6 +654,7 @@ describe('cmdTalk - --wait mode', () => {
646
654
  timeout: 5,
647
655
  pollInterval: 0.01,
648
656
  captureLines: 100,
657
+ maxCaptureLines: 2000,
649
658
  preambleEvery: 3,
650
659
  },
651
660
  },
@@ -674,6 +683,7 @@ describe('cmdTalk - --wait mode', () => {
674
683
  timeout: 0.05,
675
684
  pollInterval: 0.01,
676
685
  captureLines: 100,
686
+ maxCaptureLines: 2000,
677
687
  preambleEvery: 3,
678
688
  },
679
689
  },
@@ -728,6 +738,7 @@ describe('cmdTalk - --wait mode', () => {
728
738
  timeout: 5,
729
739
  pollInterval: 0.05,
730
740
  captureLines: 100,
741
+ maxCaptureLines: 2000,
731
742
  preambleEvery: 3,
732
743
  },
733
744
  paneRegistry: {
@@ -775,6 +786,7 @@ describe('cmdTalk - --wait mode', () => {
775
786
  timeout: 0.5,
776
787
  pollInterval: 0.02,
777
788
  captureLines: 100,
789
+ maxCaptureLines: 2000,
778
790
  preambleEvery: 3,
779
791
  },
780
792
  paneRegistry: {
@@ -832,6 +844,7 @@ describe('cmdTalk - --wait mode', () => {
832
844
  timeout: 5,
833
845
  pollInterval: 0.02,
834
846
  captureLines: 100,
847
+ maxCaptureLines: 2000,
835
848
  preambleEvery: 3,
836
849
  },
837
850
  paneRegistry: {
@@ -941,6 +954,7 @@ describe('cmdTalk - nonce collision handling', () => {
941
954
  timeout: 5,
942
955
  pollInterval: 0.01,
943
956
  captureLines: 100,
957
+ maxCaptureLines: 2000,
944
958
  preambleEvery: 3,
945
959
  },
946
960
  },
@@ -995,6 +1009,7 @@ describe('cmdTalk - JSON output contract', () => {
995
1009
  timeout: 5,
996
1010
  pollInterval: 0.01,
997
1011
  captureLines: 100,
1012
+ maxCaptureLines: 2000,
998
1013
  preambleEvery: 3,
999
1014
  },
1000
1015
  },
@@ -1035,6 +1050,7 @@ describe('cmdTalk - JSON output contract', () => {
1035
1050
  timeout: 0.05,
1036
1051
  pollInterval: 0.01,
1037
1052
  captureLines: 100,
1053
+ maxCaptureLines: 2000,
1038
1054
  preambleEvery: 3,
1039
1055
  },
1040
1056
  },
@@ -1076,6 +1092,7 @@ describe('cmdTalk - JSON output contract', () => {
1076
1092
  timeout: 0.05,
1077
1093
  pollInterval: 0.01,
1078
1094
  captureLines: 100,
1095
+ maxCaptureLines: 2000,
1079
1096
  preambleEvery: 3,
1080
1097
  },
1081
1098
  },
@@ -1112,6 +1129,7 @@ describe('cmdTalk - JSON output contract', () => {
1112
1129
  timeout: 0.05,
1113
1130
  pollInterval: 0.01,
1114
1131
  captureLines: 100,
1132
+ maxCaptureLines: 2000,
1115
1133
  preambleEvery: 3,
1116
1134
  },
1117
1135
  },
@@ -1162,6 +1180,7 @@ describe('cmdTalk - JSON output contract', () => {
1162
1180
  timeout: 0.5,
1163
1181
  pollInterval: 0.02,
1164
1182
  captureLines: 100,
1183
+ maxCaptureLines: 2000,
1165
1184
  preambleEvery: 3,
1166
1185
  },
1167
1186
  paneRegistry: {
@@ -1244,7 +1263,7 @@ describe('cmdTalk - end marker detection', () => {
1244
1263
  ui,
1245
1264
  paths: createTestPaths(testDir),
1246
1265
  flags: { wait: true, json: true, timeout: 5 },
1247
- config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
1266
+ config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 } },
1248
1267
  });
1249
1268
 
1250
1269
  await cmdTalk(ctx, 'claude', 'Test message');
@@ -1277,7 +1296,7 @@ describe('cmdTalk - end marker detection', () => {
1277
1296
  ui,
1278
1297
  paths: createTestPaths(testDir),
1279
1298
  flags: { wait: true, json: true, timeout: 5 },
1280
- config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
1299
+ config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 } },
1281
1300
  });
1282
1301
 
1283
1302
  await cmdTalk(ctx, 'claude', 'Test');
@@ -1311,7 +1330,7 @@ Line 4 final`;
1311
1330
  ui,
1312
1331
  paths: createTestPaths(testDir),
1313
1332
  flags: { wait: true, json: true, timeout: 5 },
1314
- config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
1333
+ config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 } },
1315
1334
  });
1316
1335
 
1317
1336
  await cmdTalk(ctx, 'claude', 'Test');
@@ -1342,7 +1361,7 @@ Line 4 final`;
1342
1361
  ui,
1343
1362
  paths: createTestPaths(testDir),
1344
1363
  flags: { wait: true, json: true, timeout: 5 },
1345
- config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
1364
+ config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 } },
1346
1365
  });
1347
1366
 
1348
1367
  await cmdTalk(ctx, 'claude', 'Test');
@@ -1379,7 +1398,7 @@ Line 4 final`;
1379
1398
  ui,
1380
1399
  paths: createTestPaths(testDir),
1381
1400
  flags: { wait: true, json: true, timeout: 5 },
1382
- config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, preambleEvery: 3 } },
1401
+ config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 } },
1383
1402
  });
1384
1403
 
1385
1404
  await cmdTalk(ctx, 'claude', 'Test');
@@ -1415,7 +1434,7 @@ Line 4 final`;
1415
1434
  ui,
1416
1435
  paths: createTestPaths(testDir),
1417
1436
  flags: { wait: true, json: true, timeout: 5 },
1418
- config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 200, preambleEvery: 3 } },
1437
+ config: { defaults: { timeout: 5, pollInterval: 0.01, captureLines: 200, maxCaptureLines: 2000, preambleEvery: 3 } },
1419
1438
  });
1420
1439
 
1421
1440
  await cmdTalk(ctx, 'claude', 'Test');
@@ -118,6 +118,99 @@ function extractPartialResponse(
118
118
  return partial || null;
119
119
  }
120
120
 
121
+ // ─────────────────────────────────────────────────────────────
122
+ // Expandable capture extraction
123
+ // ─────────────────────────────────────────────────────────────
124
+
125
+ interface ExtractResult {
126
+ response: string;
127
+ truncated: boolean;
128
+ }
129
+
130
+ /**
131
+ * Extract response with expandable capture.
132
+ *
133
+ * When the instruction line is not found in the initial capture (response too long),
134
+ * retry with progressively larger captures up to maxCaptureLines.
135
+ */
136
+ function extractWithExpandableCapture(
137
+ tmux: Context['tmux'],
138
+ pane: string,
139
+ nonce: string,
140
+ endMarkerRegex: RegExp,
141
+ initialCapture: string,
142
+ captureLines: number,
143
+ maxCaptureLines: number,
144
+ responseLines: number,
145
+ debug?: boolean
146
+ ): ExtractResult {
147
+ let output = initialCapture;
148
+ let currentCaptureLines = captureLines;
149
+ let truncated = false;
150
+
151
+ // Try extraction with progressively larger captures
152
+ while (true) {
153
+ const lines = output.split('\n');
154
+
155
+ // Find end marker line (case-insensitive, last occurrence)
156
+ let endMarkerLineIndex = -1;
157
+ for (let i = lines.length - 1; i >= 0; i--) {
158
+ if (endMarkerRegex.test(lines[i])) {
159
+ endMarkerLineIndex = i;
160
+ break;
161
+ }
162
+ }
163
+
164
+ if (endMarkerLineIndex === -1) {
165
+ // No marker found - shouldn't happen if called correctly
166
+ return { response: '', truncated: true };
167
+ }
168
+
169
+ // Find instruction line (contains nonce but is not the marker)
170
+ const instructionLineIndex = lines.findIndex(
171
+ (line) =>
172
+ line.toLowerCase().includes(`response-end-${nonce.toLowerCase()}`) &&
173
+ !endMarkerRegex.test(line)
174
+ );
175
+
176
+ if (instructionLineIndex !== -1 && instructionLineIndex < endMarkerLineIndex) {
177
+ // Instruction visible: extract from after instruction to marker
178
+ const response = lines.slice(instructionLineIndex + 1, endMarkerLineIndex).join('\n').trim();
179
+ return { response, truncated: false };
180
+ }
181
+
182
+ // Instruction not found - try larger capture if possible
183
+ if (currentCaptureLines >= maxCaptureLines) {
184
+ // Already at max, fall back to N lines before marker
185
+ if (debug) {
186
+ console.error(
187
+ `[DEBUG] Instruction line not found after max capture (${maxCaptureLines} lines), falling back`
188
+ );
189
+ }
190
+ truncated = true;
191
+ const startLine = Math.max(0, endMarkerLineIndex - responseLines);
192
+ const response = lines.slice(startLine, endMarkerLineIndex).join('\n').trim();
193
+ return { response, truncated };
194
+ }
195
+
196
+ // Double capture size and retry
197
+ currentCaptureLines = Math.min(currentCaptureLines * 2, maxCaptureLines);
198
+ if (debug) {
199
+ console.error(`[DEBUG] Expanding capture to ${currentCaptureLines} lines`);
200
+ }
201
+
202
+ try {
203
+ output = tmux.capture(pane, currentCaptureLines);
204
+ } catch {
205
+ // Capture failed, use what we have
206
+ truncated = true;
207
+ const startLine = Math.max(0, endMarkerLineIndex - responseLines);
208
+ const response = lines.slice(startLine, endMarkerLineIndex).join('\n').trim();
209
+ return { response, truncated };
210
+ }
211
+ }
212
+ }
213
+
121
214
  // ─────────────────────────────────────────────────────────────
122
215
  // Types for broadcast wait mode
123
216
  // ─────────────────────────────────────────────────────────────
@@ -131,6 +224,7 @@ interface AgentWaitState {
131
224
  status: 'pending' | 'completed' | 'timeout' | 'error';
132
225
  response?: string;
133
226
  partialResponse?: string | null;
227
+ truncated?: boolean;
134
228
  error?: string;
135
229
  elapsedMs?: number;
136
230
  // Per-agent timing
@@ -140,6 +234,20 @@ interface AgentWaitState {
140
234
  lastOutputChangeAt: number;
141
235
  }
142
236
 
237
+ interface AgentResultOutput {
238
+ agent: string;
239
+ pane: string;
240
+ requestId: string;
241
+ nonce: string;
242
+ endMarker: string;
243
+ status: 'pending' | 'completed' | 'timeout' | 'error';
244
+ response?: string;
245
+ partialResponse?: string | null;
246
+ truncated?: boolean;
247
+ error?: string;
248
+ elapsedMs?: number;
249
+ }
250
+
143
251
  interface BroadcastWaitResult {
144
252
  target: 'all';
145
253
  mode: 'wait';
@@ -152,7 +260,7 @@ interface BroadcastWaitResult {
152
260
  error: number;
153
261
  skipped: number;
154
262
  };
155
- results: AgentWaitState[];
263
+ results: AgentResultOutput[];
156
264
  }
157
265
 
158
266
  /**
@@ -483,47 +591,24 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
483
591
  console.error(`[DEBUG] Agent completed (elapsed: ${elapsedMs}ms, idle: ${idleMs}ms)`);
484
592
  }
485
593
 
486
- // Extract response: get N lines before the end marker
594
+ // Extract response with expandable capture (handles long responses)
487
595
  const responseLines = flags.lines ?? 100;
488
- const lines = output.split('\n');
489
-
490
- // Find the line with the end marker (last occurrence = agent's marker)
491
- // Find end marker line (case-insensitive)
492
- let endMarkerLineIndex = -1;
493
- for (let i = lines.length - 1; i >= 0; i--) {
494
- if (endMarkerRegex.test(lines[i])) {
495
- endMarkerLineIndex = i;
496
- break;
497
- }
498
- }
596
+ const maxCaptureLines = config.defaults.maxCaptureLines;
499
597
 
500
- if (endMarkerLineIndex === -1) continue;
501
-
502
- // Protocol: instruction describes the marker verbally but doesn't contain the literal string.
503
- // So any occurrence of the literal marker is definitively from the agent.
504
- //
505
- // Try to anchor extraction to the instruction line (cleaner output when visible).
506
- // Fall back to N lines before marker if instruction scrolled off.
507
- let startLine: number;
508
- const instructionLineIndex = lines.findIndex(
509
- (line) =>
510
- line.toLowerCase().includes(`response-end-${nonce.toLowerCase()}`) &&
511
- !endMarkerRegex.test(line)
598
+ const { response: extractedResponse, truncated } = extractWithExpandableCapture(
599
+ tmux,
600
+ pane,
601
+ nonce,
602
+ endMarkerRegex,
603
+ output,
604
+ captureLines,
605
+ maxCaptureLines,
606
+ responseLines,
607
+ flags.debug
512
608
  );
513
609
 
514
- if (instructionLineIndex !== -1 && instructionLineIndex < endMarkerLineIndex) {
515
- // Instruction visible: extract from after instruction to marker
516
- startLine = instructionLineIndex + 1;
517
- } else {
518
- // Instruction scrolled off: extract N lines before marker
519
- startLine = Math.max(0, endMarkerLineIndex - responseLines);
520
- }
521
-
522
- let response = lines.slice(startLine, endMarkerLineIndex).join('\n').trim();
523
610
  // Clean Gemini CLI UI artifacts
524
- if (target === 'gemini') {
525
- response = cleanGeminiResponse(response);
526
- }
611
+ const response = target === 'gemini' ? cleanGeminiResponse(extractedResponse) : extractedResponse;
527
612
 
528
613
  if (!flags.json && isTTY) {
529
614
  process.stdout.write('\r' + ' '.repeat(80) + '\r');
@@ -536,8 +621,11 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
536
621
 
537
622
  const result: WaitResult = { requestId, nonce, endMarker, response };
538
623
  if (flags.json) {
539
- ui.json({ target, pane, status: 'completed', ...result });
624
+ ui.json({ target, pane, status: 'completed', truncated, ...result });
540
625
  } else {
626
+ if (truncated) {
627
+ ui.warn('Response may be truncated (instruction line not found in scrollback)');
628
+ }
541
629
  console.log(colors.cyan(`─── Response from ${target} (${pane}) ───`));
542
630
  console.log(response);
543
631
  }
@@ -765,47 +853,26 @@ async function cmdTalkAllWait(
765
853
  continue;
766
854
  }
767
855
 
768
- // Extract response: get N lines before the agent's end marker
856
+ // Extract response with expandable capture (handles long responses)
769
857
  const responseLines = flags.lines ?? 100;
770
- const lines = output.split('\n');
771
-
772
- // Find end marker line (case-insensitive)
773
- let endMarkerLineIndex = -1;
774
- for (let i = lines.length - 1; i >= 0; i--) {
775
- if (endMarkerRegex.test(lines[i])) {
776
- endMarkerLineIndex = i;
777
- break;
778
- }
779
- }
780
-
781
- if (endMarkerLineIndex === -1) continue;
782
-
783
- // Protocol: instruction describes the marker verbally but doesn't contain the literal string.
784
- // So any occurrence of the literal marker is definitively from the agent.
785
- //
786
- // Try to anchor extraction to the instruction line (cleaner output when visible).
787
- // Fall back to N lines before marker if instruction scrolled off.
788
- let startLine: number;
789
- const instructionLineIndex = lines.findIndex(
790
- (line) =>
791
- line.toLowerCase().includes(`response-end-${state.nonce.toLowerCase()}`) &&
792
- !endMarkerRegex.test(line)
858
+ const maxCaptureLines = config.defaults.maxCaptureLines;
859
+
860
+ const { response: extractedResponse, truncated } = extractWithExpandableCapture(
861
+ tmux,
862
+ state.pane,
863
+ state.nonce,
864
+ endMarkerRegex,
865
+ output,
866
+ captureLines,
867
+ maxCaptureLines,
868
+ responseLines,
869
+ flags.debug
793
870
  );
794
871
 
795
- if (instructionLineIndex !== -1 && instructionLineIndex < endMarkerLineIndex) {
796
- // Instruction visible: extract from after instruction to marker
797
- startLine = instructionLineIndex + 1;
798
- } else {
799
- // Instruction scrolled off: extract N lines before marker
800
- startLine = Math.max(0, endMarkerLineIndex - responseLines);
801
- }
802
-
803
- let response = lines.slice(startLine, endMarkerLineIndex).join('\n').trim();
804
872
  // Clean Gemini CLI UI artifacts
805
- if (state.agent === 'gemini') {
806
- response = cleanGeminiResponse(response);
807
- }
873
+ const response = state.agent === 'gemini' ? cleanGeminiResponse(extractedResponse) : extractedResponse;
808
874
  state.response = response;
875
+ state.truncated = truncated;
809
876
  state.status = 'completed';
810
877
  state.elapsedMs = elapsedMs;
811
878
  clearActiveRequest(paths, state.agent, state.requestId);
@@ -871,6 +938,7 @@ function outputBroadcastResults(
871
938
  status: s.status,
872
939
  response: s.response,
873
940
  partialResponse: s.partialResponse,
941
+ truncated: s.truncated,
874
942
  error: s.error,
875
943
  elapsedMs: s.elapsedMs,
876
944
  })),
@@ -889,6 +957,9 @@ function outputBroadcastResults(
889
957
  // Print responses
890
958
  for (const state of agentStates) {
891
959
  if (state.status === 'completed' && state.response) {
960
+ if (state.truncated) {
961
+ ui.warn(`Response from ${state.agent} may be truncated`);
962
+ }
892
963
  console.log(colors.cyan(`─── Response from ${state.agent} (${state.pane}) ───`));
893
964
  console.log(state.response);
894
965
  console.log();
package/src/config.ts CHANGED
@@ -26,7 +26,8 @@ const DEFAULT_CONFIG: GlobalConfig = {
26
26
  timeout: 180,
27
27
  pollInterval: 1,
28
28
  captureLines: 100,
29
- preambleEvery: 3, // inject preamble every 3 messages
29
+ maxCaptureLines: 2000, // max lines for final extraction (expandable capture)
30
+ preambleEvery: 3, // inject preamble every N messages
30
31
  },
31
32
  };
32
33
 
@@ -18,7 +18,7 @@ describe('createContext', () => {
18
18
  const config: ResolvedConfig = {
19
19
  mode: 'polling',
20
20
  preambleMode: 'always',
21
- defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
21
+ defaults: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3 },
22
22
  agents: {},
23
23
  paneRegistry: {},
24
24
  };
package/src/types.ts CHANGED
@@ -18,6 +18,7 @@ export interface ConfigDefaults {
18
18
  timeout: number; // seconds
19
19
  pollInterval: number; // seconds
20
20
  captureLines: number;
21
+ maxCaptureLines: number; // max lines for final extraction (default: 2000)
21
22
  preambleEvery: number; // inject preamble every N messages (default: 3)
22
23
  }
23
24