regcode 0.11.0__tar.gz → 0.12.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 (54) hide show
  1. {regcode-0.11.0 → regcode-0.12.1}/GOALS.md +114 -0
  2. regcode-0.11.0/README.md → regcode-0.12.1/PKG-INFO +41 -0
  3. regcode-0.11.0/PKG-INFO → regcode-0.12.1/README.md +18 -22
  4. {regcode-0.11.0 → regcode-0.12.1}/pyproject.toml +2 -1
  5. {regcode-0.11.0 → regcode-0.12.1}/regcode/__init__.py +1 -1
  6. regcode-0.12.1/regcode/agents_backend.py +811 -0
  7. regcode-0.12.1/regcode/async_utils.py +58 -0
  8. {regcode-0.11.0 → regcode-0.12.1}/regcode/cli.py +12 -1
  9. {regcode-0.11.0 → regcode-0.12.1}/regcode/main.py +423 -633
  10. {regcode-0.11.0 → regcode-0.12.1}/regcode/monty_sandbox.py +13 -11
  11. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/builtins.py +61 -0
  12. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/web_crawl.py +6 -3
  13. regcode-0.12.1/tasks.md +632 -0
  14. {regcode-0.11.0 → regcode-0.12.1}/tests/conftest.py +2 -1
  15. regcode-0.12.1/tests/llm_fakes.py +187 -0
  16. {regcode-0.11.0 → regcode-0.12.1}/tests/test_clipboard.py +18 -13
  17. {regcode-0.11.0 → regcode-0.12.1}/tests/test_dependencies.py +27 -0
  18. {regcode-0.11.0 → regcode-0.12.1}/tests/test_main.py +346 -383
  19. {regcode-0.11.0 → regcode-0.12.1}/tests/test_provider_extra.py +21 -29
  20. regcode-0.12.1/tests/test_task_regression.py +755 -0
  21. {regcode-0.11.0 → regcode-0.12.1}/uv.lock +184 -39
  22. {regcode-0.11.0 → regcode-0.12.1}/.gitignore +0 -0
  23. {regcode-0.11.0 → regcode-0.12.1}/.gitlab-ci.yml +0 -0
  24. {regcode-0.11.0 → regcode-0.12.1}/.python-version +0 -0
  25. {regcode-0.11.0 → regcode-0.12.1}/.qwen/settings.json +0 -0
  26. {regcode-0.11.0 → regcode-0.12.1}/AGENTS.md +0 -0
  27. {regcode-0.11.0 → regcode-0.12.1}/config.yaml.template +0 -0
  28. {regcode-0.11.0 → regcode-0.12.1}/main.py +0 -0
  29. {regcode-0.11.0 → regcode-0.12.1}/regcode/clipboard.py +0 -0
  30. {regcode-0.11.0 → regcode-0.12.1}/regcode/config.py +0 -0
  31. {regcode-0.11.0 → regcode-0.12.1}/regcode/conversation_manager.py +0 -0
  32. {regcode-0.11.0 → regcode-0.12.1}/regcode/dependencies.py +0 -0
  33. {regcode-0.11.0 → regcode-0.12.1}/regcode/mcp_client.py +0 -0
  34. {regcode-0.11.0 → regcode-0.12.1}/regcode/paste.py +0 -0
  35. {regcode-0.11.0 → regcode-0.12.1}/regcode/permissions.py +0 -0
  36. {regcode-0.11.0 → regcode-0.12.1}/regcode/sandbox.py +0 -0
  37. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/__init__.py +0 -0
  38. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/base.py +0 -0
  39. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/registry.py +0 -0
  40. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/review_notes.py +0 -0
  41. {regcode-0.11.0 → regcode-0.12.1}/regcode/tools/web_search.py +0 -0
  42. {regcode-0.11.0 → regcode-0.12.1}/regcode/tracing.py +0 -0
  43. {regcode-0.11.0 → regcode-0.12.1}/regcode/tui.py +0 -0
  44. {regcode-0.11.0 → regcode-0.12.1}/tests/__init__.py +0 -0
  45. {regcode-0.11.0 → regcode-0.12.1}/tests/test_api.py +0 -0
  46. {regcode-0.11.0 → regcode-0.12.1}/tests/test_cli.py +0 -0
  47. {regcode-0.11.0 → regcode-0.12.1}/tests/test_config.py +0 -0
  48. {regcode-0.11.0 → regcode-0.12.1}/tests/test_context_manager.py +0 -0
  49. {regcode-0.11.0 → regcode-0.12.1}/tests/test_mcp.py +0 -0
  50. {regcode-0.11.0 → regcode-0.12.1}/tests/test_mcp_status.py +0 -0
  51. {regcode-0.11.0 → regcode-0.12.1}/tests/test_monty_sandbox.py +0 -0
  52. {regcode-0.11.0 → regcode-0.12.1}/tests/test_osv_cve.py +0 -0
  53. {regcode-0.11.0 → regcode-0.12.1}/tests/test_review_notes.py +0 -0
  54. {regcode-0.11.0 → regcode-0.12.1}/tests/test_web_search.py +0 -0
