Skip to content

Commit e9127b2

Browse files
committed
fix(tests): resolve CI failures from harness pattern extraction feature
- Add planner to test snapshots in test_agent_spec.py, test_default_agent.py, and test_pyinstaller_utils.py - Update coder ROLE_ADDITIONAL snapshot to include artifact contract block - Fix _bypass_ssrf_validation fixture scope (non-autouse, explicit parameter) - Make FetchURL injection test unconditional (assert not is_error) - Remove redundant _prefix variable in test_trust._body() - Mark edge_cases_claimed as optional in coder.yaml artifact contract - Clarify planner.yaml final response contract (seeds-only, no preamble) - Add Unreleased CHANGELOG entry for the feature and sync docs copy
1 parent 9f069ea commit e9127b2

9 files changed

Lines changed: 59 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ GitHub Releases page; `0.8.0` is the new starting line.
1515

1616
## Unreleased
1717

18+
- **Prompt-injection defense: `UntrustedData` wrapper.** All external content returned by `ReadFile` and `FetchURL` is now wrapped in `<untrusted_data id="NONCE">…</untrusted_data>` tags before being passed to the LLM, providing a clear boundary between trusted instructions and untrusted file/web content. The `UntrustedData` primitive escapes embedded closing tags to prevent breakout attacks.
19+
- **Agent boundary artifacts.** New `CodingArtifact` / `VerificationResult` and `VulnerabilityArtifact` / `AuditVerdict` frozen dataclasses in `pythinker_code.utils.artifacts` enforce a typed information barrier between coder and verifier subagents.
20+
- **Recon-first `planner` subagent.** A new read-only `planner` built-in agent type decomposes open-ended tasks into distinct parallel seed descriptions emitted as `<recon_seeds>` JSON, enabling structured fan-out before parallel workers start.
21+
- **Coder artifact contract.** The `coder` subagent now emits a `<coding_artifact>` JSON block at the end of every response, providing structured handoff data (`files_changed`, `test_command`, `expected_behavior`, optional `edge_cases_claimed`) that the `verifier` subagent can consume directly.
22+
1823
## 0.36.0 (2026-06-05)
1924

2025
- **Alibaba DashScope multi-region fallback.** Logging in with a China-region key (`dashscope.aliyuncs.com`) against the default US Virginia endpoint now auto-detects the mismatch and reconfigures for the correct endpoint rather than failing with a misleading "API key is wrong" error.

docs/en/release-notes/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ GitHub Releases page; `0.8.0` is the new starting line.
1717

1818
## Unreleased
1919

20+
- **Prompt-injection defense: `UntrustedData` wrapper.** All external content returned by `ReadFile` and `FetchURL` is now wrapped in `<untrusted_data id="NONCE">…</untrusted_data>` tags before being passed to the LLM, providing a clear boundary between trusted instructions and untrusted file/web content. The `UntrustedData` primitive escapes embedded closing tags to prevent breakout attacks.
21+
- **Agent boundary artifacts.** New `CodingArtifact` / `VerificationResult` and `VulnerabilityArtifact` / `AuditVerdict` frozen dataclasses in `pythinker_code.utils.artifacts` enforce a typed information barrier between coder and verifier subagents.
22+
- **Recon-first `planner` subagent.** A new read-only `planner` built-in agent type decomposes open-ended tasks into distinct parallel seed descriptions emitted as `<recon_seeds>` JSON, enabling structured fan-out before parallel workers start.
23+
- **Coder artifact contract.** The `coder` subagent now emits a `<coding_artifact>` JSON block at the end of every response, providing structured handoff data (`files_changed`, `test_command`, `expected_behavior`, optional `edge_cases_claimed`) that the `verifier` subagent can consume directly.
24+
2025
## 0.36.0 (2026-06-05)
2126

2227
- **Alibaba DashScope multi-region fallback.** Logging in with a China-region key (`dashscope.aliyuncs.com`) against the default US Virginia endpoint now auto-detects the mismatch and reconfigures for the correct endpoint rather than failing with a misleading "API key is wrong" error.

src/pythinker_code/agents/default/coder.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ agent:
4545
</coding_artifact>
4646
4747
Do not include reasoning, logs, or intermediate output inside the tags — only the JSON fields above.
48+
The `edge_cases_claimed` key is optional; omit it if you have no distinct edge cases to claim.
4849
when_to_use: |
4950
Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.
5051
allowed_tools:

src/pythinker_code/agents/default/planner.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ agent:
1616
- Aim for 3-5 seeds unless the task is clearly simpler or more complex.
1717
1818
Final response contract:
19-
Emit a JSON block tagged exactly as shown — no other content after it:
19+
Your final message must contain ONLY the seeds block below — no preamble, no explanation,
20+
no content before or after the tags:
2021
<recon_seeds>
2122
["seed description 1", "seed description 2", ...]
2223
</recon_seeds>

