monkeybot-cli 0.2.1__tar.gz → 0.5.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.2.1 → monkeybot_cli-0.5.0}/.gitignore +11 -4
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/PKG-INFO +4 -3
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/pyproject.toml +4 -2
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/skills/monkeybot/SKILL.md +8 -5
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/skills/monkeybot/references/config-sections.md +37 -18
- monkeybot_cli-0.5.0/src/monkeybot_cli/__init__.py +8 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/chat_file_index.py +101 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/chat_local_shell.py +178 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_session.py +33 -6
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_status_bar.py +4 -2
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_tool_display.py +34 -128
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_tui.py +447 -21
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_tui_widgets.py +128 -7
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/chat.py +78 -6
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/doctor.py +175 -29
- monkeybot_cli-0.5.0/src/monkeybot_cli/commands/refresh.py +36 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/run_cmd.py +28 -6
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/validate.py +17 -8
- monkeybot_cli-0.5.0/src/monkeybot_cli/compat.py +30 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/config_resolve.py +6 -12
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/extras_catalog.py +2 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/gateway_health.py +71 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/main.py +5 -1
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/opensandbox_lifecycle.py +25 -15
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/providers.py +24 -1
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/gateway_manager.py +5 -2
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/session.py +11 -2
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/session_controller.py +1 -1
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/talk_ui.py +7 -2
- monkeybot_cli-0.5.0/src/monkeybot_cli/runtime_python.py +746 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold.py +435 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/Dockerfile +27 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +16 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +12 -2
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/dockerignore +5 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/env.example +5 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +72 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +214 -0
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +45 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/mcp.json +7 -6
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +91 -39
- monkeybot_cli-0.5.0/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +46 -0
- monkeybot_cli-0.5.0/tests/test_chat_continue.py +198 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_errors.py +1 -0
- monkeybot_cli-0.5.0/tests/test_chat_file_index.py +84 -0
- monkeybot_cli-0.5.0/tests/test_chat_local_shell.py +157 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_session.py +89 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_tui.py +1143 -3
- monkeybot_cli-0.5.0/tests/test_cli.py +319 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_config_resolve.py +12 -1
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_doctor.py +37 -1
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_extras_catalog.py +16 -1
- monkeybot_cli-0.5.0/tests/test_gateway_health.py +163 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_opensandbox_lifecycle.py +2 -2
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_realtime_session_controller.py +18 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_run_cmd.py +15 -4
- monkeybot_cli-0.5.0/tests/test_runtime_python.py +1076 -0
- monkeybot_cli-0.5.0/tests/test_scaffold.py +220 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/uv.lock +287 -61
- monkeybot_cli-0.2.1/src/monkeybot_cli/__init__.py +0 -3
- monkeybot_cli-0.2.1/src/monkeybot_cli/compat.py +0 -7
- monkeybot_cli-0.2.1/src/monkeybot_cli/gateway_health.py +0 -34
- monkeybot_cli-0.2.1/src/monkeybot_cli/runtime_python.py +0 -91
- monkeybot_cli-0.2.1/src/monkeybot_cli/scaffold.py +0 -287
- monkeybot_cli-0.2.1/src/monkeybot_cli/scaffold_defaults/setup-workspace.sh +0 -24
- monkeybot_cli-0.2.1/tests/test_cli.py +0 -132
- monkeybot_cli-0.2.1/tests/test_runtime_python.py +0 -109
- monkeybot_cli-0.2.1/tests/test_scaffold.py +0 -115
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_renderer.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/chat_theme.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/__init__.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/loop.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/new.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/commands/talk.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/exit_commands.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/output.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/__init__.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/audio_io.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/client.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/session_controller.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/src/monkeybot_cli/terminal_markdown.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_e2e.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_grounding.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_renderer_parity.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_status_bar.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_theme.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_chat_tool_display.py +0 -0
- {monkeybot_cli-0.2.1 → monkeybot_cli-0.5.0}/tests/test_terminal_markdown.py +0 -0
|
@@ -9,9 +9,6 @@ internal/
|
|
|
9
9
|
# Local scaffolded harness config (defaults ship in monkeybot-cli scaffold_defaults/)
|
|
10
10
|
/monkeybot_config/
|
|
11
11
|
|
|
12
|
-
# Demo agent live config (materialized from monkeybot_config_example/ by demo_agent/run.sh)
|
|
13
|
-
demo_agent/monkeybot_config/
|
|
14
|
-
|
|
15
12
|
# Live eval run artifacts (only evals/baselines/ is versioned)
|
|
16
13
|
/evals/runs/
|
|
17
14
|
|
|
@@ -33,6 +30,12 @@ build/
|
|
|
33
30
|
|
|
34
31
|
# Data (SQLite DB + memory files — never commit)
|
|
35
32
|
data/
|
|
33
|
+
/memory/
|
|
34
|
+
.monkeybot/
|
|
35
|
+
*.sqlite3
|
|
36
|
+
workspace/.monkeybot/
|
|
37
|
+
*.sqlite
|
|
38
|
+
*.sqlite.writer-pid
|
|
36
39
|
|
|
37
40
|
# Testing & coverage
|
|
38
41
|
.pytest_cache/
|
|
@@ -40,6 +43,7 @@ data/
|
|
|
40
43
|
htmlcov/
|
|
41
44
|
.mypy_cache/
|
|
42
45
|
.ruff_cache/
|
|
46
|
+
.deepeval/
|
|
43
47
|
|
|
44
48
|
# macOS
|
|
45
49
|
.DS_Store
|
|
@@ -61,4 +65,7 @@ docker/data/
|
|
|
61
65
|
code/
|
|
62
66
|
|
|
63
67
|
# Local venv for evals dependency experiments
|
|
64
|
-
.evals-venv/
|
|
68
|
+
.evals-venv/
|
|
69
|
+
|
|
70
|
+
# monkeythink
|
|
71
|
+
.monkeythink/
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: monkeybot-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: CLI to create, configure, validate, and chat with monkeybot agents.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: httpx>=0.27.0
|
|
7
|
-
Requires-Dist: monkeybot
|
|
7
|
+
Requires-Dist: monkeybot-browser-mcp<1,>=0.2.0
|
|
8
|
+
Requires-Dist: monkeybot[cli]<4,>=3.0.0
|
|
8
9
|
Requires-Dist: python-dotenv>=1.0.0
|
|
9
10
|
Requires-Dist: pyyaml>=6.0.2
|
|
10
11
|
Requires-Dist: textual>=8.2.8
|
|
@@ -4,13 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "monkeybot-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "CLI to create, configure, validate, and chat with monkeybot agents."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
11
11
|
# `cli` pulls realtime + typer so `monkeybot talk` works out of the box (text mode).
|
|
12
12
|
# Audio mic/PTT needs the agent (or this env) to also install monkeybot[cli-realtime].
|
|
13
|
-
"monkeybot[cli]>=
|
|
13
|
+
"monkeybot[cli]>=3.0.0,<4",
|
|
14
|
+
"monkeybot-browser-mcp>=0.2.0,<1",
|
|
14
15
|
"httpx>=0.27.0",
|
|
15
16
|
"pyyaml>=6.0.2",
|
|
16
17
|
"python-dotenv>=1.0.0",
|
|
@@ -22,6 +23,7 @@ monkeybot = "monkeybot_cli.main:main"
|
|
|
22
23
|
|
|
23
24
|
[tool.uv.sources]
|
|
24
25
|
monkeybot = { path = "..", editable = true }
|
|
26
|
+
monkeybot-browser-mcp = { path = "../integrations/browser-mcp", editable = true }
|
|
25
27
|
|
|
26
28
|
[dependency-groups]
|
|
27
29
|
dev = [
|
|
@@ -85,7 +85,9 @@ monkeybot new --dest /path/to/bot --provider openai --yes
|
|
|
85
85
|
# monkeybot new --dest /path/to/bot --provider openai --with postgres,sandbox --yes
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Creates `monkeybot_config/`,
|
|
88
|
+
Creates `monkeybot_config/`, read-only `skills/`, writable `workspace/`,
|
|
89
|
+
`memory/`, `.env.example`, a Dockerfile, and an agent `pyproject.toml`.
|
|
90
|
+
Use `--force` only when overwriting is explicitly requested.
|
|
89
91
|
|
|
90
92
|
Then:
|
|
91
93
|
|
|
@@ -111,7 +113,7 @@ uv sync
|
|
|
111
113
|
| `huggingface` | `HF_TOKEN` (or `HUGGINGFACE_API_KEY`) | `monkeybot[huggingface]` |
|
|
112
114
|
| `ollama` | None required — `OLLAMA_BASE_URL` (default `http://localhost:11434`) for a non-default server | `monkeybot[ollama]` |
|
|
113
115
|
|
|
114
|
-
**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
|
|
116
|
+
**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`).
|
|
115
117
|
|
|
116
118
|
`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`).
|
|
117
119
|
|
|
@@ -154,13 +156,13 @@ Decision → config map:
|
|
|
154
156
|
| "I have a custom web UI" | `gateway.cors_allow_origins` |
|
|
155
157
|
| "Search the web" | `web_search.backend` + `.env` keys (Tavily/Firecrawl) |
|
|
156
158
|
| "Run untrusted code" | `sandbox.enabled` + `SANDBOX_API_KEY` |
|
|
157
|
-
| "Use specialist agents" | `subagents
|
|
159
|
+
| "Use specialist agents" | `subagents.personas` + `monkeybot_config/agents/*.md` |
|
|
158
160
|
| "Connect external tools" | `mcp.json` (`mcpServers` object), then `validate --check-mcp` |
|
|
159
161
|
| "Control cost / context size" | `model.*`, `context_curation.*` |
|
|
160
162
|
| "Restrict dangerous commands" | `tools.denied_patterns`, `command_allowlist.yaml` |
|
|
161
163
|
| "Multiple environments" | top-level `includes:` fragments |
|
|
162
164
|
|
|
163
|
-
**Subagents (`task` tool):** share the parent `AGENT.md
|
|
165
|
+
**Subagents (`task` tool):** without `subagent_type`, share the parent `AGENT.md`. With a persona, use that persona's `agent_md`. Relative paths resolve from the bot project root, not `workspace/`. Specialize via `task` / `context` or named personas. For parallel `task` fan-out, prefer Postgres: add `monkeybot[postgres]` to the **agent** `pyproject.toml` dependencies, run `uv sync`, then set `DB_URL=postgresql://...` in `.env`.
|
|
164
166
|
|
|
165
167
|
**Observability** is mostly env + add `monkeybot[observability]` to agent deps + `uv sync` + an OTel collector — not `monkeybot.yaml`. See `docs/observability-runbook.md`.
|
|
166
168
|
|
|
@@ -189,7 +191,8 @@ uv tool install --editable .
|
|
|
189
191
|
|
|
190
192
|
| Command | Purpose |
|
|
191
193
|
|---------|---------|
|
|
192
|
-
| `new` | Scaffold `monkeybot_config/`, `workspace/`, `
|
|
194
|
+
| `new` | Scaffold `monkeybot_config/`, `workspace/`, `memory/`, `skills/`, `pyproject.toml`, `.env.example` |
|
|
195
|
+
| `refresh` | Additive update of packaged YAML defaults on an existing agent (keeps AGENT.md, mcp.json, model) |
|
|
193
196
|
| `validate` | Config + paths + MCP shape (`--check-mcp` for network) |
|
|
194
197
|
| `doctor` | Python, provider extra, credentials, port |
|
|
195
198
|
| `run` | Start SSE gateway subprocess |
|
|
@@ -21,10 +21,11 @@ Validate/doctor: `doctor` → `runtime.port.free`.
|
|
|
21
21
|
| Field | Default | When to change |
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| `agent_md` | `./monkeybot_config/AGENT.md` | Alternate system-prompt location |
|
|
24
|
-
| `memory_storage_uri` | `local://./
|
|
24
|
+
| `memory_storage_uri` | `local://./memory` | `gcs://…` for shared/cloud memory (requires GCP project) |
|
|
25
25
|
| `skills_path` | `./skills` | Point at a different skills tree |
|
|
26
26
|
| `db_url` | `sqlite:///data/monkeybot.db` | **Postgres for parallel subagents** — SQLite hits `database is locked` under concurrency |
|
|
27
27
|
| `auto_schema` | `true` | Set `false` when migrations own the schema (managed Postgres with DML-only runtime user) |
|
|
28
|
+
| `agent_id` | resolved agent root path | **Set explicitly when this agent is relocatable** — moved to a new path, redeployed with a different mount point, or run as multiple replicas that must share one conversation history. Without it, conversation-history scoping (which agent owns which thread, on a shared `db_url`) keys off the filesystem path, so a path change strands existing history. Once set, keep the value stable across deploys. |
|
|
28
29
|
| `mcp_config` | `./monkeybot_config/mcp.json` | Relocate MCP definitions |
|
|
29
30
|
| `command_allowlist_config` | `./monkeybot_config/command_allowlist.yaml` | Relocate the shell allowlist |
|
|
30
31
|
| `workspace_root` | `./workspace` (if present) | Change the file-tool sandbox root |
|
|
@@ -40,8 +41,8 @@ Validate check ids: `paths.agent_md.exists`, `paths.skills_path.exists`, `paths.
|
|
|
40
41
|
| `temperature` | `0.7` | Lower for deterministic output, higher for creative |
|
|
41
42
|
| `max_tokens` | `60000` | Cap per-response length |
|
|
42
43
|
| `thinking_budget` | `-1` | Gemini: `-1` model default, `0` off, `N` token budget. Ollama reasoning models: `-1` server default, `0` off (`reasoning_effort: none`) |
|
|
43
|
-
| `context_window` | `1000000` | Summarization trigger
|
|
44
|
-
| `max_turns` | `
|
|
44
|
+
| `context_window` | `1000000` | Summarization trigger (tokens); also drives soft-spill / `read_file` char budgets |
|
|
45
|
+
| `max_turns` | `1000` | Hard cap on turns per run |
|
|
45
46
|
| `summarization_model` | (main model) | Cheaper model for history summarization (env `CONTEXT_SUMMARIZATION_MODEL`) |
|
|
46
47
|
|
|
47
48
|
Validate check ids: `model.provider.supported`, `model.name.present`. Supported YAML providers: `gemini`/`vertex`, `openai`, `anthropic`, `vertex-claude`, `huggingface`, `ollama`, `aws_bedrock`, `fake`.
|
|
@@ -86,40 +87,48 @@ Recent window by default; LLM curator only when the index is token-heavy. On cur
|
|
|
86
87
|
|
|
87
88
|
When the prompt shows fewer entries than exist, a structural confidence score triggers a `search_memory` nudge. Skill names are always shown in full in the prompt; use `list_skills` to get the skills root path.
|
|
88
89
|
|
|
89
|
-
## `
|
|
90
|
+
## `memory`
|
|
90
91
|
|
|
91
|
-
`enabled:
|
|
92
|
+
MemPalace capture, wake-up, and search teaching are on by default. Turn them off with `memory.enabled: false` or `MONKEYBOT_MEMORY_HOOK_ENABLED=0`. The vector stack is the optional `monkeybot[memory]` extra — omit it (and set `enabled: false`) when you do not want chromadb/onnxruntime.
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
| Field | Default | When to change |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `enabled` | `true` | Set `false` to skip capture, wake-up, and prompt teaching |
|
|
97
|
+
| `backend` | `chroma` | Alternate MemPalace vector backend |
|
|
98
|
+
| `embedding_model` | `embeddinggemma-300m` | Match the embedder the palace was built with |
|
|
99
|
+
|
|
100
|
+
## `subagents`
|
|
94
101
|
|
|
95
|
-
|
|
102
|
+
Global defaults for `task` calls, plus optional named personas:
|
|
96
103
|
|
|
97
104
|
| Field | Default | Notes |
|
|
98
105
|
|---|---|---|
|
|
99
106
|
| `timeout_sec` | `600` | Per-subagent timeout |
|
|
100
|
-
| `max_turns` | `
|
|
107
|
+
| `max_turns` | `1000` | Per-subagent turn cap |
|
|
101
108
|
| `vertex_google_search` | `false` | **Gemini only.** Enables native `google_search` grounding for subagent `task` runs. Config-file only. |
|
|
102
|
-
| `
|
|
103
|
-
|
|
104
|
-
`subagents[]` defines named personas the parent selects via `task(subagent_type=...)`:
|
|
109
|
+
| `personas` | (none) | Named types selected via `task(subagent_type=...)`; each persona sets its own `agent_md` |
|
|
105
110
|
|
|
106
111
|
```yaml
|
|
107
112
|
subagents:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
timeout_sec: 600
|
|
114
|
+
max_turns: 1000
|
|
115
|
+
vertex_google_search: false
|
|
116
|
+
personas:
|
|
117
|
+
- name: researcher
|
|
118
|
+
description: "Deep-dives a topic and returns a structured summary."
|
|
119
|
+
agent_md: ./monkeybot_config/agents/researcher.md
|
|
111
120
|
```
|
|
112
121
|
|
|
113
|
-
Relative paths resolve from the bot project root, not `workspace/`. For parallel fan-out, use Postgres (`db_url`).
|
|
122
|
+
Without a `subagent_type`, the task inherits the parent `AGENT.md` (`paths.agent_md`). Relative paths resolve from the bot project root, not `workspace/`. For parallel fan-out, use Postgres (`db_url`).
|
|
114
123
|
|
|
115
124
|
## `tools`
|
|
116
125
|
|
|
117
126
|
| Field | Default | When to change |
|
|
118
127
|
|---|---|---|
|
|
119
128
|
| `denied_patterns` | (none) | Block substrings in tool args, e.g. `"rm -rf"` (also env `MONKEYBOT_TOOL_DENIED_PATTERNS`) |
|
|
120
|
-
| `read_max_lines`
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
| `read_max_lines` | `5000` | Cap on `read_file` `limit` (**YAML only** — no env override). Default when `limit` is omitted is harness-fixed at **2000** (pass `limit` to request more). |
|
|
130
|
+
|
|
131
|
+
Spill and `read_file` char budgets are derived from `model.context_window` (retired keys `spill_min_chars` / `spill_read_max_lines` / `read_default_lines` warn and are ignored). Context pressure ratios and tool-result budget fractions are fixed in harness code (not YAML/env).
|
|
123
132
|
|
|
124
133
|
For shell-command safety, pair `denied_patterns` with `monkeybot_config/command_allowlist.yaml`.
|
|
125
134
|
|
|
@@ -144,6 +153,16 @@ Tavily/Firecrawl need `TAVILY_API_KEY` / `FIRECRAWL_API_KEY` in `.env`. Doctor c
|
|
|
144
153
|
|
|
145
154
|
Needs `SANDBOX_API_KEY` in `.env`.
|
|
146
155
|
|
|
156
|
+
## `scheduler`
|
|
157
|
+
|
|
158
|
+
Prompt-first scheduled loops (`start_loop`, `/scheduler/loops`). Requires durable storage (`paths.db_url`). Loops are registered at runtime — there is no static `jobs` list in YAML.
|
|
159
|
+
|
|
160
|
+
| Field | Default | When to change |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `enabled` | `false` | `true` runs the tick worker in-process on the gateway (local/dev). Production: leave `false` and run `python -m monkeybot.scheduler` as a separate process |
|
|
163
|
+
|
|
164
|
+
Env override: `MONKEYBOT_SCHEDULER_ENABLED` (`1` \| `true` \| `yes` \| `on`).
|
|
165
|
+
|
|
147
166
|
## `includes`
|
|
148
167
|
|
|
149
168
|
Top-level list of YAML fragments (paths relative to the config file's directory). Later files deep-merge over earlier ones — useful for per-environment overrides:
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Pure helpers for the ``@`` file-mention palette in ``monkeybot chat``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
_AT_TOKEN_RE = re.compile(r"@(\S*)$")
|
|
11
|
+
|
|
12
|
+
_SKIP_DIRS = frozenset(
|
|
13
|
+
{
|
|
14
|
+
".git",
|
|
15
|
+
".venv",
|
|
16
|
+
"venv",
|
|
17
|
+
"node_modules",
|
|
18
|
+
"__pycache__",
|
|
19
|
+
".mypy_cache",
|
|
20
|
+
".pytest_cache",
|
|
21
|
+
".ruff_cache",
|
|
22
|
+
"data",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def detect_at_token(line: str, col: int) -> tuple[int, str] | None:
|
|
28
|
+
"""Return ``(start_col, query)`` if the cursor sits in an ``@token``.
|
|
29
|
+
|
|
30
|
+
Only triggers when the ``@`` starts the line or follows whitespace, so
|
|
31
|
+
mid-word ``@`` (e.g. ``a@b.com``) never matches.
|
|
32
|
+
"""
|
|
33
|
+
prefix = line[:col]
|
|
34
|
+
match = _AT_TOKEN_RE.search(prefix)
|
|
35
|
+
if match is None:
|
|
36
|
+
return None
|
|
37
|
+
start = match.start()
|
|
38
|
+
if start > 0 and not prefix[start - 1].isspace():
|
|
39
|
+
return None
|
|
40
|
+
return start, match.group(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def list_workspace_files(root: Path, *, limit: int = 3000) -> list[str]:
|
|
44
|
+
"""List files under ``root``, preferring git (honors .gitignore)."""
|
|
45
|
+
try:
|
|
46
|
+
result = subprocess.run(
|
|
47
|
+
["git", "-C", str(root), "ls-files", "--cached", "--others", "--exclude-standard"],
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
timeout=5.0,
|
|
51
|
+
check=False,
|
|
52
|
+
)
|
|
53
|
+
if result.returncode == 0:
|
|
54
|
+
files = [line for line in result.stdout.splitlines() if line.strip()]
|
|
55
|
+
if files:
|
|
56
|
+
return sorted(files)[:limit]
|
|
57
|
+
except (OSError, subprocess.SubprocessError):
|
|
58
|
+
pass
|
|
59
|
+
return _walk_files(root, limit=limit)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _walk_files(root: Path, *, limit: int) -> list[str]:
|
|
63
|
+
out: list[str] = []
|
|
64
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
65
|
+
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS and not d.startswith(".")]
|
|
66
|
+
rel_dir = os.path.relpath(dirpath, root)
|
|
67
|
+
for name in filenames:
|
|
68
|
+
rel = name if rel_dir == "." else f"{rel_dir}/{name}"
|
|
69
|
+
out.append(rel.replace(os.sep, "/"))
|
|
70
|
+
if len(out) >= limit:
|
|
71
|
+
return sorted(out)
|
|
72
|
+
return sorted(out)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def fuzzy_filter_files(files: list[str], query: str, *, limit: int = 8) -> list[str]:
|
|
76
|
+
"""Case-insensitive subsequence match, ranked basename-prefix > path-prefix > contiguity."""
|
|
77
|
+
if not query:
|
|
78
|
+
return files[:limit]
|
|
79
|
+
q = query.lower()
|
|
80
|
+
scored: list[tuple[int, int, str]] = []
|
|
81
|
+
for path in files:
|
|
82
|
+
low = path.lower()
|
|
83
|
+
basename = low.rsplit("/", 1)[-1]
|
|
84
|
+
if not _is_subsequence(q, low):
|
|
85
|
+
continue
|
|
86
|
+
if basename.startswith(q):
|
|
87
|
+
rank = 0
|
|
88
|
+
elif low.startswith(q):
|
|
89
|
+
rank = 1
|
|
90
|
+
elif q in low:
|
|
91
|
+
rank = 2
|
|
92
|
+
else:
|
|
93
|
+
rank = 3
|
|
94
|
+
scored.append((rank, len(path), path))
|
|
95
|
+
scored.sort()
|
|
96
|
+
return [path for _, _, path in scored[:limit]]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _is_subsequence(query: str, text: str) -> bool:
|
|
100
|
+
it = iter(text)
|
|
101
|
+
return all(ch in it for ch in query)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Run a local shell command from the ``!`` prefix in ``monkeybot chat``.
|
|
2
|
+
|
|
3
|
+
Output is local-only — never sent to the agent.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
_IS_WINDOWS = sys.platform == "win32"
|
|
16
|
+
|
|
17
|
+
# Cap capture before TUI truncation so `!yes`-style floods cannot OOM the process.
|
|
18
|
+
_MAX_CAPTURE_CHARS = 100_000
|
|
19
|
+
_READ_CHUNK = 4_096
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _popen_kwargs_for_platform() -> dict[str, object]:
|
|
23
|
+
if _IS_WINDOWS:
|
|
24
|
+
# getattr fallback: this constant only exists on Windows builds of
|
|
25
|
+
# `subprocess`, so a plain attribute access breaks importing/testing
|
|
26
|
+
# this module on POSIX even though the branch never runs there.
|
|
27
|
+
return {"creationflags": getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)}
|
|
28
|
+
return {"start_new_session": True}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _kill_process_tree(pid: int) -> None:
|
|
32
|
+
"""Best-effort kill of the shell process group/tree.
|
|
33
|
+
|
|
34
|
+
Swallow lookup/permission races — leader exit can make killpg raise
|
|
35
|
+
``PermissionError`` even though descendants still need a signal.
|
|
36
|
+
"""
|
|
37
|
+
if _IS_WINDOWS:
|
|
38
|
+
try:
|
|
39
|
+
subprocess.run(
|
|
40
|
+
["taskkill", "/F", "/T", "/PID", str(pid)],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
check=False,
|
|
43
|
+
)
|
|
44
|
+
except OSError:
|
|
45
|
+
pass
|
|
46
|
+
return
|
|
47
|
+
try:
|
|
48
|
+
os.killpg(pid, signal.SIGKILL)
|
|
49
|
+
except (ProcessLookupError, PermissionError, OSError):
|
|
50
|
+
try:
|
|
51
|
+
os.kill(pid, signal.SIGKILL)
|
|
52
|
+
except (ProcessLookupError, PermissionError, OSError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _append_capped(chunks: list[str], total: int, data: str) -> tuple[int, bool]:
|
|
57
|
+
"""Append ``data`` up to the capture cap. Returns (new_total, hit_cap)."""
|
|
58
|
+
remain = _MAX_CAPTURE_CHARS - total
|
|
59
|
+
if remain <= 0:
|
|
60
|
+
return total, True
|
|
61
|
+
if len(data) <= remain:
|
|
62
|
+
chunks.append(data)
|
|
63
|
+
return total + len(data), False
|
|
64
|
+
chunks.append(data[:remain])
|
|
65
|
+
return total + remain, True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_local_shell(command: str, cwd: Path, *, timeout: float = 120.0) -> tuple[str, int | None]:
|
|
69
|
+
"""Run ``command`` in a shell, returning merged stdout+stderr and the exit code.
|
|
70
|
+
|
|
71
|
+
Runs as the root of its own process group/tree so a timeout can kill
|
|
72
|
+
everything it spawned — plain ``subprocess.run(..., timeout=...)`` only
|
|
73
|
+
kills the shell itself, leaving children it spawned (e.g. a backgrounded
|
|
74
|
+
``sleep``) running past the reported timeout.
|
|
75
|
+
|
|
76
|
+
Output is capped at ``_MAX_CAPTURE_CHARS`` while streaming (never fully
|
|
77
|
+
buffered first), so unbounded writers cannot exhaust memory before the
|
|
78
|
+
TUI truncates for display. Works the same on POSIX and Windows.
|
|
79
|
+
"""
|
|
80
|
+
proc = subprocess.Popen(
|
|
81
|
+
command,
|
|
82
|
+
shell=True,
|
|
83
|
+
cwd=cwd,
|
|
84
|
+
stdout=subprocess.PIPE,
|
|
85
|
+
stderr=subprocess.STDOUT,
|
|
86
|
+
text=True,
|
|
87
|
+
**_popen_kwargs_for_platform(),
|
|
88
|
+
)
|
|
89
|
+
assert proc.stdout is not None
|
|
90
|
+
|
|
91
|
+
chunks: list[str] = []
|
|
92
|
+
state = {"total": 0, "capped": False}
|
|
93
|
+
stop_reader = threading.Event()
|
|
94
|
+
|
|
95
|
+
def _reader() -> None:
|
|
96
|
+
stdout = proc.stdout
|
|
97
|
+
if stdout is None:
|
|
98
|
+
return
|
|
99
|
+
try:
|
|
100
|
+
while not stop_reader.is_set():
|
|
101
|
+
chunk = stdout.read(_READ_CHUNK)
|
|
102
|
+
if chunk == "":
|
|
103
|
+
break
|
|
104
|
+
state["total"], hit = _append_capped(chunks, state["total"], chunk)
|
|
105
|
+
if hit:
|
|
106
|
+
state["capped"] = True
|
|
107
|
+
_kill_process_tree(proc.pid)
|
|
108
|
+
break
|
|
109
|
+
except (ValueError, OSError):
|
|
110
|
+
# stdout closed from the main thread to unblock a stuck read.
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
reader = threading.Thread(target=_reader, name="monkeybot-local-shell-reader", daemon=True)
|
|
114
|
+
reader.start()
|
|
115
|
+
|
|
116
|
+
timed_out = False
|
|
117
|
+
try:
|
|
118
|
+
try:
|
|
119
|
+
proc.wait(timeout=timeout)
|
|
120
|
+
except subprocess.TimeoutExpired:
|
|
121
|
+
timed_out = True
|
|
122
|
+
_kill_process_tree(proc.pid)
|
|
123
|
+
|
|
124
|
+
# Shell may have exited while a descendant still holds the pipe —
|
|
125
|
+
# kill the tree and close stdout so the reader cannot block forever.
|
|
126
|
+
if reader.is_alive():
|
|
127
|
+
_kill_process_tree(proc.pid)
|
|
128
|
+
stop_reader.set()
|
|
129
|
+
try:
|
|
130
|
+
proc.stdout.close()
|
|
131
|
+
except OSError:
|
|
132
|
+
pass
|
|
133
|
+
reader.join(timeout=2.0)
|
|
134
|
+
if reader.is_alive() and not timed_out:
|
|
135
|
+
# Still stuck after kill+close: treat as timeout so the TUI
|
|
136
|
+
# always gets a bounded result instead of hanging.
|
|
137
|
+
timed_out = True
|
|
138
|
+
else:
|
|
139
|
+
reader.join(timeout=0.1)
|
|
140
|
+
except Exception:
|
|
141
|
+
timed_out = True
|
|
142
|
+
_kill_process_tree(proc.pid)
|
|
143
|
+
stop_reader.set()
|
|
144
|
+
try:
|
|
145
|
+
proc.stdout.close()
|
|
146
|
+
except OSError:
|
|
147
|
+
pass
|
|
148
|
+
reader.join(timeout=1.0)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
if proc.poll() is None:
|
|
152
|
+
_kill_process_tree(proc.pid)
|
|
153
|
+
try:
|
|
154
|
+
proc.wait(timeout=1)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
output = "".join(chunks)
|
|
161
|
+
if timed_out:
|
|
162
|
+
# ``None`` means timed out (distinct from a real exit status).
|
|
163
|
+
return output + f"\n(timed out after {timeout:.0f}s)", None
|
|
164
|
+
if state["capped"]:
|
|
165
|
+
output += f"\n… (output capped at {_MAX_CAPTURE_CHARS} chars)"
|
|
166
|
+
return output, proc.returncode
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def truncate_output(text: str, *, max_lines: int = 200, max_chars: int = 10_000) -> str:
|
|
170
|
+
"""Bound output for transcript display, noting how much was dropped."""
|
|
171
|
+
lines = text.splitlines()
|
|
172
|
+
if len(lines) > max_lines:
|
|
173
|
+
dropped = len(lines) - max_lines
|
|
174
|
+
text = "\n".join(lines[:max_lines]) + f"\n… (+{dropped} lines truncated)"
|
|
175
|
+
if len(text) > max_chars:
|
|
176
|
+
dropped_chars = len(text) - max_chars
|
|
177
|
+
text = text[:max_chars] + f"\n… (+{dropped_chars} chars truncated)"
|
|
178
|
+
return text
|
|
@@ -19,6 +19,7 @@ from monkeybot.core.runtime.events import (
|
|
|
19
19
|
AssistantDelta,
|
|
20
20
|
ContextSummarized,
|
|
21
21
|
ContextSummarizing,
|
|
22
|
+
ContextUsage,
|
|
22
23
|
Error,
|
|
23
24
|
FrontendToolRequestEvent,
|
|
24
25
|
GroundingEvent,
|
|
@@ -228,6 +229,7 @@ class ChatSessionController:
|
|
|
228
229
|
self.usage = UsageStore()
|
|
229
230
|
self.session_id: str | None = None
|
|
230
231
|
self.stream_error = False
|
|
232
|
+
self.transcript_report_dir: str | None = None
|
|
231
233
|
self._client: httpx.AsyncClient | None = None
|
|
232
234
|
self._event_queue: asyncio.Queue[str | None] = asyncio.Queue()
|
|
233
235
|
self._stream_task: asyncio.Task[None] | None = None
|
|
@@ -617,11 +619,21 @@ class ChatSessionController:
|
|
|
617
619
|
)
|
|
618
620
|
return
|
|
619
621
|
if isinstance(evt, ContextSummarizing):
|
|
620
|
-
self.
|
|
622
|
+
self._on_context_usage_hint(
|
|
623
|
+
estimated_tokens=evt.estimated_tokens,
|
|
624
|
+
context_window_tokens=evt.context_window_tokens,
|
|
625
|
+
)
|
|
626
|
+
self._emit("summarizing", tokens=evt.estimated_tokens)
|
|
627
|
+
return
|
|
628
|
+
if isinstance(evt, ContextUsage):
|
|
629
|
+
self._on_context_usage_hint(
|
|
630
|
+
estimated_tokens=evt.estimated_tokens,
|
|
631
|
+
context_window_tokens=evt.context_window_tokens,
|
|
632
|
+
)
|
|
621
633
|
return
|
|
622
634
|
if isinstance(evt, ContextSummarized):
|
|
635
|
+
# Mid-turn /usage is stale; post-compaction ContextUsage updates the ring.
|
|
623
636
|
self._emit("summarized", turns=evt.turns_summarized)
|
|
624
|
-
await self._fetch_usage()
|
|
625
637
|
return
|
|
626
638
|
if isinstance(evt, AssistantDelta):
|
|
627
639
|
self._on_assistant_delta(evt, state)
|
|
@@ -644,13 +656,14 @@ class ChatSessionController:
|
|
|
644
656
|
|
|
645
657
|
self._maybe_thinking_trace(evt, request_id)
|
|
646
658
|
|
|
647
|
-
def
|
|
659
|
+
def _on_context_usage_hint(
|
|
660
|
+
self, *, estimated_tokens: int, context_window_tokens: int
|
|
661
|
+
) -> None:
|
|
648
662
|
self.usage.update_context_hint(
|
|
649
|
-
estimated_prompt_tokens=
|
|
650
|
-
context_window_tokens=
|
|
663
|
+
estimated_prompt_tokens=estimated_tokens,
|
|
664
|
+
context_window_tokens=context_window_tokens,
|
|
651
665
|
)
|
|
652
666
|
self._emit("usage_updated", usage=self.usage.usage)
|
|
653
|
-
self._emit("summarizing", tokens=evt.estimated_tokens)
|
|
654
667
|
|
|
655
668
|
def _on_assistant_delta(self, evt: AssistantDelta, state: _TurnState) -> None:
|
|
656
669
|
if not state.assistant_started:
|
|
@@ -906,6 +919,20 @@ class ChatSessionController:
|
|
|
906
919
|
await asyncio.wait_for(self._stream_task, timeout=0.5)
|
|
907
920
|
for task in list(self._cancel_tasks):
|
|
908
921
|
task.cancel()
|
|
922
|
+
if self._client is not None and self.session_id is not None:
|
|
923
|
+
try:
|
|
924
|
+
resp = await self._client.delete(f"{self.base}/sessions/{self.session_id}")
|
|
925
|
+
if resp.status_code == 200:
|
|
926
|
+
data = resp.json()
|
|
927
|
+
report = data.get("transcript_report_dir") if isinstance(data, dict) else None
|
|
928
|
+
if isinstance(report, str) and report.strip():
|
|
929
|
+
self.transcript_report_dir = report.strip()
|
|
930
|
+
except Exception:
|
|
931
|
+
logger.warning(
|
|
932
|
+
"session DELETE on close failed session_id=%s",
|
|
933
|
+
self.session_id,
|
|
934
|
+
exc_info=True,
|
|
935
|
+
)
|
|
909
936
|
if self._client is not None:
|
|
910
937
|
await self._client.aclose()
|
|
911
938
|
self._client = None
|
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
|
+
from monkeybot.core.runtime.context_budget import SUMMARY_TRIGGER_RATIO
|
|
8
|
+
|
|
7
9
|
_DIM = "\x1b[2m"
|
|
8
10
|
_GREEN = "\x1b[32m"
|
|
9
11
|
_YELLOW = "\x1b[33m"
|
|
@@ -37,7 +39,7 @@ def parse_usage_response(data: dict[str, object]) -> SessionUsageView:
|
|
|
37
39
|
cap = _int("context_window_tokens", DEFAULT_CONTEXT_WINDOW)
|
|
38
40
|
thresh = _int("summarization_threshold_tokens", 0)
|
|
39
41
|
if thresh <= 0:
|
|
40
|
-
thresh = max(1, int(cap *
|
|
42
|
+
thresh = max(1, int(cap * SUMMARY_TRIGGER_RATIO))
|
|
41
43
|
return SessionUsageView(
|
|
42
44
|
input_tokens=_int("input_tokens"),
|
|
43
45
|
output_tokens=_int("output_tokens"),
|
|
@@ -85,7 +87,7 @@ def _ring_parts(
|
|
|
85
87
|
1,
|
|
86
88
|
summarization_threshold_tokens
|
|
87
89
|
if summarization_threshold_tokens > 0
|
|
88
|
-
else int(cap *
|
|
90
|
+
else int(cap * SUMMARY_TRIGGER_RATIO),
|
|
89
91
|
)
|
|
90
92
|
pct_used = min(100, max(0, round((ring_numerator / cap) * 100)))
|
|
91
93
|
return _ring_glyph(pct_used), pct_used, ring_numerator, thresh
|