webbee 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {webbee-0.3.0 → webbee-0.3.1}/CHANGELOG.md +6 -0
  2. {webbee-0.3.0 → webbee-0.3.1}/PKG-INFO +1 -1
  3. {webbee-0.3.0 → webbee-0.3.1}/pyproject.toml +1 -1
  4. webbee-0.3.1/src/webbee/__init__.py +1 -0
  5. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/checkpoints.py +25 -4
  6. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/tools.py +12 -8
  7. {webbee-0.3.0 → webbee-0.3.1}/tests/test_checkpoints.py +27 -0
  8. {webbee-0.3.0 → webbee-0.3.1}/tests/test_tools.py +40 -16
  9. webbee-0.3.0/src/webbee/__init__.py +0 -1
  10. {webbee-0.3.0 → webbee-0.3.1}/.github/workflows/publish.yml +0 -0
  11. {webbee-0.3.0 → webbee-0.3.1}/.gitignore +0 -0
  12. {webbee-0.3.0 → webbee-0.3.1}/LICENSE +0 -0
  13. {webbee-0.3.0 → webbee-0.3.1}/README.md +0 -0
  14. {webbee-0.3.0 → webbee-0.3.1}/install.sh +0 -0
  15. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/account.py +0 -0
  16. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/banner_art.py +0 -0
  17. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/cli.py +0 -0
  18. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/clipboard.py +0 -0
  19. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/commands.py +0 -0
  20. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/config.py +0 -0
  21. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/details.py +0 -0
  22. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/events.py +0 -0
  23. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/frames.py +0 -0
  24. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/__init__.py +0 -0
  25. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/chunker.py +0 -0
  26. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/embed.py +0 -0
  27. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/graph.py +0 -0
  28. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/indexer.py +0 -0
  29. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/models.py +0 -0
  30. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/query.py +0 -0
  31. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/service.py +0 -0
  32. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/store.py +0 -0
  33. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/vectors.py +0 -0
  34. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/intel/watch.py +0 -0
  35. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/output_pane.py +0 -0
  36. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/render.py +0 -0
  37. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/repl.py +0 -0
  38. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/repo.py +0 -0
  39. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/session.py +0 -0
  40. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/sessions.py +0 -0
  41. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/stream.py +0 -0
  42. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/tui.py +0 -0
  43. {webbee-0.3.0 → webbee-0.3.1}/src/webbee/update.py +0 -0
  44. {webbee-0.3.0 → webbee-0.3.1}/tests/__init__.py +0 -0
  45. {webbee-0.3.0 → webbee-0.3.1}/tests/test_account.py +0 -0
  46. {webbee-0.3.0 → webbee-0.3.1}/tests/test_cli.py +0 -0
  47. {webbee-0.3.0 → webbee-0.3.1}/tests/test_clipboard.py +0 -0
  48. {webbee-0.3.0 → webbee-0.3.1}/tests/test_commands.py +0 -0
  49. {webbee-0.3.0 → webbee-0.3.1}/tests/test_config.py +0 -0
  50. {webbee-0.3.0 → webbee-0.3.1}/tests/test_cpc_contract_stable.py +0 -0
  51. {webbee-0.3.0 → webbee-0.3.1}/tests/test_details.py +0 -0
  52. {webbee-0.3.0 → webbee-0.3.1}/tests/test_events.py +0 -0
  53. {webbee-0.3.0 → webbee-0.3.1}/tests/test_freeze_fix.py +0 -0
  54. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_chunker.py +0 -0
  55. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_embed.py +0 -0
  56. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_graph.py +0 -0
  57. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_indexer.py +0 -0
  58. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_no_numpy.py +0 -0
  59. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_query.py +0 -0
  60. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_service.py +0 -0
  61. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_store.py +0 -0
  62. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_vectors.py +0 -0
  63. {webbee-0.3.0 → webbee-0.3.1}/tests/test_intel_watch.py +0 -0
  64. {webbee-0.3.0 → webbee-0.3.1}/tests/test_marathon.py +0 -0
  65. {webbee-0.3.0 → webbee-0.3.1}/tests/test_packaging.py +0 -0
  66. {webbee-0.3.0 → webbee-0.3.1}/tests/test_render.py +0 -0
  67. {webbee-0.3.0 → webbee-0.3.1}/tests/test_repl.py +0 -0
  68. {webbee-0.3.0 → webbee-0.3.1}/tests/test_repo.py +0 -0
  69. {webbee-0.3.0 → webbee-0.3.1}/tests/test_session.py +0 -0
  70. {webbee-0.3.0 → webbee-0.3.1}/tests/test_sessions.py +0 -0
  71. {webbee-0.3.0 → webbee-0.3.1}/tests/test_stream.py +0 -0
  72. {webbee-0.3.0 → webbee-0.3.1}/tests/test_tui.py +0 -0
  73. {webbee-0.3.0 → webbee-0.3.1}/tests/test_tui_hardening.py +0 -0
  74. {webbee-0.3.0 → webbee-0.3.1}/tests/test_update.py +0 -0
  75. {webbee-0.3.0 → webbee-0.3.1}/tests/test_version.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1