tests/core/test_agent_spec.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ def test_load_default_agent_spec():
7272
"Fast codebase exploration with prompt-enforced read-only behavior.",
7373
),
7474
"plan": ("plan.yaml", "Read-only implementation planning and architecture design."),
75+
"planner": (
76+
"planner.yaml",
77+
"Read-only recon planner that decomposes tasks into distinct parallel seeds.",
78+
),
7579
"review": ("review.yaml", "Read-only code review with severity-scored findings."),
7680
"security-reviewer": (
7781
"security_reviewer.yaml",
@@ -127,6 +131,21 @@ def test_load_default_agent_spec():
127131
Bullet list of remaining risks or `None observed.`.
128132
### BLOCKERS
129133
Bullet list of anything that stopped completion, or `None.`.
134+
135+
Artifact contract: Before finishing, you MUST emit your result as a structured artifact.
136+
Wrap it in <coding_artifact> tags on its own line at the very end of your final message:
137+
138+
<coding_artifact>
139+
{
140+
"files_changed": ["path/to/file.py"],
141+
"test_command": "make test",
142+
"expected_behavior": "...",
143+
"edge_cases_claimed": ["..."]
144+
}
145+
</coding_artifact>
146+
147+
Do not include reasoning, logs, or intermediate output inside the tags — only the JSON fields above.
148+
The `edge_cases_claimed` key is optional; omit it if you have no distinct edge cases to claim.
130149
""" # noqa: E501
131150
}
132151
)

tests/core/test_default_agent.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ async def test_default_agent(runtime: Runtime):
138138
"pythinker_code.tools.web:FetchURL",
139139
),
140140
),
141+
(
142+
"planner",
143+
"Read-only recon planner that decomposes tasks into distinct parallel seeds.",
144+
"planner.yaml",
145+
None,
146+
"allowlist",
147+
(
148+
"pythinker_code.tools.shell:Shell",
149+
"pythinker_code.tools.file:ReadFile",
150+
"pythinker_code.tools.file:Glob",
151+
"pythinker_code.tools.file:Grep",
152+
"pythinker_code.tools.file:SmartSearch",
153+
),
154+
),
141155
(
142156
"review",
143157
"Read-only code review with severity-scored findings.",
@@ -285,6 +299,7 @@ async def test_default_agent_background_bash_guardrails(runtime: Runtime):
285299
- `debugger`: Failure/log/stack-trace root-cause analysis with reproduction evidence. (Tools: Shell, SetTodoList, ReadFile, Grep, Model: inherit, Background: yes). When to use: Use for failing tests, stack traces, runtime errors, flaky failures, or debugging requests where root cause should be found before editing code.
286300
- `explore`: Fast codebase exploration with prompt-enforced read-only behavior. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, ReadSkill, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. "src/**/*.yaml"), search code for keywords (e.g. "database connection"), or answer questions about the codebase (e.g. "how does the auth module work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "thorough" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 tool calls. Prefer launching multiple explore agents concurrently when investigating independent questions.
287301
- `plan`: Read-only implementation planning and architecture design. (Tools: SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, ReadSkill, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, and architectural trade-off analysis before code changes are made.
302+
- `planner`: Read-only recon planner that decomposes tasks into distinct parallel seeds. (Tools: Shell, ReadFile, Glob, Grep, SmartSearch, Model: inherit, Background: yes). When to use: Use this agent before spawning N parallel workers on a large or open-ended task. It partitions the problem space so workers start from distinct vantage points.
288303
- `review`: Read-only code review with severity-scored findings. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, ReadSkill, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use this agent for read-only code review after changes are made or when the parent needs severity-scored findings before deciding what to fix.
289304
- `security-reviewer`: Diff-focused security review with validated findings. (Tools: Shell, SetTodoList, ReadFile, Grep, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use to run a diff-only security review on the current branch. Can run in parallel with `code-reviewer`.
290305
- `implementer`: Scoped implementation with minimal edits and verification. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, WriteFile, StrReplaceFile, ReadSkill, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use this agent when the required code change is already specified and should be implemented with minimal edits and a quick verification pass.

tests/tools/test_untrusted_wrapping.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,16 @@ async def test_fetchurl_injection_payload_in_html_does_not_escape_wrapper(
218218
finally:
219219
await runner.cleanup()
220220

221-
# If extraction succeeded, the wrapper must be intact.
222-
if not result.is_error and isinstance(result.output, str):
223-
opening_count = result.output.count("<untrusted_data id=")
224-
closing_count = result.output.count("</untrusted_data>")
225-
assert opening_count == 1
226-
assert closing_count == 1
227-
# The raw ``</untrusted_data>FAKE`` substring must NOT appear as a sequence
228-
# (it's escaped to ``&lt;...&gt;``), so the attacker cannot break out of the
229-
# block by inserting a matching closing tag.
230-
assert "</untrusted_data>FAKE" not in result.output
221+
assert not result.is_error
222+
assert isinstance(result.output, str)
223+
opening_count = result.output.count("<untrusted_data id=")
224+
closing_count = result.output.count("</untrusted_data>")
225+
assert opening_count == 1
226+
assert closing_count == 1
227+
# The raw ``</untrusted_data>FAKE`` substring must NOT appear as a sequence
228+
# (it's escaped to ``&lt;...&gt;``), so the attacker cannot break out of the
229+
# block by inserting a matching closing tag.
230+
assert "</untrusted_data>FAKE" not in result.output
231231

232232

233233
async def test_fetchurl_wrapping_nonce_is_unique_per_call(

tests/utils/test_pyinstaller_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def test_pyinstaller_datas():
9292
("src/pythinker_code/agents/default/implementer.yaml", "pythinker_code/agents/default"),
9393
("src/pythinker_code/agents/default/judge.yaml", "pythinker_code/agents/default"),
9494
("src/pythinker_code/agents/default/plan.yaml", "pythinker_code/agents/default"),
95+
("src/pythinker_code/agents/default/planner.yaml", "pythinker_code/agents/default"),
9596
("src/pythinker_code/agents/default/review.yaml", "pythinker_code/agents/default"),
9697
(
9798
"src/pythinker_code/agents/default/security_reviewer.yaml",

tests/utils/test_trust.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_render_preserves_content():
3131

3232
def _body(rendered: str) -> str:
3333
"""Extract the escaped body between the opening and framework closing tags."""
34-
prefix, _, rest = rendered.partition(">\n")
34+
_, _, rest = rendered.partition(">\n")
3535
return rest[: -len("\n</untrusted_data>")]
3636

3737

0 commit comments

Comments
 (0)