@@ -581,3 +581,117 @@ mcp_servers:
581
581
  2. ✅ **TUI integration** — `_run_agent_sync()` and `_run_agent_multimodal()` now pass `on_message` callback to `agent.chat()`. The callback inserts each assistant text response directly into the chat area via `call_from_thread(self._insert_rich, text)`, so the user sees intermediate thinking ("Let me search the internet...") BEFORE tool execution markers appear. This makes the agent feel alive and responsive instead of silent during multi-tool chains.
582
582
 
583
583
  3. ✅ **All 193 tests pass, lint clean.**
584
+
585
+ ## Completed (tasks.md — upstream fixes for GitKnecht)
586
+
587
+ All twelve defects in `tasks.md` are fixed. See the ticked **Task Status** section there
588
+ for the per-task mapping to tests.
589
+
590
+ 1. ✅ **Deleted the two dead tool loops** — the real cause of most of the list was that
591
+ `Agent` carried three tool loops (`_stream`, `_chat_loop`, and the SDK path) and only
592
+ the SDK one was reachable from `chat()`. Tasks 1/2/8/9/12 were all symptoms of the same
593
+ unreachable, never-executed `_stream`. `_stream`, `_chat_loop`,
594
+ `Agent._execute_tool_calls` and `_convert_registry_tools_to_sdk_tools` are deleted;
595
+ `test_module_level_helpers_exist` pins their absence.
596
+
597
+ 2. ✅ **One implementation for both modes** — `Agent.chat()` delegates to `_run_sdk()`,
598
+ which drives the OpenAI Agents SDK `Runner` in either `run()` or `run_streamed()` form.
599
+ `RegCodeModel` (`regcode/agents_backend.py`) is now a **pure model**: one completion in,
600
+ output items out, exactly like the SDK's own `LitellmModel`. The Runner owns the tool
601
+ loop, so `max_tool_calls` (as `max_turns`), `response_format`, the budget and the
602
+ callbacks behave identically streaming or not.
603
+
604
+ 3. ✅ **Streaming actually works** — `stream_response` uses `await litellm.acompletion(
605
+ stream=True)` and hands the async iterator to `ChatCmplStreamHandler` with an empty
606
+ `Response` shell. It previously called the *sync* `completion(stream=True)` and passed
607
+ the result in twice, so `chat(stream=True)` raised
608
+ `TypeError: 'async for' requires an object with __aiter__ method, got ModelResponse`.
609
+
610
+ 4. ✅ `status_callback`/`result_callback` **never fired** — the model loop ignored them
611
+ entirely. They now fire from `_run_tool`, the single place tools execute, with
612
+ `is_error` read from `ToolResult.error`.
613
+
614
+ 5. ✅ **`FunctionTool` wrapper fixed** — it read `ctx.tool_registry` (a
615
+ `RunContextWrapper` has no such attribute) and closed over the loop variable, so every
616
+ tool would have invoked the *last* registered one. The name is now bound via a default
617
+ argument.
618
+
619
+ 6. ✅ **`directory` is now enforced** — `validate_tool_args` rejects any
620
+ `path`/`file_path`/`script_path`/`directory` argument resolving outside the root;
621
+ `shell_command` runs with `cwd=directory` and rejects absolute arguments outside it;
622
+ the CLI gained `--directory` (previously the agent was constructed with no directory at
623
+ all, so the default toolset was unconfined). README documents that `directory` is
624
+ confinement, not a sandbox.
625
+
626
+ 7. ✅ **`AGENTS.md` no longer leaks from the process cwd** — `include_cwd` defaults to
627
+ `False`, closing a prompt-injection surface where the host project's instructions were
628
+ injected into answers about an unrelated repository.
629
+
630
+ 8. ✅ **Per-iteration compaction restored** — `call_model_input_filter` on `RunConfig`
631
+ re-checks the token budget on every Runner iteration, which the SDK had silently
632
+ dropped along with the old loops.
633
+
634
+ 9. ✅ **`MaxTurnsExceeded` is handled, not fatal** — hitting the turn limit is an
635
+ expected outcome for long reviews, so the best text produced so far is recovered from
636
+ `exc.run_data` instead of crashing the caller.
637
+
638
+ 10. ✅ **Test fidelity** — `tests/llm_fakes.py` builds **real** `litellm.types.utils`
639
+ objects instead of `MagicMock`. The streaming bug survived for releases because a
640
+ `MagicMock` answered whatever the test asked it to; a response shape the SDK cannot
641
+ parse now fails the test the way it fails in production. Also fixed
642
+ `tests/test_dependencies.py`, which deleted `regcode.*` from `sys.modules` without
643
+ restoring it, so later tests received a second `AgentStatus` class and enum identity
644
+ comparisons failed purely on execution order.
645
+
646
+ 11. ✅ **285 tests pass** (up from 231), `uv run ruff check` clean.
647
+
648
+ ## Fixed (2026-10-31 — CLI broke on the first real conversation)
649
+
650
+ The rewrite passed every test but broke `regcode chat` as soon as the model said
651
+ anything before calling a tool:
652
+
653
+ ```
654
+ [ERROR] Agent error! litellm.BadRequestError: Hosted_vllmException - {"error":{"message":
655
+ "3 validation errors for ValidatorIterator\n0.ChatCompletionContentPartTextParam.type\n
656
+ Input should be 'text' [type=literal_error, input_value='output_text'...
657
+ ```
658
+
659
+ Three separate regressions, all invisible to the existing suite because **every test
660
+ made the model return *either* text *or* a tool call** — so the second turn, the one
661
+ where the Runner replays the assistant's own output, never executed.
662
+
663
+ 1. ✅ **Responses-API items were leaking into chat-completions requests** — the Runner
664
+ hands the assistant's output back as `{"type": "message", "content": [{"type":
665
+ "output_text", ...}]}`, `function_call` and `function_call_output`. `_prepare_messages`
666
+ copied those dicts straight into `litellm.completion(messages=...)`, which the provider
667
+ rejects. It now runs them through `Converter.items_to_messages(items, model=...)`, the
668
+ same translation the SDK's own `LitellmModel` uses — which also merges an assistant's
669
+ text with the tool calls it asked for into a single turn.
670
+
671
+ 2. ✅ **Replayed history was structurally invalid** — `_update_history_from_sdk_result`
672
+ stored a `"Tool call: x"` label next to a `role: "tool"` result. A tool result is only
673
+ legal when an assistant turn carrying the matching `tool_calls` precedes it, so the
674
+ second `chat()` call would have been rejected by strict providers (vLLM among them).
675
+ History now stores the real `tool_calls` array (id, name, arguments). `ToolCallItem.raw_item`
676
+ is a `ResponseFunctionToolCall` **object** when the Runner builds it and a dict elsewhere;
677
+ reading it only as a dict silently produced `arguments: "{}"`, so a replayed turn claimed
678
+ the model had called a tool with no arguments. `_item_field()` handles both.
679
+
680
+ 3. ✅ **`python_code`, `run_script` and `web_crawl` were dead** — moving tool execution
681
+ under the Runner put them *inside* an event loop, where `asyncio.run()` raises
682
+ `cannot be called from a running event loop`. Every `python_code` call returned
683
+ `SANDBOX_ERROR: ...` and the model just got an error string back. Added
684
+ `regcode/async_utils.py::run_coroutine_blocking()`, which runs the coroutine on a
685
+ dedicated thread when a loop is already active.
686
+
687
+ Also: `chat(messages=...)` is public API (`code_review`, GitKnecht, saved transcripts)
688
+ and accepts plain chat-shaped dicts, which `Converter.items_to_messages` rejects outright.
689
+ `_expand_input_items()` normalises `role: "tool"` and assistant `tool_calls` into Responses
690
+ items before conversion.
691
+
692
+ **Process lesson**: the new tests were written against the shapes the *tests* invented.
693
+ `tests/test_task_regression.py` now pins the message shape of every turn handed to litellm
694
+ (`_assert_chat_completions_shape`), covers text-plus-tool-call turns, a second `chat()`
695
+ call, and tool execution under a running loop. Each was mutation-checked — revert the fix,
696
+ watch it go red.
697
+
@@ -1,3 +1,26 @@
1
+ Metadata-Version: 2.5
2
+ Name: regcode
3
+ Version: 0.12.1
4
+ Summary: Minimalistic coding agent with Python API and CLI
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: boto3>=1.43.40
7
+ Requires-Dist: click>=8.0
8
+ Requires-Dist: crawl4ai>=0.4.0
9
+ Requires-Dist: litellm>=1.83.17
10
+ Requires-Dist: mcp>=1.0.0
11
+ Requires-Dist: mlflow>=3.14.0
12
+ Requires-Dist: openai-agents>=0.18.0
13
+ Requires-Dist: pathspec>=1.1.1
14
+ Requires-Dist: pydantic-monty>=0.0.18
15
+ Requires-Dist: pydantic-settings>=2.14.2
16
+ Requires-Dist: pyperclip>=1.9.0
17
+ Requires-Dist: pytest>=8.0
18
+ Requires-Dist: pyyaml>=6.0
19
+ Requires-Dist: ruff>=0.8
20
+ Requires-Dist: textual>=2.0.0
21
+ Provides-Extra: dev
22
+ Description-Content-Type: text/markdown
23
+
1
24
  # RegCode
2
25
 
3
26
  A minimalistic coding agent with Python API bindings and CLI interface.
@@ -101,6 +124,24 @@ RegCode includes several built-in tools the agent can use:
101
124
 
102
125
  All file-writing tools (`write_file`, `patch_file`) include path traversal protection (`..` is blocked). The `patch_file` tool validates line number bounds and type correctness before performing the replacement.
103
126
 
127
+ ### Working directory and confinement
128
+
129
+ `Agent(directory=...)` (and the CLI's `--directory`, which defaults to the current
130
+ directory) is the root every tool is confined to:
131
+
132
+ - Every path-like tool argument (`path`, `file_path`, `script_path`, `directory`) is
133
+ resolved against it and **rejected** when it resolves outside — absolute paths and
134
+ `..` escapes alike.
135
+ - `shell_command` runs with `cwd` set to it, and its arguments are checked the same way,
136
+ so `cat /etc/passwd` is rejected even though `cat` is allowlisted.
137
+ - `python_code` and `run_script` execute inside the Monty sandbox, which has no host
138
+ filesystem access at all.
139
+
140
+ > **`directory` is a confinement boundary, not a security sandbox.** `shell_command`
141
+ > still runs a real subprocess as the current user, and the toolset is only as safe as
142
+ > its permission list. Services embedding RegCode behind an authenticated endpoint should
143
+ > restrict `permissions` (or unregister tools) rather than relying on `directory` alone.
144
+
104
145
  ## Project Structure
105
146
 
106
147
  ```