4
+
5
+ - Auto-checkpointing no longer disables itself after a single transient
6
+ shadow-git error — it only pauses after repeated failures, and
7
+ `/checkpoints` shows when it's paused.
8
+
3
9
  ## 0.3.0
4
10
 
5
11
  - **The agent plans in the open.** A live todo checklist (📋) rendered from the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webbee
3
- Version: 0.3.0
3
+ Version: 0.3.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "webbee"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Webbee 🐝 — the Imperal Cloud coding agent in your terminal"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.1"
@@ -44,7 +44,8 @@ class ShadowGit:
44
44
  base = cache_dir or os.path.expanduser("~/.cache/webbee")
45
45
  self.git_dir = os.path.join(base, "shadow", repo_key)
46
46
  self.available = False
47
- self.auto_ok = True # latched False after a failed AUTO snapshot (F8)
47
+ self.auto_ok = True # auto-checkpointing enabled (F8 latch below)
48
+ self._auto_fail_streak = 0 # consecutive failed AUTO snapshots
48
49
 
49
50
  def _git(self, *args: str) -> "subprocess.CompletedProcess":
50
51
  cmd = (["git", "--git-dir", self.git_dir, "--work-tree", self.root]
@@ -202,12 +203,32 @@ class ShadowGit:
202
203
  f"was saved first as checkpoint cp-{(undo or {}).get('n', '?')} "
203
204
  f"({(undo or {}).get('id', '?')}), so this rollback is itself undoable.")}
204
205
 
206
+ _AUTO_FAIL_LATCH = 3 # consecutive AUTO failures before pausing auto-checkpointing
207
+
208
+ def note_auto_result(self, ok: bool) -> None:
209
+ """Record an AUTO (pre-write) snapshot outcome. A single transient
210
+ failure must NOT disable the session's time machine (final-review F8):
211
+ latch auto-checkpointing OFF only after _AUTO_FAIL_LATCH CONSECUTIVE
212
+ failures; any success resets the streak and re-enables it."""
213
+ if ok:
214
+ self._auto_fail_streak = 0
215
+ self.auto_ok = True
216
+ else:
217
+ self._auto_fail_streak += 1
218
+ if self._auto_fail_streak >= self._AUTO_FAIL_LATCH:
219
+ self.auto_ok = False
220
+
205
221
  def describe(self) -> str:
206
222
  """One printable block for the /checkpoints command."""
207
223
  if not self.available:
208
224
  return "Reversibility is off (git unavailable)."
209
225
  rows = self.list_checkpoints(limit=10)
210
226
  if not rows:
