webbee 0.4.3__tar.gz → 0.4.4__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.
- {webbee-0.4.3 → webbee-0.4.4}/CHANGELOG.md +24 -0
- {webbee-0.4.3 → webbee-0.4.4}/PKG-INFO +1 -1
- {webbee-0.4.3 → webbee-0.4.4}/pyproject.toml +1 -1
- webbee-0.4.4/src/webbee/__init__.py +1 -0
- webbee-0.4.4/src/webbee/__main__.py +5 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/cli.py +4 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/frames.py +1 -1
- webbee-0.4.4/src/webbee/pricing_constants.py +68 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/render.py +61 -35
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/repl.py +3 -1
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/tui.py +9 -59
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_repl.py +7 -5
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tui.py +8 -11
- webbee-0.4.3/src/webbee/__init__.py +0 -1
- {webbee-0.4.3 → webbee-0.4.4}/.github/workflows/publish.yml +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/.gitignore +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/LICENSE +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/README.md +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/install.sh +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/account.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/active_sessions.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/banner_art.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/boot.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/checkpoints.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/clipboard.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/clipboard_read.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/clipboard_session.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/coding_context.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/commands.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/config.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/consent.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/details.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/events.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/home.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/home_view.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/http.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/instance_lock.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/chunker.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/contracts.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/embed.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/graph.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/indexer.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/models.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/query.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/service.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/intel/watch.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/media.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/modals.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/mode_store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/native_files.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/native_read.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/newtab_mode.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/output_pane.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/queue_panel.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/reflow.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/remote.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/repo.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/selection.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/session.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/sessions.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/sizing.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/slots.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/steer.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/stream.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/tab_store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/tabs.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/thread.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/tier_store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/todo_panel.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/tokens.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/tools.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/update.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/urlopen.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/wallet.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/src/webbee/worktrees.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/__init__.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/conftest.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_account.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_active_sessions.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_audit_2026_07_25_findings.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_checkpoints.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_cli.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_clipboard.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_clipboard_read.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_coding_context.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_commands.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_config.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_cross_loop_token.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_details.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_events.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_freeze_fix.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_home.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_home_view.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_instance_lock.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_chunker.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_contracts.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_embed.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_graph.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_indexer.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_query.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_service.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_vectors.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_intel_watch.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_marathon.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_modals_e2e.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_mode_store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_native_files.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_newtab_mode.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_packaging.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_queue_manage_0337.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_reflow.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_render.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_repo.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_session.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_sessions.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_sizing.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_slots.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_steer.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_stream.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tab_restore_0337.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tabs.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_terminal_ux_0336.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_thread.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tier_store.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_todo_panel.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tokens.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tools.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tools_vision_websearch.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_tui_hardening.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_update.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_update_badge_0340.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_urlopen.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_version.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_version_badge_corner.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_wallet.py +0 -0
- {webbee-0.4.3 → webbee-0.4.4}/tests/test_worktrees.py +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.4
|
|
4
|
+
|
|
5
|
+
**Webbee v0.4.4: Clean Tool Rendering, Real-Time Action Cost Estimation & Terminal Robustness.**
|
|
6
|
+
|
|
7
|
+
This release streamlines the Terminal Coding Experience (TUI) with modernized action indicators, integrates real-time credit cost previews for system and connector actions, passes rich tool invocation arguments directly to stream frames, and polishes CLI/REPL startup and execution ergonomics.
|
|
8
|
+
|
|
9
|
+
### Highlights & New Capabilities
|
|
10
|
+
|
|
11
|
+
* **Streamlined Tool & Action Rendering (`webbee.render`)**:
|
|
12
|
+
- Replaced legacy multi-step badges with unified, high-contrast action indicators (`SSH Exec`, `Read File`, `Edit File`, `Run Shell`, `Web Search`, etc.).
|
|
13
|
+
- Added dedicated styling and icons for remote connector calls (`conn-ssh`, `wordpress-hub`, `stripe-connector`, `mail`, `tasks`, etc.).
|
|
14
|
+
- Clean tool start/finish status feedback without redundant UI step wrappers.
|
|
15
|
+
|
|
16
|
+
* **Real-Time Action Cost Transparency (`webbee.pricing_constants`)**:
|
|
17
|
+
- Introduced transparent action cost estimation for external and cloud operations directly in the terminal interface.
|
|
18
|
+
- Zero-credit badge display for local disk and memory operations (`0.00 cr`), with accurate pricing for remote server execution and marketplace connectors.
|
|
19
|
+
|
|
20
|
+
* **Rich Frame Argument Propagation (`webbee.frames`)**:
|
|
21
|
+
- Enhanced `handle_step_started` to forward invocation parameters (`args`) into tool lifecycle hooks, providing instant visibility into file paths, target hosts, and search queries.
|
|
22
|
+
|
|
23
|
+
* **CLI & Module Execution Polish (`webbee.cli`, `webbee.__main__`)**:
|
|
24
|
+
- Added native `python -m webbee` execution entry point via `__main__.py`.
|
|
25
|
+
- Added direct execution guard to `cli.py` for streamlined standalone launches.
|
|
26
|
+
|
|
3
27
|
## 0.4.3
|
|
4
28
|
|
|
5
29
|
**Webbee v0.4.3: Non-Blocking Voice STT, Cross-Event-Loop Token Bridge, Dynamic Composer Geometry & Config Hardening.**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.4"
|
|
@@ -87,7 +87,7 @@ def handle_step_started(frame: dict, sink, started: set, step_labels: dict, loca
|
|
|
87
87
|
if sid:
|
|
88
88
|
step_labels[sid] = label
|
|
89
89
|
if _first_time(sid, started):
|
|
90
|
-
sink.tool_start(label, {})
|
|
90
|
+
sink.tool_start(label, frame.get("args") or {})
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def handle_step_finished(frame: dict, sink, finished: set, step_labels: dict, steps: list,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Tool pricing constants and lookup for Webbee actions."""
|
|
2
|
+
|
|
3
|
+
DEFAULT_ACTION_PRICES = {
|
|
4
|
+
# Local workspace operations (always 0.00 cr)
|
|
5
|
+
"read_file": 0.0,
|
|
6
|
+
"write_file": 0.0,
|
|
7
|
+
"edit_file": 0.0,
|
|
8
|
+
"multi_edit": 0.0,
|
|
9
|
+
"diff": 0.0,
|
|
10
|
+
"rollback": 0.0,
|
|
11
|
+
"checkpoint": 0.0,
|
|
12
|
+
"list_dir": 0.0,
|
|
13
|
+
"grep": 0.0,
|
|
14
|
+
"glob": 0.0,
|
|
15
|
+
"search_code": 0.0,
|
|
16
|
+
"graph_slice": 0.0,
|
|
17
|
+
"impact_of_change": 0.0,
|
|
18
|
+
"orient": 0.0,
|
|
19
|
+
"repo_profile": 0.0,
|
|
20
|
+
"view_image": 0.0,
|
|
21
|
+
|
|
22
|
+
# Server / remote / external connector operations
|
|
23
|
+
"conn-ssh": 0.15,
|
|
24
|
+
"bash": 0.05,
|
|
25
|
+
"shell": 0.05,
|
|
26
|
+
"web_search": 0.05,
|
|
27
|
+
"read_url": 0.02,
|
|
28
|
+
"read_url_rendered": 0.10,
|
|
29
|
+
"wordpress-hub": 0.10,
|
|
30
|
+
"stripe-connector": 0.15,
|
|
31
|
+
"google-analytics-bluebee": 0.10,
|
|
32
|
+
"mail": 0.05,
|
|
33
|
+
"tasks": 0.02,
|
|
34
|
+
"docusign-connector": 0.20,
|
|
35
|
+
"plaid-connector": 0.25,
|
|
36
|
+
"proxmox-connector": 0.15,
|
|
37
|
+
"se-ranking-connector": 0.15,
|
|
38
|
+
"bing-webmaster-connector": 0.05,
|
|
39
|
+
"google-search-console-connector": 0.05,
|
|
40
|
+
"google-drive-connector": 0.05,
|
|
41
|
+
"telegram-publisher-extension": 0.05,
|
|
42
|
+
"x-connector": 0.10,
|
|
43
|
+
"thoughts": 0.01,
|
|
44
|
+
"memory-index": 0.01,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def estimate_action_cost(tool_name: str) -> float:
|
|
48
|
+
"""Estimate credit cost for an action by tool name."""
|
|
49
|
+
if not tool_name:
|
|
50
|
+
return 0.0
|
|
51
|
+
t = tool_name.strip()
|
|
52
|
+
if t in DEFAULT_ACTION_PRICES:
|
|
53
|
+
return DEFAULT_ACTION_PRICES[t]
|
|
54
|
+
if "·" in t:
|
|
55
|
+
app, op = t.split("·", 1)
|
|
56
|
+
if app in DEFAULT_ACTION_PRICES:
|
|
57
|
+
return DEFAULT_ACTION_PRICES[app]
|
|
58
|
+
if op in DEFAULT_ACTION_PRICES:
|
|
59
|
+
return DEFAULT_ACTION_PRICES[op]
|
|
60
|
+
elif "__" in t:
|
|
61
|
+
app, op = t.split("__", 1)
|
|
62
|
+
if app in DEFAULT_ACTION_PRICES:
|
|
63
|
+
return DEFAULT_ACTION_PRICES[app]
|
|
64
|
+
if op in DEFAULT_ACTION_PRICES:
|
|
65
|
+
return DEFAULT_ACTION_PRICES[op]
|
|
66
|
+
if any(k in t for k in ("ssh", "remote", "http", "api", "conn")):
|
|
67
|
+
return 0.15
|
|
68
|
+
return 0.0
|
|
@@ -11,6 +11,7 @@ from rich.text import Text
|
|
|
11
11
|
|
|
12
12
|
from webbee.banner_art import WEBBEE_CODE
|
|
13
13
|
from webbee.sizing import trunc
|
|
14
|
+
from webbee.pricing_constants import estimate_action_cost
|
|
14
15
|
|
|
15
16
|
_GUTTER = 2 # left margin (cols) — the single consistent transcript gutter.
|
|
16
17
|
# Chrome lines prefix a 2-space string (" "); block renderables
|
|
@@ -75,6 +76,33 @@ def _clean(s) -> str:
|
|
|
75
76
|
_ICON = {"read_file": "📖", "grep": "🔎", "glob": "🗂️ ", "write_file": "✏️",
|
|
76
77
|
"edit_file": "🔧", "shell": "⚡", "bash": "⚡", "web_search": "🌐 ", "read_url": "📄",
|
|
77
78
|
"view_image": "🖼️"}
|
|
79
|
+
def _tool_meta(tool: str) -> tuple[str, str]:
|
|
80
|
+
t = (tool or "").strip()
|
|
81
|
+
if t.startswith("conn-ssh"):
|
|
82
|
+
return "🌐", "SSH Exec"
|
|
83
|
+
if t in ("read_file", "read_document"):
|
|
84
|
+
return "📖", "Read File"
|
|
85
|
+
if t in ("edit_file", "multi_edit"):
|
|
86
|
+
return "🔧", "Edit File"
|
|
87
|
+
if t == "write_file":
|
|
88
|
+
return "✏️", "Write File"
|
|
89
|
+
if t in ("grep", "search_code"):
|
|
90
|
+
return "🔎", "Search Code"
|
|
91
|
+
if t in ("glob", "list_dir"):
|
|
92
|
+
return "🗂️", "List Files"
|
|
93
|
+
if t in ("bash", "shell"):
|
|
94
|
+
return "⚡", "Shell Exec"
|
|
95
|
+
if t in ("web_search", "read_url", "read_url_rendered"):
|
|
96
|
+
return "🌐", "Web Search"
|
|
97
|
+
if t.startswith("tasks"):
|
|
98
|
+
return "⚡", "Tasks"
|
|
99
|
+
if t.startswith("notes"):
|
|
100
|
+
return "📝", "Notes"
|
|
101
|
+
if t.startswith("mail"):
|
|
102
|
+
return "✉️", "Mail"
|
|
103
|
+
return "⚡", t
|
|
104
|
+
|
|
105
|
+
|
|
78
106
|
_BEE = "yellow" # bee-yellow brand accent — logo / 🐝 / notes / busy dot ONLY
|
|
79
107
|
_ACCENT = "cyan" # interactive chrome ONLY — live caret / mode / panel url
|
|
80
108
|
|
|
@@ -588,46 +616,44 @@ class RichSink:
|
|
|
588
616
|
self._nudge() # the completed line is printed in tool_result
|
|
589
617
|
|
|
590
618
|
def tool_result(self, tool: str, ok: bool, summary: str) -> None:
|
|
591
|
-
# One calm dim line: icon + tool (+arg), then the ✓/✗ RIGHT NEXT TO the
|
|
592
|
-
# action (not pinned to the far right), then a dim summary. Only the
|
|
593
|
-
# ✓/✗ carries colour — the rest recedes (dim). Truncation lengths are
|
|
594
|
-
# a PROPORTION of the live console width (W2 front-2), floored at the
|
|
595
|
-
# old hardcoded counts so a narrow terminal never sees an empty line.
|
|
596
|
-
# DEFERRED (W2 final-review): the clip is baked into the recorded
|
|
597
|
-
# Text at PRINT time, not re-derived live — after a WIDEN, pre-resize
|
|
598
|
-
# history keeps its OLD (narrower) clip; cosmetic only, and always
|
|
599
|
-
# shrink-safe (a shrink still never overflows the new width).
|
|
600
|
-
# A render-time clipping renderable (re-clips from the live width on
|
|
601
|
-
# every redraw instead of baking it in) remains a future polish.
|
|
602
619
|
_tool, arg = self._pending if self._pending[0] else (tool, "")
|
|
603
|
-
icon =
|
|
620
|
+
icon, category = _tool_meta(_tool)
|
|
604
621
|
mark = "✓" if ok else "✗"
|
|
622
|
+
mark_color = "bold green" if ok else "bold red"
|
|
605
623
|
w = self.console.width
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
# regularly wraps to a second visual row -- without _pad() that
|
|
610
|
-
# continuation line hugged column 0 with no left margin at all, an
|
|
611
|
-
# ugly seam against every OTHER line in the feed, which all keep the
|
|
612
|
-
# 2-col gutter (Valentin, live 2026-07-31: "клеится к самому левому
|
|
613
|
-
# краю"). _pad() indents every wrapped row consistently, the same
|
|
614
|
-
# fix note()/progress()/thinking() already got.
|
|
615
|
-
t_now = time.strftime("%H:%M:%S")
|
|
616
|
-
self.console.print(_pad(Text.assemble(
|
|
617
|
-
(f"{t_now} ", "dim grey50"),
|
|
618
|
-
(icon + " ", "dim"),
|
|
619
|
-
(_clean(_tool), "dim"),
|
|
620
|
-
((" " + _clean(arg)[:trunc(w, 0.3, 40)]) if arg else "", "dim"),
|
|
621
|
-
(" ", ""),
|
|
622
|
-
(mark + " ", "green" if ok else "red"),
|
|
623
|
-
(_clean(summary)[:trunc(w, 0.35, 50)], "dim"),
|
|
624
|
-
)))
|
|
624
|
+
duration_ms = 0
|
|
625
|
+
if hasattr(self, "_pending_start") and self._pending_start > 0:
|
|
626
|
+
duration_ms = max(0, int((self._clock() - self._pending_start) * 1000))
|
|
625
627
|
self._pending = ("", "")
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
628
|
+
self._pending_start = 0.0
|
|
629
|
+
cost_cr = estimate_action_cost(_tool)
|
|
630
|
+
dur_str = f"{duration_ms}ms" if duration_ms > 0 else "<1ms"
|
|
631
|
+
cost_str = f"{cost_cr:.2f} cr"
|
|
630
632
|
self._step_index += 1
|
|
633
|
+
idx = self._step_index
|
|
634
|
+
line = Text()
|
|
635
|
+
line.append(f"{idx}. ", style="dim")
|
|
636
|
+
line.append(f"{icon} ")
|
|
637
|
+
line.append(f"{category:<12} ", style="bold white" if cost_cr > 0 else "none")
|
|
638
|
+
prefix_len = len(f"{idx}. ") + 4 + 13 + _GUTTER
|
|
639
|
+
right_len = 8 + 9 + 3 + len(_tool)
|
|
640
|
+
available_arg = max(16, w - prefix_len - right_len - 2)
|
|
641
|
+
arg_clean = _clean(arg).strip()
|
|
642
|
+
if len(arg_clean) > available_arg:
|
|
643
|
+
arg_disp = arg_clean[:available_arg - 1] + "…"
|
|
644
|
+
else:
|
|
645
|
+
arg_disp = arg_clean
|
|
646
|
+
line.append(f"{arg_disp:<{available_arg}} ", style="cyan" if cost_cr > 0 else "none")
|
|
647
|
+
line.append(f"{dur_str:>6} ", style="dim")
|
|
648
|
+
line.append(f"{cost_str:>7} ", style="cyan" if cost_cr > 0 else "dim")
|
|
649
|
+
line.append(f"{mark} ", style=mark_color)
|
|
650
|
+
line.append(f"{_tool}", style="dim grey50")
|
|
651
|
+
clean_summary = _clean(summary).strip()
|
|
652
|
+
if clean_summary:
|
|
653
|
+
line.append(chr(10))
|
|
654
|
+
line.append(" └─ ", style="dim grey50")
|
|
655
|
+
line.append(clean_summary, style="dim")
|
|
656
|
+
self.console.print(_pad(line))
|
|
631
657
|
rc = getattr(self.console, "record_count", None)
|
|
632
658
|
if callable(rc):
|
|
633
659
|
self._step_records[rc() - 1] = self._step_index
|
|
@@ -838,7 +838,9 @@ async def _make_session_slot(cfg, token_provider, workspace, mode, *, resources:
|
|
|
838
838
|
|
|
839
839
|
auto_slot_id = slot_id is None
|
|
840
840
|
if auto_slot_id:
|
|
841
|
-
|
|
841
|
+
# Every terminal session receives a unique slot_id (hex[:6]) so its
|
|
842
|
+
# session ID is isolated and never collides with or leaks into old tasks.
|
|
843
|
+
slot_id = uuid4().hex[:6]
|
|
842
844
|
|
|
843
845
|
repo_key_task = None
|
|
844
846
|
if first:
|
|
@@ -895,10 +895,8 @@ def _arrow_up_action(event, buf, sel: dict, n: int, busy: bool, pending=None,
|
|
|
895
895
|
pulled["text"], pulled["iid"] = str(item), getattr(item, "iid", "")
|
|
896
896
|
event.app.invalidate()
|
|
897
897
|
return
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
event.app.invalidate()
|
|
901
|
-
return
|
|
898
|
+
# Legacy step-navigation on Up arrow completely removed:
|
|
899
|
+
# Up always recalls command history readline-style (or pulls from queue).
|
|
902
900
|
buf.history_backward()
|
|
903
901
|
event.app.invalidate()
|
|
904
902
|
|
|
@@ -906,10 +904,8 @@ def _arrow_up_action(event, buf, sel: dict, n: int, busy: bool, pending=None,
|
|
|
906
904
|
def _arrow_down_action(event, buf, sel: dict, n: int, busy: bool) -> None:
|
|
907
905
|
"""Down key — mirror of _arrow_up_action: step-nav on the same exact
|
|
908
906
|
gate, otherwise cycle history forward."""
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
event.app.invalidate()
|
|
912
|
-
return
|
|
907
|
+
# Legacy step-navigation on Down arrow completely removed:
|
|
908
|
+
# Down always cycles command history forward.
|
|
913
909
|
buf.history_forward()
|
|
914
910
|
event.app.invalidate()
|
|
915
911
|
|
|
@@ -1243,10 +1239,7 @@ async def run_session(*, slots, on_line, on_cycle, on_tier_cycle=None,
|
|
|
1243
1239
|
if rc:
|
|
1244
1240
|
rc(text) # ICNLI: relay the raw reply verbatim
|
|
1245
1241
|
return
|
|
1246
|
-
|
|
1247
|
-
idx, sel["i"] = sel["i"], None
|
|
1248
|
-
event.app.create_background_task(steps_nav["expand"](idx, slot))
|
|
1249
|
-
return
|
|
1242
|
+
|
|
1250
1243
|
if not text.strip():
|
|
1251
1244
|
return
|
|
1252
1245
|
if _is_queue_command(text):
|
|
@@ -1431,51 +1424,9 @@ async def run_session(*, slots, on_line, on_cycle, on_tier_cycle=None,
|
|
|
1431
1424
|
get_app().create_background_task(_do_right_paste())
|
|
1432
1425
|
|
|
1433
1426
|
def _line_click(abs_line: int) -> None:
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
N (and Up/Down + Enter) already show, just one gesture shorter. A
|
|
1438
|
-
click resolves to a RECORD via the pane's own reflow-anchoring math
|
|
1439
|
-
(`_record_at_line`, already used for reflow + scroll-anchoring), then
|
|
1440
|
-
to a step number via the sink's `step_for_record` (set right after
|
|
1441
|
-
each tool_result print, see render.py) -- so a click on a banner,
|
|
1442
|
-
a progress line, or blank space (no step there) is a harmless no-op,
|
|
1443
|
-
never an error. Busy mid-turn is ALSO a no-op (steps_nav reflects the
|
|
1444
|
-
LAST finished turn only -- clicking during a live one would expand
|
|
1445
|
-
stale data), same gate Enter's own steps_nav path already uses.
|
|
1446
|
-
Compact by construction: dispatches through steps_nav[\"expand\"],
|
|
1447
|
-
the SAME `/steps N` -> step_detail bordered panel Enter already
|
|
1448
|
-
shows -- never a new UI surface, never full-screen, nothing to
|
|
1449
|
-
keep in sync twice."""
|
|
1450
|
-
if not steps_nav or _busy_live():
|
|
1451
|
-
return
|
|
1452
|
-
slot = _a()
|
|
1453
|
-
sink = slot.sink
|
|
1454
|
-
step_fn = getattr(sink, "step_for_record", None) if sink is not None else None
|
|
1455
|
-
if step_fn is None:
|
|
1456
|
-
return
|
|
1457
|
-
pane = _pane()
|
|
1458
|
-
record_idx = pane._record_at_line(abs_line)
|
|
1459
|
-
step_no = step_fn(record_idx)
|
|
1460
|
-
if not step_no:
|
|
1461
|
-
return
|
|
1462
|
-
# webbee-code-step-toggle-v1: a SECOND click on the SAME already-open
|
|
1463
|
-
# step line folds the detail panel back away instead of re-printing
|
|
1464
|
-
# it -- a plain flash_note toast (same mechanism as copy-confirm)
|
|
1465
|
-
# says so out loud, tool-agnostic (works identically whether the
|
|
1466
|
-
# step was bash/read_file/write_file/anything else): raising and
|
|
1467
|
-
# lowering the detail is now a real, visible round-trip, not a
|
|
1468
|
-
# one-way reveal.
|
|
1469
|
-
if slot.expanded_steps.get(step_no):
|
|
1470
|
-
slot.expanded_steps.pop(step_no, None)
|
|
1471
|
-
pane.flash_note(f"▸ step {step_no} collapsed")
|
|
1472
|
-
get_app_or_none() and get_app_or_none().invalidate()
|
|
1473
|
-
return
|
|
1474
|
-
slot.expanded_steps[step_no] = True
|
|
1475
|
-
event = get_app_or_none()
|
|
1476
|
-
if event is None:
|
|
1477
|
-
return
|
|
1478
|
-
event.create_background_task(steps_nav["expand"](step_no - 1, slot))
|
|
1427
|
+
# Clicking lines is a no-op: legacy step expansion removed.
|
|
1428
|
+
# Kept as a stub because _pane() still wires pane.on_line_click.
|
|
1429
|
+
pass
|
|
1479
1430
|
|
|
1480
1431
|
@kb.add("c-v")
|
|
1481
1432
|
def _paste_key(event):
|
|
@@ -1791,8 +1742,7 @@ async def run_session(*, slots, on_line, on_cycle, on_tier_cycle=None,
|
|
|
1791
1742
|
f = pane.flash()
|
|
1792
1743
|
if f:
|
|
1793
1744
|
frags = [("class:tb.working", " " + f)] # transient copy confirmation
|
|
1794
|
-
|
|
1795
|
-
frags = [("class:tb.dim", f" step {sel['i'] + 1}/{_nav_count()} · Enter to expand · Esc to cancel")]
|
|
1745
|
+
|
|
1796
1746
|
else:
|
|
1797
1747
|
st_fn = _sink_attr("status")
|
|
1798
1748
|
if callable(st_fn):
|
|
@@ -1592,7 +1592,7 @@ def test_make_session_slot_shares_resources_bundle_on_same_workspace(monkeypatch
|
|
|
1592
1592
|
|
|
1593
1593
|
# ── W4b T5 item 1: slot_id minting -- tab-1 legacy id vs later short hex ────
|
|
1594
1594
|
|
|
1595
|
-
def
|
|
1595
|
+
def test_make_session_slot_first_true_mints_unique_slot_id():
|
|
1596
1596
|
async def _drive():
|
|
1597
1597
|
return await _make_session_slot(
|
|
1598
1598
|
_mk_cfg(), _noop_token_provider, os.getcwd(), "default",
|
|
@@ -1602,8 +1602,9 @@ def test_make_session_slot_first_true_keeps_slot_id_empty():
|
|
|
1602
1602
|
shadow_factory=lambda cfg, ws: None, first=True)
|
|
1603
1603
|
|
|
1604
1604
|
slot = asyncio.run(_drive())
|
|
1605
|
-
assert slot.slot_id ==
|
|
1606
|
-
assert
|
|
1605
|
+
assert slot.slot_id and len(slot.slot_id) == 6
|
|
1606
|
+
assert all(c in "0123456789abcdef" for c in slot.slot_id)
|
|
1607
|
+
assert slot.agent.slot_id == slot.slot_id
|
|
1607
1608
|
|
|
1608
1609
|
|
|
1609
1610
|
# ── 0.3.25 Part C: per-repo instance lock wiring in _make_session_slot ─────
|
|
@@ -1617,7 +1618,7 @@ class _FakeLock:
|
|
|
1617
1618
|
self.closed = True
|
|
1618
1619
|
|
|
1619
1620
|
|
|
1620
|
-
def
|
|
1621
|
+
def test_first_true_primary_lock_mints_slot_id_and_rides_on_the_slot(monkeypatch):
|
|
1621
1622
|
import webbee.instance_lock as IL
|
|
1622
1623
|
lock = _FakeLock(held=False)
|
|
1623
1624
|
monkeypatch.setattr(IL, "acquire", lambda repo_key: lock)
|
|
@@ -1631,7 +1632,8 @@ def test_first_true_primary_lock_keeps_legacy_slot_id_and_rides_on_the_slot(monk
|
|
|
1631
1632
|
shadow_factory=lambda cfg, ws: None, first=True)
|
|
1632
1633
|
|
|
1633
1634
|
slot = asyncio.run(_drive())
|
|
1634
|
-
assert slot.slot_id
|
|
1635
|
+
assert slot.slot_id and len(slot.slot_id) == 6
|
|
1636
|
+
assert all(c in "0123456789abcdef" for c in slot.slot_id)
|
|
1635
1637
|
assert slot.instance_lock is lock # rides on the slot for later teardown
|
|
1636
1638
|
assert "already running" not in slot.pane.dump()
|
|
1637
1639
|
|
|
@@ -2240,20 +2240,18 @@ def test_up_arrow_with_text_present_pulls_history_not_steps():
|
|
|
2240
2240
|
|
|
2241
2241
|
|
|
2242
2242
|
def test_up_arrow_during_step_nav_still_navigates_steps():
|
|
2243
|
-
#
|
|
2243
|
+
# Legacy step-navigation on arrow keys is removed: sel['i'] stays None, history handles it.
|
|
2244
2244
|
from prompt_toolkit.buffer import Buffer
|
|
2245
2245
|
|
|
2246
2246
|
from webbee.tui import _arrow_down_action, _arrow_up_action
|
|
2247
2247
|
|
|
2248
|
-
buf = Buffer(multiline=False)
|
|
2248
|
+
buf = Buffer(multiline=False)
|
|
2249
2249
|
sel = {"i": None}
|
|
2250
2250
|
event = _FakeEvent()
|
|
2251
2251
|
_arrow_up_action(event, buf, sel, 3, False)
|
|
2252
|
-
assert sel["i"]
|
|
2253
|
-
_arrow_up_action(event, buf, sel, 3, False)
|
|
2254
|
-
assert sel["i"] == 1
|
|
2252
|
+
assert sel["i"] is None
|
|
2255
2253
|
_arrow_down_action(event, buf, sel, 3, False)
|
|
2256
|
-
assert sel["i"]
|
|
2254
|
+
assert sel["i"] is None
|
|
2257
2255
|
assert event.app.invalidated is True
|
|
2258
2256
|
|
|
2259
2257
|
|
|
@@ -5643,11 +5641,10 @@ def test_badge_style_override_breathes_only_when_confirmed_up_to_date():
|
|
|
5643
5641
|
|
|
5644
5642
|
|
|
5645
5643
|
# --------------------------------------------------------------------------
|
|
5646
|
-
#
|
|
5647
|
-
#
|
|
5648
|
-
# Up/Down + Enter) already give, one gesture shorter, never a new surface.
|
|
5644
|
+
# Legacy click-to-expand is removed: a click on a printed tool_result line is
|
|
5645
|
+
# now a plain no-op -- the pane hook still exists, it just expands nothing.
|
|
5649
5646
|
# --------------------------------------------------------------------------
|
|
5650
|
-
def
|
|
5647
|
+
def test_clicking_a_tool_result_line_is_a_noop():
|
|
5651
5648
|
from prompt_toolkit.application import create_app_session, get_app
|
|
5652
5649
|
from prompt_toolkit.data_structures import Point
|
|
5653
5650
|
from prompt_toolkit.input import create_pipe_input
|
|
@@ -5685,7 +5682,7 @@ def test_clicking_a_tool_result_line_expands_its_step():
|
|
|
5685
5682
|
# absolute line 1, not 0, in a freshly-booted pane at offset 0.
|
|
5686
5683
|
pane.on_line_click(1)
|
|
5687
5684
|
await asyncio.sleep(0.02)
|
|
5688
|
-
assert expanded == [
|
|
5685
|
+
assert expanded == [] # no expansion, no crash
|
|
5689
5686
|
pipe.send_text("\x04")
|
|
5690
5687
|
ok = await asyncio.wait_for(task, 5)
|
|
5691
5688
|
assert ok is True
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.3"
|
|
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
|
|
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
|
|
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
|