@@ -1,25 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: regcode
3
- Version: 0.11.0
4
- Summary: Minimalistic coding agent with Python API and CLI
5
- Requires-Python: >=3.10
6
- Requires-Dist: boto3>=1.43.40
7
- Requires-Dist: click>=8.0
8
- Requires-Dist: crawl4ai>=0.4.0
9
- Requires-Dist: litellm>=1.83.17
10
- Requires-Dist: mcp>=1.0.0
11
- Requires-Dist: mlflow>=3.14.0
12
- Requires-Dist: pathspec>=1.1.1
13
- Requires-Dist: pydantic-monty>=0.0.18
14
- Requires-Dist: pydantic-settings>=2.14.2
15
- Requires-Dist: pyperclip>=1.9.0
16
- Requires-Dist: pytest>=8.0
17
- Requires-Dist: pyyaml>=6.0
18
- Requires-Dist: ruff>=0.8
19
- Requires-Dist: textual>=2.0.0
20
- Provides-Extra: dev
21
- Description-Content-Type: text/markdown
22
-
23
1
  # RegCode
24
2
 
25
3
  A minimalistic coding agent with Python API bindings and CLI interface.
@@ -123,6 +101,24 @@ RegCode includes several built-in tools the agent can use:
123
101
 
124
102
  All file-writing tools (`write_file`, `patch_file`) include path traversal protection (`..` is blocked). The `patch_file` tool validates line number bounds and type correctness before performing the replacement.