211
- return "No checkpoints yet."
212
- lines = [f"cp-{r['n']} {r['id']} {r['when']:>16} {r['label']}" for r in rows]
213
- return "Checkpoints (newest first) — /rollback <id|cp-N|N>:\n" + "\n".join(lines)
227
+ body = "No checkpoints yet."
228
+ else:
229
+ lines = [f"cp-{r['n']} {r['id']} {r['when']:>16} {r['label']}" for r in rows]
230
+ body = "Checkpoints (newest first) — /rollback <id|cp-N|N>:\n" + "\n".join(lines)
231
+ if not self.auto_ok:
232
+ body = ("⚠ Auto-checkpointing is paused after repeated shadow errors — "
233
+ "manual /checkpoint + /rollback still work.\n") + body
234
+ return body
@@ -36,16 +36,20 @@ class LocalToolExecutor:
36
36
  args = {}
37
37
  if (tool in _WRITE_TIER and self.shadow is not None
38
38
  and getattr(self.shadow, "auto_ok", True)):
39
- # The time machine snapshots BEFORE every mutation -- and must
40
- # never block or fail the actual work. One failed AUTO snapshot
41
- # latches auto-checkpointing OFF for this session (final-review
42
- # F8: a huge un-ignored tree would otherwise re-stall every write
43
- # tool); manual checkpoint/rollback still try.
39
+ # The time machine snapshots BEFORE every mutation -- and must never
40
+ # block or fail the actual work. A SINGLE transient failure must not
41
+ # disable the session (final-review F8): note_auto_result latches
42
+ # auto-checkpointing OFF only after several CONSECUTIVE failures; a
43
+ # success re-enables it. Manual checkpoint/rollback always try.
44
44
  try:
45
- if self.shadow.checkpoint(f"pre:{tool}") is None:
46
- self.shadow.auto_ok = False
45
+ _cp = self.shadow.checkpoint(f"pre:{tool}")
46
+ _note = getattr(self.shadow, "note_auto_result", None)
47
+ if _note is not None:
48
+ _note(_cp is not None)
47
49
  except Exception:
48
- self.shadow.auto_ok = False
50
+ _note = getattr(self.shadow, "note_auto_result", None)
51
+ if _note is not None:
52
+ _note(False)
49
53
  try:
50
54
  fn = getattr(self, f"_t_{tool}", None)
51
55
  if fn is None:
@@ -249,3 +249,30 @@ def test_failed_add_never_fakes_a_checkpoint(tmp_path, monkeypatch):
249
249
  def test_shadow_dir_is_private(tmp_path):
250
250
  sg, _root = _sg(tmp_path)
251
251
  assert (os.stat(sg.git_dir).st_mode & 0o777) == 0o700
252
+
253
+
254
+ def test_auto_latch_survives_a_single_transient_failure(tmp_path):
255
+ sg, _root = _sg(tmp_path)
256
+ sg.note_auto_result(False) # one transient miss
257
+ assert sg.auto_ok is True # NOT disabled on a single failure
258
+ sg.note_auto_result(True) # a success clears the streak
259
+ assert sg.auto_ok is True and sg._auto_fail_streak == 0
260
+
261
+
262
+ def test_auto_latch_trips_after_consecutive_failures(tmp_path):
263
+ sg, _root = _sg(tmp_path)
264
+ for _ in range(sg._AUTO_FAIL_LATCH):
265
+ sg.note_auto_result(False)
266
+ assert sg.auto_ok is False # latched after N consecutive
267
+ sg.note_auto_result(True) # recovery re-enables
268
+ assert sg.auto_ok is True
269
+
270
+
271
+ def test_describe_surfaces_paused_autocheckpoint(tmp_path):
272
+ sg, root = _sg(tmp_path)
273
+ _write(root, "a.txt", "x"); sg.checkpoint("c1")
274
+ for _ in range(sg._AUTO_FAIL_LATCH):
275
+ sg.note_auto_result(False)
276
+ out = sg.describe()
277
+ assert out.startswith("⚠ Auto-checkpointing is paused")
278
+ assert "cp-1" in out # the list still follows the warning
@@ -205,6 +205,8 @@ class _FakeShadow:
205
205
  def checkpoint(self, label=""):
