monkeybot-cli 0.12.0__tar.gz → 0.13.0__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.
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/PKG-INFO +1 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/pyproject.toml +1 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/skills/monkeybot/SKILL.md +8 -4
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_session.py +101 -4
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/doctor.py +13 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +1 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_session.py +24 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_doctor.py +6 -2
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/.gitignore +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/skills/monkeybot/references/config-sections.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_file_index.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_local_shell.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_renderer.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_status_bar.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_theme.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_tool_display.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_tui.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/chat_tui_widgets.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/chat.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/loop.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/mcp_login.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/new.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/refresh.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/run_cmd.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/talk.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/trace.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/commands/validate.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/compat.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/config_resolve.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/exit_commands.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/extras_catalog.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/gateway_health.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/main.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/output.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/overrides.txt +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/process_tree.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/providers.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/audio_io.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/client.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/session.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/session_controller.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/runtime_python.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/session_controller.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/terminal_markdown.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_continue.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_e2e.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_errors.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_file_index.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_grounding.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_local_shell.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_renderer_parity.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_status_bar.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_theme.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_tool_display.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_chat_tui.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_cli.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_config_resolve.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_extras_catalog.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_gateway_health.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_realtime_session_controller.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_run_cmd.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_runtime_python.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_scaffold.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_terminal_markdown.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/tests/test_trace_cmd.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/uv.lock +0 -0
|
@@ -31,11 +31,15 @@ Tell the user what you're installing and why. On macOS/Linux you can run these y
|
|
|
31
31
|
### 2. Install the global CLI
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
34
|
+
# browser-harness pins Pillow 12. Core 3.12 needs Pillow 13, and
|
|
35
|
+
# `uv tool install` ignores the package override unless this file is passed.
|
|
36
|
+
# Without it the resolver stays on core 3.10.0.
|
|
37
|
+
printf 'pillow>=13\n' > /tmp/monkeybot-overrides.txt
|
|
38
|
+
uv tool install --overrides /tmp/monkeybot-overrides.txt monkeybot-cli
|
|
35
39
|
monkeybot --help
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
Upgrade later with `uv tool
|
|
42
|
+
Upgrade later with `uv tool install --reinstall --overrides /tmp/monkeybot-overrides.txt monkeybot-cli`.
|
|
39
43
|
|
|
40
44
|
**Do not** tell day-1 users to clone `human-plus-machine/monkeybot` unless they are contributing to the harness itself (see Contributing below).
|
|
41
45
|
|
|
@@ -47,7 +51,7 @@ If the user arrived via skills.sh:
|
|
|
47
51
|
npx skills add human-plus-machine/monkeybot --skill monkeybot
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
…they have **this skill only**. Walk them through Tier 0: install `uv` → `uv tool install monkeybot-cli` → verify `monkeybot --help`. Then continue to Tier 1.
|
|
54
|
+
…they have **this skill only**. Walk them through Tier 0: install `uv` → `uv tool install --overrides /tmp/monkeybot-overrides.txt monkeybot-cli` (write `pillow>=13` into that file first) → verify `monkeybot --help`. Then continue to Tier 1.
|
|
51
55
|
|
|
52
56
|
## How config works (tell the user this first)
|
|
53
57
|
|
|
@@ -115,7 +119,7 @@ uv sync
|
|
|
115
119
|
| `ollama-local` | None required — `OLLAMA_BASE_URL` (default `http://localhost:11434`). Requests send `keep_alive` 24h so prefix KV cache survives idle. | `monkeybot[ollama]` |
|
|
116
120
|
| `ollama` | Legacy auto-route (key + blank URL = cloud; explicit URL wins) | `monkeybot[ollama]` |
|
|
117
121
|
|
|
118
|
-
**Agent-first dependencies.** The CLI is thin — it does **not** install provider/storage extras globally. `monkeybot new` scaffolds a `pyproject.toml` with the selected provider (and any `--with` extras). Run plain `uv sync` in the agent directory. `monkeybot run` / `chat` spawn the gateway from that project's interpreter (`.venv/bin/python`, else `uv run python`), and `doctor` checks extras in that same interpreter. For a config-only tree (just `monkeybot_config/`, no `pyproject.toml`) the gateway uses the CLI's interpreter when it already has MonkeyBot 3.x (and MemPalace if memory is on). If memory is enabled and that interpreter cannot import MemPalace, `run` / `chat` provision a cached CLI-managed venv under `~/.cache/monkeybot/runtimes/` holding `monkeybot[memory]` pinned to the running core (never rewrites a `pyproject.toml`). `doctor` reuses that cache when it is already present. Otherwise extras must be installed in the CLI env (`uv tool install --with 'monkeybot[<extra>]' monkeybot-cli`).
|
|
122
|
+
**Agent-first dependencies.** The CLI is thin — it does **not** install provider/storage extras globally. `monkeybot new` scaffolds a `pyproject.toml` with the selected provider (and any `--with` extras). Run plain `uv sync` in the agent directory. `monkeybot run` / `chat` spawn the gateway from that project's interpreter (`.venv/bin/python`, else `uv run python`), and `doctor` checks extras in that same interpreter. For a config-only tree (just `monkeybot_config/`, no `pyproject.toml`) the gateway uses the CLI's interpreter when it already has MonkeyBot 3.x (and MemPalace if memory is on). If memory is enabled and that interpreter cannot import MemPalace, `run` / `chat` provision a cached CLI-managed venv under `~/.cache/monkeybot/runtimes/` holding `monkeybot[memory]` pinned to the running core (never rewrites a `pyproject.toml`). `doctor` reuses that cache when it is already present. Otherwise extras must be installed in the CLI env (`uv tool install --reinstall --overrides /tmp/monkeybot-overrides.txt --with 'monkeybot[<extra>]' monkeybot-cli`, with `pillow>=13` in that file). A bare `uv tool install` resolves core 3.10.0.
|
|
119
123
|
|
|
120
124
|
`doctor` is the source of truth for credentials and extras — when in doubt, run it and read the `remediation` field (add `monkeybot[<extra>]` to agent deps + `uv sync`).
|
|
121
125
|
|
|
@@ -241,6 +241,8 @@ class ChatSessionController:
|
|
|
241
241
|
self._reconnecting = False
|
|
242
242
|
self._last_event_id: int | None = None
|
|
243
243
|
self._abandoned: deque[str] = deque(maxlen=64)
|
|
244
|
+
self._deferred: list[Any] = []
|
|
245
|
+
self._idle_task: asyncio.Task[None] | None = None
|
|
244
246
|
self._turn_abort = asyncio.Event()
|
|
245
247
|
self._active_request_id: str | None = None
|
|
246
248
|
self._hitl_future: asyncio.Future[HitlAnswer] | None = None
|
|
@@ -292,6 +294,7 @@ class ChatSessionController:
|
|
|
292
294
|
self._reconnecting = False
|
|
293
295
|
self._last_event_id = None
|
|
294
296
|
self._stream_task = asyncio.create_task(self._consume_stream())
|
|
297
|
+
self._start_idle_watch()
|
|
295
298
|
self._emit("session_ready", session_id=self.session_id)
|
|
296
299
|
self._emit("connection_state", state="connected")
|
|
297
300
|
await self._emit_transcript_backfill()
|
|
@@ -504,6 +507,14 @@ class ChatSessionController:
|
|
|
504
507
|
return
|
|
505
508
|
|
|
506
509
|
request_id = str(uuid.uuid4())
|
|
510
|
+
if self._active_request_id is not None:
|
|
511
|
+
self._emit("session_busy")
|
|
512
|
+
return
|
|
513
|
+
await self._stop_idle_watch()
|
|
514
|
+
if self._active_request_id is not None:
|
|
515
|
+
self._emit("session_busy")
|
|
516
|
+
self._start_idle_watch()
|
|
517
|
+
return
|
|
507
518
|
self._active_request_id = request_id
|
|
508
519
|
self._last_cancel_ok = None
|
|
509
520
|
self._turn_abort.clear()
|
|
@@ -513,15 +524,23 @@ class ChatSessionController:
|
|
|
513
524
|
json={"request_id": request_id, "message": message},
|
|
514
525
|
)
|
|
515
526
|
if reply.status_code == 409:
|
|
527
|
+
self._active_request_id = None
|
|
516
528
|
self._emit("session_busy")
|
|
529
|
+
self._start_idle_watch()
|
|
517
530
|
return
|
|
518
531
|
reply.raise_for_status()
|
|
519
532
|
except httpx.HTTPError as exc:
|
|
533
|
+
self._active_request_id = None
|
|
520
534
|
self._emit("error", message=format_http_error("Reply", exc))
|
|
535
|
+
self._start_idle_watch()
|
|
521
536
|
return
|
|
522
537
|
|
|
523
538
|
self._emit("turn_started", request_id=request_id)
|
|
524
|
-
|
|
539
|
+
try:
|
|
540
|
+
await self._run_turn(request_id)
|
|
541
|
+
finally:
|
|
542
|
+
if self._active_request_id is None and not self._closed:
|
|
543
|
+
self._start_idle_watch()
|
|
525
544
|
|
|
526
545
|
async def _dequeue_payload(self) -> str | None | Literal[False]:
|
|
527
546
|
"""Return payload, None for stream end, False on poll timeout."""
|
|
@@ -544,9 +563,13 @@ class ChatSessionController:
|
|
|
544
563
|
self._warn(f"skipping unparseable event: {exc}")
|
|
545
564
|
return None
|
|
546
565
|
|
|
547
|
-
async def _run_turn(self, request_id: str) -> None:
|
|
566
|
+
async def _run_turn(self, request_id: str, *, prelude: list[Any] | None = None) -> None:
|
|
548
567
|
assert self._client is not None and self.session_id is not None
|
|
549
568
|
state = _TurnState()
|
|
569
|
+
for evt in prelude or []:
|
|
570
|
+
if state.done or self._turn_abort.is_set():
|
|
571
|
+
break
|
|
572
|
+
await self._dispatch_turn_event(evt, request_id, state)
|
|
550
573
|
while not state.done and not self._turn_abort.is_set():
|
|
551
574
|
payload = await self._dequeue_payload()
|
|
552
575
|
if payload is False:
|
|
@@ -577,6 +600,71 @@ class ChatSessionController:
|
|
|
577
600
|
if not self._stream_alive and not self._reconnecting:
|
|
578
601
|
self._emit("stream_ended")
|
|
579
602
|
|
|
603
|
+
def _start_idle_watch(self) -> None:
|
|
604
|
+
"""Render turns the gateway starts while the prompt is idle."""
|
|
605
|
+
if self._closed or self._active_request_id is not None:
|
|
606
|
+
return
|
|
607
|
+
task = self._idle_task
|
|
608
|
+
if task is not None and not task.done():
|
|
609
|
+
return
|
|
610
|
+
self._idle_task = asyncio.create_task(self._watch_idle(), name="chat-idle-watch")
|
|
611
|
+
|
|
612
|
+
async def _stop_idle_watch(self) -> None:
|
|
613
|
+
task = self._idle_task
|
|
614
|
+
if task is None or task.done():
|
|
615
|
+
self._idle_task = None
|
|
616
|
+
return
|
|
617
|
+
if self._active_request_id is not None:
|
|
618
|
+
return
|
|
619
|
+
task.cancel()
|
|
620
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
621
|
+
await task
|
|
622
|
+
self._idle_task = None
|
|
623
|
+
|
|
624
|
+
async def _watch_idle(self) -> None:
|
|
625
|
+
"""Show a background-subagent wake (or any other turn) the user did not send."""
|
|
626
|
+
try:
|
|
627
|
+
while not self._closed and self._active_request_id is None:
|
|
628
|
+
await self._play_deferred()
|
|
629
|
+
if self._closed or self._active_request_id is not None:
|
|
630
|
+
return
|
|
631
|
+
payload = await self._dequeue_payload()
|
|
632
|
+
if payload is False:
|
|
633
|
+
continue
|
|
634
|
+
if payload is None:
|
|
635
|
+
return
|
|
636
|
+
if self._active_request_id is not None:
|
|
637
|
+
self._event_queue.put_nowait(payload)
|
|
638
|
+
return
|
|
639
|
+
evt = self._decode_event(payload)
|
|
640
|
+
if evt is None:
|
|
641
|
+
continue
|
|
642
|
+
rid = getattr(evt, "request_id", "") or ""
|
|
643
|
+
if not rid or rid in self._abandoned:
|
|
644
|
+
continue
|
|
645
|
+
self._deferred.append(evt)
|
|
646
|
+
except asyncio.CancelledError:
|
|
647
|
+
raise
|
|
648
|
+
|
|
649
|
+
async def _play_deferred(self) -> None:
|
|
650
|
+
while self._deferred and not self._closed and self._active_request_id is None:
|
|
651
|
+
first = self._deferred.pop(0)
|
|
652
|
+
rid = getattr(first, "request_id", "") or ""
|
|
653
|
+
if not rid or rid in self._abandoned:
|
|
654
|
+
continue
|
|
655
|
+
batch = [first]
|
|
656
|
+
rest: list[Any] = []
|
|
657
|
+
for evt in self._deferred:
|
|
658
|
+
if getattr(evt, "request_id", "") == rid:
|
|
659
|
+
batch.append(evt)
|
|
660
|
+
else:
|
|
661
|
+
rest.append(evt)
|
|
662
|
+
self._deferred = rest
|
|
663
|
+
self._active_request_id = rid
|
|
664
|
+
self._turn_abort.clear()
|
|
665
|
+
self._emit("turn_started", request_id=rid)
|
|
666
|
+
await self._run_turn(rid, prelude=batch)
|
|
667
|
+
|
|
580
668
|
async def _dispatch_turn_event(self, evt: Any, request_id: str, state: _TurnState) -> None:
|
|
581
669
|
evt_rid = getattr(evt, "request_id", "") or ""
|
|
582
670
|
|
|
@@ -589,8 +677,9 @@ class ChatSessionController:
|
|
|
589
677
|
self._emit("thinking", text="thinking…")
|
|
590
678
|
return
|
|
591
679
|
|
|
592
|
-
if evt_rid != request_id:
|
|
593
|
-
self.
|
|
680
|
+
if evt_rid and evt_rid != request_id:
|
|
681
|
+
if evt_rid not in self._abandoned:
|
|
682
|
+
self._deferred.append(evt)
|
|
594
683
|
return
|
|
595
684
|
|
|
596
685
|
if isinstance(evt, GroundingEvent):
|
|
@@ -885,6 +974,7 @@ class ChatSessionController:
|
|
|
885
974
|
if self._active_request_id is not None:
|
|
886
975
|
self._abandoned.append(self._active_request_id)
|
|
887
976
|
self._active_request_id = None
|
|
977
|
+
await self._stop_idle_watch()
|
|
888
978
|
|
|
889
979
|
if self._stream_task is not None:
|
|
890
980
|
self._stream_task.cancel()
|
|
@@ -913,6 +1003,7 @@ class ChatSessionController:
|
|
|
913
1003
|
await self._teardown_stream()
|
|
914
1004
|
await self._create_session()
|
|
915
1005
|
self._stream_task = asyncio.create_task(self._consume_stream())
|
|
1006
|
+
self._start_idle_watch()
|
|
916
1007
|
self._emit("session_ready", session_id=self.session_id)
|
|
917
1008
|
self._emit("connection_state", state="connected")
|
|
918
1009
|
|
|
@@ -926,6 +1017,7 @@ class ChatSessionController:
|
|
|
926
1017
|
await self._teardown_stream()
|
|
927
1018
|
await self._attach_or_create_session(sid)
|
|
928
1019
|
self._stream_task = asyncio.create_task(self._consume_stream())
|
|
1020
|
+
self._start_idle_watch()
|
|
929
1021
|
self._emit("session_ready", session_id=self.session_id)
|
|
930
1022
|
self._emit("connection_state", state="connected")
|
|
931
1023
|
await self._emit_transcript_backfill()
|
|
@@ -942,6 +1034,11 @@ class ChatSessionController:
|
|
|
942
1034
|
if self._closed:
|
|
943
1035
|
return
|
|
944
1036
|
self._closed = True
|
|
1037
|
+
if self._idle_task is not None:
|
|
1038
|
+
self._idle_task.cancel()
|
|
1039
|
+
with contextlib.suppress(asyncio.CancelledError, asyncio.TimeoutError, TimeoutError):
|
|
1040
|
+
await asyncio.wait_for(self._idle_task, timeout=0.5)
|
|
1041
|
+
self._idle_task = None
|
|
945
1042
|
if self._stream_task is not None:
|
|
946
1043
|
self._stream_task.cancel()
|
|
947
1044
|
with contextlib.suppress(asyncio.CancelledError, asyncio.TimeoutError, TimeoutError):
|
|
@@ -70,6 +70,16 @@ def _agent_defines_project_extra(agent_root: Path, extra: str) -> bool:
|
|
|
70
70
|
return isinstance(optional, dict) and extra in optional
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def _cli_overrides_file() -> Path:
|
|
74
|
+
"""Pillow floor shipped next to this package.
|
|
75
|
+
|
|
76
|
+
``uv tool install`` does not read ``[tool.uv] override-dependencies``.
|
|
77
|
+
browser-harness pins Pillow 12, so a bare install resolves core 3.10
|
|
78
|
+
instead of a core that requires Pillow 13.
|
|
79
|
+
"""
|
|
80
|
+
return Path(__file__).resolve().parents[1] / "overrides.txt"
|
|
81
|
+
|
|
82
|
+
|
|
73
83
|
def _extra_remediation(extra: str, agent_root: Path, runtime) -> str:
|
|
74
84
|
"""Remediation text pointing at the agent project, not the CLI env."""
|
|
75
85
|
if runtime.source in {"cli", MANAGED_RUNTIME_SOURCE}:
|
|
@@ -78,9 +88,11 @@ def _extra_remediation(extra: str, agent_root: Path, runtime) -> str:
|
|
|
78
88
|
if runtime.source == MANAGED_RUNTIME_SOURCE
|
|
79
89
|
else ""
|
|
80
90
|
)
|
|
91
|
+
overrides = _cli_overrides_file()
|
|
81
92
|
return (
|
|
82
93
|
"Config-only tree: install in the CLI env — "
|
|
83
|
-
f"uv tool install --
|
|
94
|
+
f"uv tool install --reinstall --overrides {overrides} "
|
|
95
|
+
f"--with 'monkeybot[{extra}]' monkeybot-cli"
|
|
84
96
|
f"{refresh}"
|
|
85
97
|
)
|
|
86
98
|
if _agent_defines_project_extra(agent_root, extra):
|
|
@@ -39,7 +39,7 @@ workspace-relative.
|
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Replace `<skills_path>` with the
|
|
42
|
+
Replace `<skills_path>` with the `skills_path` returned by `list_skills` (`skills`).
|
|
43
43
|
|
|
44
44
|
Supported `--aspect-ratio` values: `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`.
|
|
45
45
|
|
|
@@ -448,3 +448,27 @@ def test_close_deletes_session_and_captures_report_dir() -> None:
|
|
|
448
448
|
assert client.delete.await_count == 1
|
|
449
449
|
|
|
450
450
|
asyncio.run(_run())
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def test_idle_watch_shows_a_turn_the_user_did_not_send() -> None:
|
|
454
|
+
async def _run() -> None:
|
|
455
|
+
events: list[ChatUiEvent] = []
|
|
456
|
+
controller = ChatSessionController(base="http://localhost:8080", emit=events.append)
|
|
457
|
+
controller.session_id = "s"
|
|
458
|
+
controller._client = object() # type: ignore[assignment]
|
|
459
|
+
controller._event_queue.put_nowait(
|
|
460
|
+
'{"type":"AssistantDelta","request_id":"wake-1","delta":"survey"}'
|
|
461
|
+
)
|
|
462
|
+
controller._event_queue.put_nowait('{"type":"TurnComplete","request_id":"wake-1"}')
|
|
463
|
+
controller._start_idle_watch()
|
|
464
|
+
for _ in range(40):
|
|
465
|
+
if any(event.kind == "turn_complete" for event in events):
|
|
466
|
+
break
|
|
467
|
+
await asyncio.sleep(0.05)
|
|
468
|
+
await controller._stop_idle_watch()
|
|
469
|
+
deltas = [event.payload.get("delta") for event in events if event.kind == "assistant_delta"]
|
|
470
|
+
assert deltas == ["survey"]
|
|
471
|
+
assert any(event.kind == "turn_complete" for event in events)
|
|
472
|
+
assert any(event.kind == "turn_started" for event in events)
|
|
473
|
+
|
|
474
|
+
asyncio.run(_run())
|
|
@@ -46,14 +46,18 @@ def test_extra_remediation_keeps_extra_for_project_optionals(tmp_path: Path) ->
|
|
|
46
46
|
def test_extra_remediation_config_only_cli_env(tmp_path: Path) -> None:
|
|
47
47
|
runtime = SimpleNamespace(source="cli")
|
|
48
48
|
text = _extra_remediation("bedrock", tmp_path, runtime)
|
|
49
|
-
assert "
|
|
49
|
+
assert "--overrides " in text
|
|
50
|
+
assert "overrides.txt" in text
|
|
51
|
+
assert "--with 'monkeybot[bedrock]' monkeybot-cli" in text
|
|
50
52
|
assert "refresh the managed runtime" not in text
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
def test_extra_remediation_config_only_managed_runtime(tmp_path: Path) -> None:
|
|
54
56
|
runtime = SimpleNamespace(source="cli-managed")
|
|
55
57
|
text = _extra_remediation("openai", tmp_path, runtime)
|
|
56
|
-
assert "
|
|
58
|
+
assert "--overrides " in text
|
|
59
|
+
assert "overrides.txt" in text
|
|
60
|
+
assert "--with 'monkeybot[openai]' monkeybot-cli" in text
|
|
57
61
|
assert "refresh the managed runtime" in text
|
|
58
62
|
|
|
59
63
|
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/skills/monkeybot/references/config-sections.md
RENAMED
|
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
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/realtime/session_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/Dockerfile
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/__init__.py
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/dockerignore
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/env.example
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.0}/src/monkeybot_cli/scaffold_defaults/permissions.yaml
RENAMED
|
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
|