webbee 0.7.8__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.8 → webbee-0.7.9}/CHANGELOG.md +13 -0
- {webbee-0.7.8 → webbee-0.7.9}/PKG-INFO +3 -3
- {webbee-0.7.8 → webbee-0.7.9}/pyproject.toml +3 -3
- webbee-0.7.9/src/webbee/__init__.py +1 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/checkpoints.py +25 -5
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/repl.py +24 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/tools.py +2 -1
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/update.py +24 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_checkpoints.py +23 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_update_badge_0340.py +24 -0
- webbee-0.7.8/src/webbee/__init__.py +0 -1
- {webbee-0.7.8 → webbee-0.7.9}/.github/workflows/publish.yml +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/.gitignore +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/LICENSE +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/README.md +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/install.sh +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/__main__.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/_launch.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/account.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/active_sessions.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/autoupdate.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/banner_art.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/boot.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/browser.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/cli.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/clipboard.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/clipboard_read.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/clipboard_session.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/coding_context.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/commands.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/config.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/consent.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/details.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/events.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/frames.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/home.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/home_view.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/http.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/instance_lock.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/graph.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/outline.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/query.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/service.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/tree.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/intel/watch.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/media.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/modals.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/mode_store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/native_files.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/native_read.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/newtab_mode.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/output_pane.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/pricing_constants.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/queue_panel.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/reflow.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/remote.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/render.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/repo.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/selection.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/session.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/sessions.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/sizing.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/slots.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/steer.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/stream.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/tab_store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/tabs.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/thread.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/tier_store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/todo_panel.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/tokens.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/tui.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/urlopen.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/wallet.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/src/webbee/worktrees.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/__init__.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/conftest.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_account.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_active_sessions.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_audit_2026_07_25_findings.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_autoupdate.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_checkpoints_nested_1030.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_cli.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_clipboard.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_clipboard_read.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_code_map_fresh.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_coding_context.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_commands.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_config.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_cpc_outline.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_cross_loop_token.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_details.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_events.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_freeze_fix.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_home.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_home_view.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_home_view_mouse.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_inspect_page.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_instance_lock.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_any_folder.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_cache_and_sync_worker.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_checksum_tree.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_chunker.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_embed.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_folder_1106.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_graph.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_query.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_service.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_vectors.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_intel_watch.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_launch.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_marathon.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_modals_e2e.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_mode_and_remote_0_6_8.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_mode_store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_native_files.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_newtab_mode.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_packaging.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_probe_cost.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_queue_manage_0337.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_reflow.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_remote_code_reads.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_render.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_repl.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_repo.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_resilient_http.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_review_fixes_1030.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_session.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_session_review_1030.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_sessions.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_sizing.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_slots.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_steer.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_stream.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tab_bar_stability.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tab_restore_0337.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tabs.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_terminal_ux_0336.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_thread.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tier_invalidation.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tier_store.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_todo_panel.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tokens.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tools.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tools_eol_cancel_1030.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tools_vision_websearch.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tui.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_tui_hardening.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_update.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_urlopen.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_version.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_version_badge_corner.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_wallet.py +0 -0
- {webbee-0.7.8 → webbee-0.7.9}/tests/test_worktrees.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
3
16
|
## 0.7.8
|
|
4
17
|
|
|
5
18
|
* The webbee beside the shell's imperal-mcp (its code tools) follows webbee's releases. Live: webbee 0.7.7 was out,
|
|
@@ -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"
|
|
@@ -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 ""
|
|
@@ -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.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|