webbee 0.7.7__tar.gz → 0.7.9__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.7.7 → webbee-0.7.9}/CHANGELOG.md +19 -0
- {webbee-0.7.7 → webbee-0.7.9}/PKG-INFO +3 -3
- {webbee-0.7.7 → webbee-0.7.9}/pyproject.toml +3 -3
- webbee-0.7.9/src/webbee/__init__.py +1 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/autoupdate.py +19 -9
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/checkpoints.py +25 -5
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/repl.py +24 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/tools.py +2 -1
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/update.py +24 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_autoupdate.py +18 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_checkpoints.py +23 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_update_badge_0340.py +24 -0
- webbee-0.7.7/src/webbee/__init__.py +0 -1
- {webbee-0.7.7 → webbee-0.7.9}/.github/workflows/publish.yml +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/.gitignore +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/LICENSE +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/README.md +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/install.sh +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/__main__.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/_launch.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/account.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/active_sessions.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/banner_art.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/boot.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/browser.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/cli.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/clipboard.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/clipboard_read.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/clipboard_session.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/coding_context.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/commands.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/config.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/consent.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/details.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/events.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/frames.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/home.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/home_view.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/http.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/instance_lock.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/graph.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/outline.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/query.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/service.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/tree.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/intel/watch.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/media.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/modals.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/mode_store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/native_files.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/native_read.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/newtab_mode.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/output_pane.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/pricing_constants.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/queue_panel.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/reflow.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/remote.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/render.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/repo.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/selection.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/session.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/sessions.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/sizing.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/slots.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/steer.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/stream.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/tab_store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/tabs.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/thread.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/tier_store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/todo_panel.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/tokens.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/tui.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/urlopen.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/wallet.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/src/webbee/worktrees.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/__init__.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/conftest.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_account.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_active_sessions.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_audit_2026_07_25_findings.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_checkpoints_nested_1030.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_cli.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_clipboard.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_clipboard_read.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_code_map_fresh.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_coding_context.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_commands.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_config.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_cpc_outline.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_cross_loop_token.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_details.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_events.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_freeze_fix.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_home.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_home_view.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_home_view_mouse.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_inspect_page.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_instance_lock.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_any_folder.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_cache_and_sync_worker.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_checksum_tree.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_chunker.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_embed.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_folder_1106.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_graph.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_query.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_service.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_vectors.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_intel_watch.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_launch.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_marathon.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_modals_e2e.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_mode_and_remote_0_6_8.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_mode_store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_native_files.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_newtab_mode.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_packaging.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_probe_cost.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_queue_manage_0337.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_reflow.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_remote_code_reads.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_render.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_repl.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_repo.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_resilient_http.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_review_fixes_1030.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_session.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_session_review_1030.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_sessions.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_sizing.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_slots.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_steer.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_stream.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tab_bar_stability.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tab_restore_0337.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tabs.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_terminal_ux_0336.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_thread.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tier_invalidation.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tier_store.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_todo_panel.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tokens.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tools.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tools_eol_cancel_1030.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tools_vision_websearch.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tui.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_tui_hardening.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_update.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_urlopen.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_version.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_version_badge_corner.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_wallet.py +0 -0
- {webbee-0.7.7 → webbee-0.7.9}/tests/test_worktrees.py +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.9
|
|
4
|
+
|
|
5
|
+
Field report 06.11 #3 (a folder of nine repositories on Kubuntu):
|
|
6
|
+
* A failed `checkpoint` says why: the 30 s budget of a big folder, or git's own error. Before, every failure read
|
|
7
|
+
"checkpoint failed (shadow git error)".
|
|
8
|
+
* An upgrade while a session runs (pipx, uv, another terminal) is shown in the toolbar until you restart webbee.
|
|
9
|
+
Before, the old process ran on and `webbee --version` read the new one from disk.
|
|
10
|
+
* Needs imperal-mcp 0.9.6 and imperal-sdk 6.5.3. With them, `contract_slice` reaches endpoints through their
|
|
11
|
+
handlers' calls, and `graph_slice` / `contract_slice` work at a folder of repositories.
|
|
12
|
+
* The kernel side of the same report: co-changed files stand apart from real users in `impact_of_change`,
|
|
13
|
+
`search_code` follows one score, `orient` shows matches first, repository rows are named, `contract_slice` is in
|
|
14
|
+
the agent's tools, and `checkpoint` / `diff` / `rollback` come with the code tools.
|
|
15
|
+
|
|
16
|
+
## 0.7.8
|
|
17
|
+
|
|
18
|
+
* The webbee beside the shell's imperal-mcp (its code tools) follows webbee's releases. Live: webbee 0.7.7 was out,
|
|
19
|
+
imperal-mcp had no new release, and its environment kept webbee 0.7.6 because only imperal-mcp's version was
|
|
20
|
+
compared.
|
|
21
|
+
|
|
3
22
|
## 0.7.7
|
|
4
23
|
|
|
5
24
|
* `pipx reinstall webbee` brings the newest release for real (live check of 0.7.6: it still gave 0.7.5).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: webbee
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.9
|
|
4
4
|
Summary: Webbee 🐝 — the Imperal Cloud coding agent in your terminal
|
|
5
5
|
Project-URL: Homepage, https://imperal.io
|
|
6
6
|
Project-URL: Documentation, https://docs.imperal.io
|
|
@@ -31,8 +31,8 @@ Requires-Python: >=3.7
|
|
|
31
31
|
Requires-Dist: asyncssh>=2.14; python_version >= '3.10'
|
|
32
32
|
Requires-Dist: httpx-sse>=0.4; python_version >= '3.10'
|
|
33
33
|
Requires-Dist: httpx>=0.27; python_version >= '3.10'
|
|
34
|
-
Requires-Dist: imperal-mcp>=0.9.
|
|
35
|
-
Requires-Dist: imperal-sdk[universal-index]>=6.5.
|
|
34
|
+
Requires-Dist: imperal-mcp>=0.9.6; python_version >= '3.10'
|
|
35
|
+
Requires-Dist: imperal-sdk[universal-index]>=6.5.3; python_version >= '3.10'
|
|
36
36
|
Requires-Dist: model2vec>=0.8.2; python_version >= '3.10' and (sys_platform != 'darwin' or platform_machine != 'x86_64' or python_version < '3.13')
|
|
37
37
|
Requires-Dist: numpy>=2; python_version >= '3.10' and (sys_platform != 'darwin' or platform_machine != 'x86_64' or python_version < '3.13')
|
|
38
38
|
Requires-Dist: prompt-toolkit>=3; python_version >= '3.10'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "webbee"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.9"
|
|
4
4
|
description = "Webbee 🐝 — the Imperal Cloud coding agent in your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "GPL-3.0-or-later"
|
|
@@ -13,8 +13,8 @@ dependencies = [
|
|
|
13
13
|
# release on a Python 3.12 it sets up once with uv -- `uv` comes as a wheel on 3.8+,
|
|
14
14
|
# 3.7 fetches it with uv's own installer.
|
|
15
15
|
'uv>=0.4; python_version >= "3.8" and python_version < "3.10"',
|
|
16
|
-
'imperal-mcp>=0.9.
|
|
17
|
-
'imperal-sdk[universal-index]>=6.5.
|
|
16
|
+
'imperal-mcp>=0.9.6; python_version >= "3.10"', # raised to the newest at each release
|
|
17
|
+
'imperal-sdk[universal-index]>=6.5.3; python_version >= "3.10"',
|
|
18
18
|
'asyncssh>=2.14; python_version >= "3.10"',
|
|
19
19
|
'httpx>=0.27; python_version >= "3.10"',
|
|
20
20
|
'httpx-sse>=0.4; python_version >= "3.10"',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.9"
|
|
@@ -243,16 +243,17 @@ def mcp_installed(which=safe_which, run=subprocess.run) -> dict:
|
|
|
243
243
|
real = os.path.realpath(exe)
|
|
244
244
|
py = os.path.join(os.path.dirname(real), "python.exe" if _WINDOWS else "python")
|
|
245
245
|
out = {"tool": tool_of(real), "python": py, "prefix": os.path.dirname(os.path.dirname(real)), "version": "",
|
|
246
|
-
"code": False}
|
|
246
|
+
"code": False, "webbee": ""}
|
|
247
247
|
# asked twice when it says "no code extra": a reinstall is never made on one odd answer (live 2026-10-25: right after
|
|
248
248
|
# another install the first answer said no webbee, the next ones yes)
|
|
249
249
|
for _ in range(2):
|
|
250
250
|
try:
|
|
251
251
|
got = run([py, "-c", _NO_CWD + "import importlib.metadata as m, importlib.util as u; "
|
|
252
|
-
"
|
|
252
|
+
"w = u.find_spec('webbee') is not None; "
|
|
253
|
+
"print(m.version('imperal-mcp'), w, m.version('webbee') if w else '')"],
|
|
253
254
|
capture_output=True, text=True, timeout=20)
|
|
254
|
-
version, code = (str(got.stdout or "").split() + ["", ""])[:
|
|
255
|
-
out.update(version=version, code=code == "True")
|
|
255
|
+
version, code, webbee = (str(got.stdout or "").split() + ["", "", ""])[:3]
|
|
256
|
+
out.update(version=version, code=code == "True", webbee=webbee)
|
|
256
257
|
except Exception:
|
|
257
258
|
pass
|
|
258
259
|
if out["code"] or not out["version"]:
|
|
@@ -260,14 +261,19 @@ def mcp_installed(which=safe_which, run=subprocess.run) -> dict:
|
|
|
260
261
|
return out
|
|
261
262
|
|
|
262
263
|
|
|
263
|
-
def mcp_command(inst: dict, newest, uv, pipx, wheel=None, source: str = "pypi"):
|
|
264
|
+
def mcp_command(inst: dict, newest, uv, pipx, wheel=None, source: str = "pypi", webbee_newest=None):
|
|
264
265
|
"""The one command that makes the shell's imperal-mcp the newest with its code extra -- by the tool that installed
|
|
265
266
|
it (uv, else pipx, for a new install) -- or None when it already is, or nothing can do it. ``source`` "git": from
|
|
266
267
|
the stable branches, imperal-sdk and webbee beside it, and an install recorded by a version is moved onto the
|
|
267
|
-
branch even when its version is the newest (what ``pipx reinstall`` / ``uv tool upgrade`` would put back).
|
|
268
|
+
branch even when its version is the newest (what ``pipx reinstall`` / ``uv tool upgrade`` would put back). The
|
|
269
|
+
webbee beside it (its code tools) follows webbee's own releases: one newer than it holds upgrades the environment
|
|
270
|
+
too (live 2026-11-06: webbee 0.7.7 out, imperal-mcp unchanged, its environment kept webbee 0.7.6)."""
|
|
268
271
|
git = source == "git"
|
|
269
272
|
recorded = bool(inst) and on_stable(inst.get("tool") or "", inst.get("prefix") or "", MCP_ENV_SPECS)
|
|
270
|
-
|
|
273
|
+
behind = git and bool(inst) and bool(inst.get("webbee")) and bool(webbee_newest) \
|
|
274
|
+
and _ver(inst.get("webbee")) < _ver(webbee_newest)
|
|
275
|
+
if inst and inst.get("version") and inst.get("version") == newest and inst.get("code") and (recorded or not git) \
|
|
276
|
+
and not behind:
|
|
271
277
|
return None
|
|
272
278
|
if inst and not inst.get("version"):
|
|
273
279
|
return None # it did not answer: a working install is never replaced blind
|
|
@@ -512,9 +518,13 @@ def upgrade_before_start(argv, current: str, say=print, fetch=latest, which=safe
|
|
|
512
518
|
say(f"🐝 webbee could not update itself -- run: {_show(cmd)}")
|
|
513
519
|
mcp_newest, mcp_source = _newest(MCP, git, fetch, stable)
|
|
514
520
|
inst = mcp_installed(which, run)
|
|
515
|
-
cmd = mcp_command(inst, mcp_newest, uv, pipx, wheel, source=mcp_source
|
|
521
|
+
cmd = mcp_command(inst, mcp_newest, uv, pipx, wheel, source=mcp_source,
|
|
522
|
+
webbee_newest=newest if source == "git" else None)
|
|
516
523
|
if cmd:
|
|
517
|
-
what = (f"imperal-mcp {inst.get('version') or '?'} -> {mcp_newest or 'source'}"
|
|
524
|
+
what = (f"imperal-mcp {inst.get('version') or '?'} -> {mcp_newest or 'source'}"
|
|
525
|
+
+ (f", webbee beside it {inst.get('webbee')} -> {newest}"
|
|
526
|
+
if source == "git" and inst.get("webbee") and _ver(inst.get("webbee")) < _ver(newest) else "")
|
|
527
|
+
if inst
|
|
518
528
|
else f"imperal-mcp {mcp_newest or 'from source'}") + (" (git, stable)" if mcp_source == "git" else "")
|
|
519
529
|
say(f"🐝 updating {what} ...")
|
|
520
530
|
if _run(cmd, run):
|
|
@@ -75,6 +75,12 @@ class _Budget(Exception):
|
|
|
75
75
|
"""The snapshot ran past _SNAPSHOT_BUDGET_S."""
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def _why(r) -> str:
|
|
79
|
+
"""A failed git run's own words, one short line."""
|
|
80
|
+
text = " ".join(str(getattr(r, "stderr", "") or getattr(r, "stdout", "") or "").split())
|
|
81
|
+
return (text[:240] or f"git exited {getattr(r, 'returncode', '?')}")
|
|
82
|
+
|
|
83
|
+
|
|
78
84
|
class ShadowGit:
|
|
79
85
|
_DIFF_CAP = 20000
|
|
80
86
|
|
|
@@ -86,6 +92,9 @@ class ShadowGit:
|
|
|
86
92
|
self.auto_ok = True # auto-checkpointing enabled (F8 latch below)
|
|
87
93
|
self._auto_fail_streak = 0 # consecutive failed AUTO snapshots
|
|
88
94
|
self.pause_reason = "" # why auto-checkpointing stopped at once (a too-slow snapshot)
|
|
95
|
+
# why the last checkpoint failed, said to the agent (field report 06.11 #3: five "checkpoint failed (shadow git
|
|
96
|
+
# error)" at a folder of nine repositories, the cause -- a budget, a git error -- never shown)
|
|
97
|
+
self.last_error = ""
|
|
89
98
|
|
|
90
99
|
def _git(self, *args: str) -> "subprocess.CompletedProcess":
|
|
91
100
|
return self._git_io(list(args))
|
|
@@ -243,15 +252,20 @@ class ShadowGit:
|
|
|
243
252
|
keep, nested, uncovered = self._collect(deadline)
|
|
244
253
|
r = self._git_io(["ls-files", "-z", "-c"], deadline=deadline)
|
|
245
254
|
if r.returncode != 0:
|
|
255
|
+
self.last_error = f"listing the shadow index failed: {_why(r)}"
|
|
246
256
|
return False, nested, uncovered
|
|
247
257
|
gone = sorted(set(_split_z(r.stdout)) - set(keep))
|
|
248
258
|
for i in range(0, len(gone), _CHUNK): # first: a gitlink must leave before its files come in
|
|
249
|
-
|
|
250
|
-
|
|
259
|
+
g = self._git_io(["update-index", "-z", "--force-remove", "--stdin"],
|
|
260
|
+
stdin="\0".join(gone[i:i + _CHUNK]) + "\0", deadline=deadline)
|
|
261
|
+
if g.returncode != 0:
|
|
262
|
+
self.last_error = f"removing gone files from the snapshot failed: {_why(g)}"
|
|
251
263
|
return False, nested, uncovered
|
|
252
264
|
for i in range(0, len(keep), _CHUNK):
|
|
253
|
-
|
|
254
|
-
|
|
265
|
+
a = self._git_io(["update-index", "-z", "--add", "--remove", "--stdin"],
|
|
266
|
+
stdin="\0".join(keep[i:i + _CHUNK]) + "\0", deadline=deadline)
|
|
267
|
+
if a.returncode != 0:
|
|
268
|
+
self.last_error = f"adding {len(keep)} files to the snapshot failed: {_why(a)}"
|
|
255
269
|
return False, nested, uncovered
|
|
256
270
|
return True, nested, uncovered
|
|
257
271
|
|
|
@@ -294,7 +308,9 @@ class ShadowGit:
|
|
|
294
308
|
`force=True` always records a new checkpoint even with nothing dirty
|
|
295
309
|
-- used by rollback() for its mandatory pre-rollback safety snapshot,
|
|
296
310
|
which must exist even when the workspace already matches HEAD."""
|
|
311
|
+
self.last_error = ""
|
|
297
312
|
if not self.available:
|
|
313
|
+
self.last_error = "the shadow history could not be set up for this workspace"
|
|
298
314
|
return None
|
|
299
315
|
try:
|
|
300
316
|
deadline = time.monotonic() + _SNAPSHOT_BUDGET_S
|
|
@@ -320,6 +336,7 @@ class ShadowGit:
|
|
|
320
336
|
args.append("--allow-empty") # first-ever baseline / forced snapshot
|
|
321
337
|
c = self._git(*args)
|
|
322
338
|
if c.returncode != 0:
|
|
339
|
+
self.last_error = f"the snapshot commit failed: {_why(c)}"
|
|
323
340
|
return None
|
|
324
341
|
sha = self._git("rev-parse", "--short", "HEAD").stdout.strip()
|
|
325
342
|
n = self._count() + 1
|
|
@@ -332,8 +349,11 @@ class ShadowGit:
|
|
|
332
349
|
self._drop_stale_lock()
|
|
333
350
|
self.auto_ok = False
|
|
334
351
|
self.pause_reason = (f"a snapshot of this workspace took longer than {int(_SNAPSHOT_BUDGET_S)}s")
|
|
352
|
+
self.last_error = (self.pause_reason + " (the staged part is kept: checkpoint again to go on, or "
|
|
353
|
+
"checkpoint inside one repository of this folder)")
|
|
335
354
|
return None
|
|
336
|
-
except Exception:
|
|
355
|
+
except Exception as exc:
|
|
356
|
+
self.last_error = f"{type(exc).__name__}: {str(exc)[:200]}"
|
|
337
357
|
return None
|
|
338
358
|
|
|
339
359
|
def list_checkpoints(self, limit: int = 10) -> list:
|
|
@@ -669,6 +669,28 @@ async def _live_session_watch(cfg, token_provider, workspace, live: dict,
|
|
|
669
669
|
await _sleep(interval)
|
|
670
670
|
|
|
671
671
|
|
|
672
|
+
async def _installed_watch(update_state: dict, *, invalidate=None, every: float = 300.0, read=None,
|
|
673
|
+
sleep=None) -> None:
|
|
674
|
+
"""Every few minutes: the webbee on disk against the one running -- an upgrade during the session (pipx, uv, another
|
|
675
|
+
terminal's start) is said in the toolbar until the restart (field report 06.11 #3). Never raises."""
|
|
676
|
+
from webbee import __version__
|
|
677
|
+
from webbee.update import installed_version, restart_notice
|
|
678
|
+
read = read or installed_version
|
|
679
|
+
sleep = sleep or asyncio.sleep
|
|
680
|
+
while True:
|
|
681
|
+
try:
|
|
682
|
+
await sleep(every)
|
|
683
|
+
notice = restart_notice(__version__, await asyncio.to_thread(read))
|
|
684
|
+
if notice and update_state.get("notice") != notice:
|
|
685
|
+
update_state["notice"] = notice
|
|
686
|
+
if invalidate is not None:
|
|
687
|
+
invalidate()
|
|
688
|
+
except asyncio.CancelledError:
|
|
689
|
+
raise
|
|
690
|
+
except Exception:
|
|
691
|
+
pass
|
|
692
|
+
|
|
693
|
+
|
|
672
694
|
async def _check_update_bg(update_state: dict, *, invalidate=None,
|
|
673
695
|
fetch=None, cache_path=None) -> None:
|
|
674
696
|
"""One-shot background PyPI freshness check for the toolbar's version
|
|
@@ -1928,6 +1950,8 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
1928
1950
|
_update_state: dict = {"notice": "", "checked": None}
|
|
1929
1951
|
home_slot.bg_tasks.append(asyncio.ensure_future(
|
|
1930
1952
|
_check_update_bg(_update_state, invalidate=_invalidate_dock)))
|
|
1953
|
+
home_slot.bg_tasks.append(asyncio.ensure_future(
|
|
1954
|
+
_installed_watch(_update_state, invalidate=_invalidate_dock)))
|
|
1931
1955
|
|
|
1932
1956
|
async def _on_line(text: str, slot: SessionSlot) -> None:
|
|
1933
1957
|
if await _handle(text, slot) == "exit":
|
|
@@ -724,7 +724,8 @@ class LocalToolExecutor:
|
|
|
724
724
|
return {"ok": False, "content": "reversibility is unavailable (no shadow git)"}
|
|
725
725
|
cp = self.shadow.checkpoint(str(a.get("label", "") or "manual"))
|
|
726
726
|
if cp is None:
|
|
727
|
-
|
|
727
|
+
why = str(getattr(self.shadow, "last_error", "") or "") or "shadow git error"
|
|
728
|
+
return {"ok": False, "content": f"checkpoint failed: {why}"}
|
|
728
729
|
note = "created" if cp.get("changed") else "no changes since the last checkpoint"
|
|
729
730
|
# what it holds and what it does not, said (2026-10-30: a checkpoint of a folder of repositories held none of
|
|
730
731
|
# their files and said "created")
|
|
@@ -80,3 +80,27 @@ def check_for_update(current: str, *, cache_path, now: float, fetch, ttl: float
|
|
|
80
80
|
Kept as-is (boot's banner path); `check_update_status` adds the verdict."""
|
|
81
81
|
return _notice_for(current, _resolve_latest(
|
|
82
82
|
current, cache_path=cache_path, now=now, fetch=fetch, ttl=ttl)) or None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def installed_version(python: str = "", run=None) -> str:
|
|
86
|
+
"""The webbee version on disk now, read by a fresh interpreter (this process's own metadata is what it started
|
|
87
|
+
with). "" when it cannot be read. Never raises."""
|
|
88
|
+
import subprocess
|
|
89
|
+
import sys
|
|
90
|
+
run = run or subprocess.run
|
|
91
|
+
try:
|
|
92
|
+
got = run([python or sys.executable, "-c", "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; "
|
|
93
|
+
"import importlib.metadata as m; print(m.version('webbee'))"],
|
|
94
|
+
capture_output=True, text=True, timeout=20)
|
|
95
|
+
return str(got.stdout or "").strip() if got.returncode == 0 else ""
|
|
96
|
+
except Exception:
|
|
97
|
+
return ""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def restart_notice(running: str, installed: str) -> str:
|
|
101
|
+
"""Field report 06.11 #3: a pipx upgrade during a session left the old process running, and `webbee --version`
|
|
102
|
+
(read from disk) said the new one. Said in the toolbar until the session restarts."""
|
|
103
|
+
if installed and _ver(installed) and _ver(installed) != _ver(running):
|
|
104
|
+
return (f"\U0001f41d webbee v{installed} is installed, this session still runs v{running} "
|
|
105
|
+
"\u2014 restart webbee to use it")
|
|
106
|
+
return ""
|
|
@@ -469,3 +469,21 @@ def test_a_git_release_that_does_not_import_is_put_back_by_its_own_tag(tmp_path,
|
|
|
469
469
|
stable=lambda d, git: "0.7.5", prefix=venv)
|
|
470
470
|
assert ["/bin/pipx", "install", "--force", au.git_spec("webbee", "v0.7.4")] in run.calls
|
|
471
471
|
assert any("put 0.7.4 back" in s for s in said)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_the_webbee_beside_imperal_mcp_follows_webbee_s_releases(tmp_path):
|
|
475
|
+
uv = tmp_path / "uv" / "tools" / "imperal-mcp"
|
|
476
|
+
uv.mkdir(parents=True)
|
|
477
|
+
(uv / "uv-receipt.toml").write_text("".join(
|
|
478
|
+
f'{{ name = "x", git = "{u}?rev=stable" }}\n' for u in au.REPOS.values()))
|
|
479
|
+
inst = {"tool": "uv", "python": str(uv / "bin" / "python"), "prefix": str(uv), "version": "0.9.5", "code": True,
|
|
480
|
+
"webbee": "0.7.6"}
|
|
481
|
+
assert au.mcp_command(inst, "0.9.5", "/bin/uv", None, source="git", webbee_newest="0.7.6") is None
|
|
482
|
+
assert au.mcp_command(inst, "0.9.5", "/bin/uv", None, source="git", webbee_newest="0.7.7") == [
|
|
483
|
+
"/bin/uv", "tool", "install", "--force", "--refresh", "--with", SDK_G, "--with", WB, MCP_G]
|
|
484
|
+
run = _Run(stdout="0.9.5 True 0.7.6")
|
|
485
|
+
exe = uv / "bin" / "imperal-mcp"
|
|
486
|
+
exe.parent.mkdir()
|
|
487
|
+
exe.write_text("")
|
|
488
|
+
got = au.mcp_installed(which=lambda n: str(exe), run=run)
|
|
489
|
+
assert got["webbee"] == "0.7.6" and got["code"] is True
|
|
@@ -309,3 +309,26 @@ def test_a_path_keeps_its_first_letter_when_the_first_line_is_unstaged(tmp_path,
|
|
|
309
309
|
return real(*args)
|
|
310
310
|
monkeypatch.setattr(sg, "_git", fake)
|
|
311
311
|
assert sg.checkpoint("x")["paths"] == ["imperal_kernel/x.py", "tests/t.py"]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ── field report 06.11 #3: "checkpoint failed (shadow git error)" five times, its cause never shown ──
|
|
315
|
+
def test_a_failed_checkpoint_says_why(tmp_path, monkeypatch):
|
|
316
|
+
from webbee import checkpoints as cpm
|
|
317
|
+
from webbee.tools import LocalToolExecutor
|
|
318
|
+
sg, root = _sg(tmp_path)
|
|
319
|
+
_write(root, "a.py", "x = 1\n")
|
|
320
|
+
|
|
321
|
+
def slow(deadline):
|
|
322
|
+
raise cpm._Budget()
|
|
323
|
+
monkeypatch.setattr(sg, "_stage", slow)
|
|
324
|
+
assert sg.checkpoint("x") is None and "took longer than" in sg.last_error
|
|
325
|
+
out = LocalToolExecutor(str(root), shadow=sg).run("checkpoint", {"label": "x"})
|
|
326
|
+
assert out["ok"] is False and "took longer than" in out["content"] and "shadow git error" not in out["content"]
|
|
327
|
+
|
|
328
|
+
def git_fails(deadline):
|
|
329
|
+
sg.last_error = "adding 3 files to the snapshot failed: fatal: unable to index file"
|
|
330
|
+
return False, [], []
|
|
331
|
+
monkeypatch.setattr(sg, "_stage", git_fails)
|
|
332
|
+
assert sg.checkpoint("y") is None and "unable to index file" in sg.last_error
|
|
333
|
+
monkeypatch.undo()
|
|
334
|
+
assert sg.checkpoint("z") and sg.last_error == "" # a good one clears it
|
|
@@ -68,3 +68,27 @@ def test_check_update_bg_reuses_the_same_cache_file_home_uses(tmp_path):
|
|
|
68
68
|
asyncio.run(_check_update_bg(state2, fetch=_fetch, cache_path=cache))
|
|
69
69
|
assert calls["n"] == 1
|
|
70
70
|
assert state2["checked"] is True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ── field report 06.11 #3: upgraded during the session, the old process ran on unsaid ──
|
|
74
|
+
def test_an_upgrade_during_the_session_is_said_until_the_restart():
|
|
75
|
+
import asyncio
|
|
76
|
+
|
|
77
|
+
from webbee import __version__
|
|
78
|
+
from webbee.repl import _installed_watch
|
|
79
|
+
from webbee.update import installed_version, restart_notice
|
|
80
|
+
assert restart_notice("0.7.8", "0.7.9").startswith("\U0001f41d webbee v0.7.9 is installed, this session still runs v0.7.8")
|
|
81
|
+
assert restart_notice("0.7.8", "0.7.8") == "" and restart_notice("0.7.8", "") == ""
|
|
82
|
+
assert installed_version() # this test's own environment answers
|
|
83
|
+
state, said = {"notice": ""}, []
|
|
84
|
+
ticks = iter([None, asyncio.CancelledError()])
|
|
85
|
+
|
|
86
|
+
async def sleep(_s):
|
|
87
|
+
t = next(ticks)
|
|
88
|
+
if t is not None:
|
|
89
|
+
raise t
|
|
90
|
+
try:
|
|
91
|
+
asyncio.run(_installed_watch(state, invalidate=lambda: said.append(1), read=lambda: "99.0.0", sleep=sleep))
|
|
92
|
+
except asyncio.CancelledError:
|
|
93
|
+
pass
|
|
94
|
+
assert f"v99.0.0 is installed, this session still runs v{__version__}" in state["notice"] and said
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.7"
|
|
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
|
|
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
|