webbee 0.3.5__tar.gz → 0.3.7__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 (83) hide show
  1. {webbee-0.3.5 → webbee-0.3.7}/CHANGELOG.md +17 -0
  2. {webbee-0.3.5 → webbee-0.3.7}/PKG-INFO +1 -1
  3. {webbee-0.3.5 → webbee-0.3.7}/pyproject.toml +1 -1
  4. webbee-0.3.7/src/webbee/__init__.py +1 -0
  5. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/render.py +14 -0
  6. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/repl.py +48 -12
  7. webbee-0.3.7/src/webbee/session.py +512 -0
  8. webbee-0.3.7/src/webbee/steer.py +106 -0
  9. webbee-0.3.7/src/webbee/thread.py +68 -0
  10. {webbee-0.3.5 → webbee-0.3.7}/tests/test_marathon.py +2 -0
  11. {webbee-0.3.5 → webbee-0.3.7}/tests/test_render.py +18 -0
  12. {webbee-0.3.5 → webbee-0.3.7}/tests/test_repl.py +114 -0
  13. {webbee-0.3.5 → webbee-0.3.7}/tests/test_session.py +269 -0
  14. webbee-0.3.7/tests/test_steer.py +266 -0
  15. webbee-0.3.7/tests/test_thread.py +266 -0
  16. webbee-0.3.5/src/webbee/__init__.py +0 -1
  17. webbee-0.3.5/src/webbee/session.py +0 -361
  18. webbee-0.3.5/src/webbee/thread.py +0 -29
  19. webbee-0.3.5/tests/test_thread.py +0 -131
  20. {webbee-0.3.5 → webbee-0.3.7}/.github/workflows/publish.yml +0 -0
  21. {webbee-0.3.5 → webbee-0.3.7}/.gitignore +0 -0
  22. {webbee-0.3.5 → webbee-0.3.7}/LICENSE +0 -0
  23. {webbee-0.3.5 → webbee-0.3.7}/README.md +0 -0
  24. {webbee-0.3.5 → webbee-0.3.7}/install.sh +0 -0
  25. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/account.py +0 -0
  26. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/banner_art.py +0 -0
  27. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/checkpoints.py +0 -0
  28. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/cli.py +0 -0
  29. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/clipboard.py +0 -0
  30. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/commands.py +0 -0
  31. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/config.py +0 -0
  32. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/details.py +0 -0
  33. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/events.py +0 -0
  34. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/frames.py +0 -0
  35. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/__init__.py +0 -0
  36. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/chunker.py +0 -0
  37. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/embed.py +0 -0
  38. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/graph.py +0 -0
  39. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/indexer.py +0 -0
  40. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/models.py +0 -0
  41. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/query.py +0 -0
  42. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/service.py +0 -0
  43. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/store.py +0 -0
  44. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/vectors.py +0 -0
  45. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/intel/watch.py +0 -0
  46. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/output_pane.py +0 -0
  47. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/remote.py +0 -0
  48. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/repo.py +0 -0
  49. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/sessions.py +0 -0
  50. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/stream.py +0 -0
  51. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/tools.py +0 -0
  52. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/tui.py +0 -0
  53. {webbee-0.3.5 → webbee-0.3.7}/src/webbee/update.py +0 -0
  54. {webbee-0.3.5 → webbee-0.3.7}/tests/__init__.py +0 -0
  55. {webbee-0.3.5 → webbee-0.3.7}/tests/test_account.py +0 -0
  56. {webbee-0.3.5 → webbee-0.3.7}/tests/test_checkpoints.py +0 -0
  57. {webbee-0.3.5 → webbee-0.3.7}/tests/test_cli.py +0 -0
  58. {webbee-0.3.5 → webbee-0.3.7}/tests/test_clipboard.py +0 -0
  59. {webbee-0.3.5 → webbee-0.3.7}/tests/test_commands.py +0 -0
  60. {webbee-0.3.5 → webbee-0.3.7}/tests/test_config.py +0 -0
  61. {webbee-0.3.5 → webbee-0.3.7}/tests/test_cpc_contract_stable.py +0 -0
  62. {webbee-0.3.5 → webbee-0.3.7}/tests/test_details.py +0 -0
  63. {webbee-0.3.5 → webbee-0.3.7}/tests/test_events.py +0 -0
  64. {webbee-0.3.5 → webbee-0.3.7}/tests/test_freeze_fix.py +0 -0
  65. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_chunker.py +0 -0
  66. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_embed.py +0 -0
  67. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_graph.py +0 -0
  68. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_indexer.py +0 -0
  69. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_no_numpy.py +0 -0
  70. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_query.py +0 -0
  71. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_service.py +0 -0
  72. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_store.py +0 -0
  73. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_vectors.py +0 -0
  74. {webbee-0.3.5 → webbee-0.3.7}/tests/test_intel_watch.py +0 -0
  75. {webbee-0.3.5 → webbee-0.3.7}/tests/test_packaging.py +0 -0
  76. {webbee-0.3.5 → webbee-0.3.7}/tests/test_repo.py +0 -0
  77. {webbee-0.3.5 → webbee-0.3.7}/tests/test_sessions.py +0 -0
  78. {webbee-0.3.5 → webbee-0.3.7}/tests/test_stream.py +0 -0
  79. {webbee-0.3.5 → webbee-0.3.7}/tests/test_tools.py +0 -0
  80. {webbee-0.3.5 → webbee-0.3.7}/tests/test_tui.py +0 -0
  81. {webbee-0.3.5 → webbee-0.3.7}/tests/test_tui_hardening.py +0 -0
  82. {webbee-0.3.5 → webbee-0.3.7}/tests/test_update.py +0 -0
  83. {webbee-0.3.5 → webbee-0.3.7}/tests/test_version.py +0 -0
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.7
4
+
5
+ - Terminal liveness: a consent answered from Telegram/panel no longer
6
+ freezes the terminal — the `approve? y/n` prompt dismisses itself
7
+ ("answered from another surface") and the turn keeps rendering live.
8
+ - Idle pickup: an open, idle terminal now picks up instructions sent from
9
+ Telegram/panel within seconds and runs them as normal turns, tagged with
10
+ their origin. If no terminal is open, the instruction waits (up to an
11
+ hour) and Telegram says so instead of failing.
12
+
13
+ ## 0.3.6
14
+
15
+ - Boot replay shows only the conversation: flattened tool traffic
16
+ (`[tool_use …]` / `[tool_result] …` blocks the agent keeps as its own
17
+ working memory) no longer floods the boot screen — each replayed message
18
+ shows its human-readable part, pure tool messages are skipped.
19
+
3
20
  ## 0.3.5
