monkeybot-cli 0.11.0__tar.gz → 0.11.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.11.1/.gitignore +90 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/PKG-INFO +1 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/pyproject.toml +7 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/skills/monkeybot/SKILL.md +1 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/skills/monkeybot/references/config-sections.md +3 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tui_widgets.py +58 -1
- monkeybot_cli-0.11.1/src/monkeybot_cli/overrides.txt +5 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/runtime_python.py +161 -50
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold.py +2 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +1 -1
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_tui.py +79 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_runtime_python.py +170 -15
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_scaffold.py +1 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/uv.lock +198 -178
- monkeybot_cli-0.11.0/.gitignore +0 -75
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_file_index.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_local_shell.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_renderer.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_session.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_status_bar.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_theme.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tool_display.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tui.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/chat.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/doctor.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/loop.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/mcp_login.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/new.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/refresh.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/run_cmd.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/talk.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/trace.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/validate.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/compat.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/config_resolve.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/exit_commands.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/extras_catalog.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/gateway_health.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/main.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/output.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/process_tree.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/providers.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/audio_io.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/client.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/session.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/session_controller.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/session_controller.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/terminal_markdown.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_continue.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_e2e.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_errors.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_file_index.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_grounding.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_local_shell.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_renderer_parity.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_session.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_status_bar.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_theme.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_tool_display.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_cli.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_config_resolve.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_doctor.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_extras_catalog.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_gateway_health.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_realtime_session_controller.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_run_cmd.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_terminal_markdown.py +0 -0
- {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_trace_cmd.py +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Internal (not shipped in OSS)
|
|
2
|
+
internal/
|
|
3
|
+
|
|
4
|
+
# Environment
|
|
5
|
+
.env
|
|
6
|
+
.env.*
|
|
7
|
+
!.env.example
|
|
8
|
+
# The grants fixture's .env is fixture data, not a local secret.
|
|
9
|
+
!regression/fixtures/workspaces/grants_ws/granted/.env
|
|
10
|
+
|
|
11
|
+
# Local scaffolded harness config (defaults ship in monkeybot-cli scaffold_defaults/)
|
|
12
|
+
/monkeybot_config/
|
|
13
|
+
|
|
14
|
+
# Live eval run artifacts. Baselines under regression/t4/baselines/ stay versioned.
|
|
15
|
+
/regression/t4/runs/
|
|
16
|
+
# Live-eval agent project local state. Live secrets come from the repo-root .env; a stray key
|
|
17
|
+
# file in the agent project stays out of git. The two workspace .gitkeep files need git add -f.
|
|
18
|
+
/regression/t4/agent/.env
|
|
19
|
+
/regression/t4/agent/data/
|
|
20
|
+
/regression/t4/agent/workspace/
|
|
21
|
+
|
|
22
|
+
# Python
|
|
23
|
+
__pycache__/
|
|
24
|
+
*.py[cod]
|
|
25
|
+
*.pyo
|
|
26
|
+
*.pyd
|
|
27
|
+
.Python
|
|
28
|
+
*.egg-info/
|
|
29
|
+
dist/
|
|
30
|
+
build/
|
|
31
|
+
.eggs/
|
|
32
|
+
*.egg
|
|
33
|
+
|
|
34
|
+
# uv
|
|
35
|
+
.venv/
|
|
36
|
+
.uv/
|
|
37
|
+
|
|
38
|
+
# Data (SQLite DB + memory files — never commit)
|
|
39
|
+
data/
|
|
40
|
+
# Shared attachment fixtures live here (§5.4); they are not a local data dir.
|
|
41
|
+
!testkit/data/
|
|
42
|
+
!testkit/data/**
|
|
43
|
+
/memory/
|
|
44
|
+
.monkeybot/
|
|
45
|
+
# Store layout for the attachment loop fixture. The directory name is ignored above.
|
|
46
|
+
!regression/fixtures/workspaces/att_ws/.monkeybot/
|
|
47
|
+
!regression/fixtures/workspaces/att_ws/.monkeybot/**
|
|
48
|
+
*.sqlite3
|
|
49
|
+
workspace/.monkeybot/
|
|
50
|
+
*.sqlite
|
|
51
|
+
*.sqlite.writer-pid
|
|
52
|
+
|
|
53
|
+
# Testing & coverage
|
|
54
|
+
/test-runs/
|
|
55
|
+
/.cursor/hooks/.state/
|
|
56
|
+
.pytest_cache/
|
|
57
|
+
.coverage
|
|
58
|
+
htmlcov/
|
|
59
|
+
.mypy_cache/
|
|
60
|
+
.ruff_cache/
|
|
61
|
+
.deepeval/
|
|
62
|
+
|
|
63
|
+
# macOS
|
|
64
|
+
.DS_Store
|
|
65
|
+
|
|
66
|
+
# IDEs
|
|
67
|
+
.idea/
|
|
68
|
+
.vscode/
|
|
69
|
+
*.swp
|
|
70
|
+
*.swo
|
|
71
|
+
|
|
72
|
+
# Docker
|
|
73
|
+
docker/data/
|
|
74
|
+
|
|
75
|
+
# Planning Documents
|
|
76
|
+
.prt/
|
|
77
|
+
.monkeymode/
|
|
78
|
+
|
|
79
|
+
# Reference clones (read-only)
|
|
80
|
+
code/
|
|
81
|
+
|
|
82
|
+
# Local venv for evals dependency experiments
|
|
83
|
+
.evals-venv/
|
|
84
|
+
|
|
85
|
+
# monkeythink
|
|
86
|
+
.monkeythink/
|
|
87
|
+
# Eval fixtures ship their own build/ decoy
|
|
88
|
+
!/regression/fixtures/workspaces/calc_repo/build/
|
|
89
|
+
!/regression/fixtures/workspaces/calc_repo_green/build/
|
|
90
|
+
!/regression/fixtures/workspaces/shop_repo/build/
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "monkeybot-cli"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.1"
|
|
8
8
|
description = "CLI to create, configure, validate, and chat with monkeybot agents."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -21,6 +21,12 @@ dependencies = [
|
|
|
21
21
|
[project.scripts]
|
|
22
22
|
monkeybot = "monkeybot_cli.main:main"
|
|
23
23
|
|
|
24
|
+
[tool.uv]
|
|
25
|
+
# browser-harness 0.1.13 pins pillow==12.3.0, which is still inside the Pillow 13 advisory range.
|
|
26
|
+
# uv lock and uv sync honor this. uv tool install and uv pip install do not;
|
|
27
|
+
# those commands pass src/monkeybot_cli/overrides.txt via --overrides.
|
|
28
|
+
override-dependencies = ["pillow>=13"]
|
|
29
|
+
|
|
24
30
|
[tool.uv.sources]
|
|
25
31
|
monkeybot = { path = "..", editable = true }
|
|
26
32
|
monkeybot-browser-mcp = { path = "../integrations/browser-mcp", editable = true }
|
|
@@ -176,7 +176,7 @@ Only if the user is changing monkeybot itself (not creating an agent):
|
|
|
176
176
|
git clone https://github.com/human-plus-machine/monkeybot.git
|
|
177
177
|
cd monkeybot && uv sync
|
|
178
178
|
cd cli && uv sync
|
|
179
|
-
uv tool install --editable .
|
|
179
|
+
uv tool install --editable . --overrides src/monkeybot_cli/overrides.txt
|
|
180
180
|
```
|
|
181
181
|
|
|
182
182
|
## Rules
|
{monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/skills/monkeybot/references/config-sections.md
RENAMED
|
@@ -84,7 +84,9 @@ MemPalace capture, wake-up, and search teaching are on by default. Turn them off
|
|
|
84
84
|
|---|---|---|
|
|
85
85
|
| `enabled` | `true` | Set `false` to skip capture, wake-up, and prompt teaching |
|
|
86
86
|
| `backend` | `chroma` | Alternate MemPalace vector backend |
|
|
87
|
-
| `embedding_model` | `embeddinggemma-300m`
|
|
87
|
+
| `embedding_model` | `minilm` | MemPalace's default embedder. `embeddinggemma-300m` is a legacy alias; startup rewrites it to `minilm` |
|
|
88
|
+
|
|
89
|
+
Chats in one project share saved lines by default, including the wake-up briefing. Set `MONKEYBOT_MEMORY_SHARE_THREADS=0` to keep each chat's lines in that chat. Unset, `1`, `true`, `yes`, and `on` share.
|
|
88
90
|
|
|
89
91
|
## `subagents`
|
|
90
92
|
|
|
@@ -28,6 +28,10 @@ from monkeybot_cli.chat_tool_display import format_tool_expand_body
|
|
|
28
28
|
_SPIN = "⠋⠙⠹⠸⠴⠦⠧⠇⠏"
|
|
29
29
|
_HISTORY_LIMIT = 500
|
|
30
30
|
_COMPOSER_PLACEHOLDER = "Message the agent — / commands · @ files · ! shell"
|
|
31
|
+
# Enter's raw bytes. modifyOtherKeys and a few terminals report Shift/Alt+Enter
|
|
32
|
+
# as these characters instead of the name "enter".
|
|
33
|
+
_ENTER_KEY_NAMES = frozenset({"enter", "\r", "\n"})
|
|
34
|
+
_NEWLINE_MODIFIERS = frozenset({"shift", "alt"})
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def _plain_title(text: str) -> Content:
|
|
@@ -747,6 +751,50 @@ class ShortcutsScreen(ModalScreen[None]):
|
|
|
747
751
|
self.dismiss(None)
|
|
748
752
|
|
|
749
753
|
|
|
754
|
+
def is_newline_key(key: str) -> bool:
|
|
755
|
+
"""True when this key should insert a newline instead of sending.
|
|
756
|
+
|
|
757
|
+
Kitty-protocol terminals report ``shift+enter``. modifyOtherKeys and some
|
|
758
|
+
macOS terminals report the same chord as ``shift+`` plus a raw CR or LF.
|
|
759
|
+
``ctrl+j`` is the chord every terminal can deliver.
|
|
760
|
+
"""
|
|
761
|
+
if key in {"shift+enter", "alt+enter", "ctrl+j", "newline"}:
|
|
762
|
+
return True
|
|
763
|
+
parts = key.split("+")
|
|
764
|
+
if len(parts) < 2 or parts[-1] not in _ENTER_KEY_NAMES:
|
|
765
|
+
return False
|
|
766
|
+
return bool(_NEWLINE_MODIFIERS.intersection(parts[:-1]))
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def install_terminal_newline_keys() -> None:
|
|
770
|
+
"""Teach Textual's parser the Enter chords it currently mislabels.
|
|
771
|
+
|
|
772
|
+
Alt+Enter arrives as ESC then CR, and Textual drops the Alt because Enter
|
|
773
|
+
is not a single-character key. modifyOtherKeys Shift+Enter arrives as
|
|
774
|
+
``shift+`` plus a raw carriage return, which matches no binding.
|
|
775
|
+
"""
|
|
776
|
+
from textual._xterm_parser import XTermParser
|
|
777
|
+
|
|
778
|
+
current = XTermParser._sequence_to_key_events
|
|
779
|
+
if getattr(current, "_monkeybot_newline", False):
|
|
780
|
+
return
|
|
781
|
+
|
|
782
|
+
def _sequence_to_key_events(self: XTermParser, sequence: str, alt: bool = False): # type: ignore[no-untyped-def]
|
|
783
|
+
for event in current(self, sequence, alt=alt):
|
|
784
|
+
key = event.key
|
|
785
|
+
if alt and key == "enter":
|
|
786
|
+
yield Key("alt+enter", None)
|
|
787
|
+
elif key in {"shift+\r", "shift+\n"}:
|
|
788
|
+
yield Key("shift+enter", None)
|
|
789
|
+
elif key in {"alt+\r", "alt+\n"}:
|
|
790
|
+
yield Key("alt+enter", None)
|
|
791
|
+
else:
|
|
792
|
+
yield event
|
|
793
|
+
|
|
794
|
+
_sequence_to_key_events._monkeybot_newline = True # type: ignore[attr-defined]
|
|
795
|
+
XTermParser._sequence_to_key_events = _sequence_to_key_events # type: ignore[method-assign]
|
|
796
|
+
|
|
797
|
+
|
|
750
798
|
class Composer(TextArea):
|
|
751
799
|
"""Growing multiline input: Enter sends, Shift+Enter/Ctrl+J newline, history, slash."""
|
|
752
800
|
|
|
@@ -1003,6 +1051,11 @@ class Composer(TextArea):
|
|
|
1003
1051
|
if self._search_mode:
|
|
1004
1052
|
self._handle_search_key(event)
|
|
1005
1053
|
return
|
|
1054
|
+
if is_newline_key(event.key):
|
|
1055
|
+
self.action_insert_newline()
|
|
1056
|
+
event.prevent_default()
|
|
1057
|
+
event.stop()
|
|
1058
|
+
return
|
|
1006
1059
|
if event.character == "?" and not self.text.strip():
|
|
1007
1060
|
with contextlib.suppress(Exception):
|
|
1008
1061
|
app = self.app
|
|
@@ -1054,7 +1107,8 @@ class Composer(TextArea):
|
|
|
1054
1107
|
event.prevent_default()
|
|
1055
1108
|
event.stop()
|
|
1056
1109
|
return
|
|
1057
|
-
|
|
1110
|
+
# TextArea._on_key runs next via the message MRO and inserts printable
|
|
1111
|
+
# characters. Calling it here as well only creates an unawaited coroutine.
|
|
1058
1112
|
self.call_after_refresh(self._sync_height)
|
|
1059
1113
|
|
|
1060
1114
|
def _handle_search_key(self, event: Key) -> None:
|
|
@@ -1115,3 +1169,6 @@ class TranscriptPane(VerticalScroll):
|
|
|
1115
1169
|
on_scroll = getattr(app, "_on_transcript_scroll", None)
|
|
1116
1170
|
if callable(on_scroll):
|
|
1117
1171
|
on_scroll()
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
install_terminal_newline_keys()
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# browser-harness 0.1.13 requires pillow==12.3.0. That release is inside
|
|
2
|
+
# advisories fixed in Pillow 13. [tool.uv] override-dependencies applies this
|
|
3
|
+
# floor to uv lock and uv sync only. uv tool install and uv pip install need
|
|
4
|
+
# this file via --overrides.
|
|
5
|
+
pillow>=13
|
|
@@ -11,14 +11,19 @@ Resolution order for an agent root:
|
|
|
11
11
|
1. ``<root>/.venv/bin/python`` (or the Windows variant) — direct, no subprocess overhead.
|
|
12
12
|
2. ``uv run python`` — when ``<root>/pyproject.toml`` exists but no ``.venv``.
|
|
13
13
|
3. ``<cache>/monkeybot/runtimes/<key>/bin/python`` — a CLI-managed venv holding
|
|
14
|
-
``monkeybot[memory]``. When this process is
|
|
15
|
-
(or ``MONKEYBOT_CHECKOUT`` points at one),
|
|
16
|
-
extras from PyPI; otherwise it is
|
|
17
|
-
Used only for config-only trees
|
|
18
|
-
provisioned cache interpreter exists.
|
|
19
|
-
provider/storage extras present in the CLI
|
|
20
|
-
probe module is a base dep) and the extra
|
|
21
|
-
``model.provider`` (so a thin CLI still gets
|
|
14
|
+
``monkeybot[memory,web-search]`` and ``monkeybot-browser-mcp``. When this process is
|
|
15
|
+
running from a source checkout (or ``MONKEYBOT_CHECKOUT`` points at one),
|
|
16
|
+
the install is that tree plus extras from PyPI; otherwise it is
|
|
17
|
+
``monkeybot[memory,web-search]==<running version>``. Used only for config-only trees
|
|
18
|
+
with memory enabled when a previously provisioned cache interpreter exists.
|
|
19
|
+
The managed runtime also mirrors provider/storage extras present in the CLI
|
|
20
|
+
env (excluding extras whose probe module is a base dep) and the extra
|
|
21
|
+
required by the agent's YAML ``model.provider`` (so a thin CLI still gets
|
|
22
|
+
``tiktoken`` for NVIDIA/OpenAI). The browser package is installed here
|
|
23
|
+
because the gateway sets ``MONKEYBOT_PYTHON`` to this interpreter, and the
|
|
24
|
+
browser server is ``python -m browser_mcp.server``. ``web-search`` is always
|
|
25
|
+
installed: DuckDuckGo is the default backend, and a cache that cannot import
|
|
26
|
+
``ddgs`` is rebuilt.
|
|
22
27
|
4. ``sys.executable`` — legacy / config-only trees (just ``monkeybot_config/``, no
|
|
23
28
|
``pyproject.toml``). In this case extras must be installed in the CLI env,
|
|
24
29
|
unless memory is enabled and ``prepare_runtime_python`` provisions (3).
|
|
@@ -104,6 +109,8 @@ if not (_key({COMPATIBLE_CORE_LOWER_VERSION!r}) <= _key(ver) < _key({COMPATIBLE_
|
|
|
104
109
|
raise SystemExit(ver)
|
|
105
110
|
""".strip()
|
|
106
111
|
MEMORY_PROBE = f"import mempalace\n{CORE_PROBE}"
|
|
112
|
+
BROWSER_MCP_PROBE = "import browser_mcp"
|
|
113
|
+
WEB_SEARCH_PROBE = "import ddgs"
|
|
107
114
|
|
|
108
115
|
|
|
109
116
|
class RuntimeUpgradeError(RuntimeError):
|
|
@@ -280,12 +287,30 @@ def mirrored_monkeybot_extras() -> tuple[str, ...]:
|
|
|
280
287
|
)
|
|
281
288
|
|
|
282
289
|
|
|
290
|
+
def _newest_mtime_ns(root: Path) -> int:
|
|
291
|
+
"""Newest file mtime under ``root``, or 0 when the tree is missing."""
|
|
292
|
+
if not root.is_dir():
|
|
293
|
+
return 0
|
|
294
|
+
newest = 0
|
|
295
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
296
|
+
dirnames[:] = [name for name in dirnames if name != "__pycache__" and name != ".git"]
|
|
297
|
+
for name in filenames:
|
|
298
|
+
if name.endswith((".pyc", ".pyo")):
|
|
299
|
+
continue
|
|
300
|
+
try:
|
|
301
|
+
newest = max(newest, Path(dirpath, name).stat().st_mtime_ns)
|
|
302
|
+
except OSError:
|
|
303
|
+
continue
|
|
304
|
+
return newest
|
|
305
|
+
|
|
306
|
+
|
|
283
307
|
def _checkout_source_mtime_ns(checkout: Path) -> int:
|
|
284
308
|
"""Newest mtime under the packaged source tree (and ``pyproject.toml``).
|
|
285
309
|
|
|
286
310
|
``monkeybot @ file://`` is a snapshot copy, so the cache key must change when
|
|
287
311
|
anything that would be installed changes — not only when ``pyproject.toml``
|
|
288
|
-
itself is touched.
|
|
312
|
+
itself is touched. ``integrations/browser-mcp`` is installed into the same
|
|
313
|
+
runtime, so an edit there invalidates the snapshot too.
|
|
289
314
|
"""
|
|
290
315
|
newest = 0
|
|
291
316
|
pyproject = checkout / "pyproject.toml"
|
|
@@ -293,26 +318,35 @@ def _checkout_source_mtime_ns(checkout: Path) -> int:
|
|
|
293
318
|
newest = max(newest, pyproject.stat().st_mtime_ns)
|
|
294
319
|
except OSError:
|
|
295
320
|
pass
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return newest
|
|
299
|
-
for dirpath, dirnames, filenames in os.walk(src_root):
|
|
300
|
-
dirnames[:] = [name for name in dirnames if name != "__pycache__" and name != ".git"]
|
|
301
|
-
for name in filenames:
|
|
302
|
-
if name.endswith((".pyc", ".pyo")):
|
|
303
|
-
continue
|
|
304
|
-
try:
|
|
305
|
-
newest = max(newest, Path(dirpath, name).stat().st_mtime_ns)
|
|
306
|
-
except OSError:
|
|
307
|
-
continue
|
|
321
|
+
newest = max(newest, _newest_mtime_ns(checkout / "src"))
|
|
322
|
+
newest = max(newest, _newest_mtime_ns(checkout / "integrations" / "browser-mcp"))
|
|
308
323
|
return newest
|
|
309
324
|
|
|
310
325
|
|
|
326
|
+
def _browser_mcp_requirement(checkout: Path | None) -> str | None:
|
|
327
|
+
"""Pip spec for the browser server this gateway will spawn, if one is available.
|
|
328
|
+
|
|
329
|
+
A local checkout contributes ``integrations/browser-mcp``. Otherwise the
|
|
330
|
+
version installed next to this CLI is pinned. ``None`` when neither exists,
|
|
331
|
+
so a stripped environment still gets a memory runtime.
|
|
332
|
+
"""
|
|
333
|
+
if checkout is not None:
|
|
334
|
+
package = checkout / "integrations" / "browser-mcp"
|
|
335
|
+
if (package / "pyproject.toml").is_file():
|
|
336
|
+
return f"monkeybot-browser-mcp @ {package.resolve().as_uri()}"
|
|
337
|
+
try:
|
|
338
|
+
version = _package_version("monkeybot-browser-mcp")
|
|
339
|
+
except PackageNotFoundError:
|
|
340
|
+
return None
|
|
341
|
+
return f"monkeybot-browser-mcp=={version}"
|
|
342
|
+
|
|
343
|
+
|
|
311
344
|
def managed_memory_runtime_dir(
|
|
312
345
|
monkeybot_version: str,
|
|
313
346
|
extras: Sequence[str] = (),
|
|
314
347
|
*,
|
|
315
348
|
checkout: Path | None = None,
|
|
349
|
+
browser_requirement: str | None = None,
|
|
316
350
|
) -> Path:
|
|
317
351
|
"""Deterministic cache directory for the managed MemPalace runtime.
|
|
318
352
|
|
|
@@ -332,6 +366,8 @@ def managed_memory_runtime_dir(
|
|
|
332
366
|
if checkout is not None:
|
|
333
367
|
resolved = checkout.resolve()
|
|
334
368
|
digest_parts.append(f"checkout:{resolved.as_posix()}:{_checkout_source_mtime_ns(resolved)}")
|
|
369
|
+
if browser_requirement:
|
|
370
|
+
digest_parts.append(f"browser:{browser_requirement}")
|
|
335
371
|
if digest_parts:
|
|
336
372
|
digest = hashlib.sha256(",".join(digest_parts).encode()).hexdigest()[:8]
|
|
337
373
|
key = f"{key}-{digest}"
|
|
@@ -353,14 +389,45 @@ def _managed_runtime(runtime_dir: Path, agent_root: Path) -> RuntimePython | Non
|
|
|
353
389
|
return RuntimePython([str(interpreter)], MANAGED_RUNTIME_SOURCE, agent_root)
|
|
354
390
|
|
|
355
391
|
|
|
356
|
-
def
|
|
357
|
-
|
|
392
|
+
def _managed_runtime_cache_dir(
|
|
393
|
+
monkeybot_version: str,
|
|
394
|
+
extras: Sequence[str],
|
|
395
|
+
checkout: Path | None,
|
|
396
|
+
) -> Path:
|
|
397
|
+
"""Cache directory for this core version, extras, checkout, and browser package."""
|
|
398
|
+
return managed_memory_runtime_dir(
|
|
399
|
+
monkeybot_version,
|
|
400
|
+
extras,
|
|
401
|
+
checkout=checkout,
|
|
402
|
+
browser_requirement=_browser_mcp_requirement(checkout),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _browser_mcp_importable(runtime: RuntimePython, checkout: Path | None) -> bool:
|
|
407
|
+
"""Whether ``runtime`` can start the browser server, when one should be installed.
|
|
408
|
+
|
|
409
|
+
A missing requirement means this environment has no browser package to mirror,
|
|
410
|
+
so the memory runtime is not held to that import.
|
|
411
|
+
"""
|
|
412
|
+
if _browser_mcp_requirement(checkout) is None:
|
|
413
|
+
return True
|
|
414
|
+
return run_probe(runtime, BROWSER_MCP_PROBE)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _managed_if_ready(
|
|
418
|
+
runtime_dir: Path, agent_root: Path, checkout: Path | None
|
|
419
|
+
) -> RuntimePython | None:
|
|
420
|
+
"""Return the managed interpreter only when memory and the browser server import."""
|
|
358
421
|
runtime = _managed_runtime(runtime_dir, agent_root)
|
|
359
422
|
if runtime is None:
|
|
360
423
|
return None
|
|
361
|
-
if run_probe(runtime, MEMORY_PROBE):
|
|
362
|
-
return
|
|
363
|
-
|
|
424
|
+
if not run_probe(runtime, MEMORY_PROBE):
|
|
425
|
+
return None
|
|
426
|
+
if not _browser_mcp_importable(runtime, checkout):
|
|
427
|
+
return None
|
|
428
|
+
if not run_probe(runtime, WEB_SEARCH_PROBE):
|
|
429
|
+
return None
|
|
430
|
+
return runtime
|
|
364
431
|
|
|
365
432
|
|
|
366
433
|
def _agent_provider_extras(
|
|
@@ -421,8 +488,8 @@ def _existing_managed_runtime(
|
|
|
421
488
|
) -> RuntimePython | None:
|
|
422
489
|
"""Locate a cached managed interpreter without probing it.
|
|
423
490
|
|
|
424
|
-
``prepare_runtime_python`` / ``doctor`` own the
|
|
425
|
-
|
|
491
|
+
``prepare_runtime_python`` / ``doctor`` own the harness probes so a warm
|
|
492
|
+
cache does not pay to reprovision. Never raises
|
|
426
493
|
on agent YAML errors — provisioning is the fail-closed path.
|
|
427
494
|
"""
|
|
428
495
|
try:
|
|
@@ -430,12 +497,9 @@ def _existing_managed_runtime(
|
|
|
430
497
|
except PackageNotFoundError:
|
|
431
498
|
return None
|
|
432
499
|
extras = _managed_runtime_extras(agent_root, config_path, fail_closed=False)
|
|
500
|
+
checkout = _monkeybot_checkout_root()
|
|
433
501
|
return _managed_runtime(
|
|
434
|
-
|
|
435
|
-
monkeybot_version,
|
|
436
|
-
extras,
|
|
437
|
-
checkout=_monkeybot_checkout_root(),
|
|
438
|
-
),
|
|
502
|
+
_managed_runtime_cache_dir(monkeybot_version, extras, checkout),
|
|
439
503
|
agent_root,
|
|
440
504
|
)
|
|
441
505
|
|
|
@@ -568,13 +632,15 @@ def provision_managed_memory_runtime(
|
|
|
568
632
|
"""Return a CLI-managed venv holding ``monkeybot[memory]`` at the running core version.
|
|
569
633
|
|
|
570
634
|
Extras the CLI env already satisfies are mirrored into the runtime alongside
|
|
571
|
-
``memory``.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
635
|
+
``memory``. ``monkeybot-browser-mcp`` is installed into the same venv when
|
|
636
|
+
the checkout or this CLI provides it, because the gateway spawns that server
|
|
637
|
+
with its own interpreter. A cached runtime is reused only when both memory
|
|
638
|
+
and that browser package import. Otherwise the runtime is created in a
|
|
639
|
+
staging directory with ``uv venv --python`` pointing at this CLI interpreter,
|
|
640
|
+
then swapped into the cache with ``os.replace``. Pins in an agent
|
|
641
|
+
``pyproject.toml`` are never rewritten. An unpublished checkout is installed
|
|
642
|
+
from disk so MemPalace can still come from PyPI. Unreadable agent YAML fails
|
|
643
|
+
closed here (not during quiet resolve lookups).
|
|
578
644
|
"""
|
|
579
645
|
try:
|
|
580
646
|
monkeybot_version = _package_version("monkeybot")
|
|
@@ -583,25 +649,31 @@ def provision_managed_memory_runtime(
|
|
|
583
649
|
|
|
584
650
|
extras = _managed_runtime_extras(agent_root, config_path, fail_closed=True)
|
|
585
651
|
checkout = _monkeybot_checkout_root()
|
|
586
|
-
runtime_dir =
|
|
587
|
-
cached = _managed_if_ready(runtime_dir, agent_root)
|
|
652
|
+
runtime_dir = _managed_runtime_cache_dir(monkeybot_version, extras, checkout)
|
|
653
|
+
cached = _managed_if_ready(runtime_dir, agent_root, checkout)
|
|
588
654
|
if cached is not None:
|
|
589
655
|
return cached
|
|
590
656
|
|
|
591
657
|
if shutil.which("uv") is None:
|
|
592
658
|
raise _managed_runtime_error("uv was not found on PATH")
|
|
593
|
-
|
|
594
|
-
|
|
659
|
+
# web-search is not optional here. The tool is on by default, and mirroring
|
|
660
|
+
# only copies extras the CLI process can already import — a thin CLI never can.
|
|
661
|
+
extra_spec = ",".join(dict.fromkeys(("memory", "web-search", *extras)))
|
|
662
|
+
requirements = [_managed_memory_requirement(monkeybot_version, extra_spec, checkout=checkout)]
|
|
663
|
+
browser = _browser_mcp_requirement(checkout)
|
|
664
|
+
if browser is not None:
|
|
665
|
+
requirements.append(browser)
|
|
595
666
|
print(
|
|
596
|
-
|
|
667
|
+
"agent memory runtime is missing MemPalace; provisioning "
|
|
668
|
+
f"{' '.join(requirements)} in {runtime_dir}",
|
|
597
669
|
flush=True,
|
|
598
670
|
)
|
|
599
671
|
runtime_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
600
672
|
with _exclusive_file_lock(runtime_dir.parent / f".{runtime_dir.name}.lock"):
|
|
601
|
-
cached = _managed_if_ready(runtime_dir, agent_root)
|
|
673
|
+
cached = _managed_if_ready(runtime_dir, agent_root, checkout)
|
|
602
674
|
if cached is not None:
|
|
603
675
|
return cached
|
|
604
|
-
return _install_managed_runtime(runtime_dir, agent_root,
|
|
676
|
+
return _install_managed_runtime(runtime_dir, agent_root, requirements, checkout)
|
|
605
677
|
|
|
606
678
|
|
|
607
679
|
def _retire_runtime_dir(runtime_dir: Path) -> None:
|
|
@@ -619,9 +691,10 @@ def _retire_runtime_dir(runtime_dir: Path) -> None:
|
|
|
619
691
|
def _install_managed_runtime(
|
|
620
692
|
runtime_dir: Path,
|
|
621
693
|
agent_root: Path,
|
|
622
|
-
|
|
694
|
+
requirements: Sequence[str],
|
|
695
|
+
checkout: Path | None,
|
|
623
696
|
) -> RuntimePython:
|
|
624
|
-
"""Create ``
|
|
697
|
+
"""Create ``requirements`` in a staging dir, then atomically replace ``runtime_dir``."""
|
|
625
698
|
staging: Path | None = Path(
|
|
626
699
|
tempfile.mkdtemp(prefix=f".{runtime_dir.name}.tmp-", dir=runtime_dir.parent)
|
|
627
700
|
)
|
|
@@ -635,13 +708,33 @@ def _install_managed_runtime(
|
|
|
635
708
|
interpreter = _managed_interpreter(staging)
|
|
636
709
|
if interpreter is None:
|
|
637
710
|
raise _managed_runtime_error(f"no interpreter was created in {staging}")
|
|
711
|
+
# browser-harness pins pillow==12.3.0. The checkout override in
|
|
712
|
+
# pyproject.toml is not read by `uv pip install`.
|
|
713
|
+
overrides = Path(__file__).resolve().parent / "overrides.txt"
|
|
638
714
|
_run_provisioning(
|
|
639
|
-
[
|
|
715
|
+
[
|
|
716
|
+
"uv",
|
|
717
|
+
"pip",
|
|
718
|
+
"install",
|
|
719
|
+
"--python",
|
|
720
|
+
str(interpreter),
|
|
721
|
+
"--overrides",
|
|
722
|
+
str(overrides),
|
|
723
|
+
*requirements,
|
|
724
|
+
],
|
|
640
725
|
"uv pip install",
|
|
641
726
|
)
|
|
642
727
|
staged = RuntimePython([str(interpreter)], MANAGED_RUNTIME_SOURCE, agent_root)
|
|
643
728
|
if not run_probe(staged, MEMORY_PROBE):
|
|
644
729
|
raise _managed_runtime_error(_probe_failure_detail(staged, MEMORY_PROBE))
|
|
730
|
+
if not _browser_mcp_importable(staged, checkout):
|
|
731
|
+
raise _managed_runtime_error(
|
|
732
|
+
_probe_failure_detail(staged, BROWSER_MCP_PROBE) or "browser_mcp did not import"
|
|
733
|
+
)
|
|
734
|
+
if not run_probe(staged, WEB_SEARCH_PROBE):
|
|
735
|
+
raise _managed_runtime_error(
|
|
736
|
+
_probe_failure_detail(staged, WEB_SEARCH_PROBE) or "ddgs did not import"
|
|
737
|
+
)
|
|
645
738
|
if runtime_dir.exists():
|
|
646
739
|
_retire_runtime_dir(runtime_dir)
|
|
647
740
|
os.replace(staging, runtime_dir)
|
|
@@ -684,6 +777,24 @@ def prepare_runtime_python(
|
|
|
684
777
|
)
|
|
685
778
|
probe = MEMORY_PROBE if memory_enabled else CORE_PROBE
|
|
686
779
|
ok, detail = _probe(runtime, probe)
|
|
780
|
+
if (
|
|
781
|
+
ok
|
|
782
|
+
and memory_enabled
|
|
783
|
+
and runtime.source == MANAGED_RUNTIME_SOURCE
|
|
784
|
+
and not _browser_mcp_importable(runtime, _monkeybot_checkout_root())
|
|
785
|
+
):
|
|
786
|
+
# A cache built before the browser package was mirrored still imports
|
|
787
|
+
# MemPalace. Reprovision instead of spawning the browser server there.
|
|
788
|
+
ok = False
|
|
789
|
+
detail = "managed runtime is missing browser_mcp"
|
|
790
|
+
if (
|
|
791
|
+
ok
|
|
792
|
+
and memory_enabled
|
|
793
|
+
and runtime.source == MANAGED_RUNTIME_SOURCE
|
|
794
|
+
and not run_probe(runtime, WEB_SEARCH_PROBE)
|
|
795
|
+
):
|
|
796
|
+
ok = False
|
|
797
|
+
detail = "managed runtime is missing ddgs"
|
|
687
798
|
if ok:
|
|
688
799
|
return runtime
|
|
689
800
|
if not has_project:
|
|
@@ -142,7 +142,7 @@ memory:
|
|
|
142
142
|
# Env override: MONKEYBOT_MEMORY_HOOK_ENABLED=0
|
|
143
143
|
enabled: true
|
|
144
144
|
backend: chroma
|
|
145
|
-
embedding_model:
|
|
145
|
+
embedding_model: minilm
|
|
146
146
|
|
|
147
147
|
emission:
|
|
148
148
|
# Terse emission-style guidance injected into the cached harness prefix.
|