bone-agent 1.3.0 → 1.3.2
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 +5 -5
- package/package.json +4 -4
- package/src/core/agentic.py +8 -5
- package/src/core/chat_manager.py +78 -101
- package/src/tools/select_option.py +12 -5
package/README.md
CHANGED
|
@@ -19,16 +19,16 @@ A CLI-based AI coding assistant capable of codebase search, file editing, comput
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
# Install globally (requires Python 3.9+)
|
|
22
|
-
npm install -g bone-agent
|
|
22
|
+
npm install -g bone-agent
|
|
23
23
|
|
|
24
24
|
# Run bone-agent
|
|
25
|
-
bone
|
|
25
|
+
bone
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Or use npx without installing:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npx bone-agent
|
|
31
|
+
npx bone-agent
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
### What Gets Installed
|
|
@@ -37,7 +37,7 @@ The npm package automatically:
|
|
|
37
37
|
1. Checks for Python 3.9+ on your system
|
|
38
38
|
2. Installs Python dependencies via pip
|
|
39
39
|
3. Creates `~/.bone/config.yaml` from `config.yaml.example` if missing (persists across updates)
|
|
40
|
-
4. Sets up the `bone
|
|
40
|
+
4. Sets up the `bone` command globally
|
|
41
41
|
|
|
42
42
|
**Requirements:**
|
|
43
43
|
- Node.js 14+ (for npm)
|
|
@@ -104,7 +104,7 @@ Set environment variables (they take precedence over ~/.bone/config.yaml):
|
|
|
104
104
|
export OPENAI_API_KEY="sk-your-key-here"
|
|
105
105
|
export ANTHROPIC_API_KEY="sk-ant-your-key-here"
|
|
106
106
|
|
|
107
|
-
bone
|
|
107
|
+
bone
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
### Available Environment Variables
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bone-agent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "A terminal-based AI coding assistant powered by OpenAI-style function calling",
|
|
5
5
|
"main": "src/ui/main.py",
|
|
6
6
|
"bin": {
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "git+https://github.com/vincentm65/
|
|
39
|
+
"url": "git+https://github.com/vincentm65/bone-agent.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/vincentm65/
|
|
42
|
+
"url": "https://github.com/vincentm65/bone-agent/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/vincentm65/
|
|
44
|
+
"homepage": "https://github.com/vincentm65/bone-agent",
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=14.0.0"
|
|
47
47
|
},
|
package/src/core/agentic.py
CHANGED
|
@@ -380,7 +380,7 @@ class AgenticOrchestrator:
|
|
|
380
380
|
self.chat_manager.log_message(response)
|
|
381
381
|
|
|
382
382
|
# NEW: Compact tool results after final answer (per-message compaction)
|
|
383
|
-
self.chat_manager.compact_tool_results()
|
|
383
|
+
self.chat_manager.compact_tool_results(skip_token_update=True)
|
|
384
384
|
|
|
385
385
|
# Update context tokens with current mode's tools
|
|
386
386
|
tools_for_mode = TOOLS()
|
|
@@ -597,9 +597,8 @@ class AgenticOrchestrator:
|
|
|
597
597
|
# Log tool result
|
|
598
598
|
self.chat_manager.log_message(tool_msg)
|
|
599
599
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
self.chat_manager.compact_tool_results()
|
|
600
|
+
# Compact completed tool blocks once after all tools complete
|
|
601
|
+
self.chat_manager.compact_tool_results(skip_token_update=True)
|
|
603
602
|
|
|
604
603
|
# Update context tokens with current mode's tools
|
|
605
604
|
tools_for_mode = TOOLS()
|
|
@@ -840,7 +839,7 @@ class AgenticOrchestrator:
|
|
|
840
839
|
|
|
841
840
|
# Mid-loop compaction: compact older completed tool blocks
|
|
842
841
|
# after all parallel results are appended (safe — only compacts completed blocks)
|
|
843
|
-
self.chat_manager.compact_tool_results()
|
|
842
|
+
self.chat_manager.compact_tool_results(skip_token_update=True)
|
|
844
843
|
|
|
845
844
|
# Update context tokens with current mode's tools
|
|
846
845
|
tools_for_mode = TOOLS()
|
|
@@ -1023,6 +1022,10 @@ class AgenticOrchestrator:
|
|
|
1023
1022
|
|
|
1024
1023
|
return False, str(result)
|
|
1025
1024
|
except Exception as e:
|
|
1025
|
+
# If thinking_indicator was paused (TERMINAL_YIELD) and tool
|
|
1026
|
+
# raised, resume it so the spinner reappears for the next iteration
|
|
1027
|
+
if policy == TERMINAL_YIELD and thinking_indicator:
|
|
1028
|
+
thinking_indicator.resume()
|
|
1026
1029
|
return False, f"Error executing tool '{function_name}': {str(e)}"
|
|
1027
1030
|
|
|
1028
1031
|
return False, f"Error: Unknown tool '{function_name}'."
|
package/src/core/chat_manager.py
CHANGED
|
@@ -383,7 +383,7 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
383
383
|
|
|
384
384
|
# ===== Tool Result Compaction =====
|
|
385
385
|
|
|
386
|
-
def _find_tool_blocks(self):
|
|
386
|
+
def _find_tool_blocks(self, include_in_flight=False):
|
|
387
387
|
"""Find all tool-result blocks in message history.
|
|
388
388
|
|
|
389
389
|
Handles both single-turn and multi-turn tool chains:
|
|
@@ -394,6 +394,12 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
394
394
|
a single block spanning from the first assistant(tool_calls) to the
|
|
395
395
|
final assistant(answer).
|
|
396
396
|
|
|
397
|
+
Args:
|
|
398
|
+
include_in_flight: If True, also return blocks that lack a final
|
|
399
|
+
assistant answer (in-flight tool chains). The 'end' field points
|
|
400
|
+
to the index after the last message in the chain (or the breaking
|
|
401
|
+
message index if the chain was interrupted).
|
|
402
|
+
|
|
397
403
|
Returns:
|
|
398
404
|
list: List of block dicts with keys: user_idx, start, end, tool_calls, tool_results
|
|
399
405
|
"""
|
|
@@ -441,14 +447,25 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
441
447
|
# Non-tool, non-assistant message breaks the chain
|
|
442
448
|
break
|
|
443
449
|
|
|
444
|
-
if
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
if include_in_flight:
|
|
451
|
+
if all_tool_calls:
|
|
452
|
+
blocks.append({
|
|
453
|
+
'user_idx': user_idx,
|
|
454
|
+
'start': block_start,
|
|
455
|
+
'end': j,
|
|
456
|
+
'tool_calls': all_tool_calls,
|
|
457
|
+
'tool_results': all_tool_results,
|
|
458
|
+
'in_flight': not found_end,
|
|
459
|
+
})
|
|
460
|
+
else:
|
|
461
|
+
if found_end and all_tool_calls:
|
|
462
|
+
blocks.append({
|
|
463
|
+
'user_idx': user_idx,
|
|
464
|
+
'start': block_start,
|
|
465
|
+
'end': j,
|
|
466
|
+
'tool_calls': all_tool_calls,
|
|
467
|
+
'tool_results': all_tool_results,
|
|
468
|
+
})
|
|
452
469
|
|
|
453
470
|
# Continue scanning from after the final answer (or after the chain)
|
|
454
471
|
# Guard: always advance at least one position to prevent infinite loops
|
|
@@ -635,68 +652,21 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
635
652
|
def _find_in_flight_boundary(self):
|
|
636
653
|
"""Find the index where in-flight tool blocks begin.
|
|
637
654
|
|
|
638
|
-
|
|
639
|
-
|
|
655
|
+
Delegates to _find_tool_blocks(include_in_flight=True) to find all
|
|
656
|
+
blocks, then returns the earliest start of any in-flight block.
|
|
640
657
|
These messages must never be included in the compactable region.
|
|
641
658
|
|
|
642
659
|
Returns:
|
|
643
660
|
int: Index of the first in-flight message, or len(messages) if none.
|
|
644
661
|
"""
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
# Found an assistant with tool_calls. Check if there's a final
|
|
654
|
-
# answer (assistant without tool_calls) after it.
|
|
655
|
-
has_final_answer = False
|
|
656
|
-
j = i + 1
|
|
657
|
-
while j < n:
|
|
658
|
-
if self.messages[j].get('role') == 'assistant' and not self.messages[j].get('tool_calls'):
|
|
659
|
-
has_final_answer = True
|
|
660
|
-
break
|
|
661
|
-
elif self.messages[j].get('role') == 'assistant' and self.messages[j].get('tool_calls'):
|
|
662
|
-
# Another tool-calling assistant — skip over its tool results
|
|
663
|
-
j += 1
|
|
664
|
-
while j < n and self.messages[j].get('role') == 'tool':
|
|
665
|
-
j += 1
|
|
666
|
-
continue
|
|
667
|
-
elif self.messages[j].get('role') == 'tool':
|
|
668
|
-
j += 1
|
|
669
|
-
continue
|
|
670
|
-
else:
|
|
671
|
-
break
|
|
672
|
-
|
|
673
|
-
if not has_final_answer:
|
|
674
|
-
# This is an in-flight tool block. Find its user question.
|
|
675
|
-
user_idx = i - 1
|
|
676
|
-
while user_idx >= 0 and self.messages[user_idx].get('role') != 'user':
|
|
677
|
-
user_idx -= 1
|
|
678
|
-
return max(0, user_idx)
|
|
679
|
-
else:
|
|
680
|
-
# Completed block — continue scanning backward
|
|
681
|
-
# Skip past all the tool messages associated with this block
|
|
682
|
-
j = i + 1
|
|
683
|
-
while j < n:
|
|
684
|
-
if self.messages[j].get('role') == 'tool':
|
|
685
|
-
j += 1
|
|
686
|
-
elif self.messages[j].get('role') == 'assistant' and self.messages[j].get('tool_calls'):
|
|
687
|
-
j += 1
|
|
688
|
-
while j < n and self.messages[j].get('role') == 'tool':
|
|
689
|
-
j += 1
|
|
690
|
-
continue
|
|
691
|
-
else:
|
|
692
|
-
break
|
|
693
|
-
i = j - 1
|
|
694
|
-
else:
|
|
695
|
-
i -= 1
|
|
696
|
-
|
|
697
|
-
return n
|
|
698
|
-
|
|
699
|
-
def _compute_split_boundary(self, blocks, in_flight_start):
|
|
662
|
+
all_blocks = self._find_tool_blocks(include_in_flight=True)
|
|
663
|
+
in_flight = [b for b in all_blocks if b.get('in_flight')]
|
|
664
|
+
if in_flight:
|
|
665
|
+
return min(b['user_idx'] for b in in_flight)
|
|
666
|
+
return len(self.messages)
|
|
667
|
+
|
|
668
|
+
def _compute_split_boundary(self, blocks, in_flight_start,
|
|
669
|
+
uncompacted_tail_tokens=None, min_tool_blocks=None):
|
|
700
670
|
"""Compute the message index where the uncompacted tail begins.
|
|
701
671
|
|
|
702
672
|
Three constraints determine the boundary (take the most conservative /
|
|
@@ -709,19 +679,23 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
709
679
|
Args:
|
|
710
680
|
blocks: List of tool block dicts from _find_tool_blocks()
|
|
711
681
|
in_flight_start: Index of first in-flight message (from _find_in_flight_boundary)
|
|
682
|
+
uncompacted_tail_tokens: Override for the token budget (None = use settings)
|
|
683
|
+
min_tool_blocks: Override for minimum tool blocks to preserve (None = use settings)
|
|
712
684
|
|
|
713
685
|
Returns:
|
|
714
686
|
int: Message index where the uncompacted tail starts
|
|
715
687
|
"""
|
|
716
688
|
tc = context_settings.tool_compaction
|
|
717
|
-
token_budget = tc.uncompacted_tail_tokens
|
|
718
|
-
min_blocks = tc.min_tool_blocks
|
|
689
|
+
token_budget = uncompacted_tail_tokens if uncompacted_tail_tokens is not None else tc.uncompacted_tail_tokens
|
|
690
|
+
min_blocks = min_tool_blocks if min_tool_blocks is not None else tc.min_tool_blocks
|
|
719
691
|
n = len(self.messages)
|
|
720
692
|
|
|
721
693
|
# The verbatim region ends at the first in-flight message (exclusive)
|
|
722
694
|
verbatim_end = min(in_flight_start, n)
|
|
723
695
|
|
|
724
|
-
# Constraint 1: Token budget — walk from verbatim_end backward
|
|
696
|
+
# Constraint 1: Token budget — walk from verbatim_end backward.
|
|
697
|
+
# Note: range stops at 1 (not 0) so the system prompt is never counted
|
|
698
|
+
# toward the budget — it is always preserved uncompacted.
|
|
725
699
|
tokens_accumulated = 0
|
|
726
700
|
token_boundary = 0
|
|
727
701
|
for i in range(verbatim_end - 1, 0, -1):
|
|
@@ -734,25 +708,16 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
734
708
|
token_boundary = 1
|
|
735
709
|
|
|
736
710
|
# Constraint 2: Minimum tool blocks — ensure at least min_blocks completed
|
|
737
|
-
# blocks are within the
|
|
711
|
+
# blocks are within the uncompacted tail. Take the min_blocks most recent
|
|
712
|
+
# completed blocks and set the boundary so they all fall at or after it.
|
|
738
713
|
min_block_boundary = 1
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
# Need to extend backward to include more blocks
|
|
747
|
-
blocks_needed = min_blocks - len(completed_blocks_in_tail)
|
|
748
|
-
# Take the blocks immediately before the current tail
|
|
749
|
-
# Find blocks whose end < token_boundary (not already in tail)
|
|
750
|
-
earlier_blocks = [b for b in blocks if b['end'] < token_boundary]
|
|
751
|
-
# Sort by end index descending (most recent first)
|
|
752
|
-
earlier_blocks.sort(key=lambda b: b['end'], reverse=True)
|
|
753
|
-
# Extend boundary to include the earliest user_idx of the blocks we need
|
|
754
|
-
for b in earlier_blocks[:blocks_needed]:
|
|
755
|
-
min_block_boundary = min(min_block_boundary, b['user_idx'])
|
|
714
|
+
if min_blocks > 0 and len(blocks) >= min_blocks:
|
|
715
|
+
# Sort by end index descending (most recent first), take top min_blocks
|
|
716
|
+
sorted_blocks = sorted(blocks, key=lambda b: b['end'], reverse=True)
|
|
717
|
+
recent_blocks = sorted_blocks[:min_blocks]
|
|
718
|
+
# The boundary must be at or before the earliest user_idx of these blocks
|
|
719
|
+
# so that all of them satisfy user_idx >= boundary (i.e. block is fully in the tail)
|
|
720
|
+
min_block_boundary = min(b['user_idx'] for b in recent_blocks)
|
|
756
721
|
|
|
757
722
|
# Constraint 3: Tool-call integrity — if token_boundary lands inside a
|
|
758
723
|
# tool block, extend backward to include the complete block
|
|
@@ -763,13 +728,15 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
763
728
|
integrity_boundary = min(integrity_boundary, block['user_idx'])
|
|
764
729
|
|
|
765
730
|
# Take the most conservative (earliest) boundary
|
|
766
|
-
|
|
731
|
+
# integrity_boundary <= token_boundary always (starts equal, only decreases)
|
|
732
|
+
boundary = integrity_boundary
|
|
767
733
|
if min_block_boundary < boundary:
|
|
768
734
|
boundary = min_block_boundary
|
|
769
735
|
|
|
770
736
|
return boundary
|
|
771
737
|
|
|
772
|
-
def compact_tool_results(self
|
|
738
|
+
def compact_tool_results(self, skip_token_update=False,
|
|
739
|
+
uncompacted_tail_tokens=None, min_tool_blocks=None):
|
|
773
740
|
"""Replace completed tool-result blocks with summaries using token-budget tail.
|
|
774
741
|
|
|
775
742
|
Walks messages from the end, accumulating tokens until ~40k tokens are
|
|
@@ -779,6 +746,15 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
779
746
|
|
|
780
747
|
Safe to call mid-loop (during tool execution) because it only compacts
|
|
781
748
|
completed tool blocks — in-flight blocks are never touched.
|
|
749
|
+
|
|
750
|
+
Args:
|
|
751
|
+
skip_token_update: If True, skip the internal _update_context_tokens()
|
|
752
|
+
call. Use when the caller will update tokens with mode-specific
|
|
753
|
+
tools immediately after.
|
|
754
|
+
uncompacted_tail_tokens: Override for the token budget (None = use settings).
|
|
755
|
+
Use for aggressive compaction with a smaller tail.
|
|
756
|
+
min_tool_blocks: Override for minimum tool blocks to preserve (None = use settings).
|
|
757
|
+
Use for aggressive compaction with fewer preserved blocks.
|
|
782
758
|
"""
|
|
783
759
|
# Skip if disabled (e.g. sub-agents preserving findings)
|
|
784
760
|
if self._compaction_disabled:
|
|
@@ -801,7 +777,11 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
801
777
|
in_flight_start = self._find_in_flight_boundary()
|
|
802
778
|
|
|
803
779
|
# Compute the split boundary using token budget + constraints
|
|
804
|
-
split_boundary = self._compute_split_boundary(
|
|
780
|
+
split_boundary = self._compute_split_boundary(
|
|
781
|
+
blocks, in_flight_start,
|
|
782
|
+
uncompacted_tail_tokens=uncompacted_tail_tokens,
|
|
783
|
+
min_tool_blocks=min_tool_blocks,
|
|
784
|
+
)
|
|
805
785
|
|
|
806
786
|
# Determine which blocks fall entirely before the split boundary
|
|
807
787
|
# (those are the ones to compact)
|
|
@@ -864,7 +844,8 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
864
844
|
new_messages.append(msg)
|
|
865
845
|
|
|
866
846
|
self.messages = new_messages
|
|
867
|
-
|
|
847
|
+
if not skip_token_update:
|
|
848
|
+
self._update_context_tokens()
|
|
868
849
|
|
|
869
850
|
# ===== AI-Based History Compaction =====
|
|
870
851
|
|
|
@@ -1101,16 +1082,12 @@ Provide a concise summary (2-4 paragraphs) that captures all essential context f
|
|
|
1101
1082
|
# If compaction is NOT locked, try layers 1 and 2
|
|
1102
1083
|
if not self._compaction_locked:
|
|
1103
1084
|
# Layer 1: Aggressive tool result compaction (non-LLM, fast)
|
|
1104
|
-
#
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
self.compact_tool_results()
|
|
1111
|
-
finally:
|
|
1112
|
-
context_settings.tool_compaction.uncompacted_tail_tokens = original_tail_tokens
|
|
1113
|
-
context_settings.tool_compaction.min_tool_blocks = original_min_blocks
|
|
1085
|
+
# Use very small token budget and min blocks for aggressive compaction
|
|
1086
|
+
self.compact_tool_results(
|
|
1087
|
+
skip_token_update=True,
|
|
1088
|
+
uncompacted_tail_tokens=10_000,
|
|
1089
|
+
min_tool_blocks=1,
|
|
1090
|
+
)
|
|
1114
1091
|
|
|
1115
1092
|
self._update_context_tokens()
|
|
1116
1093
|
current_tokens = self.token_tracker.current_context_tokens
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Interactive selection tool for presenting multiple-choice questions to the user."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
from html import escape as _html_escape
|
|
5
4
|
from threading import Timer
|
|
6
5
|
from typing import Optional, List, Dict, Any, Union
|
|
@@ -49,6 +48,7 @@ class SelectionPanel:
|
|
|
49
48
|
# Inline custom input editing state
|
|
50
49
|
self._editing_custom_input = False
|
|
51
50
|
self._custom_input_texts: Dict[int, str] = {} # question_idx -> typed text
|
|
51
|
+
self._auto_advance_timer: Optional[Timer] = None # Track for cancellation
|
|
52
52
|
|
|
53
53
|
# Multi-select state: per-question set of checked option indices
|
|
54
54
|
self._checked_indices: Dict[int, set] = {
|
|
@@ -265,7 +265,8 @@ class SelectionPanel:
|
|
|
265
265
|
# Single question - show summary then auto-exit
|
|
266
266
|
self._showing_summary = True
|
|
267
267
|
event.app.invalidate()
|
|
268
|
-
Timer(1.0, lambda: event.app.exit(result=self.selections[0]))
|
|
268
|
+
self._auto_advance_timer = Timer(1.0, lambda: event.app.exit(result=self.selections[0]))
|
|
269
|
+
self._auto_advance_timer.start()
|
|
269
270
|
else:
|
|
270
271
|
# Multi-question - advance or finish
|
|
271
272
|
if self.current_question_idx < len(self.questions) - 1:
|
|
@@ -275,7 +276,8 @@ class SelectionPanel:
|
|
|
275
276
|
else:
|
|
276
277
|
self._showing_summary = True
|
|
277
278
|
event.app.invalidate()
|
|
278
|
-
Timer(1.0, lambda: event.app.exit(result=self.selections))
|
|
279
|
+
self._auto_advance_timer = Timer(1.0, lambda: event.app.exit(result=self.selections))
|
|
280
|
+
self._auto_advance_timer.start()
|
|
279
281
|
|
|
280
282
|
def run(self) -> Optional[Union[str, List[str]]]:
|
|
281
283
|
"""Display the selection panel and wait for user input.
|
|
@@ -401,6 +403,9 @@ class SelectionPanel:
|
|
|
401
403
|
event.app.invalidate()
|
|
402
404
|
else:
|
|
403
405
|
# Cancel entire selection
|
|
406
|
+
if self._auto_advance_timer:
|
|
407
|
+
self._auto_advance_timer.cancel()
|
|
408
|
+
self._auto_advance_timer = None
|
|
404
409
|
event.app.exit(result=None)
|
|
405
410
|
|
|
406
411
|
# Printable character input for custom input editing
|
|
@@ -465,8 +470,10 @@ class SelectionPanel:
|
|
|
465
470
|
style=TOOLBAR_STYLE,
|
|
466
471
|
)
|
|
467
472
|
|
|
468
|
-
# Use
|
|
469
|
-
|
|
473
|
+
# Use prompt_toolkit's synchronous runner — avoids creating/destroying
|
|
474
|
+
# an event loop with asyncio.run(), which corrupts the parent
|
|
475
|
+
# PromptSession's event loop state and causes 100% CPU hangs.
|
|
476
|
+
result = application.run()
|
|
470
477
|
|
|
471
478
|
return result
|
|
472
479
|
|