125
103
 
104
+ ### Working directory and confinement
105
+
106
+ `Agent(directory=...)` (and the CLI's `--directory`, which defaults to the current
107
+ directory) is the root every tool is confined to:
108
+
109
+ - Every path-like tool argument (`path`, `file_path`, `script_path`, `directory`) is
110
+ resolved against it and **rejected** when it resolves outside — absolute paths and
111
+ `..` escapes alike.
112
+ - `shell_command` runs with `cwd` set to it, and its arguments are checked the same way,
113
+ so `cat /etc/passwd` is rejected even though `cat` is allowlisted.
114
+ - `python_code` and `run_script` execute inside the Monty sandbox, which has no host
115
+ filesystem access at all.
116
+
117
+ > **`directory` is a confinement boundary, not a security sandbox.** `shell_command`
118
+ > still runs a real subprocess as the current user, and the toolset is only as safe as
119
+ > its permission list. Services embedding RegCode behind an authenticated endpoint should
120
+ > restrict `permissions` (or unregister tools) rather than relying on `directory` alone.
121
+
126
122
  ## Project Structure
127
123
 
128
124
  ```
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "regcode"
3
- version = "0.11.0"
3
+ version = "0.12.1"
4
4
  description = "Minimalistic coding agent with Python API and CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "crawl4ai>=0.4.0",
9
9
  "litellm>=1.83.17",
10
+ "openai-agents>=0.18.0",
10
11
  "pydantic-settings>=2.14.2",
11
12
  "pyyaml>=6.0",
12
13
  "click>=8.0",
@@ -2,4 +2,4 @@ from regcode.config import Config
2
2
  from regcode.main import Agent, AgentStatus, ToolCall
3
3
 
4
4
  __all__ = ["Agent", "Config", "AgentStatus", "ToolCall"]
5
- __version__ = "0.10.0"
5
+ __version__ = "0.12.1"