monkeybot-cli 0.11.0__tar.gz → 0.12.0__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.
- monkeybot_cli-0.12.0/.gitignore +93 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/PKG-INFO +1 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/pyproject.toml +7 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/skills/monkeybot/SKILL.md +1 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/skills/monkeybot/references/config-sections.md +3 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_tui.py +91 -19
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_tui_widgets.py +210 -52
- monkeybot_cli-0.12.0/src/monkeybot_cli/overrides.txt +5 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/runtime_python.py +161 -50
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold.py +2 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +1 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_tui.py +99 -3
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_runtime_python.py +170 -15
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_scaffold.py +1 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/uv.lock +200 -179
- monkeybot_cli-0.11.0/.gitignore +0 -75
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_file_index.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_local_shell.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_renderer.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_session.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_status_bar.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_theme.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_tool_display.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/chat.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/doctor.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/loop.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/mcp_login.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/new.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/refresh.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/run_cmd.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/talk.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/trace.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/validate.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/compat.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/config_resolve.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/exit_commands.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/extras_catalog.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/gateway_health.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/main.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/output.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/process_tree.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/providers.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/audio_io.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/client.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/session.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/session_controller.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/session_controller.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/terminal_markdown.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_continue.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_e2e.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_errors.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_file_index.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_grounding.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_local_shell.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_renderer_parity.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_session.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_status_bar.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_theme.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_tool_display.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_cli.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_config_resolve.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_doctor.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_extras_catalog.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_gateway_health.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_realtime_session_controller.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_run_cmd.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_terminal_markdown.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_trace_cmd.py +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Internal (not shipped in OSS)
|
|
2
|
+
internal/
|
|
3
|
+
|
|
4
|
+
# Environment
|
|
5
|
+
.env
|
|
6
|
+
.env.*
|
|
7
|
+
!.env.example
|
|
8
|
+
# The grants fixture's .env is fixture data, not a local secret.
|
|
9
|
+
!regression/fixtures/workspaces/grants_ws/granted/.env
|
|
10
|
+
|
|
11
|
+
# Local scaffolded harness config (defaults ship in monkeybot-cli scaffold_defaults/)
|
|
12
|
+
/monkeybot_config/
|
|
13
|
+
|
|
14
|
+
# Local agent for manual CLI testing. Recreate with `monkeybot new --dest ./cli-agent`.
|
|
15
|
+
/cli-agent/
|
|
16
|
+
|
|
17
|
+
# Live eval run artifacts. Baselines under regression/t4/baselines/ stay versioned.
|
|
18
|
+
/regression/t4/runs/
|
|
19
|
+
# Live-eval agent project local state. Live secrets come from the repo-root .env; a stray key
|
|
20
|
+
# file in the agent project stays out of git. The two workspace .gitkeep files need git add -f.
|
|
21
|
+
/regression/t4/agent/.env
|
|
22
|
+
/regression/t4/agent/data/
|
|
23
|
+
/regression/t4/agent/workspace/
|
|
24
|
+
|
|
25
|
+
# Python
|
|
26
|
+
__pycache__/
|
|
27
|
+
*.py[cod]
|
|
28
|
+
*.pyo
|
|
29
|
+
*.pyd
|
|
30
|
+
.Python
|
|
31
|
+
*.egg-info/
|
|
32
|
+
dist/
|
|
33
|
+
build/
|
|
34
|
+
.eggs/
|
|
35
|
+
*.egg
|
|
36
|
+
|
|
37
|
+
# uv
|
|
38
|
+
.venv/
|
|
39
|
+
.uv/
|
|
40
|
+
|
|
41
|
+
# Data (SQLite DB + memory files — never commit)
|
|
42
|
+
data/
|
|
43
|
+
# Shared attachment fixtures live here (§5.4); they are not a local data dir.
|
|
44
|
+
!testkit/data/
|
|
45
|
+
!testkit/data/**
|
|
46
|
+
/memory/
|
|
47
|
+
.monkeybot/
|
|
48
|
+
# Store layout for the attachment loop fixture. The directory name is ignored above.
|
|
49
|
+
!regression/fixtures/workspaces/att_ws/.monkeybot/
|
|
50
|
+
!regression/fixtures/workspaces/att_ws/.monkeybot/**
|
|
51
|
+
*.sqlite3
|
|
52
|
+
workspace/.monkeybot/
|
|
53
|
+
*.sqlite
|
|
54
|
+
*.sqlite.writer-pid
|
|
55
|
+
|
|
56
|
+
# Testing & coverage
|
|
57
|
+
/test-runs/
|
|
58
|
+
/.cursor/hooks/.state/
|
|
59
|
+
.pytest_cache/
|
|
60
|
+
.coverage
|
|
61
|
+
htmlcov/
|
|
62
|
+
.mypy_cache/
|
|
63
|
+
.ruff_cache/
|
|
64
|
+
.deepeval/
|
|
65
|
+
|
|
66
|
+
# macOS
|
|
67
|
+
.DS_Store
|
|
68
|
+
|
|
69
|
+
# IDEs
|
|
70
|
+
.idea/
|
|
71
|
+
.vscode/
|
|
72
|
+
*.swp
|
|
73
|
+
*.swo
|
|
74
|
+
|
|
75
|
+
# Docker
|
|
76
|
+
docker/data/
|
|
77
|
+
|
|
78
|
+
# Planning Documents
|
|
79
|
+
.prt/
|
|
80
|
+
.monkeymode/
|
|
81
|
+
|
|
82
|
+
# Reference clones (read-only)
|
|
83
|
+
code/
|
|
84
|
+
|
|
85
|
+
# Local venv for evals dependency experiments
|
|
86
|
+
.evals-venv/
|
|
87
|
+
|
|
88
|
+
# monkeythink
|
|
89
|
+
.monkeythink/
|
|
90
|
+
# Eval fixtures ship their own build/ decoy
|
|
91
|
+
!/regression/fixtures/workspaces/calc_repo/build/
|
|
92
|
+
!/regression/fixtures/workspaces/calc_repo_green/build/
|
|
93
|
+
!/regression/fixtures/workspaces/shop_repo/build/
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "monkeybot-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.12.0"
|
|
8
8
|
description = "CLI to create, configure, validate, and chat with monkeybot agents."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -21,6 +21,12 @@ dependencies = [
|
|
|
21
21
|
[project.scripts]
|
|
22
22
|
monkeybot = "monkeybot_cli.main:main"
|
|
23
23
|
|
|
24
|
+
[tool.uv]
|
|
25
|
+
# browser-harness 0.1.13 pins pillow==12.3.0, which is still inside the Pillow 13 advisory range.
|
|
26
|
+
# uv lock and uv sync honor this. uv tool install and uv pip install do not;
|
|
27
|
+
# those commands pass src/monkeybot_cli/overrides.txt via --overrides.
|
|
28
|
+
override-dependencies = ["pillow>=13"]
|
|
29
|
+
|
|
24
30
|
[tool.uv.sources]
|
|
25
31
|
monkeybot = { path = "..", editable = true }
|
|
26
32
|
monkeybot-browser-mcp = { path = "../integrations/browser-mcp", editable = true }
|
|
@@ -176,7 +176,7 @@ Only if the user is changing monkeybot itself (not creating an agent):
|
|
|
176
176
|
git clone https://github.com/human-plus-machine/monkeybot.git
|
|
177
177
|
cd monkeybot && uv sync
|
|
178
178
|
cd cli && uv sync
|
|
179
|
-
uv tool install --editable .
|
|
179
|
+
uv tool install --editable . --overrides src/monkeybot_cli/overrides.txt
|
|
180
180
|
```
|
|
181
181
|
|
|
182
182
|
## Rules
|
{monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/skills/monkeybot/references/config-sections.md
RENAMED
|
@@ -84,7 +84,9 @@ MemPalace capture, wake-up, and search teaching are on by default. Turn them off
|
|
|
84
84
|
|---|---|---|
|
|
85
85
|
| `enabled` | `true` | Set `false` to skip capture, wake-up, and prompt teaching |
|
|
86
86
|
| `backend` | `chroma` | Alternate MemPalace vector backend |
|
|
87
|
-
| `embedding_model` | `embeddinggemma-300m`
|
|
87
|
+
| `embedding_model` | `minilm` | MemPalace's default embedder. `embeddinggemma-300m` is a legacy alias; startup rewrites it to `minilm` |
|
|
88
|
+
|
|
89
|
+
Chats in one project share saved lines by default, including the wake-up briefing. Set `MONKEYBOT_MEMORY_SHARE_THREADS=0` to keep each chat's lines in that chat. Unset, `1`, `true`, `yes`, and `on` share.
|
|
88
90
|
|
|
89
91
|
## `subagents`
|
|
90
92
|
|
|
@@ -57,6 +57,7 @@ from monkeybot_cli.chat_tui_widgets import (
|
|
|
57
57
|
ToolCallBlock,
|
|
58
58
|
TranscriptPane,
|
|
59
59
|
UserTurn,
|
|
60
|
+
activity_label,
|
|
60
61
|
write_osc52_clipboard,
|
|
61
62
|
)
|
|
62
63
|
from monkeybot_cli.config_resolve import load_config_doc, resolve_config
|
|
@@ -360,6 +361,7 @@ class ChatApp(App[int]):
|
|
|
360
361
|
self._assistant: AssistantTurn | None = None
|
|
361
362
|
self._thinking: ThinkingLine | None = None
|
|
362
363
|
self._thinking_trace: ThinkingTrace | None = None
|
|
364
|
+
self._status_note = ""
|
|
363
365
|
self._hitl_card: HitlCard | None = None
|
|
364
366
|
self._hitl_status_flash: str | None = None
|
|
365
367
|
self._ring_text = "[dim]○ 0%[/]"
|
|
@@ -433,7 +435,10 @@ class ChatApp(App[int]):
|
|
|
433
435
|
if self._hitl_status_flash:
|
|
434
436
|
parts.append(self._hitl_status_flash)
|
|
435
437
|
if self._turn_active and not self._hitl_active:
|
|
436
|
-
|
|
438
|
+
if self._status_note:
|
|
439
|
+
parts.append(f"{self._status_note} · Esc interrupt")
|
|
440
|
+
else:
|
|
441
|
+
parts.append("working · Esc interrupt")
|
|
437
442
|
elif self._hitl_active:
|
|
438
443
|
if self._hitl_kind == "elicit":
|
|
439
444
|
parts.append("Enter submit · Ctrl-C cancel")
|
|
@@ -647,8 +652,24 @@ class ChatApp(App[int]):
|
|
|
647
652
|
transcript.mount(earlier, before=0)
|
|
648
653
|
earlier.absorb(digest)
|
|
649
654
|
|
|
655
|
+
def _mark_activity_lead(self, widget: object) -> None:
|
|
656
|
+
"""First row of a thought/tool/search stack gets the gap above it."""
|
|
657
|
+
if not isinstance(widget, (ThinkingTrace, ToolCallBlock, GroundingBlock)):
|
|
658
|
+
return
|
|
659
|
+
prev = None
|
|
660
|
+
for child in reversed(self._transcript().children):
|
|
661
|
+
if child is widget or getattr(child, "id", None) == "empty-hint":
|
|
662
|
+
continue
|
|
663
|
+
if isinstance(child, ThinkingTrace) and not child._raw.strip():
|
|
664
|
+
continue
|
|
665
|
+
prev = child
|
|
666
|
+
break
|
|
667
|
+
if not isinstance(prev, (ThinkingTrace, ToolCallBlock, GroundingBlock)):
|
|
668
|
+
widget.add_class("-lead")
|
|
669
|
+
|
|
650
670
|
def _mount(self, widget: object) -> None:
|
|
651
671
|
self._hide_empty_hint()
|
|
672
|
+
self._mark_activity_lead(widget)
|
|
652
673
|
if self._is_counted_turn(widget):
|
|
653
674
|
self._trim_old_turns()
|
|
654
675
|
self._turn_count += 1
|
|
@@ -673,10 +694,33 @@ class ChatApp(App[int]):
|
|
|
673
694
|
self._thinking.remove()
|
|
674
695
|
self._thinking = None
|
|
675
696
|
|
|
697
|
+
def _drop_empty_thinking(self) -> None:
|
|
698
|
+
trace = self._thinking_trace
|
|
699
|
+
if trace is None or not trace.is_attached or trace._raw.strip():
|
|
700
|
+
return
|
|
701
|
+
already = bool(getattr(trace, "_trimmed", False))
|
|
702
|
+
trace._trimmed = True # type: ignore[attr-defined]
|
|
703
|
+
trace.remove()
|
|
704
|
+
self._thinking_trace = None
|
|
705
|
+
if not already:
|
|
706
|
+
self._turn_count = max(0, self._turn_count - 1)
|
|
707
|
+
|
|
708
|
+
def _ensure_live_thinking(self) -> None:
|
|
709
|
+
trace = self._thinking_trace
|
|
710
|
+
if trace is not None and trace.is_attached and not trace.has_class("-done"):
|
|
711
|
+
return
|
|
712
|
+
self._clear_thinking()
|
|
713
|
+
block = ThinkingTrace()
|
|
714
|
+
self._thinking_trace = block
|
|
715
|
+
self._mount(block)
|
|
716
|
+
|
|
676
717
|
def _finish_thinking_trace(self, *, clear: bool = False) -> None:
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
718
|
+
trace = self._thinking_trace
|
|
719
|
+
if trace is not None and trace.is_attached:
|
|
720
|
+
if not trace._raw.strip():
|
|
721
|
+
self._drop_empty_thinking()
|
|
722
|
+
elif not trace.has_class("-done"):
|
|
723
|
+
trace.finish()
|
|
680
724
|
if clear:
|
|
681
725
|
self._thinking_trace = None
|
|
682
726
|
|
|
@@ -973,7 +1017,11 @@ class ChatApp(App[int]):
|
|
|
973
1017
|
self._follow_scroll()
|
|
974
1018
|
|
|
975
1019
|
def _ev_thinking(self, p: dict) -> None:
|
|
976
|
-
|
|
1020
|
+
text = str(p.get("text") or "thinking…")
|
|
1021
|
+
if text.strip().lower() in {"thinking…", "thinking...", "thinking"}:
|
|
1022
|
+
self._ensure_live_thinking()
|
|
1023
|
+
return
|
|
1024
|
+
self._show_thinking(text)
|
|
977
1025
|
|
|
978
1026
|
def _ev_thinking_clear(self, _p: dict) -> None:
|
|
979
1027
|
self._clear_thinking()
|
|
@@ -981,13 +1029,16 @@ class ChatApp(App[int]):
|
|
|
981
1029
|
def _ev_turn_started(self, _p: dict) -> None:
|
|
982
1030
|
self._turn_active = True
|
|
983
1031
|
self._assistant = None
|
|
1032
|
+
self._status_note = ""
|
|
984
1033
|
self._seal_thinking_trace()
|
|
985
1034
|
self._clear_open_tools()
|
|
986
|
-
self.
|
|
1035
|
+
self._clear_thinking()
|
|
1036
|
+
self._ensure_live_thinking()
|
|
987
1037
|
self._refresh_status()
|
|
988
1038
|
|
|
989
1039
|
def _ev_assistant_start(self, _p: dict) -> None:
|
|
990
1040
|
self._clear_thinking()
|
|
1041
|
+
self._drop_empty_thinking()
|
|
991
1042
|
|
|
992
1043
|
def _ev_assistant_delta(self, p: dict) -> None:
|
|
993
1044
|
delta = str(p.get("delta", ""))
|
|
@@ -1027,30 +1078,49 @@ class ChatApp(App[int]):
|
|
|
1027
1078
|
|
|
1028
1079
|
def _ev_summarizing(self, p: dict) -> None:
|
|
1029
1080
|
tokens = int(p.get("tokens") or 0)
|
|
1030
|
-
self.
|
|
1081
|
+
self._drop_empty_thinking()
|
|
1082
|
+
self._clear_thinking()
|
|
1083
|
+
self._status_note = f"summarizing context ({tokens:,} tokens)"
|
|
1084
|
+
self._refresh_status()
|
|
1031
1085
|
|
|
1032
1086
|
def _ev_summarized(self, p: dict) -> None:
|
|
1033
1087
|
turns = int(p.get("turns") or 0)
|
|
1034
1088
|
noun = "turn" if turns == 1 else "turns"
|
|
1035
|
-
self.
|
|
1089
|
+
self._status_note = f"summarized {turns} {noun}"
|
|
1090
|
+
self._refresh_status()
|
|
1036
1091
|
|
|
1037
1092
|
def _ev_grounding(self, p: dict) -> None:
|
|
1038
1093
|
self._clear_thinking()
|
|
1094
|
+
self._drop_empty_thinking()
|
|
1039
1095
|
queries = p.get("search_queries") or []
|
|
1040
|
-
|
|
1041
|
-
if
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
continue
|
|
1096
|
+
raw_sources = p.get("sources") or []
|
|
1097
|
+
sources = [source for source in raw_sources if isinstance(source, dict)]
|
|
1098
|
+
lines: list[str] = []
|
|
1099
|
+
shown = 0
|
|
1100
|
+
linked = 0
|
|
1101
|
+
for source in sources:
|
|
1047
1102
|
title = str(source.get("title") or "").strip()
|
|
1048
1103
|
uri = str(source.get("uri") or "").strip()
|
|
1049
1104
|
if not uri:
|
|
1050
1105
|
continue
|
|
1106
|
+
linked += 1
|
|
1107
|
+
if shown >= 5:
|
|
1108
|
+
continue
|
|
1109
|
+
shown += 1
|
|
1051
1110
|
label = title or uri
|
|
1052
1111
|
lines.append(f"- [{label}]({uri})")
|
|
1053
|
-
|
|
1112
|
+
query_text = ", ".join(str(query).strip() for query in queries if str(query).strip())
|
|
1113
|
+
if query_text and linked:
|
|
1114
|
+
noun = "source" if linked == 1 else "sources"
|
|
1115
|
+
hint = f"{query_text} · {linked} {noun}"
|
|
1116
|
+
elif query_text:
|
|
1117
|
+
hint = query_text
|
|
1118
|
+
elif linked:
|
|
1119
|
+
noun = "source" if linked == 1 else "sources"
|
|
1120
|
+
hint = f"{linked} {noun}"
|
|
1121
|
+
else:
|
|
1122
|
+
hint = ""
|
|
1123
|
+
self._mount(GroundingBlock("\n".join(lines), title=activity_label("Web", hint)))
|
|
1054
1124
|
|
|
1055
1125
|
def _ev_turn_error(self, p: dict) -> None:
|
|
1056
1126
|
self._close_assistant()
|
|
@@ -1157,9 +1227,10 @@ class ChatApp(App[int]):
|
|
|
1157
1227
|
def _ev_stream_ended(self, _p: dict) -> None:
|
|
1158
1228
|
self._exit_code = 1 if self._controller.stream_error else self._exit_code
|
|
1159
1229
|
|
|
1160
|
-
def _ev_thinking_trace(self,
|
|
1161
|
-
#
|
|
1162
|
-
|
|
1230
|
+
def _ev_thinking_trace(self, _p: dict) -> None:
|
|
1231
|
+
# thinking_block_* owns the collapsed row. This legacy event would
|
|
1232
|
+
# print the trace inline.
|
|
1233
|
+
return
|
|
1163
1234
|
|
|
1164
1235
|
def _ev_thinking_block_delta(self, p: dict) -> None:
|
|
1165
1236
|
text = str(p.get("text") or "")
|
|
@@ -1230,6 +1301,7 @@ class ChatApp(App[int]):
|
|
|
1230
1301
|
def _finish_turn_ui(self) -> None:
|
|
1231
1302
|
self._turn_active = False
|
|
1232
1303
|
self._assistant = None
|
|
1304
|
+
self._status_note = ""
|
|
1233
1305
|
self._clear_open_tools()
|
|
1234
1306
|
self._refresh_status()
|
|
1235
1307
|
self.query_one("#prompt", Composer).focus()
|