4
21
 
5
22
  - Surface-aware conversation: turns steered from Telegram/the panel render
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webbee
3
- Version: 0.3.5
3
+ Version: 0.3.7
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.5"
3
+ version = "0.3.7"
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.7"
@@ -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()
@@ -134,6 +134,7 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
134
134
  intel = None # assigned by _boot -- IntelService, or None (off/base-install/boot failure)
135
135
  shadow = None # assigned by _boot -- ShadowGit, or None (git unavailable / boot failure)
136
136
  watcher_task = None # assigned by _boot -- background watchfiles task, cancelled on exit
137
+ steer_task = None # assigned by _boot -- idle-steer poller (webbee.steer), cancelled on exit
137
138
 
138
139
  def _cycle() -> None:
139
140
  state["mode"] = next_mode(state["mode"])
@@ -269,21 +270,42 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
269
270
 
270
271
  # A task for the agent.
271
272
  _sink.user_echo(line)
273
+ await _run_turn(line)
274
+ return "continue"
275
+
276
+ async def _run_turn(line: str, surface: str = "") -> None:
277
+ """ONE agent turn -- the SAME path for a typed line and an idle-steer
278
+ pickup (liveness v2 §B), which threads the queued item's origin
279
+ `surface` into the turn so the kernel stamps provenance start-path.
280
+ Only the echo differs at the call sites: user_echo for a typed line,
281
+ foreign_turn for a remote one."""
272
282
  _sink.begin_turn()
283
+ kw = {"surface": surface} if surface else {}
273
284
  try:
