aru-code 0.40.0__tar.gz → 0.42.0__tar.gz
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.
- {aru_code-0.40.0/aru_code.egg-info → aru_code-0.42.0}/PKG-INFO +1 -1
- aru_code-0.42.0/aru/__init__.py +1 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/app.py +18 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/chat.py +147 -5
- {aru_code-0.40.0 → aru_code-0.42.0/aru_code.egg-info}/PKG-INFO +1 -1
- {aru_code-0.40.0 → aru_code-0.42.0}/pyproject.toml +1 -1
- aru_code-0.40.0/aru/__init__.py +0 -1
- {aru_code-0.40.0 → aru_code-0.42.0}/LICENSE +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/README.md +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/agent_factory.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/agents/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/agents/base.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/agents/catalog.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/agents/planner.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/cache_patch.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/checkpoints.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/cli.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/commands.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/completers.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/config.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/context.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/display.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/events.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/format/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/format/manager.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/format/runner.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/history_blocks.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/lsp/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/lsp/client.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/lsp/manager.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/lsp/protocol.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/memory/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/memory/extractor.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/memory/loader.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/memory/store.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/permissions.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/plugin_cache.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/plugins/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/plugins/custom_tools.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/plugins/hooks.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/plugins/manager.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/plugins/tool_api.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/providers.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/runner.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/runtime.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/select.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/session.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/sinks.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/streaming.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tool_policy.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/_diff.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/_shared.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/apply_patch.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/apply_patch_prompt.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/ast_tools.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/codebase.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/delegate.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/delegate_prompt.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/file_ops.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/gitignore.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/lsp.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/mcp_client.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/memory_tool.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/plan_mode.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/ranker.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/registry.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/search.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/shell.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/skill.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/tasklist.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/web.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tools/worktree.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/screens/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/screens/choice.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/screens/confirm.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/screens/search.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/screens/text_input.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/sinks.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/slash_bridge.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/ui.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/__init__.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/completer.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/context_pane.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/header.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/inline_choice.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/loaded_pane.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/status.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/thinking.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/tui/widgets/tools.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru/ui.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru_code.egg-info/SOURCES.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru_code.egg-info/dependency_links.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru_code.egg-info/entry_points.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru_code.egg-info/requires.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/aru_code.egg-info/top_level.txt +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/setup.cfg +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_agents_base.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_agents_md_coverage.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_apply_patch.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_async_tool_permission.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cache_patch_metrics.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cache_patch_stop_reason.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_catalog.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_chat_scrollable.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_checkpoints.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_advanced.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_base.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_completers.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_new.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_run_cli.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_session.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cli_shell.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_codebase.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_confabulation_regression.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_config.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_context.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_context_pane.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_cwd_awareness.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_delegate.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_events_backward_compat.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_events_schema.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_fork_ctx_concurrency.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_format.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_gitignore.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_guardrails_scenarios.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_invoke_skill.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_invoked_skills.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_loaded_pane_path.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_lsp.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_lsp_rename.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_main.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_markdown_to_text.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_mcp_client.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_mcp_health.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_memory.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_memory_tool.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_microcompact.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_permissions.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_plan_mode_refactor.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_plugin_cache.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_plugin_errors.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_plugin_hooks_v2.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_plugins.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_providers.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_ranker.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_reasoning.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_runner_interrupt.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_runner_recovery.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_runtime.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_select.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_skill_disallowed_tools.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_status_breakdown.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_status_cost.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_streaming_sink.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tasklist.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_thread_tool_timeout.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tool_policy.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_truncation_marker.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_app_boot.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_bindings.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_bus_flow.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_chat.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_chat_adversarial.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_completer.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_completer_dynamic.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_copy.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_input_behaviour.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_mention_expand.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_modals.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_mode_cycle.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_native_selection.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_permission_flow.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_plan_task_render.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_sidebar_toggle.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_slash_bridge.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_snapshot_smoke.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_thinking_and_boot.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_tui_widgets_visual.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_ui_adapter.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_worktree.py +0 -0
- {aru_code-0.40.0 → aru_code-0.42.0}/tests/test_worktree_session_restore.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.42.0"
|
|
@@ -1068,6 +1068,24 @@ class AruApp(App):
|
|
|
1068
1068
|
self.query_one(ContextPane).refresh_from_session()
|
|
1069
1069
|
except Exception:
|
|
1070
1070
|
pass
|
|
1071
|
+
# Layer 9 self-heal — re-assert Textual's mouse-tracking
|
|
1072
|
+
# sequences at every turn boundary. If any content path
|
|
1073
|
+
# emitted a rogue DEC private-mode escape during the turn
|
|
1074
|
+
# (``\x1b[?1000l`` or similar) the terminal would have
|
|
1075
|
+
# silently disabled wheel reporting for every scroll area
|
|
1076
|
+
# in the app, with no way for us to detect it. Calling the
|
|
1077
|
+
# driver's enable-mouse path writes four short SGR sequences
|
|
1078
|
+
# (``?1000h`` / ``?1003h`` / ``?1015h`` / ``?1006h``) and
|
|
1079
|
+
# restores wheel input no matter what corrupted the state.
|
|
1080
|
+
# Idempotent when mouse tracking was never disabled. See the
|
|
1081
|
+
# Layer 9 post-mortem at the top of
|
|
1082
|
+
# ``aru/tui/widgets/chat.py`` for the full analysis.
|
|
1083
|
+
try:
|
|
1084
|
+
driver = self._driver
|
|
1085
|
+
if driver is not None:
|
|
1086
|
+
driver._enable_mouse_support()
|
|
1087
|
+
except Exception:
|
|
1088
|
+
pass
|
|
1071
1089
|
|
|
1072
1090
|
# ── Bus wiring — ToolsPane + StatusPane subscribe to plugin events ──
|
|
1073
1091
|
|
|
@@ -128,6 +128,91 @@ cost and layout cost on the Aru side. This bug is about the
|
|
|
128
128
|
terminal's own private-mode state being corrupted from outside —
|
|
129
129
|
entirely unrelated to how fast we render, and invisible to any
|
|
130
130
|
latency benchmark. Treat as a seventh layer: output-hygiene.
|
|
131
|
+
|
|
132
|
+
----
|
|
133
|
+
|
|
134
|
+
Post-mortem — "mouse wheel dead during heavy streaming" (2026-04-23,
|
|
135
|
+
``fix/scroll-refinement``)
|
|
136
|
+
---------------------------------------------------------------------
|
|
137
|
+
**Symptom:** mouse wheel over the ChatPane does nothing while the
|
|
138
|
+
agent is actively streaming / running tool batches. TAB to focus
|
|
139
|
+
the pane + arrow keys / PgUp / PgDn works fine. Asymmetric enough
|
|
140
|
+
that it felt like "the mouse lost focus" — not a freeze.
|
|
141
|
+
|
|
142
|
+
Reported against session ``final-fantasy-battle/.aru/sessions/
|
|
143
|
+
e9397dc3.json``.
|
|
144
|
+
|
|
145
|
+
**Original (incorrect) theory:** interaction between
|
|
146
|
+
``self.anchor()`` (layer 4) and ``_scroll_up_for_pointer``. This
|
|
147
|
+
was based on a misreading of Textual's source — see the Layer 9
|
|
148
|
+
correction below. ``_scroll_up_for_pointer`` does *not* pass
|
|
149
|
+
``release_anchor=False``; it defaults to ``True`` (``widget.py:3378``
|
|
150
|
+
→ ``widget.py:2730``), so wheel-up already releases the anchor via
|
|
151
|
+
the framework. The ``on_mouse_scroll_up`` handler we added
|
|
152
|
+
(``ChatPane.on_mouse_scroll_up``) is therefore redundant with the
|
|
153
|
+
framework's own behaviour — a no-op on the happy path. It is kept
|
|
154
|
+
as defensive redundancy because removing it is the same shape of
|
|
155
|
+
change as keeping it, but it should not be credited for "fixing"
|
|
156
|
+
anything.
|
|
157
|
+
|
|
158
|
+
**What the bug probably was:** the same Layer-7 class of issue
|
|
159
|
+
that the next session surfaced again — a rogue DEC private-mode
|
|
160
|
+
escape reaching the terminal and disabling X10 mouse reporting.
|
|
161
|
+
See Layer 9 for the real signature and the robust fix.
|
|
162
|
+
|
|
163
|
+
----
|
|
164
|
+
|
|
165
|
+
Post-mortem — "wheel globally dead at end of stream" (2026-04-24,
|
|
166
|
+
``fix/scroll-refinement`` continued)
|
|
167
|
+
---------------------------------------------------------------------
|
|
168
|
+
**Symptom:** immediately after a long streaming turn concluded,
|
|
169
|
+
mouse wheel stopped working on *every* scrollable surface in the
|
|
170
|
+
app — ChatPane, sidebars, modals — simultaneously. TAB to walk
|
|
171
|
+
focus into a scrollbar and arrow-key scrolling from there worked.
|
|
172
|
+
Classic Layer-7 fingerprint: terminal-level mouse reporting got
|
|
173
|
+
turned off.
|
|
174
|
+
|
|
175
|
+
Reported against session ``final-fantasy-battle3/.aru/sessions/
|
|
176
|
+
7e9e4549.json``: one mega-turn with 120 tool calls interleaved
|
|
177
|
+
with 66 text blocks, 31 plan-panel mounts via
|
|
178
|
+
``add_renderable(scrollable=True)``, ~245 widgets in the pane.
|
|
179
|
+
|
|
180
|
+
**What we could prove:** a byte-level scan of the saved session
|
|
181
|
+
for C0 control chars turned up zero ``\\x1b`` bytes. The leak is
|
|
182
|
+
either (a) from a path that isn't persisted to ``session.json``
|
|
183
|
+
(tool ``stdout``/``stderr`` never reaches the chat directly but
|
|
184
|
+
transient UI strings, skill output, or reasoning tokens might),
|
|
185
|
+
or (b) a Windows ConPTY quirk during high-volume redraw where the
|
|
186
|
+
driver's mouse-enable state drops without us emitting anything
|
|
187
|
+
hostile. Chasing the exact source is caça ao fantasma; the
|
|
188
|
+
mitigation is structural.
|
|
189
|
+
|
|
190
|
+
**Two-prong fix:**
|
|
191
|
+
|
|
192
|
+
1. **Close the last unsanitised content path —
|
|
193
|
+
``_SanitizedRenderable``.** ``ChatMessageWidget`` already
|
|
194
|
+
sanitises everything that goes through its ``buffer``. Arbitrary
|
|
195
|
+
Rich renderables handed to ``add_renderable`` (plan panels, task
|
|
196
|
+
lists, diff previews, the logo) bypass that path and mount as
|
|
197
|
+
``Static(renderable)``. The wrapper sits between the renderable
|
|
198
|
+
and Rich's console, filtering C0 bytes out of every segment's
|
|
199
|
+
``.text`` before it reaches Textual's compositor. Matches the
|
|
200
|
+
Layer 7 sanitisation boundary for the unchecked route.
|
|
201
|
+
|
|
202
|
+
2. **Self-heal at turn boundary —
|
|
203
|
+
``AruApp._run_turn`` finally clause.** Call the driver's
|
|
204
|
+
``_enable_mouse_support()`` after each turn finishes. That
|
|
205
|
+
re-emits Textual's own four mouse-enable sequences (``?1000h``,
|
|
206
|
+
``?1003h``, ``?1015h``, ``?1006h`` — see
|
|
207
|
+
``textual/drivers/windows_driver.py:56``). Cost is four short
|
|
208
|
+
writes; benefit is full recovery of wheel input regardless of
|
|
209
|
+
what corrupted the terminal state mid-turn. Idempotent: a no-op
|
|
210
|
+
when mouse tracking was never disabled.
|
|
211
|
+
|
|
212
|
+
Treat as a ninth layer: defence-in-depth against terminal-state
|
|
213
|
+
corruption. Prong 1 plugs the last known-possible leak inside our
|
|
214
|
+
code; prong 2 recovers even if something outside our reach drops
|
|
215
|
+
the state anyway.
|
|
131
216
|
"""
|
|
132
217
|
|
|
133
218
|
from __future__ import annotations
|
|
@@ -182,6 +267,39 @@ def _sanitize_for_terminal(raw: str) -> str:
|
|
|
182
267
|
return raw.translate(_CTRL_CHAR_TRANSLATION)
|
|
183
268
|
|
|
184
269
|
|
|
270
|
+
class _SanitizedRenderable:
|
|
271
|
+
"""Wraps a Rich renderable so its output segments are stripped of C0 bytes.
|
|
272
|
+
|
|
273
|
+
``_sanitize_for_terminal`` covers every string passing through
|
|
274
|
+
``ChatMessageWidget.buffer``. Arbitrary Rich renderables handed to
|
|
275
|
+
``ChatPane.add_renderable`` (plan panels, task lists, diff previews, the
|
|
276
|
+
startup logo) skip that widget and mount as a plain ``Static(renderable)``
|
|
277
|
+
— so a rogue ``\\x1b[?1000l`` inside a task description, a panel title, or
|
|
278
|
+
subprocess output echoed into a panel would flow straight through Rich
|
|
279
|
+
segments to Textual's compositor and onto the terminal, disabling mouse
|
|
280
|
+
tracking globally (Layer 7 signature).
|
|
281
|
+
|
|
282
|
+
This wrapper closes that gap: ``console.render`` yields segments from the
|
|
283
|
+
inner renderable, we strip C0 bytes from any segment whose ``.text``
|
|
284
|
+
contains them, and re-emit the cleaned stream. Rich's ``Segment`` is a
|
|
285
|
+
``NamedTuple`` so ``seg._replace(text=...)`` is a cheap immutable swap.
|
|
286
|
+
Unchanged segments are re-emitted unmodified — the hot path is a single
|
|
287
|
+
``str.translate`` on segment text which typically no-ops.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
def __init__(self, inner: Any) -> None:
|
|
291
|
+
self._inner = inner
|
|
292
|
+
|
|
293
|
+
def __rich_console__(self, console: Any, options: Any) -> Any:
|
|
294
|
+
for seg in console.render(self._inner, options):
|
|
295
|
+
if seg.text:
|
|
296
|
+
clean = _sanitize_for_terminal(seg.text)
|
|
297
|
+
if clean != seg.text:
|
|
298
|
+
yield seg._replace(text=clean)
|
|
299
|
+
continue
|
|
300
|
+
yield seg
|
|
301
|
+
|
|
302
|
+
|
|
185
303
|
def _scan_fences(text: str) -> tuple[int, int]:
|
|
186
304
|
"""One-pass fence scanner. Returns ``(last_stable_split, open_fence_start)``.
|
|
187
305
|
|
|
@@ -875,16 +993,36 @@ class ChatPane(VerticalScroll):
|
|
|
875
993
|
# us enqueuing a ``scroll_end`` after every delta / tool event.
|
|
876
994
|
# (a) kills the ``call_after_refresh`` backlog that piled up when
|
|
877
995
|
# the UI thread was busy rendering markdown; (b) releases the anchor
|
|
878
|
-
# when the user manually scrolls
|
|
879
|
-
#
|
|
880
|
-
# (c) re-engages automatically when they return to the bottom
|
|
881
|
-
# ``_check_anchor``. Matches Textual's own "streaming Markdown"
|
|
996
|
+
# when the user manually scrolls — wheel, keyboard, or drag all go
|
|
997
|
+
# through ``_scroll_to`` which releases by default (widget.py:2730);
|
|
998
|
+
# and (c) re-engages automatically when they return to the bottom
|
|
999
|
+
# via ``_check_anchor``. Matches Textual's own "streaming Markdown"
|
|
882
1000
|
# recipe (see ``Markdown.get_stream`` docstring).
|
|
883
1001
|
self.anchor()
|
|
884
1002
|
# Periodic flush; cheap because the reactive watcher already
|
|
885
1003
|
# debounces repaints when buffer doesn't actually change.
|
|
886
1004
|
self.set_interval(self.DEBOUNCE_SEC, self._flush_pending_delta)
|
|
887
1005
|
|
|
1006
|
+
def on_mouse_scroll_up(self, event) -> None:
|
|
1007
|
+
"""Defensive redundancy — explicitly release the anchor on wheel-up.
|
|
1008
|
+
|
|
1009
|
+
Originally added under a misreading of Textual's source (see the
|
|
1010
|
+
Layer 8 correction in the module post-mortem). The framework's
|
|
1011
|
+
``_scroll_up_for_pointer`` calls ``_scroll_to`` *without*
|
|
1012
|
+
``release_anchor``, which defaults to ``True`` in
|
|
1013
|
+
``widget.py:2730`` — so Textual already releases the anchor on
|
|
1014
|
+
wheel-up. This handler does the same thing one beat earlier and
|
|
1015
|
+
is effectively a no-op on the normal path.
|
|
1016
|
+
|
|
1017
|
+
Kept because (a) removing it has the same shape of change as
|
|
1018
|
+
keeping it and (b) if some future Textual refactor ever flips
|
|
1019
|
+
the default, this keeps wheel-up behaving the way ChatPane
|
|
1020
|
+
needs. No ``event.stop()`` — the framework handler still runs
|
|
1021
|
+
after this and does the actual scroll.
|
|
1022
|
+
"""
|
|
1023
|
+
if self._anchored and not self._anchor_released:
|
|
1024
|
+
self.release_anchor()
|
|
1025
|
+
|
|
888
1026
|
# ── API used by TextualBusSink and the App ────────────────────────
|
|
889
1027
|
|
|
890
1028
|
def add_user_message(self, text: str) -> None:
|
|
@@ -924,7 +1062,11 @@ class ChatPane(VerticalScroll):
|
|
|
924
1062
|
"""
|
|
925
1063
|
from textual.widgets import Static
|
|
926
1064
|
self._close_active_assistant()
|
|
927
|
-
|
|
1065
|
+
# Sanitise the renderable's segment stream — see
|
|
1066
|
+
# ``_SanitizedRenderable`` docstring. This is the only content path
|
|
1067
|
+
# into the ChatPane that doesn't go through ``ChatMessageWidget``,
|
|
1068
|
+
# so it needs its own Layer-7 barrier.
|
|
1069
|
+
widget = Static(_SanitizedRenderable(renderable))
|
|
928
1070
|
if scrollable:
|
|
929
1071
|
from textual.containers import VerticalScroll
|
|
930
1072
|
wrapper = VerticalScroll()
|
aru_code-0.40.0/aru/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.40.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|