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.
Files changed (96) hide show
  1. monkeybot_cli-0.11.1/.gitignore +90 -0
  2. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/PKG-INFO +1 -1
  3. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/pyproject.toml +7 -1
  4. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/skills/monkeybot/SKILL.md +1 -1
  5. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/skills/monkeybot/references/config-sections.md +3 -1
  6. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tui_widgets.py +58 -1
  7. monkeybot_cli-0.11.1/src/monkeybot_cli/overrides.txt +5 -0
  8. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/runtime_python.py +161 -50
  9. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold.py +2 -0
  10. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +1 -1
  11. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_tui.py +79 -0
  12. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_runtime_python.py +170 -15
  13. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_scaffold.py +1 -0
  14. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/uv.lock +198 -178
  15. monkeybot_cli-0.11.0/.gitignore +0 -75
  16. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/__init__.py +0 -0
  17. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_file_index.py +0 -0
  18. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_local_shell.py +0 -0
  19. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_renderer.py +0 -0
  20. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_session.py +0 -0
  21. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_status_bar.py +0 -0
  22. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_theme.py +0 -0
  23. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tool_display.py +0 -0
  24. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tui.py +0 -0
  25. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/__init__.py +0 -0
  26. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/chat.py +0 -0
  27. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/doctor.py +0 -0
  28. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/loop.py +0 -0
  29. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/mcp_login.py +0 -0
  30. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/new.py +0 -0
  31. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/refresh.py +0 -0
  32. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/run_cmd.py +0 -0
  33. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/talk.py +0 -0
  34. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/trace.py +0 -0
  35. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/validate.py +0 -0
  36. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/compat.py +0 -0
  37. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/config_resolve.py +0 -0
  38. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/exit_commands.py +0 -0
  39. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/extras_catalog.py +0 -0
  40. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/gateway_health.py +0 -0
  41. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/main.py +0 -0
  42. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
  43. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/output.py +0 -0
  44. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/process_tree.py +0 -0
  45. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/providers.py +0 -0
  46. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/__init__.py +0 -0
  47. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/audio_io.py +0 -0
  48. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/client.py +0 -0
  49. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
  50. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
  51. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/session.py +0 -0
  52. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/session_controller.py +0 -0
  53. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
  54. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
  55. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
  56. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
  57. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
  58. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
  59. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +0 -0
  60. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
  61. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
  62. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -0
  63. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +0 -0
  64. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
  65. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
  66. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
  67. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
  68. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
  69. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
  70. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +0 -0
  71. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +0 -0
  72. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +0 -0
  73. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +0 -0
  74. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/session_controller.py +0 -0
  75. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/terminal_markdown.py +0 -0
  76. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_continue.py +0 -0
  77. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_e2e.py +0 -0
  78. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_errors.py +0 -0
  79. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_file_index.py +0 -0
  80. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_grounding.py +0 -0
  81. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_local_shell.py +0 -0
  82. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_renderer_parity.py +0 -0
  83. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_session.py +0 -0
  84. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_status_bar.py +0 -0
  85. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_theme.py +0 -0
  86. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_chat_tool_display.py +0 -0
  87. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_cli.py +0 -0
  88. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_config_resolve.py +0 -0
  89. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_doctor.py +0 -0
  90. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_extras_catalog.py +0 -0
  91. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_gateway_health.py +0 -0
  92. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_opensandbox_lifecycle.py +0 -0
  93. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_realtime_session_controller.py +0 -0
  94. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_run_cmd.py +0 -0
  95. {monkeybot_cli-0.11.0 → monkeybot_cli-0.11.1}/tests/test_terminal_markdown.py +0 -0
  96. {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/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: monkeybot-cli
3
- Version: 0.11.0
3
+ Version: 0.11.1
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "monkeybot-cli"
7
- version = "0.11.0"
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
@@ -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` | Match the embedder the palace was built with |
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
- super()._on_key(event)
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 running from a source checkout
15
- (or ``MONKEYBOT_CHECKOUT`` points at one), the install is that tree plus
16
- extras from PyPI; otherwise it is ``monkeybot[memory]==<running version>``.
17
- Used only for config-only trees with memory enabled when a previously
18
- provisioned cache interpreter exists. The managed runtime also mirrors
19
- provider/storage extras present in the CLI env (excluding extras whose
20
- probe module is a base dep) and the extra required by the agent's YAML
21
- ``model.provider`` (so a thin CLI still gets ``tiktoken`` for NVIDIA/OpenAI).
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
- src_root = checkout / "src"
297
- if not src_root.is_dir():
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 _managed_if_ready(runtime_dir: Path, agent_root: Path) -> RuntimePython | None:
357
- """Return the managed interpreter only when it already passes the memory probe."""
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 runtime
363
- return None
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 single harness probe so a
425
- warm cache does not pay for two interpreter spawns per start. Never raises
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
- managed_memory_runtime_dir(
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``. A cached runtime that already passes the memory probe is reused
572
- as-is. Otherwise the runtime is created in a staging directory with
573
- ``uv venv --python`` pointing at this CLI interpreter, then swapped into
574
- the cache with ``os.replace``. Pins in an agent ``pyproject.toml`` are never
575
- rewritten. An unpublished checkout is installed from disk so MemPalace can
576
- still come from PyPI. Unreadable agent YAML fails closed here (not during
577
- quiet resolve lookups).
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 = managed_memory_runtime_dir(monkeybot_version, extras, checkout=checkout)
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
- extra_spec = ",".join(("memory", *extras))
594
- requirement = _managed_memory_requirement(monkeybot_version, extra_spec, checkout=checkout)
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
- f"agent memory runtime is missing MemPalace; provisioning {requirement} in {runtime_dir}",
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, requirement)
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
- requirement: str,
694
+ requirements: Sequence[str],
695
+ checkout: Path | None,
623
696
  ) -> RuntimePython:
624
- """Create ``requirement`` in a staging dir, then atomically replace ``runtime_dir``."""
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
- ["uv", "pip", "install", "--python", str(interpreter), requirement],
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:
@@ -335,6 +335,8 @@ def write_agent_pyproject(
335
335
  "]\n"
336
336
  "\n[tool.uv]\n"
337
337
  "package = false\n"
338
+ "# browser-harness pins Pillow 12.3.0; 13 is the patched release.\n"
339
+ 'override-dependencies = ["pillow>=13"]\n'
338
340
  ),
339
341
  encoding="utf-8",
340
342
  )
@@ -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: embeddinggemma-300m
145
+ embedding_model: minilm
146
146
 
147
147
  emission:
148
148
  # Terse emission-style guidance injected into the cached harness prefix.