webbee 0.3.6__tar.gz → 0.3.8__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.6 → webbee-0.3.8}/CHANGELOG.md +20 -0
- {webbee-0.3.6 → webbee-0.3.8}/PKG-INFO +1 -1
- {webbee-0.3.6 → webbee-0.3.8}/pyproject.toml +1 -1
- webbee-0.3.8/src/webbee/__init__.py +1 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/render.py +14 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/repl.py +47 -13
- webbee-0.3.8/src/webbee/session.py +512 -0
- webbee-0.3.8/src/webbee/steer.py +112 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/thread.py +20 -1
- webbee-0.3.8/src/webbee/tokens.py +39 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/tui.py +17 -7
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_marathon.py +2 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_render.py +18 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_repl.py +122 -2
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_session.py +269 -0
- webbee-0.3.8/tests/test_steer.py +266 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_thread.py +113 -0
- webbee-0.3.8/tests/test_tokens.py +57 -0
- webbee-0.3.6/src/webbee/__init__.py +0 -1
- webbee-0.3.6/src/webbee/session.py +0 -361
- {webbee-0.3.6 → webbee-0.3.8}/.github/workflows/publish.yml +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/.gitignore +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/LICENSE +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/README.md +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/install.sh +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/account.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/banner_art.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/checkpoints.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/cli.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/clipboard.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/commands.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/config.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/details.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/events.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/frames.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/chunker.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/embed.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/graph.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/indexer.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/models.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/query.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/service.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/store.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/intel/watch.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/output_pane.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/remote.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/repo.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/sessions.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/stream.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/tools.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/src/webbee/update.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/__init__.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_account.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_checkpoints.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_cli.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_clipboard.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_commands.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_config.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_details.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_events.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_freeze_fix.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_chunker.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_embed.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_graph.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_indexer.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_query.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_service.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_store.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_vectors.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_intel_watch.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_packaging.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_repo.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_sessions.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_stream.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_tools.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_tui.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_tui_hardening.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_update.py +0 -0
- {webbee-0.3.6 → webbee-0.3.8}/tests/test_version.py +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.8
|
|
4
|
+
|
|
5
|
+
- No more sign-in races: token refresh is serialized and retries once after
|
|
6
|
+
a sibling terminal rotates the session — multiple open terminals no longer
|
|
7
|
+
knock each other out with "session expired".
|
|
8
|
+
- No more dock lockup: a turn that fails (for example, an expired session)
|
|
9
|
+
now clears the "working" state, and every key keeps responding even if the
|
|
10
|
+
turn state ever goes stale. The idle pickup poller also backs off instead
|
|
11
|
+
of hammering a signed-out session.
|
|
12
|
+
|
|
13
|
+
## 0.3.7
|
|
14
|
+
|
|
15
|
+
- Terminal liveness: a consent answered from Telegram/panel no longer
|
|
16
|
+
freezes the terminal — the `approve? y/n` prompt dismisses itself
|
|
17
|
+
("answered from another surface") and the turn keeps rendering live.
|
|
18
|
+
- Idle pickup: an open, idle terminal now picks up instructions sent from
|
|
19
|
+
Telegram/panel within seconds and runs them as normal turns, tagged with
|
|
20
|
+
their origin. If no terminal is open, the instruction waits (up to an
|
|
21
|
+
hour) and Telegram says so instead of failing.
|
|
22
|
+
|
|
3
23
|
## 0.3.6
|
|
4
24
|
|
|
5
25
|
- Boot replay shows only the conversation: flattened tool traffic
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.8"
|
|
@@ -233,6 +233,20 @@ class RichSink:
|
|
|
233
233
|
self.console.print(_pad(Text(f"{who}{tag}: {_clean(text)}", style=_BEE)))
|
|
234
234
|
self._nudge()
|
|
235
235
|
|
|
236
|
+
def consent_dismissed(self, note: str) -> None:
|
|
237
|
+
"""Liveness A: the pending consent was answered on ANOTHER surface
|
|
238
|
+
(e.g. relayed from Telegram) — the kernel park is over. Retire the
|
|
239
|
+
pinned prompt so the dock leaves `approve? y/n`: cancel the armed
|
|
240
|
+
Future (consent_pending() flips False, so the toolbar repaints out of
|
|
241
|
+
the consent state on the next _nudge) and print ONE note-style line
|
|
242
|
+
so the scrollback shows why the y/n prompt vanished."""
|
|
243
|
+
if self._consent is not None and not self._consent.done():
|
|
244
|
+
self._consent.cancel()
|
|
245
|
+
self._consent = None
|
|
246
|
+
self._consent_summary = ""
|
|
247
|
+
self.console.print(_pad(Text(_clean(note), style=_BEE)))
|
|
248
|
+
self._nudge()
|
|
249
|
+
|
|
236
250
|
def clear(self) -> None:
|
|
237
251
|
"""/clear: wipe the pane/screen + reset the session counters."""
|
|
238
252
|
self.console.clear()
|
|
@@ -58,8 +58,8 @@ async def run_marathon(cfg, mode: str, goal: str, *, sink=None, auth=None,
|
|
|
58
58
|
|
|
59
59
|
workspace = os.getcwd()
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
from webbee.tokens import make_token_provider
|
|
62
|
+
token_provider = make_token_provider(cfg, auth)
|
|
63
63
|
|
|
64
64
|
if agent_factory is None:
|
|
65
65
|
agent_factory = lambda c, tp, ws, m: AgentSession(c, tp, ws, m) # noqa: E731
|
|
@@ -72,9 +72,11 @@ async def run_marathon(cfg, mode: str, goal: str, *, sink=None, auth=None,
|
|
|
72
72
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
73
73
|
await agent.stop()
|
|
74
74
|
sink.note("Interrupted.")
|
|
75
|
+
sink.end_turn("") # clear busy (poller starvation guard)
|
|
75
76
|
return ""
|
|
76
77
|
except Exception as e: # network/auth/etc — never crash
|
|
77
78
|
sink.note(f"Error: {type(e).__name__}: {e}")
|
|
79
|
+
sink.end_turn("") # clear busy: a stuck 'working' also starves the idle-steer poller
|
|
78
80
|
return ""
|
|
79
81
|
sink.end_turn(text)
|
|
80
82
|
return text
|
|
@@ -122,8 +124,8 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
122
124
|
|
|
123
125
|
workspace = os.getcwd()
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
from webbee.tokens import make_token_provider
|
|
128
|
+
token_provider = make_token_provider(cfg, auth)
|
|
127
129
|
|
|
128
130
|
# Prod dock path = the default reader + a real tty + no injected sink; tests
|
|
129
131
|
# inject sink/read_line and take the plain fallback loop.
|
|
@@ -134,6 +136,7 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
134
136
|
intel = None # assigned by _boot -- IntelService, or None (off/base-install/boot failure)
|
|
135
137
|
shadow = None # assigned by _boot -- ShadowGit, or None (git unavailable / boot failure)
|
|
136
138
|
watcher_task = None # assigned by _boot -- background watchfiles task, cancelled on exit
|
|
139
|
+
steer_task = None # assigned by _boot -- idle-steer poller (webbee.steer), cancelled on exit
|
|
137
140
|
|
|
138
141
|
def _cycle() -> None:
|
|
139
142
|
state["mode"] = next_mode(state["mode"])
|
|
@@ -269,21 +272,44 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
269
272
|
|
|
270
273
|
# A task for the agent.
|
|
271
274
|
_sink.user_echo(line)
|
|
275
|
+
await _run_turn(line)
|
|
276
|
+
return "continue"
|
|
277
|
+
|
|
278
|
+
async def _run_turn(line: str, surface: str = "") -> None:
|
|
279
|
+
"""ONE agent turn -- the SAME path for a typed line and an idle-steer
|
|
280
|
+
pickup (liveness v2 §B), which threads the queued item's origin
|
|
281
|
+
`surface` into the turn so the kernel stamps provenance start-path.
|
|
282
|
+
Only the echo differs at the call sites: user_echo for a typed line,
|
|
283
|
+
foreign_turn for a remote one."""
|
|
272
284
|
_sink.begin_turn()
|
|
285
|
+
kw = {"surface": surface} if surface else {}
|
|
273
286
|
try:
|
|
274
|
-
text = await agent.run(line, _sink, marathon=not once,
|
|
287
|
+
text = await agent.run(line, _sink, marathon=not once,
|
|
288
|
+
goal=(line if not once else ""), **kw)
|
|
275
289
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
276
290
|
_sink.abort()
|
|
277
291
|
_sink.note("Interrupted.")
|
|
278
|
-
|
|
292
|
+
_sink.end_turn("") # clear busy (poller starvation guard)
|
|
293
|
+
return
|
|
279
294
|
except Exception as e: # network/auth/etc — never crash the REPL
|
|
280
295
|
_sink.note(f"Error: {type(e).__name__}: {e}")
|
|
281
|
-
|
|
296
|
+
_sink.end_turn("") # clear busy: a stuck 'working' also starves the idle-steer poller
|
|
297
|
+
return
|
|
282
298
|
_sink.end_turn(text)
|
|
283
|
-
|
|
299
|
+
|
|
300
|
+
async def _steer_submit(text: str, surface: str) -> None:
|
|
301
|
+
"""webbee.steer hands a drained remote instruction here: render it as
|
|
302
|
+
the remote user's own line, then run it as a normal turn."""
|
|
303
|
+
_sink.foreign_turn(surface, "user", text)
|
|
304
|
+
await _run_turn(text, surface=surface)
|
|
305
|
+
|
|
306
|
+
def _cancel_background() -> None:
|
|
307
|
+
for _t in (watcher_task, steer_task):
|
|
308
|
+
if _t is not None:
|
|
309
|
+
_t.cancel()
|
|
284
310
|
|
|
285
311
|
async def _boot(s) -> None:
|
|
286
|
-
nonlocal _sink, agent, intel, watcher_task, shadow
|
|
312
|
+
nonlocal _sink, agent, intel, watcher_task, shadow, steer_task
|
|
287
313
|
_sink = s
|
|
288
314
|
# Cache git branch OFF the event loop (subprocess.run blocks it). Only
|
|
289
315
|
# /status reads it; recomputing it per input line froze the dock.
|
|
@@ -333,6 +359,16 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
333
359
|
except Exception:
|
|
334
360
|
shadow = None
|
|
335
361
|
agent = agent_factory(cfg, token_provider, workspace, state["mode"])
|
|
362
|
+
# Liveness v2 §B: idle-steer pickup. All poll/drain logic lives in
|
|
363
|
+
# webbee.steer -- this is wiring only: the sink's live turn state
|
|
364
|
+
# gates polling, the agent's session id (once a turn has run) is the
|
|
365
|
+
# gateway truth, and _steer_submit is the normal turn path.
|
|
366
|
+
from webbee import steer as _steer
|
|
367
|
+
steer_task = asyncio.ensure_future(_steer.poll_idle_steer(
|
|
368
|
+
cfg, token_provider, workspace=workspace, marathon=not once,
|
|
369
|
+
is_busy=lambda: bool(getattr(_sink, "is_busy", None) and _sink.is_busy()),
|
|
370
|
+
live_session_id=lambda: getattr(agent, "session_id", ""),
|
|
371
|
+
submit=_steer_submit))
|
|
336
372
|
|
|
337
373
|
if use_dock:
|
|
338
374
|
ok = False
|
|
@@ -369,8 +405,7 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
369
405
|
stop_turn=lambda: agent.stop(),
|
|
370
406
|
)
|
|
371
407
|
finally:
|
|
372
|
-
|
|
373
|
-
watcher_task.cancel()
|
|
408
|
+
_cancel_background()
|
|
374
409
|
except Exception:
|
|
375
410
|
ok = False
|
|
376
411
|
finally:
|
|
@@ -404,5 +439,4 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
404
439
|
if await _handle(line) == "exit":
|
|
405
440
|
return
|
|
406
441
|
finally:
|
|
407
|
-
|
|
408
|
-
watcher_task.cancel()
|
|
442
|
+
_cancel_background()
|