206
206
  self.labels.append(label)
207
207
  return {"id": "abc1234", "n": len(self.labels), "label": label, "changed": True}
208
+ def note_auto_result(self, ok):
209
+ pass
208
210
  def diff(self, since=""):
209
211
  return f"DIFF since={since or 'HEAD'}"
210
212
  def rollback(self, to):
@@ -265,21 +267,43 @@ def test_reversibility_tools_honest_without_shadow(tmp_path):
265
267
  assert not r["ok"] and "unavailable" in r["content"]
266
268
 
267
269
 
268
- def test_auto_checkpoint_latches_off_after_failure(tmp_path):
270
+ def test_auto_checkpoint_latches_off_after_consecutive_failures(tmp_path):
271
+ # Adapted from the P4 latch test (final-review F8): a SINGLE failed AUTO
272
+ # snapshot must no longer latch auto-checkpointing off; it now takes
273
+ # _AUTO_FAIL_LATCH CONSECUTIVE failures, driven through the real
274
+ # ShadowGit.note_auto_result via the executor.
269
275
  from webbee.tools import LocalToolExecutor
276
+ from webbee.checkpoints import ShadowGit
270
277
 
271
- class _Flaky:
272
- def __init__(self):
273
- self.calls = 0
274
- self.auto_ok = True
275
- def checkpoint(self, label=""):
276
- self.calls += 1
277
- return None # simulated add/commit failure
278
-
279
- sh = _Flaky()
280
- ex = LocalToolExecutor(str(tmp_path), shadow=sh)
281
- ex.run("write_file", {"path": "a.txt", "content": "1"})
282
- ex.run("write_file", {"path": "b.txt", "content": "2"})
283
- ex.run("write_file", {"path": "c.txt", "content": "3"})
284
- assert sh.calls == 1 # latched after the first failure
285
- assert sh.auto_ok is False
278
+ sg = ShadowGit(str(tmp_path), "rk_f8_latch", cache_dir=str(tmp_path / "c3"))
279
+ assert sg.ensure()
280
+ calls = {"n": 0}
281
+ def _always_fails(label=""):
282
+ calls["n"] += 1
283
+ return None # every AUTO snapshot fails
284
+ sg.checkpoint = _always_fails
285
+ ex = LocalToolExecutor(str(tmp_path), shadow=sg)
286
+
287
+ for i in range(sg._AUTO_FAIL_LATCH - 1):
288
+ ex.run("write_file", {"path": f"f{i}.txt", "content": str(i)})
289
+ assert sg.auto_ok is True # still enabled before the Nth failure
290
+
291
+ ex.run("write_file", {"path": "final.txt", "content": "x"})
292
+ assert calls["n"] == sg._AUTO_FAIL_LATCH # latched only after N consecutive failures
293
+ assert sg.auto_ok is False
294
+
295
+
296
+ def test_executor_single_transient_does_not_latch(tmp_path):
297
+ from webbee.tools import LocalToolExecutor
298
+ from webbee.checkpoints import ShadowGit
299
+ sg = ShadowGit(str(tmp_path), "rk_f8", cache_dir=str(tmp_path / "c"))
300
+ assert sg.ensure()
301
+ calls = {"n": 0}
302
+ def _flaky(label=""):
303
+ calls["n"] += 1
304
+ return None if calls["n"] == 1 else {"id": "x", "n": calls["n"], "label": label, "changed": True}
305
+ sg.checkpoint = _flaky
306
+ ex = LocalToolExecutor(str(tmp_path), shadow=sg)
307
+ ex.run("write_file", {"path": "a.txt", "content": "1"}) # transient miss
308
+ ex.run("write_file", {"path": "b.txt", "content": "2"}) # success -> streak reset
309
+ assert sg.auto_ok is True # still enabled
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes