webbee 0.3.3__tar.gz → 0.3.4__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.3 → webbee-0.3.4}/CHANGELOG.md +8 -0
- {webbee-0.3.3 → webbee-0.3.4}/PKG-INFO +1 -1
- {webbee-0.3.3 → webbee-0.3.4}/pyproject.toml +1 -1
- webbee-0.3.4/src/webbee/__init__.py +1 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/commands.py +3 -0
- webbee-0.3.4/src/webbee/remote.py +41 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/repl.py +18 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_commands.py +12 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_repl.py +33 -0
- webbee-0.3.3/src/webbee/__init__.py +0 -1
- {webbee-0.3.3 → webbee-0.3.4}/.github/workflows/publish.yml +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/.gitignore +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/LICENSE +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/README.md +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/install.sh +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/account.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/banner_art.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/checkpoints.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/cli.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/clipboard.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/config.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/details.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/events.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/frames.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/chunker.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/embed.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/graph.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/indexer.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/models.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/query.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/service.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/store.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/intel/watch.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/output_pane.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/render.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/repo.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/session.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/sessions.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/stream.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/tools.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/tui.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/src/webbee/update.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/__init__.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_account.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_checkpoints.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_cli.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_clipboard.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_config.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_details.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_events.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_freeze_fix.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_chunker.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_embed.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_graph.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_indexer.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_query.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_service.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_store.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_vectors.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_intel_watch.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_marathon.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_packaging.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_render.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_repo.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_session.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_sessions.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_stream.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_tools.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_tui.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_tui_hardening.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_update.py +0 -0
- {webbee-0.3.3 → webbee-0.3.4}/tests/test_version.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
- `/notify [tg|panel|both|off]` — mirror this coding session to Telegram
|
|
6
|
+
and/or the panel, and let either surface steer it back (approve/deny
|
|
7
|
+
prompts, follow-up turns). No argument shows the current routing. Talks to
|
|
8
|
+
the gateway's per-session remote-control state; a network hiccup notes
|
|
9
|
+
cleanly instead of crashing the dock.
|
|
10
|
+
|
|
3
11
|
## 0.3.3
|
|
4
12
|
|
|
5
13
|
- Mouse-garbage / dead-keyboard fix (Linux, occasionally macOS): the dock now
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.4"
|
|
@@ -16,6 +16,7 @@ _HELP = """Commands:
|
|
|
16
16
|
/sessions list active sessions (this + other devices)
|
|
17
17
|
/sessions revoke <#> revoke a session by its number
|
|
18
18
|
/logout-others sign out every session except this one
|
|
19
|
+
/notify [tg|panel|both|off] mirror this session to Telegram/panel & steer it back
|
|
19
20
|
/exit (=/quit) quit"""
|
|
20
21
|
|
|
21
22
|
|
|
@@ -85,6 +86,8 @@ def dispatch(line: str, ctx: CommandContext) -> SlashResult:
|
|
|
85
86
|
return SlashResult(handled=True, action="checkpoints")
|
|
86
87
|
if cmd == "/rollback":
|
|
87
88
|
return SlashResult(handled=True, action="rollback", arg=" ".join(args))
|
|
89
|
+
if cmd == "/notify":
|
|
90
|
+
return SlashResult(handled=True, action="notify", arg=(args[0].lower() if args else ""))
|
|
88
91
|
if cmd == "/mode":
|
|
89
92
|
if not args:
|
|
90
93
|
return SlashResult(handled=True, action="mode", new_mode=None,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Remote-control client — read/set this coding session's mirror+steer routing
|
|
2
|
+
(Telegram / panel) via the gateway. Best-effort: network errors never raise."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
_ARG_TO_STATE = {
|
|
6
|
+
"tg": {"enabled": True, "mirror": ["telegram"], "steer": ["telegram"]},
|
|
7
|
+
"panel": {"enabled": True, "mirror": ["panel"], "steer": []},
|
|
8
|
+
"both": {"enabled": True, "mirror": ["telegram", "panel"], "steer": ["telegram"]},
|
|
9
|
+
"off": {"enabled": False, "mirror": [], "steer": []},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def get_remote(cfg, token_provider, session_id: str) -> dict:
|
|
14
|
+
import httpx
|
|
15
|
+
token = await token_provider()
|
|
16
|
+
async with httpx.AsyncClient(base_url=cfg.api_url, timeout=10) as c:
|
|
17
|
+
r = await c.get(f"/v1/agent/sessions/{session_id}/remote",
|
|
18
|
+
headers={"Authorization": f"Bearer {token}"})
|
|
19
|
+
r.raise_for_status()
|
|
20
|
+
return (r.json() or {}).get("state", {})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def set_remote(cfg, token_provider, session_id: str, arg: str) -> dict:
|
|
24
|
+
import httpx
|
|
25
|
+
body = _ARG_TO_STATE.get(arg)
|
|
26
|
+
if body is None:
|
|
27
|
+
raise ValueError(arg)
|
|
28
|
+
token = await token_provider()
|
|
29
|
+
async with httpx.AsyncClient(base_url=cfg.api_url, timeout=10) as c:
|
|
30
|
+
r = await c.put(f"/v1/agent/sessions/{session_id}/remote", json=body,
|
|
31
|
+
headers={"Authorization": f"Bearer {token}"})
|
|
32
|
+
r.raise_for_status()
|
|
33
|
+
return (r.json() or {}).get("state", {})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def describe(state: dict) -> str:
|
|
37
|
+
if not state.get("enabled"):
|
|
38
|
+
return "Remote control: OFF."
|
|
39
|
+
m = ", ".join(state.get("mirror", [])) or "—"
|
|
40
|
+
s = ", ".join(state.get("steer", [])) or "—"
|
|
41
|
+
return f"Remote control: ON · mirror → {m} · steer ← {s}"
|
|
@@ -238,6 +238,24 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
238
238
|
_r = await asyncio.to_thread(shadow.rollback, res.arg)
|
|
239
239
|
_sink.note(str(_r.get("content", "")))
|
|
240
240
|
return "continue"
|
|
241
|
+
if res.action == "notify":
|
|
242
|
+
from webbee import remote as _remote
|
|
243
|
+
sid = getattr(agent, "session_id", "")
|
|
244
|
+
if not sid:
|
|
245
|
+
_sink.note("Start a coding turn first, then /notify to route it.")
|
|
246
|
+
return "continue"
|
|
247
|
+
try:
|
|
248
|
+
if res.arg in ("tg", "panel", "both", "off"):
|
|
249
|
+
st = await _remote.set_remote(cfg, token_provider, sid, res.arg)
|
|
250
|
+
elif res.arg:
|
|
251
|
+
_sink.note("Usage: /notify [tg|panel|both|off]")
|
|
252
|
+
return "continue"
|
|
253
|
+
else:
|
|
254
|
+
st = await _remote.get_remote(cfg, token_provider, sid)
|
|
255
|
+
_sink.note(_remote.describe(st))
|
|
256
|
+
except Exception as e:
|
|
257
|
+
_sink.note(f"Remote control unavailable: {type(e).__name__}")
|
|
258
|
+
return "continue"
|
|
241
259
|
if res.action == "clear":
|
|
242
260
|
_sink.clear()
|
|
243
261
|
_sink.note(res.message)
|
|
@@ -140,3 +140,15 @@ def test_help_mentions_the_time_machine():
|
|
|
140
140
|
from webbee.commands import dispatch
|
|
141
141
|
res = dispatch("/help", _anyctx())
|
|
142
142
|
assert "/checkpoints" in res.message and "/rollback" in res.message
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_notify_command_dispatches():
|
|
146
|
+
from webbee.commands import dispatch
|
|
147
|
+
assert dispatch("/notify", _anyctx()).action == "notify"
|
|
148
|
+
r = dispatch("/notify tg", _anyctx())
|
|
149
|
+
assert r.action == "notify" and r.arg == "tg"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_help_mentions_notify():
|
|
153
|
+
from webbee.commands import dispatch
|
|
154
|
+
assert "/notify" in dispatch("/help", _anyctx()).message
|
|
@@ -404,6 +404,39 @@ def test_boot_skips_intel_when_disabled_in_config(monkeypatch):
|
|
|
404
404
|
assert _SpyAgent.last_intel is None
|
|
405
405
|
|
|
406
406
|
|
|
407
|
+
# ── /notify remote control (Task 8) ───────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
def test_notify_sets_remote_and_shows_state(monkeypatch):
|
|
410
|
+
agent = StepAgent()
|
|
411
|
+
|
|
412
|
+
async def fake_set(cfg, tp, sid, arg):
|
|
413
|
+
assert sid == "sess-1" and arg == "tg"
|
|
414
|
+
return {"enabled": True, "mirror": ["telegram"], "steer": ["telegram"]}
|
|
415
|
+
monkeypatch.setattr("webbee.remote.set_remote", fake_set)
|
|
416
|
+
|
|
417
|
+
sink, agent = _run(read_line=_lines("/notify tg", "/exit"), agent=agent)
|
|
418
|
+
assert any("ON" in n for n in sink.notes)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def test_notify_without_session_prompts_to_start_a_turn():
|
|
422
|
+
sink, agent = _run(read_line=_lines("/notify tg", "/exit"), agent=StepAgent(session_id=""))
|
|
423
|
+
assert any("Start a coding turn first" in n for n in sink.notes)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_notify_bad_arg_shows_usage():
|
|
427
|
+
sink, agent = _run(read_line=_lines("/notify bogus", "/exit"), agent=StepAgent())
|
|
428
|
+
assert any("Usage: /notify" in n for n in sink.notes)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def test_notify_network_error_notes_cleanly(monkeypatch):
|
|
432
|
+
async def fake_set(cfg, tp, sid, arg):
|
|
433
|
+
raise RuntimeError("connection refused")
|
|
434
|
+
monkeypatch.setattr("webbee.remote.set_remote", fake_set)
|
|
435
|
+
|
|
436
|
+
sink, agent = _run(read_line=_lines("/notify tg", "/exit"), agent=StepAgent())
|
|
437
|
+
assert any("Remote control unavailable" in n for n in sink.notes)
|
|
438
|
+
|
|
439
|
+
|
|
407
440
|
def test_watcher_started_at_intel_root_not_workspace(monkeypatch, tmp_path):
|
|
408
441
|
"""F1: IntelService.root is the discovered repo root, which can differ
|
|
409
442
|
from the raw cwd (e.g. the REPL launched from a subdir). The watcher must
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.3"
|
|
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
|