kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl

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.
Files changed (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -1,15 +1,21 @@
1
- """Terminal rendering system for Kollabor CLI."""
1
+ """Terminal rendering system for Kollabor CLI.
2
2
 
3
+ This module provides comprehensive terminal rendering for the Kollabor CLI
4
+ application, including visual effects, layout management, message
5
+ display, and terminal state management.
6
+ """
7
+
8
+ import asyncio
3
9
  import logging
4
10
  from collections import deque
5
- from typing import List, Optional, TYPE_CHECKING
11
+ from typing import TYPE_CHECKING, List, Optional
6
12
 
7
- from .visual_effects import VisualEffects
8
- from .terminal_state import TerminalState
9
13
  from .layout import LayoutManager, ThinkingAnimationManager
10
- from .status_renderer import StatusRenderer
11
- from .message_renderer import MessageRenderer
12
14
  from .message_coordinator import MessageDisplayCoordinator
15
+ from .message_renderer import MessageRenderer
16
+ from .status_renderer import StatusRenderer
17
+ from .terminal_state import TerminalState
18
+ from .visual_effects import VisualEffects
13
19
 
14
20
  if TYPE_CHECKING:
15
21
  from ..config.manager import ConfigManager
@@ -59,7 +65,6 @@ class TerminalRenderer:
59
65
  # Interface properties
60
66
  self.input_buffer = ""
61
67
  self.cursor_position = 0
62
- self.status_areas = {"A": [], "B": [], "C": []}
63
68
  self.thinking_active = False
64
69
 
65
70
  # State management
@@ -165,9 +170,9 @@ class TerminalRenderer:
165
170
  """Set the thinking text effect.
166
171
 
167
172
  Args:
168
- effect: Effect type - "dim", "shimmer", or "normal"
173
+ effect: Effect type - "dim", "shimmer", "pulse", "scramble", or "none"
169
174
  """
170
- if effect in ["dim", "shimmer", "normal"]:
175
+ if effect in ["dim", "shimmer", "pulse", "scramble", "none", "normal"]:
171
176
  self.thinking_effect = effect
172
177
  self.visual_effects.configure_effect("thinking", enabled=True)
173
178
  logger.debug(f"Set thinking effect to: {effect}")
@@ -181,9 +186,7 @@ class TerminalRenderer:
181
186
  speed: Number of frames between shimmer updates
182
187
  wave_width: Number of characters in the shimmer wave
183
188
  """
184
- self.visual_effects.configure_effect(
185
- "thinking", speed=speed, width=wave_width
186
- )
189
+ self.visual_effects.configure_effect("thinking", speed=speed, width=wave_width)
187
190
  logger.debug(f"Configured shimmer: speed={speed}, wave_width={wave_width}")
188
191
 
189
192
  def configure_thinking_limit(self, limit: int) -> None:
@@ -201,14 +204,15 @@ class TerminalRenderer:
201
204
  This method renders dynamic interface parts:
202
205
  thinking animation, input prompt, and status lines.
203
206
  """
204
- # logger.info("[START] render_active_area() called")
205
-
206
207
  # CRITICAL: Skip ALL rendering when modal is active to prevent interference
207
208
  if hasattr(self, "input_handler") and self.input_handler:
208
209
  try:
209
210
  from ..events.models import CommandMode
210
211
 
211
- if self.input_handler.command_mode in (CommandMode.MODAL, CommandMode.LIVE_MODAL):
212
+ if self.input_handler.command_mode in (
213
+ CommandMode.MODAL,
214
+ CommandMode.LIVE_MODAL,
215
+ ):
212
216
  return
213
217
  except Exception as e:
214
218
  logger.error(f"Error checking modal state: {e}")
@@ -243,23 +247,51 @@ class TerminalRenderer:
243
247
  if not has_enhanced_input:
244
248
  return
245
249
 
246
- # Update terminal size and invalidate cache if resized
250
+ # Update terminal size and invalidate cache if resized (with 0.9s debouncing)
247
251
  old_size = self.terminal_state.get_size()
248
- self.terminal_state.update_size()
249
- terminal_width, terminal_height = self.terminal_state.get_size()
250
252
 
251
- # Check for terminal resize and invalidate cache if needed
252
- if old_size != (terminal_width, terminal_height):
253
- self.invalidate_render_cache()
254
- logger.debug(
255
- f"Terminal resize detected: {old_size} -> ({terminal_width}, {terminal_height})"
256
- )
253
+ # Check if resize has settled (0.9s debounce to prevent rapid re-renders)
254
+ resize_settled = self.terminal_state.check_and_clear_resize_flag()
255
+
256
+ size_changed = False
257
+ if resize_settled:
258
+ # Resize has settled - poll actual terminal size
259
+ self.terminal_state.update_size()
260
+ terminal_width, terminal_height = self.terminal_state.get_size()
261
+
262
+ # Only trigger aggressive clearing if width reduced by 10% or more
263
+ # Small reductions don't cause artifacts, so we skip clearing for minor changes
264
+ # Height changes don't matter for clearing (only width affects layout)
265
+ width_reduction = (old_size[0] - terminal_width) / old_size[0]
266
+ if terminal_width < old_size[0] and width_reduction >= 0.1:
267
+ size_changed = True
268
+ self.invalidate_render_cache()
269
+
270
+ # Note: Input buffer and cursor position are preserved on resize
271
+ # Active area will be cleared by _render_lines() using aggressive clearing
272
+ logger.debug(
273
+ f"Terminal width reduced by {width_reduction*100:.1f}% ({old_size[0]} -> {terminal_width}) - will use aggressive clearing"
274
+ )
275
+ elif old_size != (terminal_width, terminal_height):
276
+ # Size increased or small reduction - just invalidate cache, no aggressive clearing
277
+ self.invalidate_render_cache()
278
+ logger.debug(
279
+ f"Terminal size changed: {old_size[0]}x{old_size[1]} -> {terminal_width}x{terminal_height} - cache invalidated, no clearing"
280
+ )
281
+ else:
282
+ # No resize or still debouncing - use current size
283
+ terminal_width, terminal_height = old_size
257
284
 
258
285
  self.layout_manager.set_terminal_size(terminal_width, terminal_height)
259
286
  self.status_renderer.set_terminal_width(terminal_width)
260
287
 
261
288
  lines = []
262
289
 
290
+ # Add safety buffer line at top of active area
291
+ # This provides a "clear zone" for aggressive clearing during resize
292
+ # without deleting conversation content above the active area
293
+ lines.append("")
294
+
263
295
  # Add thinking animation if active
264
296
  if self.thinking_active:
265
297
  thinking_lines = self.thinking_animation.get_display_lines(
@@ -290,15 +322,15 @@ class TerminalRenderer:
290
322
  # Replace status with status modal
291
323
  lines.extend(status_modal_lines)
292
324
  else:
293
- # Update status areas and render normally
294
- self._update_status_areas()
325
+ # Render status views
295
326
  status_lines = self.status_renderer.render_horizontal_layout(
296
327
  self.visual_effects.apply_status_colors
297
328
  )
298
329
  lines.extend(status_lines)
299
330
 
300
331
  # Clear previous render and write new content
301
- await self._render_lines(lines)
332
+ # Pass resize flag to ensure aggressive clearing is used when size changed
333
+ await self._render_lines(lines, size_changed=size_changed)
302
334
 
303
335
  async def _render_input_area(self, lines: List[str]) -> None:
304
336
  """Render the input area, checking for plugin overrides.
@@ -346,9 +378,7 @@ class TerminalRenderer:
346
378
  )
347
379
 
348
380
  # Insert cursor character at position
349
- text_with_cursor = (
350
- buffer_text[:cursor_pos] + "▌" + buffer_text[cursor_pos:]
351
- )
381
+ text_with_cursor = buffer_text[:cursor_pos] + "▌" + buffer_text[cursor_pos:]
352
382
  lines.append(f"> {text_with_cursor}")
353
383
 
354
384
  def _write(self, text: str) -> None:
@@ -358,7 +388,7 @@ class TerminalRenderer:
358
388
  text: Text to write.
359
389
  """
360
390
  # Collect in buffer if buffered mode is active
361
- if hasattr(self, '_write_buffer') and self._write_buffer is not None:
391
+ if hasattr(self, "_write_buffer") and self._write_buffer is not None:
362
392
  self._write_buffer.append(text)
363
393
  else:
364
394
  self.terminal_state.write_raw(text)
@@ -369,9 +399,9 @@ class TerminalRenderer:
369
399
 
370
400
  def _flush_buffered_write(self) -> None:
371
401
  """Flush all buffered writes at once to reduce flickering."""
372
- if hasattr(self, '_write_buffer') and self._write_buffer:
402
+ if hasattr(self, "_write_buffer") and self._write_buffer:
373
403
  # Join all buffered content and write in one operation
374
- self.terminal_state.write_raw(''.join(self._write_buffer))
404
+ self.terminal_state.write_raw("".join(self._write_buffer))
375
405
  self._write_buffer = None
376
406
 
377
407
  def _get_terminal_width(self) -> int:
@@ -462,23 +492,17 @@ class TerminalRenderer:
462
492
 
463
493
  return []
464
494
 
465
- def _update_status_areas(self) -> None:
466
- """Update status areas for rendering."""
467
- for area_name, content in self.status_areas.items():
468
- self.status_renderer.update_area_content(area_name, content)
469
-
470
- async def _render_lines(self, lines: List[str]) -> None:
495
+ async def _render_lines(self, lines: List[str], size_changed: bool = False) -> None:
471
496
  """Render lines to terminal with proper clearing.
472
497
 
473
498
  Args:
474
499
  lines: Lines to render.
500
+ size_changed: True if terminal size changed (triggers aggressive clearing).
475
501
  """
476
502
  # RENDER OPTIMIZATION: Only render if content actually changed
477
503
  # Check if render caching is enabled via config
478
504
  if self._app_config is not None:
479
- cache_enabled = self._app_config.get(
480
- "terminal.render_cache_enabled", True
481
- )
505
+ cache_enabled = self._app_config.get("terminal.render_cache_enabled", True)
482
506
  else:
483
507
  cache_enabled = self._render_cache_enabled # Fallback to local setting
484
508
 
@@ -491,25 +515,32 @@ class TerminalRenderer:
491
515
 
492
516
  current_line_count = len(lines)
493
517
 
494
- # Check if terminal was resized - if so, use aggressive clearing
495
- resize_occurred = self.terminal_state.check_and_clear_resize_flag()
496
-
497
518
  # Use buffered write to reduce flickering (especially on Windows)
498
519
  # Start buffering BEFORE clearing so clear+redraw happens atomically
499
520
  self._start_buffered_write()
521
+
522
+ if size_changed:
523
+ await asyncio.sleep(1)
500
524
 
501
525
  # Clear previous active area (now buffered to reduce flicker)
502
526
  if self.input_line_written and hasattr(self, "last_line_count"):
503
- if resize_occurred:
527
+ if size_changed:
504
528
  # RESIZE FIX: On resize, restore to saved cursor position (where active area started)
505
529
  # and clear everything from there to bottom of screen
506
530
  logger.debug(
507
- "🔄 Terminal resize detected - restoring cursor and clearing"
531
+ f"🔄 Terminal resize detected (size_changed={size_changed}) - restoring cursor and clearing"
508
532
  )
509
533
 
510
534
  if self.active_area_start_position:
511
535
  # Restore to where active area started before resize
512
536
  self._write("\033[u") # Restore cursor position
537
+ # Move up extra lines to catch box drawing artifacts above saved position
538
+ self._write("\033[6A") # Move up 1 line (into safety buffer zone)
539
+ self._write("\033[1A") # Move up 1 line (into safety buffer zone)
540
+ self._write("\n")
541
+ self._write("\n")
542
+ self._write("\n")
543
+ self._write("\n")
513
544
  # Clear from that position to end of screen
514
545
  self._write("\033[J") # Clear from cursor to end of screen
515
546
  else:
@@ -531,20 +562,35 @@ class TerminalRenderer:
531
562
  if i > 0:
532
563
  self._write("\n")
533
564
  self._write(f"\r{line}")
565
+
534
566
 
535
567
  # Hide cursor
536
568
  self._write("\033[?25l") # Write hide cursor to buffer too
537
-
569
+
570
+ # Add small sleep to let terminal process ANSI escape sequences on resize
571
+ # This prevents visual artifacts when the terminal is still processing
572
+ if size_changed:
573
+ await asyncio.sleep(.1)
538
574
  # Flush all writes at once
539
575
  self._flush_buffered_write()
540
576
 
577
+ # Add small sleep to let terminal process ANSI escape sequences on resize
578
+ # This prevents visual artifacts when the terminal is still processing
579
+ if size_changed:
580
+ await asyncio.sleep(.4)
581
+
541
582
  # Remember line count for next render
542
583
  self.last_line_count = current_line_count
543
584
  self.input_line_written = True
544
585
 
545
- def clear_active_area(self) -> None:
546
- """Clear the active area before writing conversation messages."""
547
- if self.input_line_written and hasattr(self, "last_line_count"):
586
+ def clear_active_area(self, force: bool = False) -> None:
587
+ """Clear the active area before writing conversation messages.
588
+
589
+ Args:
590
+ force: If True, clear regardless of input_line_written state.
591
+ Use for exit cleanup.
592
+ """
593
+ if (force or self.input_line_written) and hasattr(self, "last_line_count"):
548
594
  self.terminal_state.clear_line()
549
595
  for _ in range(self.last_line_count - 1):
550
596
  self.terminal_state.move_cursor_up(1)
core/io/terminal_state.py CHANGED
@@ -1,4 +1,9 @@
1
- """Terminal state management for rendering system."""
1
+ """Terminal state management for rendering system.
2
+
3
+ This module provides comprehensive terminal state management for rendering
4
+ systems, including mode switching, capability detection, and
5
+ cross-platform terminal control.
6
+ """
2
7
 
3
8
  import logging
4
9
  import os
@@ -145,7 +150,7 @@ class TerminalState:
145
150
  self._last_size = (0, 0)
146
151
  self._resize_occurred = False # Track if terminal resize happened
147
152
  self._last_resize_time = 0 # Track when last resize signal arrived
148
- self._resize_debounce_delay = 0.2 # Wait 200ms for resize to settle
153
+ self._resize_debounce_delay = .9 # Wait 200ms for resize to settle
149
154
 
150
155
  # Initialize terminal state
151
156
  self._initialize_terminal()
@@ -275,8 +280,20 @@ class TerminalState:
275
280
  new_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT
276
281
  kernel32.SetConsoleMode(stdin_handle, new_mode)
277
282
  else:
278
- # Unix: Use tty.setraw
279
- tty.setraw(sys.stdin.fileno())
283
+ # Unix: Set raw mode with flow control disabled
284
+ # tty.setraw() doesn't disable IXON, so Ctrl+S (XOFF) gets
285
+ # intercepted by terminal driver. We need to manually clear IXON.
286
+ fd = sys.stdin.fileno()
287
+ tty.setraw(fd)
288
+ # Now disable XON/XOFF flow control so Ctrl+S reaches the app
289
+ attrs = termios.tcgetattr(fd)
290
+ attrs[0] &= ~termios.IXON # Disable XOFF (Ctrl+S) interception
291
+ attrs[0] &= ~termios.IXOFF # Disable XON (Ctrl+Q) interception
292
+ termios.tcsetattr(fd, termios.TCSANOW, attrs)
293
+ # Ensure stdin is in blocking mode (not non-blocking)
294
+ import fcntl
295
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
296
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
280
297
  self.current_mode = TerminalMode.RAW
281
298
  logger.debug("Entered raw terminal mode")
282
299
  return True