Skip to content

Commit 495cbfd

Browse files
authored
Merge pull request #418 from dwash96/v0.96.5
V0.96.5
2 parents d0299f3 + 0b494d0 commit 495cbfd

8 files changed

Lines changed: 90 additions & 23 deletions

File tree

cecli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from packaging import version
22

3-
__version__ = "0.96.4.dev"
3+
__version__ = "0.96.5.dev"
44
safe_version = __version__
55

66
try:

cecli/coders/agent_coder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,7 @@ async def reply_completed(self):
724724
) = await self._process_tool_commands(content)
725725
if self.agent_finished:
726726
self.tool_usage_history = []
727+
self.reflected_message = None
727728
if self.files_edited_by_tools:
728729
_ = await self.auto_commit(self.files_edited_by_tools)
729730
return False

cecli/coders/architect_coder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ async def reply_completed(self):
6161
if self.verbose:
6262
editor_coder.show_announcements()
6363

64+
postamble = """
65+
The above changes are proposed changes.
66+
You must repeat SEARCH/REPLACE blocks in order to apply edits.
67+
Shell commands must also be duplicated in order to run them.
68+
"""
69+
70+
content = f"Please implement all requested changes from:\n{content}\n{postamble}"
71+
6472
try:
6573
await editor_coder.generate(user_message=content, preproc=False)
6674

cecli/coders/base_coder.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,7 @@ async def run_one(self, user_message, preproc):
15841584
pass
15851585

15861586
if not self.reflected_message:
1587+
await self.auto_save_session(force=True)
15871588
break
15881589

15891590
if self.num_reflections >= self.max_reflections:
@@ -1603,6 +1604,8 @@ async def run_one(self, user_message, preproc):
16031604
if self.enable_context_compaction:
16041605
await self.compact_context_if_needed()
16051606

1607+
await self.auto_save_session(force=True)
1608+
16061609
async def check_and_open_urls(self, exc, friendly_msg=None):
16071610
"""Check exception for URLs, offer to open in a browser, with user-friendly error msgs."""
16081611
text = str(exc)
@@ -3776,7 +3779,7 @@ def apply_edits(self, edits):
37763779
def apply_edits_dry_run(self, edits):
37773780
return edits
37783781

3779-
async def auto_save_session(self):
3782+
async def auto_save_session(self, force=False):
37803783
"""Automatically save the current session to {auto-save-session-name}.json."""
37813784
if not getattr(self.args, "auto_save", False):
37823785
return
@@ -3789,11 +3792,17 @@ async def auto_save_session(self):
37893792
self._autosave_future = None
37903793

37913794
if self._autosave_future and not self._autosave_future.done():
3792-
return
3795+
if force:
3796+
try:
3797+
await self._autosave_future
3798+
except Exception:
3799+
pass
3800+
else:
3801+
return
37933802

37943803
# Throttle autosave to run at most once every 15 seconds
37953804
current_time = time.time()
3796-
if current_time - self._last_autosave_time >= 15.0:
3805+
if current_time - self._last_autosave_time >= 15.0 or force:
37973806
try:
37983807
self._last_autosave_time = current_time
37993808
session_manager = SessionManager(self, self.io)

