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.
Files changed (96) hide show
  1. monkeybot_cli-0.12.0/.gitignore +93 -0
  2. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/PKG-INFO +1 -1
  3. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/pyproject.toml +7 -1
  4. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/skills/monkeybot/SKILL.md +1 -1
  5. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/skills/monkeybot/references/config-sections.md +3 -1
  6. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_tui.py +91 -19
  7. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_tui_widgets.py +210 -52
  8. monkeybot_cli-0.12.0/src/monkeybot_cli/overrides.txt +5 -0
  9. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/runtime_python.py +161 -50
  10. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold.py +2 -0
  11. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +1 -1
  12. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_tui.py +99 -3
  13. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_runtime_python.py +170 -15
  14. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_scaffold.py +1 -0
  15. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/uv.lock +200 -179
  16. monkeybot_cli-0.11.0/.gitignore +0 -75
  17. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/__init__.py +0 -0
  18. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_file_index.py +0 -0
  19. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_local_shell.py +0 -0
  20. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_renderer.py +0 -0
  21. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_session.py +0 -0
  22. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_status_bar.py +0 -0
  23. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_theme.py +0 -0
  24. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/chat_tool_display.py +0 -0
  25. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/__init__.py +0 -0
  26. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/chat.py +0 -0
  27. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/doctor.py +0 -0
  28. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/loop.py +0 -0
  29. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/mcp_login.py +0 -0
  30. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/new.py +0 -0
  31. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/refresh.py +0 -0
  32. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/run_cmd.py +0 -0
  33. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/talk.py +0 -0
  34. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/trace.py +0 -0
  35. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/commands/validate.py +0 -0
  36. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/compat.py +0 -0
  37. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/config_resolve.py +0 -0
  38. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/exit_commands.py +0 -0
  39. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/extras_catalog.py +0 -0
  40. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/gateway_health.py +0 -0
  41. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/main.py +0 -0
  42. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
  43. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/output.py +0 -0
  44. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/process_tree.py +0 -0
  45. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/providers.py +0 -0
  46. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/__init__.py +0 -0
  47. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/audio_io.py +0 -0
  48. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/client.py +0 -0
  49. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
  50. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
  51. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/session.py +0 -0
  52. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/session_controller.py +0 -0
  53. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
  54. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
  55. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
  56. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
  57. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
  58. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
  59. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +0 -0
  60. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
  61. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
  62. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -0
  63. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +0 -0
  64. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
  65. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
  66. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
  67. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
  68. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
  69. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
  70. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +0 -0
  71. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +0 -0
  72. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +0 -0
  73. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +0 -0
  74. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/session_controller.py +0 -0
  75. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/src/monkeybot_cli/terminal_markdown.py +0 -0
  76. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_continue.py +0 -0
  77. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_e2e.py +0 -0
  78. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_errors.py +0 -0
  79. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_file_index.py +0 -0
  80. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_grounding.py +0 -0
  81. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_local_shell.py +0 -0
  82. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_renderer_parity.py +0 -0
  83. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_session.py +0 -0
  84. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_status_bar.py +0 -0
  85. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_theme.py +0 -0
  86. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_chat_tool_display.py +0 -0
  87. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_cli.py +0 -0
  88. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_config_resolve.py +0 -0
  89. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_doctor.py +0 -0
  90. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_extras_catalog.py +0 -0
  91. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_gateway_health.py +0 -0
  92. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_opensandbox_lifecycle.py +0 -0
  93. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_realtime_session_controller.py +0 -0
  94. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_run_cmd.py +0 -0
  95. {monkeybot_cli-0.11.0 → monkeybot_cli-0.12.0}/tests/test_terminal_markdown.py +0 -0
  96. {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/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: monkeybot-cli
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: CLI to create, configure, validate, and chat with monkeybot agents.
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "monkeybot-cli"
7
- version = "0.11.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
@@ -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` | Match the embedder the palace was built with |
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
- parts.append("working · Esc interrupt")
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
- if self._thinking_trace is not None and self._thinking_trace.is_attached:
678
- if not self._thinking_trace.has_class("-done"):
679
- self._thinking_trace.finish()
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
- self._show_thinking(str(p.get("text") or "thinking…"))
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._show_thinking("thinking…")
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._show_thinking(f"summarizing context ({tokens:,} tokens)")
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._show_thinking(f"summarized {turns} {noun}")
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
- lines = ["**grounded search**"]
1041
- if queries:
1042
- q = ", ".join(f'"{q}"' for q in queries)
1043
- lines[0] += f" — {q}"
1044
- for source in (p.get("sources") or [])[:5]:
1045
- if not isinstance(source, dict):
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
- self._mount(GroundingBlock("\n".join(lines)))
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, p: dict) -> None:
1161
- # Legacy --show-thinking path; prefer thinking_block_* when present.
1162
- self._show_thinking(f"thinking {p.get('text')}")
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()