python-agent-harness 1.5.6.2__tar.gz → 1.5.6.4__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.
- {python_agent_harness-1.5.6.2/python_agent_harness.egg-info → python_agent_harness-1.5.6.4}/PKG-INFO +2 -3
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/README.md +1 -2
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/pyproject.toml +1 -1
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/__init__.py +1 -4
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/agent.py +14 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/models.py +37 -5
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/cli.py +0 -13
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/headless.py +24 -8
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/server.py +19 -2
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/io/persistence.py +10 -1
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/agent.md +4 -3
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/agents/reviewer.md +0 -2
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/session/config.py +0 -38
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/session/session.py +7 -11
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/__init__.py +1 -8
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/base.py +1 -2
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/bash.py +2 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/diffapply.py +1 -1
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/edit.py +47 -12
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/filesystem.py +6 -96
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/grep.py +0 -1
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tui/commands.py +4 -2
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tui/core.py +1 -1
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tui/render.py +47 -5
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4/python_agent_harness.egg-info}/PKG-INFO +2 -3
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness.egg-info/SOURCES.txt +0 -7
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/tests/test_invariants.py +16 -17
- python_agent_harness-1.5.6.2/python_agent_harness/lsp/__init__.py +0 -14
- python_agent_harness-1.5.6.2/python_agent_harness/lsp/client.py +0 -314
- python_agent_harness-1.5.6.2/python_agent_harness/lsp/config.py +0 -108
- python_agent_harness-1.5.6.2/python_agent_harness/lsp/manager.py +0 -117
- python_agent_harness-1.5.6.2/python_agent_harness/tools/insert.py +0 -87
- python_agent_harness-1.5.6.2/python_agent_harness/tools/lsp.py +0 -287
- python_agent_harness-1.5.6.2/python_agent_harness/tools/mkdir.py +0 -32
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/LICENSE +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/__main__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/context_manager.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/planmode.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/subagent.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/token_estimator.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/tool_runner.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/controller.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/view.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/io/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/io/attachments.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/io/diffrender.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/io/text_filter.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/llm/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/llm/client.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/mcp/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/mcp/client.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/mcp/config.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/mcp/manager.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/build-switch.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/commands/explain.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/compact.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/core.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/initialize.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/plan-mode.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/plan.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/review.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/subagent.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/summary.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/task-completion-rules.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/title.md +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/session/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/session/commands.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/agent_tool.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/edit_mac.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/glob.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/glob_mac.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/grep_mac.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/mcp.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/planexit.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/question.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/read.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/skill.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/todo.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/write.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tui/__init__.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tui/input.py +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness.egg-info/dependency_links.txt +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness.egg-info/entry_points.txt +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness.egg-info/requires.txt +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness.egg-info/top_level.txt +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/setup.cfg +0 -0
- {python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/tests/test_scenarios.py +0 -0
{python_agent_harness-1.5.6.2/python_agent_harness.egg-info → python_agent_harness-1.5.6.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-agent-harness
|
|
3
|
-
Version: 1.5.6.
|
|
3
|
+
Version: 1.5.6.4
|
|
4
4
|
Summary: A lightweight, hackable mini-OpenCode written in Python.
|
|
5
5
|
Author-email: Huming Chen <chenhuming@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -74,7 +74,7 @@ Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_
|
|
|
74
74
|
- **FSM-driven execution** — explicit `WAIT` / `TOOL` / `TRET` / `SUPERVISE` / `DONE` / `ERRS` / `ABRT` states. Completion supervision nudges the model when it stops early; failed tool calls are sanitized. Transient API failures (`429` / `5xx`) retry with exponential backoff. Auth-expired codes (configurable via `AUTH_REFRESH_STATUS_CODES` in `config.py`; defaults to `[401, 502]`) trigger automatic API key re-read from config/env.
|
|
75
75
|
- **Context management** — CJK-aware token estimation, per-model context windows, automatic compaction at 70% usage.
|
|
76
76
|
- **Image & text attachments** — attach with `@path` (e.g. `@screenshot.png`, `@README.md`): images (PNG/JPEG/GIF/WebP, up to 20 MB) become multimodal `image_url` parts; text files are inlined. Web URLs supported for images.
|
|
77
|
-
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `
|
|
77
|
+
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `Edit` (incl. unified diffs), `Write`, `Bash`, `Skill`, `Question`, `PlanExit`. Read-only tool rounds (`Read`, `Glob`, `Grep`, `Skill`) dispatch concurrently; async tools (`Bash`, `Agent`) can also run concurrently. Results always delivered in emitted order.
|
|
78
78
|
- **Plan / Build modes** — plan mode is read-only except for the per-session plan file.
|
|
79
79
|
- **Persistent sessions** — auto-saved after every response to `~/.local/share/python-agent-harness/sessions/`, with LLM-generated titles and `/restore --latest` / `/sessions`.
|
|
80
80
|
- **Focused TUI** — Rich-based with pinned status bar, Todos panel, inline diff rendering, and a `prompt_toolkit` editor with history and completion. `Esc+Enter` submits, `Ctrl-D` quits, `Ctrl-C` cancels.
|
|
@@ -191,7 +191,6 @@ All LLM settings live in `~/.config/python-agent-harness/config.json`. Environme
|
|
|
191
191
|
- **`subagent_llm`** — LLM config for `Agent` tool. Unset values inherit from `llm`. Set `profile` to reuse a `models` entry. Precedence: profile > `subagent_llm` > `llm` > env vars.
|
|
192
192
|
- **`default_agent`** — agent name to use at session start (must exist in `prompts/agents/`). `null` = built-in default.
|
|
193
193
|
- **`paths.context_path` / `paths.skill_path`** — locations for context files and skills. Default: project-local `contexts/` and `skills/`.
|
|
194
|
-
- **`lsp.servers`** — per-extension LSP server overrides for the `LSP` tool. Keys are extensions (`.py`, `.cpp`); each has `command` (argv) and optional `language_id`. Layers on top of `DEFAULT_SERVERS` in `lsp/manager.py`.
|
|
195
194
|
- **`mcp.servers`** — MCP server config (requires `[mcp]` extra). Each supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, `enabled`.
|
|
196
195
|
- **`headless` / `serve`** — optional budget limits: `max_rounds` (LLM rounds) and `timeout` (wall-clock seconds). `--max-rounds` / `--timeout` override per call; `<= 0` or `null` disables. Unset = unlimited.
|
|
197
196
|
- **Precedence** — code defaults < config file < `OPENAI_*` env vars. Sub-agents also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
@@ -40,7 +40,7 @@ Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_
|
|
|
40
40
|
- **FSM-driven execution** — explicit `WAIT` / `TOOL` / `TRET` / `SUPERVISE` / `DONE` / `ERRS` / `ABRT` states. Completion supervision nudges the model when it stops early; failed tool calls are sanitized. Transient API failures (`429` / `5xx`) retry with exponential backoff. Auth-expired codes (configurable via `AUTH_REFRESH_STATUS_CODES` in `config.py`; defaults to `[401, 502]`) trigger automatic API key re-read from config/env.
|
|
41
41
|
- **Context management** — CJK-aware token estimation, per-model context windows, automatic compaction at 70% usage.
|
|
42
42
|
- **Image & text attachments** — attach with `@path` (e.g. `@screenshot.png`, `@README.md`): images (PNG/JPEG/GIF/WebP, up to 20 MB) become multimodal `image_url` parts; text files are inlined. Web URLs supported for images.
|
|
43
|
-
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `
|
|
43
|
+
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `Edit` (incl. unified diffs), `Write`, `Bash`, `Skill`, `Question`, `PlanExit`. Read-only tool rounds (`Read`, `Glob`, `Grep`, `Skill`) dispatch concurrently; async tools (`Bash`, `Agent`) can also run concurrently. Results always delivered in emitted order.
|
|
44
44
|
- **Plan / Build modes** — plan mode is read-only except for the per-session plan file.
|
|
45
45
|
- **Persistent sessions** — auto-saved after every response to `~/.local/share/python-agent-harness/sessions/`, with LLM-generated titles and `/restore --latest` / `/sessions`.
|
|
46
46
|
- **Focused TUI** — Rich-based with pinned status bar, Todos panel, inline diff rendering, and a `prompt_toolkit` editor with history and completion. `Esc+Enter` submits, `Ctrl-D` quits, `Ctrl-C` cancels.
|
|
@@ -157,7 +157,6 @@ All LLM settings live in `~/.config/python-agent-harness/config.json`. Environme
|
|
|
157
157
|
- **`subagent_llm`** — LLM config for `Agent` tool. Unset values inherit from `llm`. Set `profile` to reuse a `models` entry. Precedence: profile > `subagent_llm` > `llm` > env vars.
|
|
158
158
|
- **`default_agent`** — agent name to use at session start (must exist in `prompts/agents/`). `null` = built-in default.
|
|
159
159
|
- **`paths.context_path` / `paths.skill_path`** — locations for context files and skills. Default: project-local `contexts/` and `skills/`.
|
|
160
|
-
- **`lsp.servers`** — per-extension LSP server overrides for the `LSP` tool. Keys are extensions (`.py`, `.cpp`); each has `command` (argv) and optional `language_id`. Layers on top of `DEFAULT_SERVERS` in `lsp/manager.py`.
|
|
161
160
|
- **`mcp.servers`** — MCP server config (requires `[mcp]` extra). Each supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, `enabled`.
|
|
162
161
|
- **`headless` / `serve`** — optional budget limits: `max_rounds` (LLM rounds) and `timeout` (wall-clock seconds). `--max-rounds` / `--timeout` override per call; `<= 0` or `null` disables. Unset = unlimited.
|
|
163
162
|
- **Precedence** — code defaults < config file < `OPENAI_*` env vars. Sub-agents also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-agent-harness"
|
|
7
|
-
version = "1.5.6.
|
|
7
|
+
version = "1.5.6.4"
|
|
8
8
|
description = "A lightweight, hackable mini-OpenCode written in Python."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Huming Chen", email = "chenhuming@gmail.com"}
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/__init__.py
RENAMED
|
@@ -3,20 +3,17 @@
|
|
|
3
3
|
from .core.models import AgentMode, ImagePart, Message, TextPart, ToolCall, ToolSpec
|
|
4
4
|
from .entry.cli import main # noqa: F401 (public lib surface: main entry point)
|
|
5
5
|
from .llm.client import Client, LLMClient
|
|
6
|
-
from .lsp.config import LSPConfig, LSPServerConfig
|
|
7
6
|
from .mcp.config import MCPConfig, MCPServerConfig
|
|
8
7
|
from .mcp.manager import MCPManager
|
|
9
8
|
from .session.session import Session
|
|
10
9
|
|
|
11
|
-
__version__ = "1.5.6.
|
|
10
|
+
__version__ = "1.5.6.4"
|
|
12
11
|
|
|
13
12
|
__all__ = [
|
|
14
13
|
"Client",
|
|
15
14
|
"LLMClient",
|
|
16
15
|
"Session",
|
|
17
16
|
"AgentMode",
|
|
18
|
-
"LSPConfig",
|
|
19
|
-
"LSPServerConfig",
|
|
20
17
|
"MCPConfig",
|
|
21
18
|
"MCPManager",
|
|
22
19
|
"MCPServerConfig",
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/agent.py
RENAMED
|
@@ -552,6 +552,20 @@ class AgentLoop:
|
|
|
552
552
|
self._just_compacted = True
|
|
553
553
|
self.info["compacted"] = True
|
|
554
554
|
return
|
|
555
|
+
# Compaction failed (empty summary, no reducible
|
|
556
|
+
# prompts, or an exception -- see ContextManager.
|
|
557
|
+
# compact): the history is unchanged, so without a
|
|
558
|
+
# guard the next WAIT round sees the same
|
|
559
|
+
# above-trigger ratio and compacts again, re-billing
|
|
560
|
+
# the summary request every round for nothing.
|
|
561
|
+
# Remember the current ratio as the floor -- reusing
|
|
562
|
+
# the same mechanism as the "still above trigger
|
|
563
|
+
# after a successful compaction" case above -- so
|
|
564
|
+
# the guard below suppresses re-compaction until the
|
|
565
|
+
# context grows meaningfully past it, at which point
|
|
566
|
+
# a retry is warranted (the conversation really did
|
|
567
|
+
# grow) rather than a no-op loop.
|
|
568
|
+
self._compaction_floor = ratio
|
|
555
569
|
else:
|
|
556
570
|
self._compaction_floor = None
|
|
557
571
|
self._just_compacted = False
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/core/models.py
RENAMED
|
@@ -5,9 +5,30 @@ from __future__ import annotations
|
|
|
5
5
|
import base64
|
|
6
6
|
import enum
|
|
7
7
|
import json
|
|
8
|
+
import re
|
|
8
9
|
from dataclasses import dataclass, field
|
|
9
10
|
from typing import Any, Union
|
|
10
11
|
|
|
12
|
+
# Lone surrogates (\ud800-\udfff) reach a str when a file is decoded with
|
|
13
|
+
# errors="surrogateescape" — which every tool deliberately does, so a
|
|
14
|
+
# non-UTF-8 source file round-trips instead of becoming U+FFFD. Such a
|
|
15
|
+
# string cannot be encoded to UTF-8, so anything that leaves the process
|
|
16
|
+
# (a JSON request body, a terminal write) must replace them first.
|
|
17
|
+
_SURROGATE_RE = re.compile(r"[\ud800-\udfff]")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def api_safe(text: str) -> str:
|
|
21
|
+
"""TEXT with lone surrogates replaced by U+FFFD, safe to serialize.
|
|
22
|
+
|
|
23
|
+
The tools read files with ``errors="surrogateescape"``, so a
|
|
24
|
+
conversation can carry lone surrogates from a non-UTF-8 (Latin-1,
|
|
25
|
+
GBK, ...) source. A request body containing one cannot be encoded
|
|
26
|
+
to UTF-8 — ``httpx`` raised ``UnicodeEncodeError`` — so they are
|
|
27
|
+
replaced at the OUTBOUND boundary only: the stored history, the
|
|
28
|
+
session file and the file on disk keep the exact bytes.
|
|
29
|
+
"""
|
|
30
|
+
return _SURROGATE_RE.sub("\ufffd", text)
|
|
31
|
+
|
|
11
32
|
|
|
12
33
|
class AgentMode(enum.Enum):
|
|
13
34
|
BUILD = "build"
|
|
@@ -136,7 +157,10 @@ class Message:
|
|
|
136
157
|
def to_api(self) -> dict[str, Any]:
|
|
137
158
|
d: dict[str, Any] = {"role": self.role}
|
|
138
159
|
if self.content is not None:
|
|
139
|
-
|
|
160
|
+
content = self._api_content()
|
|
161
|
+
# outbound boundary: a lone surrogate cannot be encoded into
|
|
162
|
+
# the JSON body (see api_safe)
|
|
163
|
+
d["content"] = api_safe(content) if isinstance(content, str) else content
|
|
140
164
|
if self.tool_calls:
|
|
141
165
|
d["tool_calls"] = [
|
|
142
166
|
{
|
|
@@ -144,9 +168,13 @@ class Message:
|
|
|
144
168
|
"type": "function",
|
|
145
169
|
"function": {
|
|
146
170
|
"name": tc.name,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
171
|
+
# ensure_ascii=False keeps non-ASCII readable;
|
|
172
|
+
# api_safe then removes the unencodable surrogates
|
|
173
|
+
"arguments": api_safe(
|
|
174
|
+
tc.arguments
|
|
175
|
+
if isinstance(tc.arguments, str)
|
|
176
|
+
else json.dumps(tc.arguments, ensure_ascii=False)
|
|
177
|
+
),
|
|
150
178
|
},
|
|
151
179
|
}
|
|
152
180
|
for tc in self.tool_calls
|
|
@@ -188,7 +216,11 @@ class Message:
|
|
|
188
216
|
@staticmethod
|
|
189
217
|
def _part_to_api(part: ContentPart) -> Any:
|
|
190
218
|
"""Serialize one content part to its provider-specific dict."""
|
|
191
|
-
if isinstance(part,
|
|
219
|
+
if isinstance(part, TextPart):
|
|
220
|
+
# text parts are a second carrier of file content into the
|
|
221
|
+
# payload, so they need the same outbound treatment
|
|
222
|
+
return {"type": "text", "text": api_safe(part.text)}
|
|
223
|
+
if isinstance(part, ImagePart):
|
|
192
224
|
return part.to_api()
|
|
193
225
|
return part
|
|
194
226
|
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/cli.py
RENAMED
|
@@ -45,11 +45,6 @@ def make_session(
|
|
|
45
45
|
settings = config.load_llm_config(config_path)
|
|
46
46
|
paths = config.load_paths_config(config_path)
|
|
47
47
|
mcp_config = config.load_mcp_config(config_path)
|
|
48
|
-
# Validate lsp.servers eagerly so a malformed override fails fast at
|
|
49
|
-
# session start (with a clear message) instead of poisoning the first
|
|
50
|
-
# LSP tool call mid-session. The LSP manager re-reads it lazily via
|
|
51
|
-
# config_path; this call only surfaces config errors early.
|
|
52
|
-
config.load_lsp_config(config_path)
|
|
53
48
|
model = model or settings["model"]
|
|
54
49
|
# resolve sub-agent overrides against the EFFECTIVE main settings
|
|
55
50
|
# (so a CLI/caller model override is inherited too when the
|
|
@@ -345,14 +340,6 @@ def cmd_config(args: argparse.Namespace) -> int:
|
|
|
345
340
|
print(f" {pattern}: {size}")
|
|
346
341
|
else:
|
|
347
342
|
print("context_windows: (none configured — built-in table in config.py applies)")
|
|
348
|
-
# Show LSP server overrides (per-extension, layered over the built-ins)
|
|
349
|
-
lsp_config = config.load_lsp_config(args.path)
|
|
350
|
-
if lsp_config.servers:
|
|
351
|
-
print("lsp servers:")
|
|
352
|
-
for ext, server in sorted(lsp_config.servers.items()):
|
|
353
|
-
print(f" {ext}: language_id={server.language_id}, command={' '.join(server.command)}")
|
|
354
|
-
else:
|
|
355
|
-
print("lsp servers: (none configured — built-in DEFAULT_SERVERS in lsp/manager.py applies)")
|
|
356
343
|
# Show default agent
|
|
357
344
|
default_agent = config.load_default_agent(args.path)
|
|
358
345
|
print(f"default_agent: {default_agent or '(default: agent.md)'}")
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/headless.py
RENAMED
|
@@ -22,6 +22,7 @@ import sys
|
|
|
22
22
|
import threading
|
|
23
23
|
from typing import Any, TextIO
|
|
24
24
|
|
|
25
|
+
from ..core.models import api_safe
|
|
25
26
|
from ..io.text_filter import strip_final_check
|
|
26
27
|
from ..session.session import Session
|
|
27
28
|
from .controller import Controller
|
|
@@ -58,8 +59,8 @@ def _as_error(value: Any) -> dict[str, Any]:
|
|
|
58
59
|
``HeadlessView.errors`` accumulates whatever the notifier passed:
|
|
59
60
|
the agent loop sends a structured ``{"code", "message"}`` for
|
|
60
61
|
classified failures (``budget``/``timeout``), other sites send a
|
|
61
|
-
plain string whose code is then derived from the text, and
|
|
62
|
-
|
|
62
|
+
plain string whose code is then derived from the text, and some
|
|
63
|
+
callers send a ``{"message": ...}`` dict. A nested
|
|
63
64
|
``{"error": {...}}`` shape is unwrapped; a dict carrying only a
|
|
64
65
|
``code`` falls back to that code for its message so the two never
|
|
65
66
|
disagree on emptiness.
|
|
@@ -105,8 +106,7 @@ def error_display_text(value: Any) -> str:
|
|
|
105
106
|
|
|
106
107
|
``notify("error", ...)`` carries either a plain message string or a
|
|
107
108
|
structured ``{"code", "message"}`` payload (the agent loop sends
|
|
108
|
-
the latter where the outcome is known
|
|
109
|
-
``{"message": ...}`` dict too). Views render this rather than the
|
|
109
|
+
the latter where the outcome is known). Views render this rather than the
|
|
110
110
|
raw value, so a structured error does not reach a human as a dict
|
|
111
111
|
repr.
|
|
112
112
|
|
|
@@ -166,7 +166,13 @@ def final_answer_text(session: Session) -> str:
|
|
|
166
166
|
continue
|
|
167
167
|
text = strip_final_check(msg.text_without_reasoning()).strip()
|
|
168
168
|
if text:
|
|
169
|
-
|
|
169
|
+
# api_safe: the answer is written to stdout (headless prints it
|
|
170
|
+
# verbatim; --json and serve JSON-encode it), and a lone
|
|
171
|
+
# surrogate from a restored non-UTF-8 session raised
|
|
172
|
+
# UnicodeEncodeError there — headless died before writing the
|
|
173
|
+
# result line, and serve's _write_line SWALLOWED it (it is a
|
|
174
|
+
# ValueError), reporting success with an empty stream.
|
|
175
|
+
return api_safe(text)
|
|
170
176
|
return ""
|
|
171
177
|
|
|
172
178
|
|
|
@@ -295,7 +301,10 @@ def restore_session(controller: Controller, spec: str, err: TextIO) -> bool:
|
|
|
295
301
|
if not path or not os.path.isfile(path):
|
|
296
302
|
err.write(f"no session found: {spec or 'latest'}\n")
|
|
297
303
|
return False
|
|
298
|
-
|
|
304
|
+
# surrogateescape must mirror SessionPersistence.save: the saved body
|
|
305
|
+
# may carry lone surrogates (the tools read non-UTF-8 files that way),
|
|
306
|
+
# and a strict decode raised UnicodeDecodeError here.
|
|
307
|
+
with open(path, encoding="utf-8", errors="surrogateescape") as fh:
|
|
299
308
|
text = fh.read()
|
|
300
309
|
meta = SessionPersistence.parse_metadata(text)
|
|
301
310
|
body = SessionPersistence.strip_metadata(text)
|
|
@@ -417,7 +426,10 @@ def run_headless(
|
|
|
417
426
|
return 1
|
|
418
427
|
answer = final_answer_text(session)
|
|
419
428
|
if answer:
|
|
420
|
-
|
|
429
|
+
# api_safe: the answer carries whatever the conversation carried
|
|
430
|
+
# (a restored non-UTF-8 session), and a strict-UTF-8 stdout
|
|
431
|
+
# raised UnicodeEncodeError here — losing the result line.
|
|
432
|
+
view.out.write(api_safe(answer) + "\n")
|
|
421
433
|
view.out.flush()
|
|
422
434
|
return 1 if view.errors else 0
|
|
423
435
|
|
|
@@ -512,7 +524,11 @@ class JsonlView(HeadlessView):
|
|
|
512
524
|
line = {"seq": self._seq, "protocol": PROTOCOL_VERSION, **payload}
|
|
513
525
|
if self.run_id is not None:
|
|
514
526
|
line["run_id"] = self.run_id
|
|
515
|
-
|
|
527
|
+
# api_safe at the sink: the JSON body is written to the driver's
|
|
528
|
+
# stdout, and a lone surrogate (restored non-UTF-8 session) made
|
|
529
|
+
# the write raise UnicodeEncodeError — the run then ended without
|
|
530
|
+
# its terminal `result` line.
|
|
531
|
+
self.out.write(api_safe(json.dumps(line, ensure_ascii=False, default=str)) + "\n")
|
|
516
532
|
self.out.flush()
|
|
517
533
|
|
|
518
534
|
def _emit(self, payload: dict[str, Any]) -> None:
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/entry/server.py
RENAMED
|
@@ -127,6 +127,7 @@ import time
|
|
|
127
127
|
import uuid
|
|
128
128
|
from typing import Any, TextIO
|
|
129
129
|
|
|
130
|
+
from ..core.models import api_safe
|
|
130
131
|
from ..session.session import Session
|
|
131
132
|
from .controller import Controller
|
|
132
133
|
from .headless import PROTOCOL_VERSION, JsonlView, _restorable_handler, final_answer_text
|
|
@@ -530,7 +531,11 @@ class AgentServer:
|
|
|
530
531
|
live run lines get ``seq``/``run_id`` via the view's ``_write``.
|
|
531
532
|
"""
|
|
532
533
|
text = (
|
|
533
|
-
|
|
534
|
+
api_safe(
|
|
535
|
+
json.dumps(
|
|
536
|
+
{"protocol": PROTOCOL_VERSION, **payload}, ensure_ascii=False, default=str
|
|
537
|
+
)
|
|
538
|
+
)
|
|
534
539
|
+ "\n"
|
|
535
540
|
)
|
|
536
541
|
view = self.view
|
|
@@ -892,7 +897,19 @@ class AgentServer:
|
|
|
892
897
|
def _read_ops(self) -> None:
|
|
893
898
|
"""The reader loop proper: dispatch ops until EOF/shutdown."""
|
|
894
899
|
while not self._stopped.is_set():
|
|
895
|
-
|
|
900
|
+
try:
|
|
901
|
+
line = self.inp.readline()
|
|
902
|
+
except (UnicodeDecodeError, ValueError, OSError) as e:
|
|
903
|
+
# Unreadable input must never take the resident process
|
|
904
|
+
# down. A single non-UTF-8 byte on a strict-UTF-8 stdin
|
|
905
|
+
# (the common C.UTF-8/LANG=en_US.UTF-8 container setup)
|
|
906
|
+
# raised out of serve_forever, which skipped the run drain
|
|
907
|
+
# below: an in-flight run then emitted NO result line and
|
|
908
|
+
# its token usage was lost. Report it as a protocol error
|
|
909
|
+
# and stop reading, so the drain still runs. ValueError
|
|
910
|
+
# covers a stdin closed underneath us.
|
|
911
|
+
self._error("protocol", f"cannot read op: {e}")
|
|
912
|
+
break
|
|
896
913
|
if not line: # EOF: the host closed the pipe
|
|
897
914
|
break
|
|
898
915
|
line = line.strip()
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/io/persistence.py
RENAMED
|
@@ -195,7 +195,16 @@ class SessionPersistence:
|
|
|
195
195
|
session_dir().mkdir(parents=True, exist_ok=True)
|
|
196
196
|
content = conversation_text.rstrip("\n") + "\n\n" + self.metadata_block() + "\n"
|
|
197
197
|
tmp = path + ".tmp"
|
|
198
|
-
|
|
198
|
+
# surrogateescape: the tools deliberately read files with it
|
|
199
|
+
# (see atomic_write_text / Edit._string_replace), so a
|
|
200
|
+
# conversation can legitimately carry lone surrogates from a
|
|
201
|
+
# non-UTF-8 source file. A strict write raised
|
|
202
|
+
# UnicodeEncodeError, which is NOT an OSError, so it escaped
|
|
203
|
+
# auto_save's handler: the turn was never saved and a stray
|
|
204
|
+
# <name>.md.tmp was left behind. The matching reads
|
|
205
|
+
# (headless restore_session, TUI /restore and /sessions) use
|
|
206
|
+
# the same error handler so the bytes round-trip.
|
|
207
|
+
Path(tmp).write_text(content, encoding="utf-8", errors="surrogateescape")
|
|
199
208
|
os.replace(tmp, path)
|
|
200
209
|
return path
|
|
201
210
|
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/prompts/agent.md
RENAMED
|
@@ -56,9 +56,10 @@ The user will primarily request you perform software engineering tasks. This inc
|
|
|
56
56
|
- Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially.
|
|
57
57
|
- Implement the solution using all tools available to you
|
|
58
58
|
- Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach.
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
- Test edge cases to ensure your solution is robust.
|
|
60
|
+
- VERY IMPORTANT:
|
|
61
|
+
- When you have completed a task, you MUST run the lint and typecheck commands (e.g. npm run lint, npm run typecheck, ruff, etc.) with Bash if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to AGENTS.md so that you will know to run it next time.
|
|
62
|
+
- NEVER commit changes unless the user explicitly asks you to.
|
|
62
63
|
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result.
|
|
63
64
|
|
|
64
65
|
# Tool usage policy
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/session/config.py
RENAMED
|
@@ -6,7 +6,6 @@ import json
|
|
|
6
6
|
import os
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from ..lsp.config import LSPConfig
|
|
10
9
|
from ..mcp.config import MCPConfig
|
|
11
10
|
|
|
12
11
|
# ---- context management -------------------------------------------------
|
|
@@ -88,14 +87,11 @@ DEFAULT_TOOLS: list[str] = [
|
|
|
88
87
|
"Glob",
|
|
89
88
|
"Grep",
|
|
90
89
|
"Read",
|
|
91
|
-
"Insert",
|
|
92
90
|
"Edit",
|
|
93
91
|
"Write",
|
|
94
|
-
"Mkdir",
|
|
95
92
|
"Bash",
|
|
96
93
|
"Skill",
|
|
97
94
|
"Question",
|
|
98
|
-
"LSP",
|
|
99
95
|
]
|
|
100
96
|
|
|
101
97
|
# ---- tool output limits --------------------------------------------------------
|
|
@@ -261,15 +257,6 @@ CONFIG_TEMPLATE = """\
|
|
|
261
257
|
"context_path": null,
|
|
262
258
|
"skill_path": null
|
|
263
259
|
}},
|
|
264
|
-
"lsp": {{
|
|
265
|
-
"_comment": "Optional per-extension LSP server overrides. Keys are file extensions (e.g. '.py', '.cpp'); 'command' is the server argv; 'language_id' defaults to the extension without the dot. These override the built-in DEFAULT_SERVERS table. Remove this section to use only the built-ins. The server binary must be on PATH. The '.example' entry below is inert (no such extension) — copy it to a real extension like '.cpp' to activate.",
|
|
266
|
-
"servers": {{
|
|
267
|
-
".example": {{
|
|
268
|
-
"command": ["clangd", "--background-index", "--clang-tidy"],
|
|
269
|
-
"language_id": "cpp"
|
|
270
|
-
}}
|
|
271
|
-
}}
|
|
272
|
-
}},
|
|
273
260
|
"mcp": {{
|
|
274
261
|
"_comment": "Optional MCP servers (requires: pip install -e '.[mcp]'). Each server's tools become agent tools named mcp__<server>__<tool>. Transports: stdio (spawn command+args, pass through env var names), streamable-http / sse (connect to url, optional headers). 'parallel: true' marks read-only servers whose tools may run concurrently; default is serial. 'timeout' bounds connects, discovery and calls (seconds).",
|
|
275
262
|
"servers": {{
|
|
@@ -526,31 +513,6 @@ def load_mcp_config(path: str | os.PathLike | None = None) -> MCPConfig:
|
|
|
526
513
|
return MCPConfig.from_dict(section.get("servers"))
|
|
527
514
|
|
|
528
515
|
|
|
529
|
-
def load_lsp_config(path: str | os.PathLike | None = None) -> LSPConfig:
|
|
530
|
-
"""Load per-extension LSP server overrides from the config file.
|
|
531
|
-
|
|
532
|
-
Reads the optional ``lsp.servers`` object (keyed by file extension,
|
|
533
|
-
e.g. ``".py"``) and returns an ``LSPConfig`` (empty when the file
|
|
534
|
-
has no ``lsp`` section or no servers). These entries layer on top of
|
|
535
|
-
the built-in ``DEFAULT_SERVERS`` table in ``lsp/manager.py``.
|
|
536
|
-
|
|
537
|
-
Malformed entries raise ValueError so config errors surface at
|
|
538
|
-
session start rather than the first LSP call.
|
|
539
|
-
"""
|
|
540
|
-
data = _read_config(path)
|
|
541
|
-
section = data.get("lsp") or {}
|
|
542
|
-
if not isinstance(section, dict):
|
|
543
|
-
raise ValueError(f"config file {_config_path(path)}: lsp must be an object")
|
|
544
|
-
servers = section.get("servers") or {}
|
|
545
|
-
if not isinstance(servers, dict):
|
|
546
|
-
raise ValueError(f"config file {_config_path(path)}: lsp.servers must be an object")
|
|
547
|
-
try:
|
|
548
|
-
return LSPConfig.from_dict(servers)
|
|
549
|
-
except ValueError as e:
|
|
550
|
-
# Re-raise with the config-file path for a clearer message.
|
|
551
|
-
raise ValueError(f"config file {_config_path(path)}: {e}") from e
|
|
552
|
-
|
|
553
|
-
|
|
554
516
|
def load_models_config(path: str | os.PathLike | None = None) -> dict[str, dict]:
|
|
555
517
|
"""Load named LLM profiles from the config file's ``models`` object.
|
|
556
518
|
|
|
@@ -370,7 +370,7 @@ class Session:
|
|
|
370
370
|
# write_file/create_directory), so plan mode stays read-only
|
|
371
371
|
# by refusing every mcp__ tool.
|
|
372
372
|
if self.plan_mode.is_plan and (
|
|
373
|
-
name in ("Write", "Edit", "
|
|
373
|
+
name in ("Write", "Edit", "Bash") or name.startswith("mcp__")
|
|
374
374
|
):
|
|
375
375
|
blocked = self._plan_blocked(name, args)
|
|
376
376
|
if blocked:
|
|
@@ -467,16 +467,8 @@ class Session:
|
|
|
467
467
|
),
|
|
468
468
|
self.project_dir,
|
|
469
469
|
)
|
|
470
|
-
if name
|
|
470
|
+
if name == "Edit":
|
|
471
471
|
return resolve_path(str(args.get("path", "")), self.project_dir)
|
|
472
|
-
if name == "Mkdir":
|
|
473
|
-
# Mirrors Mkdir.run: only the parent is ~-expanded.
|
|
474
|
-
return os.path.realpath(
|
|
475
|
-
os.path.join(
|
|
476
|
-
resolve_path(str(args.get("parent", "")), self.project_dir),
|
|
477
|
-
str(args.get("name", "")),
|
|
478
|
-
)
|
|
479
|
-
)
|
|
480
472
|
return None
|
|
481
473
|
|
|
482
474
|
# ------------------------------------------------------------------
|
|
@@ -698,7 +690,11 @@ class Session:
|
|
|
698
690
|
self.store.save(text)
|
|
699
691
|
self._save_error = None
|
|
700
692
|
return
|
|
701
|
-
except OSError as e:
|
|
693
|
+
except (OSError, UnicodeError) as e:
|
|
694
|
+
# UnicodeError too: a save failure must never escape into
|
|
695
|
+
# the agent loop (it unwound the run and lost the turn).
|
|
696
|
+
# The writer uses surrogateescape now, so this is a
|
|
697
|
+
# defensive net rather than the primary fix.
|
|
702
698
|
self._save_error = str(e)
|
|
703
699
|
if attempt == 1:
|
|
704
700
|
time.sleep(0.2)
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/__init__.py
RENAMED
|
@@ -9,10 +9,9 @@ from .base import PendingToolResult, Registry, Tool, ToolContext
|
|
|
9
9
|
from .bash import Bash
|
|
10
10
|
from .edit import Edit
|
|
11
11
|
from .edit_mac import EditMac
|
|
12
|
-
from .filesystem import GlobTool, Grep,
|
|
12
|
+
from .filesystem import GlobTool, Grep, Read, Write
|
|
13
13
|
from .glob_mac import GlobMac
|
|
14
14
|
from .grep_mac import GrepMac
|
|
15
|
-
from .lsp import LSP
|
|
16
15
|
from .mcp import MCPTool, mcp_tools_from_manager, normalize_mcp_result
|
|
17
16
|
from .planexit import PlanExit
|
|
18
17
|
from .question import Question
|
|
@@ -32,10 +31,7 @@ __all__ = [
|
|
|
32
31
|
"GlobTool",
|
|
33
32
|
"Grep",
|
|
34
33
|
"GrepMac",
|
|
35
|
-
"Insert",
|
|
36
|
-
"LSP",
|
|
37
34
|
"MCPTool",
|
|
38
|
-
"Mkdir",
|
|
39
35
|
"PlanExit",
|
|
40
36
|
"Question",
|
|
41
37
|
"Read",
|
|
@@ -60,14 +56,11 @@ def default_registry() -> Registry:
|
|
|
60
56
|
for tool in (
|
|
61
57
|
AgentTool(),
|
|
62
58
|
TodoWrite(),
|
|
63
|
-
LSP(),
|
|
64
59
|
glob_tool,
|
|
65
60
|
grep_tool,
|
|
66
61
|
Read(),
|
|
67
|
-
Insert(),
|
|
68
62
|
edit_tool,
|
|
69
63
|
Write(),
|
|
70
|
-
Mkdir(),
|
|
71
64
|
bash_tool,
|
|
72
65
|
Skill(),
|
|
73
66
|
Question(),
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/base.py
RENAMED
|
@@ -165,8 +165,7 @@ class ToolContext:
|
|
|
165
165
|
def config_path(self) -> str | None:
|
|
166
166
|
"""Path to the active config file (None = default resolution).
|
|
167
167
|
|
|
168
|
-
Tools that read the config file
|
|
169
|
-
per-extension server overrides) use this so a session started
|
|
168
|
+
Tools that read the config file use this so a session started
|
|
170
169
|
with ``--config PATH`` reads the same file.
|
|
171
170
|
"""
|
|
172
171
|
if self.session:
|
{python_agent_harness-1.5.6.2 → python_agent_harness-1.5.6.4}/python_agent_harness/tools/bash.py
RENAMED
|
@@ -217,6 +217,8 @@ class Bash(Tool):
|
|
|
217
217
|
- Commands that truly require shell execution
|
|
218
218
|
- Running builds, tests, or development servers
|
|
219
219
|
- System administration tasks
|
|
220
|
+
- Creating empty directories: use `mkdir -p <path>` (idempotent; `Write`
|
|
221
|
+
already creates parent directories for any file it writes)
|
|
220
222
|
|
|
221
223
|
**When NOT to use `Bash`:**
|
|
222
224
|
- File operations → use `Read`, `Write`, `Edit`, `Glob`, `Grep` instead
|
|
@@ -227,7 +227,7 @@ def _apply_section(section: _Section, cwd: str, fallback_path: str | None) -> tu
|
|
|
227
227
|
# realpath BEFORE writing (the same resolution diff_targets applies):
|
|
228
228
|
# the write goes through atomic_write_text, whose os.replace would
|
|
229
229
|
# otherwise overwrite a symlink with a regular file and leave the real
|
|
230
|
-
# target untouched. Edit/
|
|
230
|
+
# target untouched. Edit/Write resolve their own paths already.
|
|
231
231
|
target = os.path.realpath(_resolve_target(section.new_path, cwd, fallback_path))
|
|
232
232
|
if not os.path.isfile(target):
|
|
233
233
|
return False, f"target file does not exist: {target}"
|