kiwi-code 0.0.439__tar.gz → 0.0.440__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.439 → kiwi_code-0.0.440}/PKG-INFO +2 -2
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/pyproject.toml +2 -2
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/__init__.py +1 -1
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_runtime/__init__.py +1 -1
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_runtime/main.py +3 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/__init__.py +1 -1
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/term_dashboard.py +178 -40
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/uv.lock +5 -5
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/.github/workflows/test.yml +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/.gitignore +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/.python-version +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/CLAUDE.md +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/Makefile +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/README.md +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/auth.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/checkpoints.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/cli.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/client.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/commands.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/logger.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/models.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/runtime_manager.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/server.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_cli/terminal_mode.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/inline_file_picker.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/main.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/random_words.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/runtime_agent.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/attach_content.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/command_result.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/dashboard.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/detach_files.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/file_browser.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/help.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/id_picker.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/login.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/runtime_cleanup.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/screens/slash_picker.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/slash_commands.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/status_words.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/term_app.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/widgets.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/src/kiwi_tui/worktrees.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/test_hello.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/__init__.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/conftest.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_checkpoints.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_cli_help.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_imports.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_reexec_kiwi.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_runtime_log_trimming.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_slash_commands.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_term_dashboard_ui.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_terminal_mode.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_tokens.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_tui_headless.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_tui_interactive_runtime.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_tui_palette.py +0 -0
- {kiwi_code-0.0.439 → kiwi_code-0.0.440}/tests/test_worktrees.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kiwi-code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.440
|
|
4
4
|
Summary: A textual-based terminal user interface application
|
|
5
5
|
Project-URL: Homepage, https://meetkiwi.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/jetoslabs/kiwi-code
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Python: <4.0,>=3.11
|
|
18
|
-
Requires-Dist: autobots-client==0.1.
|
|
18
|
+
Requires-Dist: autobots-client==0.1.1
|
|
19
19
|
Requires-Dist: httpx>=0.25.0
|
|
20
20
|
Requires-Dist: loguru>=0.7.3
|
|
21
21
|
Requires-Dist: psutil>=5.9.0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kiwi-code"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.440"
|
|
4
4
|
description = "A textual-based terminal user interface application"
|
|
5
5
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
6
6
|
requires-python = ">=3.11,<4.0"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"autobots-client==0.1.
|
|
8
|
+
"autobots-client==0.1.1",
|
|
9
9
|
"loguru>=0.7.3",
|
|
10
10
|
"pydantic>=2.12.5",
|
|
11
11
|
"textual>=8.1.1",
|
|
@@ -1184,6 +1184,9 @@ class TermDashboardScreen(Screen):
|
|
|
1184
1184
|
self._cmd_result_visible: bool = False # True while inline cmd output is shown
|
|
1185
1185
|
self._cmd_running: bool = False
|
|
1186
1186
|
self._is_streaming: bool = False
|
|
1187
|
+
# True when the current run's latest prompt is still processing server-side.
|
|
1188
|
+
# Used to prevent sending concurrent prompts (especially after reattaching with /continue).
|
|
1189
|
+
self._run_inflight: bool = False
|
|
1187
1190
|
self._pending_urls: list[str] = []
|
|
1188
1191
|
self._detach_selected_urls: set[str] = set()
|
|
1189
1192
|
self._metadata: dict = {}
|
|
@@ -1198,6 +1201,7 @@ class TermDashboardScreen(Screen):
|
|
|
1198
1201
|
self._spinner_i: int = 0
|
|
1199
1202
|
|
|
1200
1203
|
self._last_copy_notify_at: float = 0.0
|
|
1204
|
+
self._last_submit_block_notify_at: float = 0.0
|
|
1201
1205
|
self._clickable_list_seq: int = 0
|
|
1202
1206
|
|
|
1203
1207
|
# Inline list mode for #term-runs-list (affects click behavior)
|
|
@@ -1737,7 +1741,6 @@ class TermDashboardScreen(Screen):
|
|
|
1737
1741
|
elif event.action == "logs":
|
|
1738
1742
|
self.action_show_logs()
|
|
1739
1743
|
elif event.action == "quit":
|
|
1740
|
-
# Use the app-level quit flow so we still prompt for runtime cleanup if needed.
|
|
1741
1744
|
try:
|
|
1742
1745
|
self.app.action_quit()
|
|
1743
1746
|
except Exception:
|
|
@@ -1872,7 +1875,6 @@ class TermDashboardScreen(Screen):
|
|
|
1872
1875
|
logger.debug(f"_show_slash_autocomplete: {exc}")
|
|
1873
1876
|
return
|
|
1874
1877
|
|
|
1875
|
-
# Tell the input widget to intercept navigation keys
|
|
1876
1878
|
try:
|
|
1877
1879
|
inp = self.query_one("#term-chat-input", TermSlashInput)
|
|
1878
1880
|
inp.autocomplete_active = True
|
|
@@ -1929,25 +1931,75 @@ class TermDashboardScreen(Screen):
|
|
|
1929
1931
|
if not command.needs_args:
|
|
1930
1932
|
self.call_after_refresh(lambda v=val: self._do_send_value(v))
|
|
1931
1933
|
|
|
1934
|
+
def _toast_submit_blocked(self, message: str) -> None:
|
|
1935
|
+
"""Show a throttled toast when the user tries to submit while busy."""
|
|
1936
|
+
try:
|
|
1937
|
+
now = time.monotonic()
|
|
1938
|
+
if (now - self._last_submit_block_notify_at) < 1.0:
|
|
1939
|
+
return
|
|
1940
|
+
self._last_submit_block_notify_at = now
|
|
1941
|
+
self.notify(
|
|
1942
|
+
message,
|
|
1943
|
+
title="Please wait",
|
|
1944
|
+
severity="warning",
|
|
1945
|
+
timeout=2.5,
|
|
1946
|
+
markup=False,
|
|
1947
|
+
)
|
|
1948
|
+
except Exception:
|
|
1949
|
+
pass
|
|
1950
|
+
|
|
1932
1951
|
def _do_send_value(self, value: str) -> None:
|
|
1933
1952
|
"""Programmatically send a value as if the user typed + pressed Enter."""
|
|
1934
1953
|
self._hide_slash_autocomplete()
|
|
1954
|
+
val = str(value or "")
|
|
1955
|
+
msg = val.strip()
|
|
1956
|
+
if not msg:
|
|
1957
|
+
return
|
|
1958
|
+
|
|
1935
1959
|
if self._is_streaming or self._cmd_running:
|
|
1960
|
+
try:
|
|
1961
|
+
inp = self.query_one("#term-chat-input", TermSlashInput)
|
|
1962
|
+
if not inp.value.strip():
|
|
1963
|
+
inp.value = msg
|
|
1964
|
+
except Exception:
|
|
1965
|
+
pass
|
|
1966
|
+
self._toast_submit_blocked("Busy streaming — you can keep typing, but can't send yet.")
|
|
1967
|
+
return
|
|
1968
|
+
|
|
1969
|
+
# Slash commands are allowed when we're not actively streaming.
|
|
1970
|
+
if msg.startswith("/"):
|
|
1971
|
+
try:
|
|
1972
|
+
inp = self.query_one("#term-chat-input", TermSlashInput)
|
|
1973
|
+
inp.record(msg)
|
|
1974
|
+
inp.value = ""
|
|
1975
|
+
except Exception:
|
|
1976
|
+
pass
|
|
1977
|
+
self._dispatch_slash_command(msg)
|
|
1978
|
+
return
|
|
1979
|
+
|
|
1980
|
+
if self._run_inflight:
|
|
1981
|
+
# Keep the value in the input as a draft (best-effort).
|
|
1982
|
+
try:
|
|
1983
|
+
inp = self.query_one("#term-chat-input", TermSlashInput)
|
|
1984
|
+
if not inp.value.strip():
|
|
1985
|
+
inp.value = msg
|
|
1986
|
+
except Exception:
|
|
1987
|
+
pass
|
|
1988
|
+
self._toast_submit_blocked(
|
|
1989
|
+
f"Run {self.current_run_id or '(unknown)'} is still processing — draft your next message and send when done."
|
|
1990
|
+
)
|
|
1936
1991
|
return
|
|
1992
|
+
|
|
1937
1993
|
try:
|
|
1938
1994
|
inp = self.query_one("#term-chat-input", TermSlashInput)
|
|
1939
|
-
inp.record(
|
|
1995
|
+
inp.record(msg)
|
|
1940
1996
|
inp.value = ""
|
|
1941
1997
|
except Exception:
|
|
1942
1998
|
pass
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
self._hide_cmd_result()
|
|
1948
|
-
self._add_message(value, "user")
|
|
1949
|
-
self._process_message(value)
|
|
1950
|
-
|
|
1999
|
+
self._enter_conversation_mode()
|
|
2000
|
+
self._hide_cmd_result()
|
|
2001
|
+
self._add_message(msg, "user")
|
|
2002
|
+
self._process_message(msg)
|
|
1951
2003
|
# Top bar / hints
|
|
1952
2004
|
def _refresh_top_bar(self) -> None:
|
|
1953
2005
|
try:
|
|
@@ -1962,6 +2014,7 @@ class TermDashboardScreen(Screen):
|
|
|
1962
2014
|
run = self.current_run_id or ""
|
|
1963
2015
|
run_short = (run[:8] + "…") if len(run) > 8 else run
|
|
1964
2016
|
streaming_flag = " ● streaming" if self._is_streaming else ""
|
|
2017
|
+
processing_flag = " ● processing" if (self._run_inflight and not self._is_streaming) else ""
|
|
1965
2018
|
|
|
1966
2019
|
parts: list[str] = [f"Kiwi Code v{KIWI_CODE_VERSION}", now, cwd_short]
|
|
1967
2020
|
if action_short:
|
|
@@ -1977,7 +2030,9 @@ class TermDashboardScreen(Screen):
|
|
|
1977
2030
|
x0 = cell_len(prefix)
|
|
1978
2031
|
self._top_bar_run_bounds = (x0, x0 + cell_len(run_part))
|
|
1979
2032
|
|
|
1980
|
-
self.query_one("#term-top-bar", Static).update(
|
|
2033
|
+
self.query_one("#term-top-bar", Static).update(
|
|
2034
|
+
" " + " ── ".join(parts) + streaming_flag + processing_flag
|
|
2035
|
+
)
|
|
1981
2036
|
self._refresh_welcome_meta()
|
|
1982
2037
|
except Exception:
|
|
1983
2038
|
pass
|
|
@@ -1991,8 +2046,6 @@ class TermDashboardScreen(Screen):
|
|
|
1991
2046
|
except Exception:
|
|
1992
2047
|
pass
|
|
1993
2048
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
2049
|
def _refresh_hint_model(self) -> None:
|
|
1997
2050
|
try:
|
|
1998
2051
|
action = self.current_action_name or ""
|
|
@@ -2151,10 +2204,8 @@ class TermDashboardScreen(Screen):
|
|
|
2151
2204
|
pass
|
|
2152
2205
|
self._on_files_selected(file_paths)
|
|
2153
2206
|
return
|
|
2154
|
-
# If the input is focused, let TextArea's paste handling do its normal thing.
|
|
2155
2207
|
if chat_input.has_focus:
|
|
2156
2208
|
return
|
|
2157
|
-
# Forward paste to the input when focus is elsewhere (prevents "paste does nothing").
|
|
2158
2209
|
event.prevent_default()
|
|
2159
2210
|
event.stop()
|
|
2160
2211
|
try:
|
|
@@ -2171,24 +2222,37 @@ class TermDashboardScreen(Screen):
|
|
|
2171
2222
|
pass
|
|
2172
2223
|
# Send
|
|
2173
2224
|
def _do_send(self) -> None:
|
|
2174
|
-
# Always dismiss autocomplete before sending
|
|
2175
2225
|
self._hide_slash_autocomplete()
|
|
2226
|
+
|
|
2176
2227
|
if self._is_streaming or self._cmd_running:
|
|
2228
|
+
self._toast_submit_blocked("Busy streaming — you can keep typing, but can't send yet.")
|
|
2177
2229
|
return
|
|
2230
|
+
|
|
2178
2231
|
chat_input = self.query_one("#term-chat-input", ChatInput)
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2232
|
+
raw = chat_input.value
|
|
2233
|
+
message = raw.strip()
|
|
2234
|
+
if not message:
|
|
2235
|
+
return
|
|
2183
2236
|
|
|
2184
2237
|
if message.startswith("/"):
|
|
2238
|
+
chat_input.record(message)
|
|
2239
|
+
chat_input.value = ""
|
|
2185
2240
|
self._dispatch_slash_command(message)
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
self.
|
|
2190
|
-
|
|
2191
|
-
|
|
2241
|
+
return
|
|
2242
|
+
|
|
2243
|
+
if self._run_inflight:
|
|
2244
|
+
self._toast_submit_blocked(
|
|
2245
|
+
f"Run {self.current_run_id or '(unknown)'} is still processing — draft your next message and send when done."
|
|
2246
|
+
)
|
|
2247
|
+
return
|
|
2248
|
+
|
|
2249
|
+
chat_input.record(message)
|
|
2250
|
+
chat_input.value = ""
|
|
2251
|
+
|
|
2252
|
+
self._enter_conversation_mode()
|
|
2253
|
+
self._hide_cmd_result()
|
|
2254
|
+
self._add_message(message, "user")
|
|
2255
|
+
self._process_message(message)
|
|
2192
2256
|
|
|
2193
2257
|
# Messages
|
|
2194
2258
|
@staticmethod
|
|
@@ -2298,6 +2362,7 @@ class TermDashboardScreen(Screen):
|
|
|
2298
2362
|
if not w.is_finished:
|
|
2299
2363
|
w.cancel()
|
|
2300
2364
|
self._set_streaming(False)
|
|
2365
|
+
self._set_run_inflight(False)
|
|
2301
2366
|
self.current_action_id = self.DEFAULT_ACTION_ID
|
|
2302
2367
|
self.current_action_name = None
|
|
2303
2368
|
self.current_run_id = None
|
|
@@ -2345,6 +2410,7 @@ class TermDashboardScreen(Screen):
|
|
|
2345
2410
|
self.current_action_name = None
|
|
2346
2411
|
self.current_run_id = None
|
|
2347
2412
|
self.current_run_kind = None
|
|
2413
|
+
self._set_run_inflight(False)
|
|
2348
2414
|
self._clear_messages()
|
|
2349
2415
|
self._refresh_top_bar()
|
|
2350
2416
|
self.run_worker(self._fetch_action_name_async(), group="status",
|
|
@@ -2862,6 +2928,7 @@ class TermDashboardScreen(Screen):
|
|
|
2862
2928
|
self.current_action_name = None
|
|
2863
2929
|
self.current_run_id = None
|
|
2864
2930
|
self.current_run_kind = None
|
|
2931
|
+
self._set_run_inflight(False)
|
|
2865
2932
|
self._refresh_top_bar()
|
|
2866
2933
|
self._clear_messages()
|
|
2867
2934
|
self.run_worker(self._fetch_action_name_async(), group="status", exclusive=False, exit_on_error=False)
|
|
@@ -2972,12 +3039,10 @@ class TermDashboardScreen(Screen):
|
|
|
2972
3039
|
scope=effective_args.scope,
|
|
2973
3040
|
allow_dirs=effective_args.allow_dirs,
|
|
2974
3041
|
)
|
|
2975
|
-
# Ensure runtime won't attribute our restore edits to an active prompt.
|
|
2976
3042
|
try:
|
|
2977
3043
|
set_active_entry(run_dir, None)
|
|
2978
3044
|
except Exception:
|
|
2979
3045
|
pass
|
|
2980
|
-
# If latest doesn't exist yet, create it from current state (no-op restore).
|
|
2981
3046
|
try:
|
|
2982
3047
|
ensure_latest_snapshot_exists(run_dir=run_dir, meta=meta)
|
|
2983
3048
|
except Exception:
|
|
@@ -3077,8 +3142,16 @@ class TermDashboardScreen(Screen):
|
|
|
3077
3142
|
)
|
|
3078
3143
|
if not success or not result:
|
|
3079
3144
|
return
|
|
3145
|
+
if self.current_run_id and run_id != self.current_run_id:
|
|
3146
|
+
return
|
|
3080
3147
|
self._render_history(result)
|
|
3081
3148
|
|
|
3149
|
+
status = str(result.get("status", "") or "").lower()
|
|
3150
|
+
inflight = status in ("processing", "running", "pending")
|
|
3151
|
+
self._set_run_inflight(inflight)
|
|
3152
|
+
if inflight:
|
|
3153
|
+
self._start_reattach_stream(run_id)
|
|
3154
|
+
|
|
3082
3155
|
def _render_history(self, result: dict) -> None:
|
|
3083
3156
|
if not isinstance(result, dict):
|
|
3084
3157
|
return
|
|
@@ -3103,8 +3176,62 @@ class TermDashboardScreen(Screen):
|
|
|
3103
3176
|
if text_content:
|
|
3104
3177
|
self._add_message(text_content, "assistant")
|
|
3105
3178
|
|
|
3179
|
+
|
|
3180
|
+
def _start_reattach_stream(self, run_id: str) -> None:
|
|
3181
|
+
"""Resume SSE streaming for an *existing* run that is still processing."""
|
|
3182
|
+
if self._is_streaming:
|
|
3183
|
+
return
|
|
3184
|
+
if not run_id:
|
|
3185
|
+
return
|
|
3186
|
+
if self.current_run_id and run_id != self.current_run_id:
|
|
3187
|
+
return
|
|
3188
|
+
if not hasattr(self.app, "autobots_client"):
|
|
3189
|
+
return
|
|
3190
|
+
# Mount a streaming placeholder if one isn't already present.
|
|
3191
|
+
try:
|
|
3192
|
+
existing = self._active_streaming_row()
|
|
3193
|
+
if existing:
|
|
3194
|
+
self._streaming_widget_ref = existing
|
|
3195
|
+
else:
|
|
3196
|
+
messages = self.query_one("#term-messages", Vertical)
|
|
3197
|
+
placeholder = AssistantMessageRow(
|
|
3198
|
+
"",
|
|
3199
|
+
verb="Reattaching",
|
|
3200
|
+
streaming=True,
|
|
3201
|
+
classes="term-message term-assistant-message",
|
|
3202
|
+
)
|
|
3203
|
+
messages.mount(placeholder)
|
|
3204
|
+
self._scroll_end_root(after_refresh=True)
|
|
3205
|
+
self._streaming_widget_ref = placeholder
|
|
3206
|
+
except Exception:
|
|
3207
|
+
self._streaming_widget_ref = None
|
|
3208
|
+
# Mark in-flight + disable input while actively streaming.
|
|
3209
|
+
self._set_run_inflight(True)
|
|
3210
|
+
self._set_streaming(True)
|
|
3211
|
+
self.run_worker(
|
|
3212
|
+
self._reattach_worker(run_id),
|
|
3213
|
+
exclusive=True,
|
|
3214
|
+
group="stream",
|
|
3215
|
+
exit_on_error=False,
|
|
3216
|
+
)
|
|
3217
|
+
|
|
3218
|
+
async def _reattach_worker(self, run_id: str) -> None:
|
|
3219
|
+
"""Worker that attaches to SSE/polling for an already-started run."""
|
|
3220
|
+
try:
|
|
3221
|
+
self.app._sync_autobots_client_from_disk()
|
|
3222
|
+
except Exception:
|
|
3223
|
+
pass
|
|
3224
|
+
if not hasattr(self.app, "autobots_client"):
|
|
3225
|
+
return
|
|
3226
|
+
client = self.app.autobots_client
|
|
3227
|
+
# Best-effort: ensure CLI runtime is connected for this run while we stream.
|
|
3228
|
+
try:
|
|
3229
|
+
self.app.ensure_runtime_for_run_id(run_id)
|
|
3230
|
+
except Exception:
|
|
3231
|
+
pass
|
|
3232
|
+
await self._consume_sse(run_id, client)
|
|
3233
|
+
|
|
3106
3234
|
# Checkpoints / rewind
|
|
3107
|
-
# --------------------
|
|
3108
3235
|
# kiwi-runtime has built-in checkpointing hooks for filesystem mutations, but only when
|
|
3109
3236
|
# an "active entry" is set for the current run. The classic dashboard wires this up;
|
|
3110
3237
|
# TermDashboard must do the same so /rewind has checkpoints to restore.
|
|
@@ -3188,6 +3315,7 @@ class TermDashboardScreen(Screen):
|
|
|
3188
3315
|
except Exception:
|
|
3189
3316
|
self._streaming_widget_ref = None
|
|
3190
3317
|
|
|
3318
|
+
self._set_run_inflight(True)
|
|
3191
3319
|
self._set_streaming(True)
|
|
3192
3320
|
self.run_worker(self._sse_worker(user_input), exclusive=True, group="stream")
|
|
3193
3321
|
|
|
@@ -3210,7 +3338,6 @@ class TermDashboardScreen(Screen):
|
|
|
3210
3338
|
|
|
3211
3339
|
def _cleanup_checkpoint() -> None:
|
|
3212
3340
|
if checkpoint_run_dir is not None and checkpoint_entry_id is not None:
|
|
3213
|
-
# Update latest snapshot so "Latest Code" stays accurate across prompts.
|
|
3214
3341
|
try:
|
|
3215
3342
|
from kiwi_cli.checkpoints import ensure_meta, update_latest_from_entry, workspace_root_from_cwd
|
|
3216
3343
|
workspace_root = workspace_root_from_cwd()
|
|
@@ -3230,15 +3357,11 @@ class TermDashboardScreen(Screen):
|
|
|
3230
3357
|
pass
|
|
3231
3358
|
self._checkpoint_clear_active(run_dir=checkpoint_run_dir)
|
|
3232
3359
|
|
|
3233
|
-
# Auto-connect local CLI runtime for every prompt.
|
|
3234
|
-
# For existing runs, ensure a pinned runtime is available before running the action.
|
|
3235
3360
|
if self.current_run_id:
|
|
3236
3361
|
try:
|
|
3237
3362
|
self.app.ensure_runtime_for_run_id(self.current_run_id)
|
|
3238
3363
|
except Exception:
|
|
3239
3364
|
pass
|
|
3240
|
-
# If this is a continuation, start the checkpoint entry before sending the request
|
|
3241
|
-
# to reduce races with early file edits.
|
|
3242
3365
|
checkpoint_run_dir, checkpoint_entry_id = self._checkpoint_start_for_prompt(
|
|
3243
3366
|
run_id=self.current_run_id,
|
|
3244
3367
|
user_prompt=user_input,
|
|
@@ -3260,6 +3383,7 @@ class TermDashboardScreen(Screen):
|
|
|
3260
3383
|
self._add_message(f"Error starting action: {e}", "error")
|
|
3261
3384
|
self._drop_placeholder()
|
|
3262
3385
|
self._set_streaming(False)
|
|
3386
|
+
self._set_run_inflight(False)
|
|
3263
3387
|
_cleanup_checkpoint()
|
|
3264
3388
|
return
|
|
3265
3389
|
|
|
@@ -3285,6 +3409,7 @@ class TermDashboardScreen(Screen):
|
|
|
3285
3409
|
self._add_message(f"Error starting action: {message}", "error")
|
|
3286
3410
|
self._drop_placeholder()
|
|
3287
3411
|
self._set_streaming(False)
|
|
3412
|
+
self._set_run_inflight(False)
|
|
3288
3413
|
_cleanup_checkpoint()
|
|
3289
3414
|
return
|
|
3290
3415
|
|
|
@@ -3413,6 +3538,7 @@ class TermDashboardScreen(Screen):
|
|
|
3413
3538
|
f"(HTTP {c2}). Please log in again."
|
|
3414
3539
|
)
|
|
3415
3540
|
got_final = True
|
|
3541
|
+
self._set_run_inflight(False)
|
|
3416
3542
|
self._set_streaming(False)
|
|
3417
3543
|
return True
|
|
3418
3544
|
if not ok or not res:
|
|
@@ -3431,6 +3557,7 @@ class TermDashboardScreen(Screen):
|
|
|
3431
3557
|
else:
|
|
3432
3558
|
self._add_message(f"Error: {err_msg}", "error")
|
|
3433
3559
|
got_final = True
|
|
3560
|
+
self._set_run_inflight(False)
|
|
3434
3561
|
self._set_streaming(False)
|
|
3435
3562
|
return True
|
|
3436
3563
|
if status not in ("completed", "success", "finished"):
|
|
@@ -3447,6 +3574,7 @@ class TermDashboardScreen(Screen):
|
|
|
3447
3574
|
sw_ref[0] = None
|
|
3448
3575
|
self._streaming_widget_ref = None
|
|
3449
3576
|
got_final = True
|
|
3577
|
+
self._set_run_inflight(False)
|
|
3450
3578
|
self._set_streaming(False)
|
|
3451
3579
|
return True
|
|
3452
3580
|
return False
|
|
@@ -3716,6 +3844,13 @@ class TermDashboardScreen(Screen):
|
|
|
3716
3844
|
except Exception:
|
|
3717
3845
|
pass
|
|
3718
3846
|
|
|
3847
|
+
def _set_run_inflight(self, inflight: bool) -> None:
|
|
3848
|
+
"""Track whether the backend is still processing the latest prompt for this run."""
|
|
3849
|
+
self._run_inflight = bool(inflight)
|
|
3850
|
+
self._apply_blocking_state()
|
|
3851
|
+
self._refresh_top_bar()
|
|
3852
|
+
|
|
3853
|
+
|
|
3719
3854
|
def _set_streaming(self, streaming: bool) -> None:
|
|
3720
3855
|
self._is_streaming = streaming
|
|
3721
3856
|
try:
|
|
@@ -3754,19 +3889,21 @@ class TermDashboardScreen(Screen):
|
|
|
3754
3889
|
self._refresh_top_bar()
|
|
3755
3890
|
|
|
3756
3891
|
def _apply_blocking_state(self) -> None:
|
|
3757
|
-
|
|
3892
|
+
blocked = self._is_streaming or self._cmd_running or self._run_inflight
|
|
3758
3893
|
try:
|
|
3759
3894
|
ci = self.query_one("#term-chat-input", ChatInput)
|
|
3760
|
-
ci.disabled =
|
|
3895
|
+
ci.disabled = False
|
|
3761
3896
|
if self._is_streaming:
|
|
3762
|
-
ci.placeholder = "\u25cf Streaming\u2026"
|
|
3897
|
+
ci.placeholder = "\u25cf Streaming\u2026 (draft now; send when finished)"
|
|
3763
3898
|
elif self._cmd_running:
|
|
3764
|
-
ci.placeholder = "
|
|
3899
|
+
ci.placeholder = "Busy\u2026 (draft now; send when finished)"
|
|
3900
|
+
elif self._run_inflight:
|
|
3901
|
+
ci.placeholder = "\u25cf Processing\u2026 (draft now; send when finished)"
|
|
3765
3902
|
else:
|
|
3766
3903
|
ci.placeholder = (
|
|
3767
3904
|
"Message\u2026" if self._has_conversation else "What would you like to do?"
|
|
3768
3905
|
)
|
|
3769
|
-
if not
|
|
3906
|
+
if not self._cmd_running:
|
|
3770
3907
|
ci.focus()
|
|
3771
3908
|
except Exception:
|
|
3772
3909
|
pass
|
|
@@ -3939,6 +4076,7 @@ class TermDashboardScreen(Screen):
|
|
|
3939
4076
|
pass
|
|
3940
4077
|
try:
|
|
3941
4078
|
self._set_streaming(False)
|
|
4079
|
+
self._set_run_inflight(False)
|
|
3942
4080
|
except Exception:
|
|
3943
4081
|
pass
|
|
3944
4082
|
self._drop_placeholder()
|
|
@@ -181,16 +181,16 @@ wheels = [
|
|
|
181
181
|
|
|
182
182
|
[[package]]
|
|
183
183
|
name = "autobots-client"
|
|
184
|
-
version = "0.1.
|
|
184
|
+
version = "0.1.1"
|
|
185
185
|
source = { registry = "https://pypi.org/simple" }
|
|
186
186
|
dependencies = [
|
|
187
187
|
{ name = "attrs" },
|
|
188
188
|
{ name = "httpx" },
|
|
189
189
|
{ name = "python-dateutil" },
|
|
190
190
|
]
|
|
191
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
191
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1d/ab/1e6a7c2a71d3080dc0b07abffe023acf97c1aa23437b9c1fc1bdb493c33e/autobots_client-0.1.1.tar.gz", hash = "sha256:6c8164713793e6424458260a7a67216a8ffb52fccf6c34d6c8c45684fe78b7a3", size = 152893, upload-time = "2026-06-11T21:36:36.548Z" }
|
|
192
192
|
wheels = [
|
|
193
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/87/57/b5c5ed1a6cba22b6f243e13143d8b31ebb5075fec4451c0a9ea0b1f3046b/autobots_client-0.1.1-py3-none-any.whl", hash = "sha256:a3c2aa84c6b53eb6818f218fc0eff369d16b46244a239132ead5d0b111757183", size = 622952, upload-time = "2026-06-11T21:36:37.776Z" },
|
|
194
194
|
]
|
|
195
195
|
|
|
196
196
|
[[package]]
|
|
@@ -397,7 +397,7 @@ wheels = [
|
|
|
397
397
|
|
|
398
398
|
[[package]]
|
|
399
399
|
name = "kiwi-code"
|
|
400
|
-
version = "0.0.
|
|
400
|
+
version = "0.0.440"
|
|
401
401
|
source = { editable = "." }
|
|
402
402
|
dependencies = [
|
|
403
403
|
{ name = "autobots-client" },
|
|
@@ -421,7 +421,7 @@ test = [
|
|
|
421
421
|
|
|
422
422
|
[package.metadata]
|
|
423
423
|
requires-dist = [
|
|
424
|
-
{ name = "autobots-client", specifier = "==0.1.
|
|
424
|
+
{ name = "autobots-client", specifier = "==0.1.1" },
|
|
425
425
|
{ name = "httpx", specifier = ">=0.25.0" },
|
|
426
426
|
{ name = "loguru", specifier = ">=0.7.3" },
|
|
427
427
|
{ name = "psutil", specifier = ">=5.9.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
|