klaude-code 2.10.3__py3-none-any.whl → 2.10.4__py3-none-any.whl
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.
- klaude_code/auth/AGENTS.md +4 -24
- klaude_code/auth/__init__.py +1 -17
- klaude_code/cli/auth_cmd.py +3 -53
- klaude_code/cli/list_model.py +0 -50
- klaude_code/config/assets/builtin_config.yaml +0 -28
- klaude_code/config/config.py +5 -42
- klaude_code/const.py +5 -2
- klaude_code/core/agent_profile.py +2 -10
- klaude_code/core/backtrack/__init__.py +3 -0
- klaude_code/core/backtrack/manager.py +48 -0
- klaude_code/core/memory.py +25 -9
- klaude_code/core/task.py +53 -7
- klaude_code/core/tool/__init__.py +2 -0
- klaude_code/core/tool/backtrack/__init__.py +3 -0
- klaude_code/core/tool/backtrack/backtrack_tool.md +17 -0
- klaude_code/core/tool/backtrack/backtrack_tool.py +65 -0
- klaude_code/core/tool/context.py +5 -0
- klaude_code/core/turn.py +3 -0
- klaude_code/llm/input_common.py +70 -1
- klaude_code/llm/openai_compatible/input.py +5 -2
- klaude_code/llm/openrouter/input.py +5 -2
- klaude_code/llm/registry.py +0 -1
- klaude_code/protocol/events.py +10 -0
- klaude_code/protocol/llm_param.py +0 -1
- klaude_code/protocol/message.py +10 -1
- klaude_code/protocol/tools.py +1 -0
- klaude_code/session/session.py +111 -2
- klaude_code/session/store.py +2 -0
- klaude_code/skill/assets/executing-plans/SKILL.md +84 -0
- klaude_code/skill/assets/writing-plans/SKILL.md +116 -0
- klaude_code/tui/commands.py +15 -0
- klaude_code/tui/components/developer.py +1 -1
- klaude_code/tui/components/rich/status.py +7 -76
- klaude_code/tui/components/rich/theme.py +10 -0
- klaude_code/tui/components/tools.py +31 -18
- klaude_code/tui/display.py +4 -0
- klaude_code/tui/input/prompt_toolkit.py +15 -1
- klaude_code/tui/machine.py +26 -8
- klaude_code/tui/renderer.py +97 -0
- klaude_code/tui/runner.py +7 -2
- klaude_code/tui/terminal/image.py +28 -12
- klaude_code/ui/terminal/title.py +8 -3
- {klaude_code-2.10.3.dist-info → klaude_code-2.10.4.dist-info}/METADATA +1 -1
- {klaude_code-2.10.3.dist-info → klaude_code-2.10.4.dist-info}/RECORD +46 -49
- klaude_code/auth/antigravity/__init__.py +0 -20
- klaude_code/auth/antigravity/exceptions.py +0 -17
- klaude_code/auth/antigravity/oauth.py +0 -315
- klaude_code/auth/antigravity/pkce.py +0 -25
- klaude_code/auth/antigravity/token_manager.py +0 -27
- klaude_code/core/prompts/prompt-antigravity.md +0 -80
- klaude_code/llm/antigravity/__init__.py +0 -3
- klaude_code/llm/antigravity/client.py +0 -558
- klaude_code/llm/antigravity/input.py +0 -268
- klaude_code/skill/assets/create-plan/SKILL.md +0 -74
- {klaude_code-2.10.3.dist-info → klaude_code-2.10.4.dist-info}/WHEEL +0 -0
- {klaude_code-2.10.3.dist-info → klaude_code-2.10.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: writing-plans
|
|
3
|
+
description: Use when you have a spec or requirements for a multi-step task, before touching code
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Writing Plans
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
|
|
11
|
+
|
|
12
|
+
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
|
|
13
|
+
|
|
14
|
+
**Announce at start:** "I'm using the writing-plans skill to create the implementation plan."
|
|
15
|
+
|
|
16
|
+
**Context:** This should be run in a dedicated worktree (created by brainstorming skill).
|
|
17
|
+
|
|
18
|
+
**Save plans to:** `docs/plans/YYYY-MM-DD-<feature-name>.md`
|
|
19
|
+
|
|
20
|
+
## Bite-Sized Task Granularity
|
|
21
|
+
|
|
22
|
+
**Each step is one action (2-5 minutes):**
|
|
23
|
+
- "Write the failing test" - step
|
|
24
|
+
- "Run it to make sure it fails" - step
|
|
25
|
+
- "Implement the minimal code to make the test pass" - step
|
|
26
|
+
- "Run the tests and make sure they pass" - step
|
|
27
|
+
- "Commit" - step
|
|
28
|
+
|
|
29
|
+
## Plan Document Header
|
|
30
|
+
|
|
31
|
+
**Every plan MUST start with this header:**
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
# [Feature Name] Implementation Plan
|
|
35
|
+
|
|
36
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
37
|
+
|
|
38
|
+
**Goal:** [One sentence describing what this builds]
|
|
39
|
+
|
|
40
|
+
**Architecture:** [2-3 sentences about approach]
|
|
41
|
+
|
|
42
|
+
**Tech Stack:** [Key technologies/libraries]
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Task Structure
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
### Task N: [Component Name]
|
|
51
|
+
|
|
52
|
+
**Files:**
|
|
53
|
+
- Create: `exact/path/to/file.py`
|
|
54
|
+
- Modify: `exact/path/to/existing.py:123-145`
|
|
55
|
+
- Test: `tests/exact/path/to/test.py`
|
|
56
|
+
|
|
57
|
+
**Step 1: Write the failing test**
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
def test_specific_behavior():
|
|
61
|
+
result = function(input)
|
|
62
|
+
assert result == expected
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Step 2: Run test to verify it fails**
|
|
66
|
+
|
|
67
|
+
Run: `pytest tests/path/test.py::test_name -v`
|
|
68
|
+
Expected: FAIL with "function not defined"
|
|
69
|
+
|
|
70
|
+
**Step 3: Write minimal implementation**
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
def function(input):
|
|
74
|
+
return expected
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Step 4: Run test to verify it passes**
|
|
78
|
+
|
|
79
|
+
Run: `pytest tests/path/test.py::test_name -v`
|
|
80
|
+
Expected: PASS
|
|
81
|
+
|
|
82
|
+
**Step 5: Commit**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git add tests/path/test.py src/path/file.py
|
|
86
|
+
git commit -m "feat: add specific feature"
|
|
87
|
+
```
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Remember
|
|
91
|
+
- Exact file paths always
|
|
92
|
+
- Complete code in plan (not "add validation")
|
|
93
|
+
- Exact commands with expected output
|
|
94
|
+
- Reference relevant skills with @ syntax
|
|
95
|
+
- DRY, YAGNI, TDD, frequent commits
|
|
96
|
+
|
|
97
|
+
## Execution Handoff
|
|
98
|
+
|
|
99
|
+
After saving the plan, offer execution choice:
|
|
100
|
+
|
|
101
|
+
**"Plan complete and saved to `docs/plans/<filename>.md`. Two execution options:**
|
|
102
|
+
|
|
103
|
+
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
|
|
104
|
+
|
|
105
|
+
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
|
|
106
|
+
|
|
107
|
+
**Which approach?"**
|
|
108
|
+
|
|
109
|
+
**If Subagent-Driven chosen:**
|
|
110
|
+
- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
|
|
111
|
+
- Stay in this session
|
|
112
|
+
- Fresh subagent per task + code review
|
|
113
|
+
|
|
114
|
+
**If Parallel Session chosen:**
|
|
115
|
+
- Guide them to open new session in worktree
|
|
116
|
+
- **REQUIRED SUB-SKILL:** New session uses superpowers:executing-plans
|
klaude_code/tui/commands.py
CHANGED
|
@@ -183,3 +183,18 @@ class TaskClockClear(RenderCommand):
|
|
|
183
183
|
class RenderCompactionSummary(RenderCommand):
|
|
184
184
|
summary: str
|
|
185
185
|
kept_items_brief: tuple[tuple[str, int, str], ...] = () # (item_type, count, preview)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass(frozen=True, slots=True)
|
|
189
|
+
class RenderBacktrack(RenderCommand):
|
|
190
|
+
checkpoint_id: int
|
|
191
|
+
note: str
|
|
192
|
+
rationale: str
|
|
193
|
+
original_user_message: str
|
|
194
|
+
messages_discarded: int | None = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(frozen=True, slots=True)
|
|
198
|
+
class UpdateTerminalTitlePrefix(RenderCommand):
|
|
199
|
+
prefix: str | None
|
|
200
|
+
model_name: str | None
|
|
@@ -6,7 +6,7 @@ from klaude_code.tui.components.common import create_grid
|
|
|
6
6
|
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
7
7
|
from klaude_code.tui.components.tools import render_path
|
|
8
8
|
|
|
9
|
-
REMINDER_BULLET = "
|
|
9
|
+
REMINDER_BULLET = "+"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import math
|
|
5
|
-
import random
|
|
6
5
|
import time
|
|
7
6
|
from collections.abc import Callable
|
|
8
7
|
|
|
@@ -21,23 +20,14 @@ from klaude_code.const import (
|
|
|
21
20
|
STATUS_HINT_TEXT,
|
|
22
21
|
STATUS_SHIMMER_ALPHA_SCALE,
|
|
23
22
|
STATUS_SHIMMER_BAND_HALF_WIDTH,
|
|
23
|
+
STATUS_SHIMMER_ENABLED,
|
|
24
24
|
STATUS_SHIMMER_PADDING,
|
|
25
25
|
)
|
|
26
26
|
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
27
|
-
from klaude_code.tui.terminal.color import get_last_terminal_background_rgb
|
|
28
27
|
|
|
29
28
|
# Use an existing Rich spinner name; BreathingSpinner overrides its rendering
|
|
30
29
|
BREATHING_SPINNER_NAME = "dots"
|
|
31
30
|
|
|
32
|
-
# Alternating glyphs for the breathing spinner - switches at each "transparent" point
|
|
33
|
-
_BREATHING_SPINNER_GLYPHS_BASE = [
|
|
34
|
-
"✦",
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
# Shuffle glyphs on module load for variety across sessions
|
|
38
|
-
BREATHING_SPINNER_GLYPHS = _BREATHING_SPINNER_GLYPHS_BASE.copy()
|
|
39
|
-
random.shuffle(BREATHING_SPINNER_GLYPHS)
|
|
40
|
-
|
|
41
31
|
|
|
42
32
|
_process_start: float | None = None
|
|
43
33
|
_task_start: float | None = None
|
|
@@ -158,6 +148,9 @@ def _shimmer_profile(main_text: str) -> list[tuple[str, float]]:
|
|
|
158
148
|
if not chars:
|
|
159
149
|
return []
|
|
160
150
|
|
|
151
|
+
if not STATUS_SHIMMER_ENABLED:
|
|
152
|
+
return [(ch, 0.0) for ch in chars]
|
|
153
|
+
|
|
161
154
|
padding = STATUS_SHIMMER_PADDING
|
|
162
155
|
char_count = len(chars)
|
|
163
156
|
period = char_count + padding * 2
|
|
@@ -211,58 +204,6 @@ def _shimmer_style(console: Console, base_style: Style, intensity: float) -> Sty
|
|
|
211
204
|
return base_style + Style(color=shimmer_color)
|
|
212
205
|
|
|
213
206
|
|
|
214
|
-
def _breathing_intensity() -> float:
|
|
215
|
-
"""Compute breathing intensity in [0, 1] for the spinner.
|
|
216
|
-
|
|
217
|
-
Intensity follows a smooth cosine curve over the configured period, starting
|
|
218
|
-
from 0 (fully blended into background), rising to 1 (full style color),
|
|
219
|
-
then returning to 0, giving a subtle "breathing" effect.
|
|
220
|
-
"""
|
|
221
|
-
|
|
222
|
-
period = max(SPINNER_BREATH_PERIOD_SECONDS, 0.1)
|
|
223
|
-
elapsed = _elapsed_since_start()
|
|
224
|
-
phase = (elapsed % period) / period
|
|
225
|
-
return 0.5 * (1.0 - math.cos(2.0 * math.pi * phase))
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def _breathing_glyph() -> str:
|
|
229
|
-
"""Get the current glyph for the breathing spinner.
|
|
230
|
-
|
|
231
|
-
Alternates between glyphs at each breath cycle (when intensity reaches 0).
|
|
232
|
-
"""
|
|
233
|
-
period = max(SPINNER_BREATH_PERIOD_SECONDS, 0.1)
|
|
234
|
-
elapsed = _elapsed_since_start()
|
|
235
|
-
cycle = int(elapsed / period)
|
|
236
|
-
return BREATHING_SPINNER_GLYPHS[cycle % len(BREATHING_SPINNER_GLYPHS)]
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def _breathing_style(console: Console, base_style: Style, intensity: float) -> Style:
|
|
240
|
-
"""Blend a base style's foreground color toward terminal background.
|
|
241
|
-
|
|
242
|
-
When intensity is 0, the color matches the background (effectively
|
|
243
|
-
"transparent"); when intensity is 1, the color is the base style color.
|
|
244
|
-
"""
|
|
245
|
-
|
|
246
|
-
base_color = base_style.color or Color.default()
|
|
247
|
-
base_triplet = base_color.get_truecolor()
|
|
248
|
-
base_r, base_g, base_b = base_triplet
|
|
249
|
-
|
|
250
|
-
cached_bg = get_last_terminal_background_rgb()
|
|
251
|
-
if cached_bg is not None:
|
|
252
|
-
bg_r, bg_g, bg_b = cached_bg
|
|
253
|
-
else:
|
|
254
|
-
bg_triplet = Color.default().get_truecolor(foreground=False)
|
|
255
|
-
bg_r, bg_g, bg_b = bg_triplet
|
|
256
|
-
|
|
257
|
-
intensity_clamped = max(0.0, min(1.0, intensity))
|
|
258
|
-
r = int(bg_r * (1.0 - intensity_clamped) + base_r * intensity_clamped)
|
|
259
|
-
g = int(bg_g * (1.0 - intensity_clamped) + base_g * intensity_clamped)
|
|
260
|
-
b = int(bg_b * (1.0 - intensity_clamped) + base_b * intensity_clamped)
|
|
261
|
-
|
|
262
|
-
breathing_color = Color.from_rgb(r, g, b)
|
|
263
|
-
return base_style + Style(color=breathing_color)
|
|
264
|
-
|
|
265
|
-
|
|
266
207
|
def truncate_left(text: Text, max_cells: int, *, console: Console, ellipsis: str = "…") -> Text:
|
|
267
208
|
"""Left-truncate Text to fit within max_cells.
|
|
268
209
|
|
|
@@ -409,21 +350,11 @@ class BreathingSpinner(RichSpinner):
|
|
|
409
350
|
return console.get_style(style_name)
|
|
410
351
|
|
|
411
352
|
def _render_breathing(self, console: Console) -> RenderableType:
|
|
412
|
-
base_style = self._resolve_base_style(console)
|
|
413
|
-
intensity = _breathing_intensity()
|
|
414
|
-
style = _breathing_style(console, base_style, intensity)
|
|
415
|
-
|
|
416
|
-
glyph = _breathing_glyph()
|
|
417
|
-
frame = Text(glyph, style=style)
|
|
418
|
-
|
|
419
353
|
if not self.text:
|
|
420
|
-
return
|
|
354
|
+
return Text()
|
|
421
355
|
if isinstance(self.text, (str, Text)):
|
|
422
|
-
return
|
|
423
|
-
|
|
424
|
-
table = Table.grid(padding=1)
|
|
425
|
-
table.add_row(frame, self.text)
|
|
426
|
-
return table
|
|
356
|
+
return self.text if isinstance(self.text, Text) else Text(self.text)
|
|
357
|
+
return self.text
|
|
427
358
|
|
|
428
359
|
|
|
429
360
|
# Monkey-patch Rich's Status module to use the breathing spinner implementation
|
|
@@ -189,6 +189,11 @@ class ThemeKey(str, Enum):
|
|
|
189
189
|
THINKING_BOLD = "thinking.bold"
|
|
190
190
|
# COMPACTION
|
|
191
191
|
COMPACTION_SUMMARY = "compaction.summary"
|
|
192
|
+
# BACKTRACK
|
|
193
|
+
BACKTRACK = "backtrack"
|
|
194
|
+
BACKTRACK_INFO = "backtrack.info"
|
|
195
|
+
BACKTRACK_USER_MESSAGE = "backtrack.user_message"
|
|
196
|
+
BACKTRACK_NOTE = "backtrack.note"
|
|
192
197
|
# TODO_ITEM
|
|
193
198
|
TODO_EXPLANATION = "todo.explanation"
|
|
194
199
|
TODO_PENDING_MARK = "todo.pending.mark"
|
|
@@ -311,6 +316,11 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
311
316
|
ThemeKey.THINKING_BOLD.value: "italic " + palette.grey1,
|
|
312
317
|
# COMPACTION
|
|
313
318
|
ThemeKey.COMPACTION_SUMMARY.value: palette.grey1,
|
|
319
|
+
# BACKTRACK
|
|
320
|
+
ThemeKey.BACKTRACK.value: palette.orange,
|
|
321
|
+
ThemeKey.BACKTRACK_INFO.value: "dim " + palette.grey2,
|
|
322
|
+
ThemeKey.BACKTRACK_USER_MESSAGE.value: palette.cyan,
|
|
323
|
+
ThemeKey.BACKTRACK_NOTE.value: palette.grey1,
|
|
314
324
|
# TODO_ITEM
|
|
315
325
|
ThemeKey.TODO_EXPLANATION.value: palette.grey1 + " italic",
|
|
316
326
|
ThemeKey.TODO_PENDING_MARK.value: "bold " + palette.grey1,
|
|
@@ -10,7 +10,6 @@ from rich.style import Style
|
|
|
10
10
|
from rich.text import Text
|
|
11
11
|
|
|
12
12
|
from klaude_code.const import (
|
|
13
|
-
BASH_OUTPUT_PANEL_THRESHOLD,
|
|
14
13
|
DIFF_PREFIX_WIDTH,
|
|
15
14
|
INVALID_TOOL_CALL_MAX_LENGTH,
|
|
16
15
|
QUERY_DISPLAY_TRUNCATE_LENGTH,
|
|
@@ -24,7 +23,6 @@ from klaude_code.tui.components import diffs as r_diffs
|
|
|
24
23
|
from klaude_code.tui.components import mermaid_viewer as r_mermaid_viewer
|
|
25
24
|
from klaude_code.tui.components.bash_syntax import highlight_bash_command
|
|
26
25
|
from klaude_code.tui.components.common import create_grid, truncate_middle
|
|
27
|
-
from klaude_code.tui.components.rich.code_panel import CodePanel
|
|
28
26
|
from klaude_code.tui.components.rich.markdown import NoInsetMarkdown
|
|
29
27
|
from klaude_code.tui.components.rich.quote import TreeQuote
|
|
30
28
|
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
@@ -40,6 +38,7 @@ MARK_MERMAID = "⧉"
|
|
|
40
38
|
MARK_WEB_FETCH = "→"
|
|
41
39
|
MARK_WEB_SEARCH = "✱"
|
|
42
40
|
MARK_DONE = "✔"
|
|
41
|
+
MARK_BACKTRACK = "↶"
|
|
43
42
|
|
|
44
43
|
# Todo status markers
|
|
45
44
|
MARK_TODO_PENDING = "▢"
|
|
@@ -168,22 +167,6 @@ def render_bash_tool_call(arguments: str) -> RenderableType:
|
|
|
168
167
|
cmd_str = command.strip()
|
|
169
168
|
highlighted = highlight_bash_command(cmd_str)
|
|
170
169
|
|
|
171
|
-
display_line_count = len(highlighted.plain.splitlines())
|
|
172
|
-
|
|
173
|
-
if display_line_count > BASH_OUTPUT_PANEL_THRESHOLD:
|
|
174
|
-
code_panel = CodePanel(highlighted, border_style=ThemeKey.LINES)
|
|
175
|
-
if isinstance(timeout_ms, int):
|
|
176
|
-
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
177
|
-
timeout_text = Text(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
178
|
-
else:
|
|
179
|
-
timeout_text = Text(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
180
|
-
return _render_tool_call_tree(
|
|
181
|
-
mark=MARK_BASH,
|
|
182
|
-
tool_name=tool_name,
|
|
183
|
-
details=Group(code_panel, timeout_text),
|
|
184
|
-
)
|
|
185
|
-
else:
|
|
186
|
-
return _render_tool_call_tree(mark=MARK_BASH, tool_name=tool_name, details=code_panel)
|
|
187
170
|
if isinstance(timeout_ms, int):
|
|
188
171
|
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
189
172
|
highlighted.append(f" {timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
@@ -552,6 +535,33 @@ def render_report_back_tool_call() -> RenderableType:
|
|
|
552
535
|
return _render_tool_call_tree(mark=MARK_DONE, tool_name="Report Back", details=None)
|
|
553
536
|
|
|
554
537
|
|
|
538
|
+
def render_backtrack_tool_call(arguments: str) -> RenderableType:
|
|
539
|
+
tool_name = "Backtrack"
|
|
540
|
+
|
|
541
|
+
try:
|
|
542
|
+
payload: dict[str, Any] = json.loads(arguments)
|
|
543
|
+
except json.JSONDecodeError:
|
|
544
|
+
details = Text(
|
|
545
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
546
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
547
|
+
)
|
|
548
|
+
return _render_tool_call_tree(mark=MARK_BACKTRACK, tool_name=tool_name, details=details)
|
|
549
|
+
|
|
550
|
+
checkpoint_id = payload.get("checkpoint_id")
|
|
551
|
+
rationale = payload.get("rationale", "")
|
|
552
|
+
|
|
553
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
554
|
+
if isinstance(checkpoint_id, int):
|
|
555
|
+
summary.append(f"Checkpoint {checkpoint_id}", ThemeKey.TOOL_PARAM_BOLD)
|
|
556
|
+
if rationale:
|
|
557
|
+
rationale_preview = rationale if len(rationale) <= 50 else rationale[:47] + "..."
|
|
558
|
+
if summary.plain:
|
|
559
|
+
summary.append(" - ")
|
|
560
|
+
summary.append(rationale_preview, ThemeKey.TOOL_PARAM)
|
|
561
|
+
|
|
562
|
+
return _render_tool_call_tree(mark=MARK_BACKTRACK, tool_name=tool_name, details=summary if summary.plain else None)
|
|
563
|
+
|
|
564
|
+
|
|
555
565
|
# Tool name to active form mapping (for spinner status)
|
|
556
566
|
_TOOL_ACTIVE_FORM: dict[str, str] = {
|
|
557
567
|
tools.BASH: "Bashing",
|
|
@@ -567,6 +577,7 @@ _TOOL_ACTIVE_FORM: dict[str, str] = {
|
|
|
567
577
|
tools.REPORT_BACK: "Reporting",
|
|
568
578
|
tools.IMAGE_GEN: "Generating Image",
|
|
569
579
|
tools.TASK: "Spawning Task",
|
|
580
|
+
tools.BACKTRACK: "Backtracking",
|
|
570
581
|
}
|
|
571
582
|
|
|
572
583
|
|
|
@@ -609,6 +620,8 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
|
|
|
609
620
|
return render_mermaid_tool_call(e.arguments)
|
|
610
621
|
case tools.REPORT_BACK:
|
|
611
622
|
return render_report_back_tool_call()
|
|
623
|
+
case tools.BACKTRACK:
|
|
624
|
+
return render_backtrack_tool_call(e.arguments)
|
|
612
625
|
case tools.WEB_FETCH:
|
|
613
626
|
return render_web_fetch_tool_call(e.arguments)
|
|
614
627
|
case tools.WEB_SEARCH:
|
klaude_code/tui/display.py
CHANGED
|
@@ -101,3 +101,7 @@ class TUIDisplay(DisplayABC):
|
|
|
101
101
|
self._renderer.spinner_stop()
|
|
102
102
|
with contextlib.suppress(Exception):
|
|
103
103
|
self._renderer.stop_bottom_live()
|
|
104
|
+
|
|
105
|
+
def set_model_name(self, model_name: str | None) -> None:
|
|
106
|
+
"""Set model name for terminal title updates."""
|
|
107
|
+
self._machine.set_model_name(model_name)
|
|
@@ -448,6 +448,9 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
448
448
|
# Keep a comfortable multiline editing area even when no completion
|
|
449
449
|
# space is reserved. (We set reserve_space_for_menu=0 to avoid the
|
|
450
450
|
# bottom toolbar jumping when completions open/close.)
|
|
451
|
+
#
|
|
452
|
+
# Also allow the input area to grow with content so that large multi-line
|
|
453
|
+
# inputs expand the prompt instead of scrolling within a fixed-height window.
|
|
451
454
|
base_rows = 10
|
|
452
455
|
|
|
453
456
|
def _height(): # type: ignore[no-untyped-def]
|
|
@@ -465,7 +468,18 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
465
468
|
elif isinstance(original_height_value, int):
|
|
466
469
|
original_min = int(original_height_value)
|
|
467
470
|
|
|
468
|
-
|
|
471
|
+
try:
|
|
472
|
+
buffer_line_count = int(self._session.default_buffer.document.line_count)
|
|
473
|
+
except Exception:
|
|
474
|
+
buffer_line_count = 1
|
|
475
|
+
|
|
476
|
+
# Grow with content (based on newline count), but keep a sensible minimum.
|
|
477
|
+
content_rows = max(1, buffer_line_count)
|
|
478
|
+
target_rows = max(base_rows, content_rows)
|
|
479
|
+
|
|
480
|
+
# When a picker overlay is open, keep enough height for it to be usable.
|
|
481
|
+
if picker_open:
|
|
482
|
+
target_rows = max(target_rows, 24)
|
|
469
483
|
|
|
470
484
|
# Cap to the current terminal size.
|
|
471
485
|
# Leave a small buffer to avoid triggering "Window too small".
|
klaude_code/tui/machine.py
CHANGED
|
@@ -24,6 +24,7 @@ from klaude_code.tui.commands import (
|
|
|
24
24
|
EndThinkingStream,
|
|
25
25
|
PrintBlankLine,
|
|
26
26
|
RenderAssistantImage,
|
|
27
|
+
RenderBacktrack,
|
|
27
28
|
RenderBashCommandEnd,
|
|
28
29
|
RenderBashCommandStart,
|
|
29
30
|
RenderCommand,
|
|
@@ -47,6 +48,7 @@ from klaude_code.tui.commands import (
|
|
|
47
48
|
StartThinkingStream,
|
|
48
49
|
TaskClockClear,
|
|
49
50
|
TaskClockStart,
|
|
51
|
+
UpdateTerminalTitlePrefix,
|
|
50
52
|
)
|
|
51
53
|
from klaude_code.tui.components.rich import status as r_status
|
|
52
54
|
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
@@ -65,6 +67,7 @@ FAST_TOOLS: frozenset[str] = frozenset(
|
|
|
65
67
|
tools.UPDATE_PLAN,
|
|
66
68
|
tools.APPLY_PATCH,
|
|
67
69
|
tools.REPORT_BACK,
|
|
70
|
+
tools.BACKTRACK,
|
|
68
71
|
}
|
|
69
72
|
)
|
|
70
73
|
|
|
@@ -135,7 +138,7 @@ class ActivityState:
|
|
|
135
138
|
for name, count in counts.items():
|
|
136
139
|
if not first:
|
|
137
140
|
activity_text.append(", ")
|
|
138
|
-
activity_text.append(Text(name, style=ThemeKey.
|
|
141
|
+
activity_text.append(Text(name, style=ThemeKey.STATUS_TEXT))
|
|
139
142
|
if count > 1:
|
|
140
143
|
activity_text.append(f" x {count}")
|
|
141
144
|
first = False
|
|
@@ -238,24 +241,21 @@ class SpinnerStatusState:
|
|
|
238
241
|
|
|
239
242
|
if extra_reasoning is not None:
|
|
240
243
|
if activity_text is None:
|
|
241
|
-
activity_text = Text(extra_reasoning, style=ThemeKey.
|
|
244
|
+
activity_text = Text(extra_reasoning, style=ThemeKey.STATUS_TEXT)
|
|
242
245
|
else:
|
|
243
|
-
prefixed = Text(extra_reasoning, style=ThemeKey.
|
|
246
|
+
prefixed = Text(extra_reasoning, style=ThemeKey.STATUS_TEXT)
|
|
244
247
|
prefixed.append(" , ")
|
|
245
248
|
prefixed.append_text(activity_text)
|
|
246
249
|
activity_text = prefixed
|
|
247
250
|
|
|
248
251
|
if base_status:
|
|
249
|
-
# Default "Thinking ..." uses normal style; custom headers use bold italic
|
|
250
|
-
is_default_reasoning = base_status in {STATUS_THINKING_TEXT, STATUS_RUNNING_TEXT}
|
|
251
|
-
status_style = ThemeKey.STATUS_TEXT if is_default_reasoning else ThemeKey.STATUS_TEXT_BOLD_ITALIC
|
|
252
252
|
if activity_text:
|
|
253
253
|
result = Text()
|
|
254
|
-
result.append(base_status, style=
|
|
254
|
+
result.append(base_status, style=ThemeKey.STATUS_TEXT)
|
|
255
255
|
result.append(" | ")
|
|
256
256
|
result.append_text(activity_text)
|
|
257
257
|
else:
|
|
258
|
-
result = Text(base_status, style=
|
|
258
|
+
result = Text(base_status, style=ThemeKey.STATUS_TEXT)
|
|
259
259
|
elif activity_text:
|
|
260
260
|
activity_text.append(" …")
|
|
261
261
|
result = activity_text
|
|
@@ -323,6 +323,10 @@ class DisplayStateMachine:
|
|
|
323
323
|
self._sessions: dict[str, _SessionState] = {}
|
|
324
324
|
self._primary_session_id: str | None = None
|
|
325
325
|
self._spinner = SpinnerStatusState()
|
|
326
|
+
self._model_name: str | None = None
|
|
327
|
+
|
|
328
|
+
def set_model_name(self, model_name: str | None) -> None:
|
|
329
|
+
self._model_name = model_name
|
|
326
330
|
|
|
327
331
|
def _reset_sessions(self) -> None:
|
|
328
332
|
self._sessions = {}
|
|
@@ -442,6 +446,7 @@ class DisplayStateMachine:
|
|
|
442
446
|
self._primary_session_id = e.session_id
|
|
443
447
|
if not is_replay:
|
|
444
448
|
cmds.append(TaskClockStart())
|
|
449
|
+
cmds.append(UpdateTerminalTitlePrefix(prefix="\u26ac", model_name=self._model_name))
|
|
445
450
|
|
|
446
451
|
if not is_replay:
|
|
447
452
|
cmds.append(SpinnerStart())
|
|
@@ -469,6 +474,18 @@ class DisplayStateMachine:
|
|
|
469
474
|
cmds.append(RenderCompactionSummary(summary=e.summary, kept_items_brief=kept_brief))
|
|
470
475
|
return cmds
|
|
471
476
|
|
|
477
|
+
case events.BacktrackEvent() as e:
|
|
478
|
+
cmds.append(
|
|
479
|
+
RenderBacktrack(
|
|
480
|
+
checkpoint_id=e.checkpoint_id,
|
|
481
|
+
note=e.note,
|
|
482
|
+
rationale=e.rationale,
|
|
483
|
+
original_user_message=e.original_user_message,
|
|
484
|
+
messages_discarded=e.messages_discarded,
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
return cmds
|
|
488
|
+
|
|
472
489
|
case events.DeveloperMessageEvent() as e:
|
|
473
490
|
cmds.append(RenderDeveloperMessage(e))
|
|
474
491
|
return cmds
|
|
@@ -747,6 +764,7 @@ class DisplayStateMachine:
|
|
|
747
764
|
self._spinner.reset()
|
|
748
765
|
cmds.append(SpinnerStop())
|
|
749
766
|
cmds.append(EmitTmuxSignal())
|
|
767
|
+
cmds.append(UpdateTerminalTitlePrefix(prefix="\u2714", model_name=self._model_name))
|
|
750
768
|
return cmds
|
|
751
769
|
|
|
752
770
|
case events.InterruptEvent() as e:
|