monkeybot-cli 0.12.0__tar.gz → 0.13.1__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.1}/PKG-INFO +1 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/pyproject.toml +1 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/skills/monkeybot/SKILL.md +8 -4
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_session.py +101 -4
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/doctor.py +13 -1
- monkeybot_cli-0.13.1/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +67 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +1 -1
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_session.py +24 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_doctor.py +6 -2
- monkeybot_cli-0.12.0/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -31
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/.gitignore +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/skills/monkeybot/references/config-sections.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_file_index.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_local_shell.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_renderer.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_status_bar.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_theme.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_tool_display.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_tui.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/chat_tui_widgets.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/chat.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/loop.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/mcp_login.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/new.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/refresh.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/run_cmd.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/talk.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/trace.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/commands/validate.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/compat.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/config_resolve.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/exit_commands.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/extras_catalog.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/gateway_health.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/main.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/output.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/overrides.txt +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/process_tree.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/providers.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/audio_io.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/client.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/session.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/session_controller.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/runtime_python.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/session_controller.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/terminal_markdown.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_continue.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_e2e.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_errors.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_file_index.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_grounding.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_local_shell.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_renderer_parity.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_status_bar.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_theme.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_tool_display.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_chat_tui.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_cli.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_config_resolve.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_extras_catalog.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_gateway_health.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_realtime_session_controller.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_run_cmd.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_runtime_python.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_scaffold.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_terminal_markdown.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/tests/test_trace_cmd.py +0 -0
- {monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/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):
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: goal
|
|
3
|
+
description: Create one durable objective that continues across scheduled turns until evidence proves it is done.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Goal
|
|
7
|
+
|
|
8
|
+
Use this skill only when the user explicitly invokes `/goal` with an objective
|
|
9
|
+
that may require multiple turns.
|
|
10
|
+
|
|
11
|
+
A goal is complete only when current evidence proves every requirement of the
|
|
12
|
+
objective. Stopping a turn, running out of time, or having a plausible answer
|
|
13
|
+
does not finish it.
|
|
14
|
+
|
|
15
|
+
There is no deadline that finishes the work. The goal stays active across turns
|
|
16
|
+
until the end state is true, or until a budget pause or a persistent failure
|
|
17
|
+
stops the ticks. A budget pause is not completion. The objective stays intact
|
|
18
|
+
and the user can resume it.
|
|
19
|
+
|
|
20
|
+
## Start
|
|
21
|
+
|
|
22
|
+
1. Preserve the user's complete objective without narrowing its scope.
|
|
23
|
+
2. Call `create_goal` exactly once with that objective.
|
|
24
|
+
3. Do the first concrete increment of work in the same turn.
|
|
25
|
+
4. Keep the returned goal id. Every `update_goal` call must include it.
|
|
26
|
+
|
|
27
|
+
## Continue
|
|
28
|
+
|
|
29
|
+
- Goal continuations are scheduled automatically. A webhook can also wake the goal.
|
|
30
|
+
- Treat the stored objective as authoritative. Do not shrink it to work already done.
|
|
31
|
+
- Make measurable progress. Verify results before you even consider completion.
|
|
32
|
+
- Temporary session-busy deferrals are normal and are not goal failures.
|
|
33
|
+
- Persistent execution failures stop the goal after the retry limit.
|
|
34
|
+
- Using up the tick or runtime budget pauses the goal. It does not complete it.
|
|
35
|
+
|
|
36
|
+
## Waiting
|
|
37
|
+
|
|
38
|
+
If you are blocked on a person or an external event (review, approval, CI, a
|
|
39
|
+
publish), call `update_goal` with `status="waiting"`, the goal id, and a
|
|
40
|
+
`reason`. Pass `recheck_after` (for example `2h`) only when you should look
|
|
41
|
+
again even if no event arrives. Do not keep waking on the timer while blocked.
|
|
42
|
+
You cannot pause a goal. That is the user's control.
|
|
43
|
+
|
|
44
|
+
## Complete
|
|
45
|
+
|
|
46
|
+
Before `update_goal` with `status="complete"`, completion is unproven. In a
|
|
47
|
+
separate step, after the evidence tool calls:
|
|
48
|
+
|
|
49
|
+
1. Pull concrete requirements out of the objective and anything it points at
|
|
50
|
+
(files, plans, specs, issues, instructions). Do not shrink the original scope.
|
|
51
|
+
2. For each requirement, name the evidence that would prove it, then look at
|
|
52
|
+
the current state: files, command output, tests, pull-request state, rendered
|
|
53
|
+
UI, runtime behavior.
|
|
54
|
+
3. Judge each item as `proved`, `contradicted`, `incomplete`, `too_weak`, or
|
|
55
|
+
`missing`. A green test counts only if it covers that requirement. Uncertain
|
|
56
|
+
or indirect evidence is not done.
|
|
57
|
+
4. The audit has to prove completion. Failing to notice leftover work is not enough.
|
|
58
|
+
|
|
59
|
+
Intent, partial progress, memory of earlier turns, and a reasonable-sounding
|
|
60
|
+
answer are not proof.
|
|
61
|
+
|
|
62
|
+
Call `update_goal` with `status="complete"` and an `audit` array only after
|
|
63
|
+
every item is `proved` and no required work remains. The call must be its own
|
|
64
|
+
step so the transcript already contains the evidence. Ending the turn does not
|
|
65
|
+
call `update_goal`. The goal stays active and the full objective stays intact.
|
|
66
|
+
|
|
67
|
+
`status="active"` is only for resuming a goal the user paused.
|
|
@@ -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
|
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: goal
|
|
3
|
-
description: Create one durable objective that continues across scheduled turns until completed.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Goal
|
|
7
|
-
|
|
8
|
-
Use this skill only when the user explicitly invokes `/goal` with an objective
|
|
9
|
-
that may require multiple turns.
|
|
10
|
-
|
|
11
|
-
## Start
|
|
12
|
-
|
|
13
|
-
1. Preserve the user's complete objective without narrowing its scope.
|
|
14
|
-
2. Call `create_goal` exactly once with that objective.
|
|
15
|
-
3. Do the first concrete increment of work in the same turn.
|
|
16
|
-
4. Keep the returned goal id; every `update_goal` call must include it.
|
|
17
|
-
|
|
18
|
-
## Continue
|
|
19
|
-
|
|
20
|
-
- Goal continuations are scheduled automatically.
|
|
21
|
-
- Treat the stored objective as authoritative.
|
|
22
|
-
- Make measurable progress and verify results before declaring completion.
|
|
23
|
-
- Temporary session-busy deferrals are normal and are not goal failures.
|
|
24
|
-
- Persistent execution failures stop the goal after the retry limit so it
|
|
25
|
-
cannot consume resources indefinitely.
|
|
26
|
-
|
|
27
|
-
## Complete
|
|
28
|
-
|
|
29
|
-
Call `update_goal` with the returned `goal_id` and `status="complete"` only
|
|
30
|
-
after evidence proves every requirement is satisfied. Otherwise leave the goal
|
|
31
|
-
active. Use the `/goals` control plane to pause, resume, or stop it manually.
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/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.1}/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.1}/src/monkeybot_cli/scaffold_defaults/Dockerfile
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/__init__.py
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/dockerignore
RENAMED
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/src/monkeybot_cli/scaffold_defaults/env.example
RENAMED
|
File without changes
|
|
File without changes
|
{monkeybot_cli-0.12.0 → monkeybot_cli-0.13.1}/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.1}/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
|