274
- text = await agent.run(line, _sink, marathon=not once, goal=(line if not once else ""))
285
+ text = await agent.run(line, _sink, marathon=not once,
286
+ goal=(line if not once else ""), **kw)
275
287
  except (KeyboardInterrupt, asyncio.CancelledError):
276
288
  _sink.abort()
277
289
  _sink.note("Interrupted.")
278
- return "continue"
290
+ return
279
291
  except Exception as e: # network/auth/etc — never crash the REPL
280
292
  _sink.note(f"Error: {type(e).__name__}: {e}")
281
- return "continue"
293
+ return
282
294
  _sink.end_turn(text)
283
- return "continue"
295
+
296
+ async def _steer_submit(text: str, surface: str) -> None:
297
+ """webbee.steer hands a drained remote instruction here: render it as
298
+ the remote user's own line, then run it as a normal turn."""
299
+ _sink.foreign_turn(surface, "user", text)
300
+ await _run_turn(text, surface=surface)
301
+
302
+ def _cancel_background() -> None:
303
+ for _t in (watcher_task, steer_task):
304
+ if _t is not None:
305
+ _t.cancel()
284
306
 
285
307
  async def _boot(s) -> None:
286
- nonlocal _sink, agent, intel, watcher_task, shadow
308
+ nonlocal _sink, agent, intel, watcher_task, shadow, steer_task
287
309
  _sink = s
288
310
  # Cache git branch OFF the event loop (subprocess.run blocks it). Only
289
311
  # /status reads it; recomputing it per input line froze the dock.
@@ -296,13 +318,19 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
296
318
  # never a boot blocker (network down, no such session yet, etc.).
297
319
  try:
298
320
  from imperal_mcp.client import ImperalClient
299
- from webbee.thread import fetch_recent_thread, truncate_for_display
321
+ from webbee.thread import (conversational_text, fetch_recent_thread,
322
+ truncate_for_display)
300
323
  _iid = await ImperalClient(cfg, token_provider).whoami()
301
324
  _msgs = await fetch_recent_thread(cfg, token_provider, f"marathon-{_iid}-rboot")
325
+ _shown = 0
302
326
  for _m in _msgs[-40:]:
327
+ _text = conversational_text(_m.get("content", ""))
328
+ if not _text:
329
+ continue # pure tool traffic -- mind-food, not conversation
303
330
  _sink.foreign_turn(_m.get("surface", "terminal"), _m.get("role", ""),
304
- truncate_for_display(_m.get("content", "")))
305
- if _msgs:
331
+ truncate_for_display(_text))
332
+ _shown += 1
333
+ if _shown:
306
334
  _sink.note("— live —")
307
335
  except Exception:
308
336
  pass # replay is best-effort; never block boot
@@ -327,6 +355,16 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
327
355
  except Exception:
328
356
  shadow = None
329
357
  agent = agent_factory(cfg, token_provider, workspace, state["mode"])
358
+ # Liveness v2 §B: idle-steer pickup. All poll/drain logic lives in
359
+ # webbee.steer -- this is wiring only: the sink's live turn state
360
+ # gates polling, the agent's session id (once a turn has run) is the
361
+ # gateway truth, and _steer_submit is the normal turn path.
362
+ from webbee import steer as _steer
363
+ steer_task = asyncio.ensure_future(_steer.poll_idle_steer(
364
+ cfg, token_provider, workspace=workspace, marathon=not once,
365
+ is_busy=lambda: bool(getattr(_sink, "is_busy", None) and _sink.is_busy()),
366
+ live_session_id=lambda: getattr(agent, "session_id", ""),
367
+ submit=_steer_submit))
330
368
 
331
369
  if use_dock:
332
370
  ok = False
@@ -363,8 +401,7 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
363
401
  stop_turn=lambda: agent.stop(),
364
402
  )
365
403
  finally:
366
- if watcher_task is not None:
367
- watcher_task.cancel()
404
+ _cancel_background()
368
405
  except Exception:
369
406
  ok = False
370
407
  finally:
@@ -398,5 +435,4 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
398
435
  if await _handle(line) == "exit":
399
436
  return
400
437
  finally:
401
- if watcher_task is not None:
402
- watcher_task.cancel()
438
+ _cancel_background()