kiwi-code 0.0.37__tar.gz → 0.0.39__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.
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/PKG-INFO +1 -1
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/pyproject.toml +1 -1
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/commands.py +1 -1
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/dashboard.py +39 -1
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/slash_commands.py +1 -1
- kiwi_code-0.0.39/tests/test_slash_commands.py +11 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_tui_headless.py +108 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/uv.lock +1 -1
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/.github/workflows/test.yml +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/.gitignore +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/.python-version +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/CLAUDE.md +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/Makefile +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/README.md +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/__init__.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/auth.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/cli.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/client.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/logger.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/models.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/runtime_manager.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_cli/server.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_runtime/__init__.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_runtime/main.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_runtime/snake_game/.gitignore +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_runtime/snake_game/requirements.txt +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/__init__.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/inline_file_picker.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/main.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/random_words.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/runtime_agent.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/attach_content.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/command_result.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/file_browser.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/help.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/id_picker.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/login.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/runtime_cleanup.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/screens/slash_picker.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/status_words.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/src/kiwi_tui/widgets.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/test_hello.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/__init__.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/conftest.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_cli_help.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_imports.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_reexec_kiwi.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_runtime_log_trimming.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_tokens.py +0 -0
- {kiwi_code-0.0.37 → kiwi_code-0.0.39}/tests/test_tui_palette.py +0 -0
|
@@ -320,7 +320,7 @@ Runtime:
|
|
|
320
320
|
Query:
|
|
321
321
|
/actions list [--name NAME] [--limit N]
|
|
322
322
|
/actions get <id>
|
|
323
|
-
/autocode-select
|
|
323
|
+
/autocode-select Choose the version of AutoCode you wish to use
|
|
324
324
|
/runs list [--action-id ID] [--status STATUS] [--limit N]
|
|
325
325
|
/runs get <id>
|
|
326
326
|
/graphs list [--name NAME] [--limit N]
|
|
@@ -2354,6 +2354,42 @@ class DashboardScreen(Screen):
|
|
|
2354
2354
|
return None
|
|
2355
2355
|
return None
|
|
2356
2356
|
|
|
2357
|
+
_MESSAGES_AUTOFOLLOW_THRESHOLD: int = 2
|
|
2358
|
+
|
|
2359
|
+
def _messages_is_near_bottom(self, *, threshold: int | None = None) -> bool:
|
|
2360
|
+
"""Return True when the chat viewport is already at or near the bottom.
|
|
2361
|
+
|
|
2362
|
+
Streaming updates should only auto-follow when the user hasn't intentionally
|
|
2363
|
+
scrolled upward to inspect earlier messages.
|
|
2364
|
+
"""
|
|
2365
|
+
try:
|
|
2366
|
+
messages = self.query_one("#messages", VerticalScroll)
|
|
2367
|
+
margin = (
|
|
2368
|
+
self._MESSAGES_AUTOFOLLOW_THRESHOLD
|
|
2369
|
+
if threshold is None
|
|
2370
|
+
else max(0, int(threshold))
|
|
2371
|
+
)
|
|
2372
|
+
current_position = max(
|
|
2373
|
+
int(messages.scroll_offset.y),
|
|
2374
|
+
int(messages.scroll_target_y),
|
|
2375
|
+
)
|
|
2376
|
+
remaining = max(0, int(messages.max_scroll_y) - current_position)
|
|
2377
|
+
return remaining <= margin
|
|
2378
|
+
except Exception:
|
|
2379
|
+
# Fail open so unrelated rendering issues don't block visible output.
|
|
2380
|
+
return True
|
|
2381
|
+
|
|
2382
|
+
def _scroll_messages_to_end(self, *, after_refresh: bool = False) -> None:
|
|
2383
|
+
"""Scroll the messages viewport to the bottom without animation."""
|
|
2384
|
+
try:
|
|
2385
|
+
messages = self.query_one("#messages", VerticalScroll)
|
|
2386
|
+
if after_refresh:
|
|
2387
|
+
self.call_after_refresh(messages.scroll_end, animate=False, immediate=True)
|
|
2388
|
+
else:
|
|
2389
|
+
messages.scroll_end(animate=False)
|
|
2390
|
+
except Exception:
|
|
2391
|
+
pass
|
|
2392
|
+
|
|
2357
2393
|
def _blink_streaming_dot_tick(self) -> None:
|
|
2358
2394
|
"""Timer tick: blink the dot for the active streaming assistant row."""
|
|
2359
2395
|
w = getattr(self, "_streaming_widget_ref", None)
|
|
@@ -3113,6 +3149,7 @@ class DashboardScreen(Screen):
|
|
|
3113
3149
|
return widget_ref
|
|
3114
3150
|
|
|
3115
3151
|
messages = self.query_one("#messages", VerticalScroll)
|
|
3152
|
+
should_autofollow = self._messages_is_near_bottom()
|
|
3116
3153
|
|
|
3117
3154
|
markdown_text = self._prepare_markdown(text_content)
|
|
3118
3155
|
if widget_ref is None:
|
|
@@ -3163,7 +3200,8 @@ class DashboardScreen(Screen):
|
|
|
3163
3200
|
except Exception:
|
|
3164
3201
|
pass
|
|
3165
3202
|
|
|
3166
|
-
|
|
3203
|
+
if should_autofollow:
|
|
3204
|
+
self._scroll_messages_to_end(after_refresh=True)
|
|
3167
3205
|
return widget_ref
|
|
3168
3206
|
|
|
3169
3207
|
def extract_text_from_output(self, output: any) -> str:
|
|
@@ -20,7 +20,7 @@ SLASH_COMMANDS: list[SlashCommand] = [
|
|
|
20
20
|
SlashCommand("Session", "/status", "Show current action & run", "/status"),
|
|
21
21
|
SlashCommand("Session", "/cancel", "Cancel active request", "/cancel"),
|
|
22
22
|
SlashCommand("Session", "/use <action_id>", "Switch to a different action", "/use "),
|
|
23
|
-
SlashCommand("Session", "/autocode-select", "
|
|
23
|
+
SlashCommand("Session", "/autocode-select", "Choose the version of AutoCode you wish to use", "/autocode-select"),
|
|
24
24
|
SlashCommand("Session", "/name <new name>", "Rename the current run (action run or graph run)", "/name "),
|
|
25
25
|
SlashCommand("Session", "/continue <run_id>", "Continue an existing run", "/continue "),
|
|
26
26
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from kiwi_cli.commands import HELP_TEXT
|
|
2
|
+
from kiwi_tui.slash_commands import SLASH_COMMANDS
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_slash_commands_lists_autocode_select_with_updated_description() -> None:
|
|
6
|
+
command = next(cmd for cmd in SLASH_COMMANDS if cmd.template == "/autocode-select")
|
|
7
|
+
assert command.description == "Choose the version of AutoCode you wish to use"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_cli_help_text_lists_autocode_select_with_updated_description() -> None:
|
|
11
|
+
assert "/autocode-select Choose the version of AutoCode you wish to use" in HELP_TEXT
|
|
@@ -805,6 +805,114 @@ async def test_tui_allows_empty_prompt(isolated_home: Path, monkeypatch: pytest.
|
|
|
805
805
|
|
|
806
806
|
assert called == [""]
|
|
807
807
|
|
|
808
|
+
|
|
809
|
+
@pytest.mark.asyncio
|
|
810
|
+
async def test_tui_streaming_requests_autofollow_when_viewport_is_near_bottom(
|
|
811
|
+
isolated_home: Path, monkeypatch: pytest.MonkeyPatch
|
|
812
|
+
) -> None:
|
|
813
|
+
"""Streaming updates should request auto-follow only when the viewport is near bottom."""
|
|
814
|
+
tokens_path = isolated_home / ".kiwi" / "tokens.json"
|
|
815
|
+
tokens_path.parent.mkdir(parents=True, exist_ok=True)
|
|
816
|
+
tokens_path.write_text(
|
|
817
|
+
json.dumps(
|
|
818
|
+
{
|
|
819
|
+
"access_token": "test-access-token",
|
|
820
|
+
"refresh_token": "test-refresh-token",
|
|
821
|
+
"token_type": "Bearer",
|
|
822
|
+
"expires_at": None,
|
|
823
|
+
}
|
|
824
|
+
),
|
|
825
|
+
encoding="utf-8",
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
from kiwi_tui.main import AutobotsTUI
|
|
829
|
+
|
|
830
|
+
app = AutobotsTUI()
|
|
831
|
+
async with app.run_test(size=(80, 24)) as pilot:
|
|
832
|
+
await pilot.pause()
|
|
833
|
+
assert type(app.screen).__name__ == "DashboardScreen"
|
|
834
|
+
screen = app.screen
|
|
835
|
+
|
|
836
|
+
calls: list[dict] = []
|
|
837
|
+
monkeypatch.setattr(screen, "_messages_is_near_bottom", lambda *args, **kwargs: True)
|
|
838
|
+
monkeypatch.setattr(
|
|
839
|
+
screen,
|
|
840
|
+
"_scroll_messages_to_end",
|
|
841
|
+
lambda *args, **kwargs: calls.append(dict(kwargs)),
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
widget = screen.update_streaming_message({"blocks": [{"text": "stream start"}]})
|
|
845
|
+
await pilot.pause()
|
|
846
|
+
assert widget is not None
|
|
847
|
+
assert calls == [{"after_refresh": True}]
|
|
848
|
+
|
|
849
|
+
calls.clear()
|
|
850
|
+
screen.update_streaming_message(
|
|
851
|
+
{"blocks": [{"text": "\n".join(f"stream line {i}" for i in range(10))}]},
|
|
852
|
+
widget,
|
|
853
|
+
)
|
|
854
|
+
await pilot.pause()
|
|
855
|
+
assert calls == [{"after_refresh": True}]
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
@pytest.mark.asyncio
|
|
859
|
+
async def test_tui_streaming_does_not_force_scroll_when_user_reads_older_messages(
|
|
860
|
+
isolated_home: Path,
|
|
861
|
+
) -> None:
|
|
862
|
+
"""Streaming updates should not yank the viewport back to the bottom."""
|
|
863
|
+
tokens_path = isolated_home / ".kiwi" / "tokens.json"
|
|
864
|
+
tokens_path.parent.mkdir(parents=True, exist_ok=True)
|
|
865
|
+
tokens_path.write_text(
|
|
866
|
+
json.dumps(
|
|
867
|
+
{
|
|
868
|
+
"access_token": "test-access-token",
|
|
869
|
+
"refresh_token": "test-refresh-token",
|
|
870
|
+
"token_type": "Bearer",
|
|
871
|
+
"expires_at": None,
|
|
872
|
+
}
|
|
873
|
+
),
|
|
874
|
+
encoding="utf-8",
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
from kiwi_tui.main import AutobotsTUI
|
|
878
|
+
|
|
879
|
+
app = AutobotsTUI()
|
|
880
|
+
async with app.run_test(size=(80, 24)) as pilot:
|
|
881
|
+
await pilot.pause()
|
|
882
|
+
assert type(app.screen).__name__ == "DashboardScreen"
|
|
883
|
+
screen = app.screen
|
|
884
|
+
messages = screen.query_one("#messages")
|
|
885
|
+
|
|
886
|
+
for i in range(30):
|
|
887
|
+
screen.add_message(
|
|
888
|
+
"\n".join(f"history {i} line {j}" for j in range(4)),
|
|
889
|
+
"assistant",
|
|
890
|
+
)
|
|
891
|
+
await pilot.pause()
|
|
892
|
+
assert messages.max_scroll_y > 0
|
|
893
|
+
|
|
894
|
+
widget = screen.update_streaming_message({"blocks": [{"text": "stream start"}]})
|
|
895
|
+
await pilot.pause()
|
|
896
|
+
assert widget is not None
|
|
897
|
+
|
|
898
|
+
target_y = max(0, int(messages.max_scroll_y) - 5)
|
|
899
|
+
messages.scroll_to(y=target_y, animate=False, immediate=True)
|
|
900
|
+
await pilot.pause()
|
|
901
|
+
y_before = int(messages.scroll_offset.y)
|
|
902
|
+
target_before = int(messages.scroll_target_y)
|
|
903
|
+
assert y_before == target_before
|
|
904
|
+
assert y_before < int(messages.max_scroll_y)
|
|
905
|
+
|
|
906
|
+
screen.update_streaming_message(
|
|
907
|
+
{"blocks": [{"text": "\n".join(f"stream line {i}" for i in range(60))}]},
|
|
908
|
+
widget,
|
|
909
|
+
)
|
|
910
|
+
await pilot.pause()
|
|
911
|
+
|
|
912
|
+
assert int(messages.scroll_offset.y) == y_before
|
|
913
|
+
assert int(messages.scroll_target_y) == target_before
|
|
914
|
+
assert int(messages.scroll_offset.y) < int(messages.max_scroll_y)
|
|
915
|
+
|
|
808
916
|
@pytest.mark.asyncio
|
|
809
917
|
async def test_tui_quits_cleanly(isolated_home: Path) -> None:
|
|
810
918
|
from kiwi_tui.main import AutobotsTUI
|
|
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
|