cecli/io.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ def __init__(
148148
self.rel_fnames = rel_fnames
149149
self.encoding = encoding
150150
self.abs_read_only_fnames = abs_read_only_fnames or []
151-
self.post_filter_commands = ["/add"]
152151

153152
fname_to_rel_fnames = defaultdict(list)
154153
for rel_fname in addable_rel_fnames:
@@ -214,22 +213,48 @@ def tokenize(self):
214213

215214
def get_command_completions(self, document, complete_event, text, words):
216215
if len(words) == 1 and not text[-1].isspace():
216+
# Handle command completion (e.g., typing "/ad" should complete to "/add")
217217
partial = words[0].lower()
218-
candidates = [cmd for cmd in self.command_names if cmd.startswith(partial)]
218+
# Strip leading '/' if present for comparison with command names
219+
if partial.startswith("/"):
220+
partial = partial[1:]
221+
# Compare with command names without leading '/'
222+
candidates = [cmd for cmd in self.command_names if cmd[1:].startswith(partial)]
219223
for candidate in sorted(candidates):
224+
# Add back the leading '/' for the completion
220225
yield Completion(candidate, start_position=-len(words[-1]))
221226
return
222227

223-
if len(words) <= 1 or text[-1].isspace():
224-
return
228+
# Handle command followed by space: trigger auto-completion with empty partial
229+
if text[-1].isspace():
230+
# We have a command followed by space, trigger auto-completion with empty string
231+
if len(words) == 1:
232+
# Command with no arguments yet, just a trailing space
233+
partial = ""
234+
# We need to get the command name without the trailing space
235+
# The command is words[0] but might have leading '/'
236+
cmd_text = words[0]
237+
else:
238+
# Command with arguments and trailing space
239+
partial = ""
240+
cmd_text = text.rstrip() # Remove trailing space for matching
241+
else:
242+
# No trailing space
243+
if len(words) <= 1:
244+
return
245+
partial = words[-1].lower()
246+
cmd_text = text
225247

226-
cmd = words[0]
227-
partial = words[-1].lower()
248+
# Pass the text (without trailing space if present) to matching_commands
249+
matches, matched_cmd, _ = self.commands.matching_commands(cmd_text.rstrip())
250+
if not matches:
251+
return
228252

229-
matches, _, _ = self.commands.matching_commands(cmd)
230253
if len(matches) == 1:
231254
cmd = matches[0]
232-
elif cmd not in matches:
255+
elif matched_cmd in matches:
256+
cmd = matched_cmd
257+
else:
233258
return
234259

235260
raw_completer = self.commands.get_raw_completions(cmd)
@@ -242,11 +267,14 @@ def get_command_completions(self, document, complete_event, text, words):
242267
if candidates is None:
243268
return
244269

245-
if cmd in self.post_filter_commands:
246-
candidates = [word for word in candidates if partial in word.lower()]
270+
candidates = [word for word in candidates if partial in word.lower()]
247271

248272
for candidate in sorted(candidates):
249-
yield Completion(candidate, start_position=-len(words[-1]))
273+
# Calculate start position based on partial, not words[-1]
274+
# When partial is empty (trailing space), start_position should be 0
275+
# When partial is not empty, replace that many characters
276+
start_position = -len(partial) if partial else 0
277+
yield Completion(candidate, start_position=start_position)
250278

251279
def get_completions(self, document, complete_event):
252280
self.tokenize()
@@ -256,8 +284,9 @@ def get_completions(self, document, complete_event):
256284
if not words:
257285
return
258286

259-
if text and text[-1].isspace():
260-
# don't keep completing after a space
287+
if text and text[-1].isspace() and not text.startswith("/"):
288+
# don't keep completing after a space for non-commands
289+
# For commands, we want to allow completion with empty string partial
261290
return
262291

263292
if text[0] == "/":

cecli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,7 @@ def apply_model_overrides(model_name):
12021202
return await graceful_exit(coder)
12031203
except SwitchCoderSignal as switch:
12041204
coder.ok_to_warm_cache = False
1205+
await coder.auto_save_session(force=True)
12051206

12061207
if hasattr(switch, "placeholder") and switch.placeholder is not None:
12071208
io.placeholder = switch.placeholder
@@ -1229,6 +1230,7 @@ def apply_model_overrides(model_name):
12291230

12301231
except SystemExit:
12311232
sys.settrace(None)
1233+
await coder.auto_save_session(force=True)
12321234
return await graceful_exit(coder)
12331235

12341236

cecli/tui/app.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,9 @@ def _get_suggestions(self, text: str) -> list[str]:
884884
suggestions = []
885885
commands = self.worker.coder.commands
886886

887-
if len(text) and text[-1] == " ":
887+
# Only return early for non-commands ending with space
888+
# For commands, we want to allow completion with empty string partial
889+
if len(text) and text[-1] == " " and not text.startswith("/"):
888890
return
889891

890892
if "@" in text:
@@ -905,12 +907,21 @@ def _get_suggestions(self, text: str) -> list[str]:
905907
suggestions = all_commands
906908
else:
907909
suggestions = [c for c in all_commands if c.startswith(cmd_part)]
908-
elif len(parts) > 1:
910+
else:
909911
# Complete command argument
912+
# This handles both:
913+
# 1. len(parts) > 1: command with arguments
914+
# 2. len(parts) == 1 and text.endswith(" "): command with trailing space
910915
cmd_name = cmd_part
911-
end_lookup = text.rsplit(maxsplit=1)
912916

913-
arg_prefix = end_lookup[-1]
917+
if text.endswith(" "):
918+
# Command with trailing space, empty argument prefix
919+
arg_prefix = ""
920+
else:
921+
# Get the last word as argument prefix
922+
end_lookup = text.rsplit(maxsplit=1)
923+
arg_prefix = end_lookup[-1]
924+
914925
arg_prefix_lower = arg_prefix.lower()
915926

916927
# Check if this command needs path-based completion
@@ -955,8 +966,14 @@ def _get_completed_text(self, current_text: str, completion: str) -> str:
955966
"""Calculate the new text after applying completion."""
956967
if current_text.startswith("/"):
957968
parts = current_text.rsplit(maxsplit=1)
958-
if len(parts) == 1:
959-
# Replace entire command
969+
970+
# Check if we have a command with trailing space
971+
# This is when we want to insert argument completions after the space
972+
if len(parts) == 1 and current_text.endswith(" "):
973+
# Command with trailing space, insert completion after space
974+
return current_text + completion
975+
elif len(parts) == 1:
976+
# Replace entire command (command name completion)
960977
# Only add space if command takes arguments
961978
commands = self.worker.coder.commands
962979
try:

cecli/tui/worker.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ async def _async_run(self):
9595
except SwitchCoderSignal as switch:
9696
# Handle chat mode switches (e.g., /chat-mode architect)
9797
try:
98+
await self.coder.auto_save_session(force=True)
9899
kwargs = dict(io=self.coder.io, from_coder=self.coder)
99100
kwargs.update(switch.kwargs)
100101
if "show_announcements" in kwargs:

0 commit comments

Comments
 (0)