webbee 0.3.15__tar.gz → 0.3.16__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.3.15 → webbee-0.3.16}/CHANGELOG.md +7 -0
- {webbee-0.3.15 → webbee-0.3.16}/PKG-INFO +1 -1
- {webbee-0.3.15 → webbee-0.3.16}/pyproject.toml +1 -1
- webbee-0.3.16/src/webbee/__init__.py +1 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/render.py +36 -8
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/repl.py +5 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_render.py +49 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_repl.py +9 -0
- webbee-0.3.15/src/webbee/__init__.py +0 -1
- {webbee-0.3.15 → webbee-0.3.16}/.github/workflows/publish.yml +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/.gitignore +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/LICENSE +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/README.md +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/install.sh +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/account.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/banner_art.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/boot.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/checkpoints.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/cli.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/clipboard.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/coding_context.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/commands.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/config.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/consent.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/details.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/events.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/frames.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/chunker.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/embed.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/graph.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/indexer.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/models.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/query.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/service.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/store.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/intel/watch.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/output_pane.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/queue_panel.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/remote.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/repo.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/session.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/sessions.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/steer.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/stream.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/thread.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/todo_panel.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/tokens.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/tools.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/tui.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/src/webbee/update.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/__init__.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_account.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_checkpoints.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_cli.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_clipboard.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_commands.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_config.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_details.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_events.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_freeze_fix.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_chunker.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_embed.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_graph.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_indexer.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_query.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_service.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_store.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_vectors.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_intel_watch.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_marathon.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_packaging.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_repo.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_session.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_sessions.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_steer.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_stream.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_thread.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_todo_panel.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_tokens.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_tools.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_tui.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_tui_hardening.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_update.py +0 -0
- {webbee-0.3.15 → webbee-0.3.16}/tests/test_version.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.16
|
|
4
|
+
|
|
5
|
+
- A message typed while Webbee is working now shows in the queue panel
|
|
6
|
+
exactly once: the queue reconciles every entry by its id, so a retried
|
|
7
|
+
delivery or a send that only looked failed no longer produces a duplicate
|
|
8
|
+
row.
|
|
9
|
+
|
|
3
10
|
## 0.3.15
|
|
4
11
|
|
|
5
12
|
- Type a follow-up while Webbee is working and it now flies into the current
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.16"
|
|
@@ -139,6 +139,13 @@ class RichSink:
|
|
|
139
139
|
# queue panel reads THIS list object every redraw — mutate in place
|
|
140
140
|
# only (append/del/clear), never rebind.
|
|
141
141
|
self.remote_pending: list = []
|
|
142
|
+
# The repl's OWN type-ahead deque (the SAME object tui mutates),
|
|
143
|
+
# shared by reference at boot. remote_queued reconciles against it:
|
|
144
|
+
# a kernel task_queued echo carrying a local QueuedLine's steer_iid
|
|
145
|
+
# is positive proof the inject landed — the local twin is removed so
|
|
146
|
+
# one message is one row with ONE owner (the kernel). None until the
|
|
147
|
+
# repl wires it (minimal sinks / unit tests without a queue).
|
|
148
|
+
self.local_pending = None
|
|
142
149
|
# The current coding checklist (sticky todo panel, 0.3.15) — the twin
|
|
143
150
|
# of remote_pending: todos() replaces its contents in place on every
|
|
144
151
|
# todo frame; the dock's todo panel reads THIS list object every
|
|
@@ -325,25 +332,46 @@ class RichSink:
|
|
|
325
332
|
queue panel the instant it queues — tagged `[origin]`, "as if typed".
|
|
326
333
|
DISPLAY-ONLY: it renders above the local rows but is never pullable
|
|
327
334
|
(↑/click) — the kernel owns it; only a `task_dequeued` (or turn end)
|
|
328
|
-
removes it.
|
|
335
|
+
removes it. This is the ONE reconciliation chokepoint (0.3.16): the
|
|
336
|
+
steer_iid minted at enqueue time dedups the display too — a frame
|
|
337
|
+
delivered twice (publish retry / SSE resume) never doubles a row, and
|
|
338
|
+
the echo of a terminal-injected line whose POST merely LOOKED failed
|
|
339
|
+
removes its local fallback twin (the echo is positive proof it
|
|
340
|
+
landed; kernel-owned = one row). Empty iid (legacy kernels) always
|
|
341
|
+
appends — text is never a dedup key."""
|
|
342
|
+
iid = str(iid or "")
|
|
343
|
+
if iid:
|
|
344
|
+
if any(r.get("iid") == iid for r in self.remote_pending):
|
|
345
|
+
self._nudge()
|
|
346
|
+
return
|
|
347
|
+
lp = self.local_pending
|
|
348
|
+
if lp is not None:
|
|
349
|
+
twin = next((q for q in lp if getattr(q, "iid", "") == iid), None)
|
|
350
|
+
if twin is not None:
|
|
351
|
+
try:
|
|
352
|
+
lp.remove(twin)
|
|
353
|
+
except ValueError:
|
|
354
|
+
pass
|
|
329
355
|
self.remote_pending.append({"origin": _clean(str(origin or "")),
|
|
330
356
|
"text": _clean(str(text or "")),
|
|
331
|
-
"iid":
|
|
357
|
+
"iid": iid})
|
|
332
358
|
self._nudge()
|
|
333
359
|
|
|
334
360
|
def remote_dequeued(self, origin: str, iid: str) -> None:
|
|
335
361
|
"""The kernel drained (or dedup-dropped) one queued item
|
|
336
|
-
(`task_dequeued` frame): remove its panel row
|
|
337
|
-
|
|
338
|
-
FIFO, so
|
|
339
|
-
|
|
340
|
-
|
|
362
|
+
(`task_dequeued` frame): remove its panel row by `iid`. The
|
|
363
|
+
oldest-same-origin fallback is LEGACY-ONLY (empty iid — the kernel
|
|
364
|
+
queue is FIFO, so the oldest row of that origin is the one that just
|
|
365
|
+
started): a NON-empty unmatched iid is a no-op, because the kernel
|
|
366
|
+
emits one dequeue per pop INCLUDING dedup-dropped twins, and a twin's
|
|
367
|
+
second dequeue must never eat a different same-origin row. Nothing
|
|
368
|
+
matched → no-op (the row was already cleared or never announced)."""
|
|
341
369
|
iid = str(iid or "")
|
|
342
370
|
origin = _clean(str(origin or ""))
|
|
343
371
|
rows = self.remote_pending
|
|
344
372
|
idx = next((i for i, r in enumerate(rows) if iid and r.get("iid") == iid),
|
|
345
373
|
None)
|
|
346
|
-
if idx is None:
|
|
374
|
+
if idx is None and not iid:
|
|
347
375
|
idx = next((i for i, r in enumerate(rows) if r.get("origin") == origin),
|
|
348
376
|
None)
|
|
349
377
|
if idx is None:
|
|
@@ -344,6 +344,11 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
344
344
|
async def _boot(s) -> None:
|
|
345
345
|
nonlocal _sink, agent, intel, watcher_task, shadow, steer_task
|
|
346
346
|
_sink = s
|
|
347
|
+
# Queue-panel single-source dedup (0.3.16): hand the sink the SAME
|
|
348
|
+
# type-ahead deque tui mutates, so a kernel task_queued echo can
|
|
349
|
+
# promote a landed local twin (matched by steer_iid) into the one
|
|
350
|
+
# kernel-owned row. Reference share — never a copy.
|
|
351
|
+
s.local_pending = pending_queue
|
|
347
352
|
# Cache git branch OFF the event loop (subprocess.run blocks it). Only
|
|
348
353
|
# /status reads it; recomputing it per input line froze the dock.
|
|
349
354
|
state["git_branch"] = await asyncio.to_thread(_git_branch, workspace)
|
|
@@ -603,6 +603,55 @@ def test_end_turn_and_abort_clear_remote_rows_in_place():
|
|
|
603
603
|
assert rows == [] and s.remote_pending is rows
|
|
604
604
|
|
|
605
605
|
|
|
606
|
+
# ── 0.3.16: queue-panel single-source dedup (steer_iid reconciliation) ────────
|
|
607
|
+
# The steer_iid minted at enqueue time is the ONE key both legs carry; the
|
|
608
|
+
# display layer now consumes it too: a duplicated task_queued frame
|
|
609
|
+
# (at-least-once delivery) never doubles a row, and the kernel echo of a
|
|
610
|
+
# terminal-injected line REPLACES its local fallback twin — one message,
|
|
611
|
+
# one owner (the kernel), one panel row.
|
|
612
|
+
|
|
613
|
+
def test_remote_queued_ignores_duplicate_frame_same_iid():
|
|
614
|
+
s = _sink()
|
|
615
|
+
s.remote_queued("terminal", "ship it", "i1")
|
|
616
|
+
s.remote_queued("terminal", "ship it", "i1") # publish retry / SSE resume
|
|
617
|
+
assert [r["iid"] for r in s.remote_pending] == ["i1"]
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def test_remote_queued_empty_iid_never_dedups():
|
|
621
|
+
# Legacy kernels emit steer_iid="" — identical re-typed lines must keep
|
|
622
|
+
# appending (text is NOT a dedup key).
|
|
623
|
+
s = _sink()
|
|
624
|
+
s.remote_queued("telegram", "same text", "")
|
|
625
|
+
s.remote_queued("telegram", "same text", "")
|
|
626
|
+
assert len(s.remote_pending) == 2
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def test_remote_queued_promotes_local_twin_to_kernel_owned():
|
|
630
|
+
# A failed-LOOKING inject that actually landed leaves a local QueuedLine
|
|
631
|
+
# twin; the kernel echo (same iid) is positive proof it landed — the
|
|
632
|
+
# local row goes, the message shows exactly once (kernel-owned).
|
|
633
|
+
from collections import deque
|
|
634
|
+
|
|
635
|
+
from webbee.tui import QueuedLine
|
|
636
|
+
s = _sink()
|
|
637
|
+
lp = deque([QueuedLine("ship it", "i1"), QueuedLine("other", "i9")])
|
|
638
|
+
s.local_pending = lp
|
|
639
|
+
s.remote_queued("terminal", "ship it", "i1")
|
|
640
|
+
assert [str(x) for x in lp] == ["other"]
|
|
641
|
+
assert [r["iid"] for r in s.remote_pending] == ["i1"]
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def test_remote_dequeued_nonempty_iid_unmatched_is_noop():
|
|
645
|
+
# task_dequeued twins (the kernel emits one per pop, dedup-dropped twins
|
|
646
|
+
# included) must never eat a DIFFERENT same-origin row: the origin-FIFO
|
|
647
|
+
# fallback is legacy-only (empty iid).
|
|
648
|
+
s = _sink()
|
|
649
|
+
s.remote_queued("terminal", "a", "i1")
|
|
650
|
+
s.remote_queued("terminal", "b", "i2")
|
|
651
|
+
s.remote_dequeued("terminal", "i-unknown")
|
|
652
|
+
assert [r["iid"] for r in s.remote_pending] == ["i1", "i2"]
|
|
653
|
+
|
|
654
|
+
|
|
606
655
|
# ── 0.3.14: the terminal-local yes/no confirm (autopilot safe-asymmetry) ──────
|
|
607
656
|
|
|
608
657
|
def test_ask_yes_no_sync_fallback_strict_yes_only():
|
|
@@ -395,6 +395,15 @@ def test_boot_survives_intel_import_failure_base_install(monkeypatch):
|
|
|
395
395
|
assert sink.turns == [] # boot completed cleanly, no crash mid-boot
|
|
396
396
|
|
|
397
397
|
|
|
398
|
+
def test_boot_shares_local_queue_with_sink():
|
|
399
|
+
# The repl hands the sink the SAME type-ahead deque tui mutates, so the
|
|
400
|
+
# kernel's task_queued echo can promote a local twin into the single
|
|
401
|
+
# kernel-owned row (queue-panel single-source dedup, 0.3.16).
|
|
402
|
+
from collections import deque
|
|
403
|
+
sink, agent = _run(read_line=_lines("/exit"))
|
|
404
|
+
assert isinstance(getattr(sink, "local_pending", None), deque)
|
|
405
|
+
|
|
406
|
+
|
|
398
407
|
def test_boot_skips_intel_when_disabled_in_config(monkeypatch):
|
|
399
408
|
monkeypatch.setattr("webbee.repl.AgentSession", _SpyAgent)
|
|
400
409
|
called = []
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.15"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|