Skip to content

feat: tool-call dedup plus shell/terminal hardening and editor fixes - #116

Merged
elkaix merged 10 commits into
mainfrom
chore/clean-todo
Jun 11, 2026
Merged

feat: tool-call dedup plus shell/terminal hardening and editor fixes#116
elkaix merged 10 commits into
mainfrom
chore/clean-todo

Conversation

@elkaix

@elkaix elkaix commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Brings chore/clean-todo up to date with main and bundles the work that accumulated on it:

  • Tool-call dedup (soul/toolset.py, soul/pythinkersoul.py): identical calls within a step share one result; cross-step repeats are detected via canonical-JSON args and nudged with sparse reminders at consecutive streaks of 3/5/8. Echoed reminder args are bounded to a 256-char preview (identity is carried by the dedup telemetry hash).
  • Shell / terminal / plan-mode hardening: failed-command briefs surface the trailing output; subagents no longer receive plan-mode reminders for tools they lack; the terminal cursor-probe can't wedge the TTY in raw mode.
  • Editor / draft / auto-save fixes.
  • Cleanup: trimmed completed tasks from tasks/todo.md; CodeRabbit review nits (telemetry event reports_opened, capability comment, tail() doc/naming, observable-behavior test).

main (#117) was merged in to resolve drift; only CHANGELOG.md and tasks/todo.md conflicted (docs/tracking — resolved keeping both changelog sets and the cleaned todo).

Verification

Dedup + /goal suites green locally on the merged tree (62 tests); lint/format clean. Full CI runs on the branch.

Summary by CodeRabbit

  • New Features

    • Added /reports slash command for opening session reports.
    • Tool-call deduplication to avoid repeated executions and surface repeat reminders.
    • Richer shell error briefs now include trailing command output; welcome banner improved (ASCII/reduced-motion handling, branch display).
  • Bug Fixes

    • Reduced terminal raw-mode hang risk and restored terminal I/O defaults.
    • Plan-mode reminders are root-only (subagents no longer receive them).
  • Documentation

    • Updated docs and changelog for /reports and related behavior.
  • Chores

    • Project rename to pythinker-code recorded.
  • Tests

    • Added coverage for deduplication, shell UI variants, plan-mode behavior, and error-brief rendering.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Implements cross-step and same-step tool-call deduplication with toolset and soul wiring and tests; extracts and appends command stderr tails to briefs and renders error briefs as plain text; makes terminal cursor probe non-blocking and restores VMIN/VTIME; refactors shell welcome UI (ASCII/motion-aware) and MCP timing; renames slash command /vis/reports across code, docs, and tests; plus assorted doc/comment and changelog/todo updates.

Changes

Deduplication & runtime fixes

Layer / File(s) Summary
Toolset dedup utilities and reminders
src/pythinker_code/soul/toolset.py
Add canonical JSON arg canonicalization, ToolCallKey type, reminder-text helpers, per-step tracking fields, begin_step/end_step API, dedup-triggering, telemetry dup_type, and wrapper-return reminder behavior.
PythinkerSoul dedup integration
src/pythinker_code/soul/pythinkersoul.py
Add _last_tool_calls and _current_turn_id, reset per turn and on BackToTheFuture, call toolset.begin_step/end_step around step attempts to seed and persist dedup state.
Dedup tests
tests/core/test_toolset.py
Add tests covering same-step task reuse, canonicalized-arg keys, cross-step reminder streaks and metadata, state seeding, cancelled-task retry clearing, and back_to_the_future behavior.

Terminal & cursor probe

Layer / File(s) Summary
TTY restore and non-blocking cursor probe
src/pythinker_code/utils/term.py
Reset VMIN/VTIME in ensure_tty_sane(); make stdin non-blocking during _cursor_position_unix(), ignore BlockingIOError in the read loop, and restore original stdin blocking mode.

Shell welcome, MCP, and UI

Layer / File(s) Summary
Welcome banner refactor & MCP timing
src/pythinker_code/ui/shell/__init__.py, src/pythinker_code/ui/shell/components/*
Start background MCP loading earlier; add ascii_glyphs_enabled/motion_disabled flags; new _logo_text, ASCII glyph translation, ellipsis-aware truncation, two-column layout, and consolidated Panel rendering.
MCP heartbeat & prompt style
src/pythinker_code/ui/shell/mcp_status.py
Add coral heartbeat style with colors-disabled fallback and update prompt prefix styling.
Welcome UI tests
tests/ui_and_conv/test_shell_welcome_info.py
Add tests for full-width rendering, two-column layout, ASCII-only mode, chip ASCII fallback, motion-disabled styling, and tiny-width safety.

Command brief tail & worklog

Layer / File(s) Summary
ToolResultBuilder.tail()
src/pythinker_code/tools/utils.py, tests/utils/test_result_builder.py
Add tail() to extract last non-empty buffered lines with truncation and max-line limits; tests for empty buffer, skipping blanks, max_lines, truncation, and multi-write aggregation.
Shell brief enrichment
src/pythinker_code/acp/tools.py, src/pythinker_code/tools/shell/__init__.py
Append builder.tail() output to detailed error messages and to brief for timeouts, signals, and non-zero exits; pass constructed brief into builder.error(...).
Worklog error brief rendering
src/pythinker_code/ui/shell/visualize/_worklog.py, tests/ui_and_conv/test_worklog_render.py
Render error briefs as plain Text (not Markdown) so stderr tails remain verbatim; add regression test preserving backticks.

Slash command rename and docs

Layer / File(s) Summary
reports slash handler
src/pythinker_code/ui/shell/slash.py, tests/ui_and_conv/test_shell_switch_slash.py
Rename vis handler → reports(...)->NoReturn, update telemetry key to reports_opened, and adjust tests for registration and behavior.
Docs: /vis → /reports
docs/en/reference/slash-commands.md, docs/en/reference/pythinker-vis.md, CHANGELOG.md
Replace /vis references with /reports in docs and add changelog bullets describing the behavioral notes.

App welcome facts and misc docs/comments

Layer / File(s) Summary
run_shell facts reorder
src/pythinker_code/app.py
Add git branch to early facts, move Session/Auto-save later, compute auto-save with guarded error logging.
Changelog & tasks
CHANGELOG.md, tasks/todo.md
Add Unreleased bullets and update tasks/todo.md top "Recently completed" and "Dropped" notes.
Plan-mode guard + test
src/pythinker_code/soul/dynamic_injections/plan_mode.py, tests/core/test_plan_mode_injection_provider.py
Skip plan-mode injections for subagents and add test helper param + unit test.
Misc comment/doc edits
several modules
Small wording/comment/docstring updates across auth, llm, cli, hooks, session_cleanup, color_utils, terminal_background, terminal_capabilities, theme, path, usage_adapters, stats_pricing, prompt, render_constants, and markdown component.

Sequence Diagram(s)

(omitted — primary new control flow is internal dedup handling and local UI/TTY adjustments; a detailed multi-actor sequence diagram is unnecessary.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

"A rabbit wrote a tiny patch note,
tails of stderr in a neat little quote.
Dedup hops once, then twice, then stays—
shell greets in ASCII or bright displays.
Hooray for tidy logs and fewer replays!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: tool-call deduplication, shell/terminal hardening, and editor fixes.
Description check ✅ Passed The PR description provides a comprehensive summary of changes including tool-call dedup, shell/terminal hardening, editor fixes, and cleanup work, with verification details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/clean-todo

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tasks/todo.md`:
- Line 144: Markdown has fenced code blocks missing surrounding blank lines at
the locations where the snippet shows "```bash" (notably around the block
starting at the snippet and similar blocks at lines 150 and 156); fix by adding
a blank line immediately before and after each fenced code block so there is an
empty line between the preceding paragraph/list item and the opening "```bash"
and another empty line after the closing "```", ensuring the code fences in the
tasks/todo.md (the blocks starting with "```bash") follow markdownlint
expectations.
- Around line 143-159: Update tasks/todo.md to replace the outdated guidance
about renaming pythinker_cli_session with current pythinker_code_session usage
(remove the suggested sed command that targets pythinker_cli_session and instead
reference pythinker_code_session where relevant), remove or adjust the proposed
telemetry regex change (do not add
r"^(.*?)(site-packages|pythinker_code|src/pythinker_code)/" since telemetry
paths are already handled in src/pythinker_code/telemetry/sentry.py), and re-run
Markdown lint to fix fencing/blank-line formatting around that section so the
fenced code blocks and checklist items conform to the repo markdown rules.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4bca9608-e3f9-4faf-8d0a-20fe9c5b490e

📥 Commits

Reviewing files that changed from the base of the PR and between 0b99d2c and 1e064d5.

📒 Files selected for processing (1)
  • tasks/todo.md

Comment thread tasks/todo.md Outdated
Comment thread tasks/todo.md Outdated
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

Files modified:

  • tasks/todo.md

Commit: c7be8a2623c44f09cd0c134007a7d21d035899bd

The changes have been pushed to the chore/clean-todo branch.

Time taken: 2m 18s

Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (4)
tasks/todo.md (4)

100-100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale mapping entry.

Line 100 maps pythinker_cli_session to pythinker_code_session as if it's future work, but the code shows pythinker_code_session is already the current attribute name. Remove this entry or update the document to reflect that this rename is complete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tasks/todo.md` at line 100, Remove the stale mapping line that maps
pythinker_cli_session to pythinker_code_session in the tasks/todo.md table;
since the code already uses pythinker_code_session, either delete the entire row
or change its wording to indicate the rename is complete (e.g., mark as
"renamed" or remove the mapping), ensuring no reference remains that suggests
pythinker_cli_session is still the current attribute.

138-140: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Add blank line before fenced code block.

Markdownlint requires a blank line before the code block at line 139. Add a blank line after line 138.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tasks/todo.md` around lines 138 - 140, Add a blank line immediately before
the fenced code block that begins with "```bash" (the grep command block) so the
Markdown has an empty line separating the preceding paragraph and the code
fence; update the tasks/todo.md content to insert that single blank line right
above the "```bash" fence.

28-34: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Planning document describes already-completed work.

Lines 28 and 34 present pythinker_cli_session and the telemetry path regex as current problems requiring fixes, but the code shows both have already been renamed:

  • pythinker_code_session is the current attribute name (see src/pythinker_code/web/store/sessions.py:67 and src/pythinker_code/web/runner/worker.py:32).
  • The telemetry regex already includes pythinker_code and src/pythinker_code (see src/pythinker_code/telemetry/sentry.py:38).

Either update the document status to reflect that Phase 1–3 work is complete, or revise these sections to describe only the remaining work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tasks/todo.md` around lines 28 - 34, Update the planning document to reflect
that the renaming and telemetry regex changes are already done: replace
references to the old attribute name pythinker_cli_session with the current
pythinker_code_session (and note the locations using symbol
pythinker_code_session such as sessions.py and worker.py), and mark item 4 as
completed or revise its description to reference the updated telemetry regex
that already includes pythinker_code and src/pythinker_code (symbol: the regex
string used in sentry.py); alternatively, reword items 1 and 4 to describe any
remaining follow-up work instead of presenting them as unresolved issues.

139-146: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Verification steps assume completed rename.

The grep command at line 139 expects "empty output" (line 142) and line 144-146 verifies pythinker_code_session usage — both assume the rename is already done. This contradicts the document status "DO NOT execute yet" (line 3).

If this is a planning document, these steps should describe the work to be done (e.g., "Run sed to replace pythinker_cli_session"). If the rename is complete, update the document status to reflect that Phases 1–3 are done.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tasks/todo.md` around lines 139 - 146, The verification steps incorrectly
assume the rename is already complete; update the doc to either (A) change the
status from "DO NOT execute yet" to "Phases 1–3 completed" if the rename from
"pythinker_cli" to "pythinker_code" and attribute rename to
"pythinker_code_session" is already finished, or (B) make the steps actionable
by adding exact commands and intent (e.g., run a sed/grep replace for
"pythinker_cli" -> "pythinker_code" and then grep to confirm no matches) and
explicitly instruct to check references like
joint_session.pythinker_code_session and session.pythinker_code_session in web
API and worker files; ensure the document text clearly reflects which path
(execute now vs already done) is correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tasks/todo.md`:
- Line 100: Remove the stale mapping line that maps pythinker_cli_session to
pythinker_code_session in the tasks/todo.md table; since the code already uses
pythinker_code_session, either delete the entire row or change its wording to
indicate the rename is complete (e.g., mark as "renamed" or remove the mapping),
ensuring no reference remains that suggests pythinker_cli_session is still the
current attribute.
- Around line 138-140: Add a blank line immediately before the fenced code block
that begins with "```bash" (the grep command block) so the Markdown has an empty
line separating the preceding paragraph and the code fence; update the
tasks/todo.md content to insert that single blank line right above the "```bash"
fence.
- Around line 28-34: Update the planning document to reflect that the renaming
and telemetry regex changes are already done: replace references to the old
attribute name pythinker_cli_session with the current pythinker_code_session
(and note the locations using symbol pythinker_code_session such as sessions.py
and worker.py), and mark item 4 as completed or revise its description to
reference the updated telemetry regex that already includes pythinker_code and
src/pythinker_code (symbol: the regex string used in sentry.py); alternatively,
reword items 1 and 4 to describe any remaining follow-up work instead of
presenting them as unresolved issues.
- Around line 139-146: The verification steps incorrectly assume the rename is
already complete; update the doc to either (A) change the status from "DO NOT
execute yet" to "Phases 1–3 completed" if the rename from "pythinker_cli" to
"pythinker_code" and attribute rename to "pythinker_code_session" is already
finished, or (B) make the steps actionable by adding exact commands and intent
(e.g., run a sed/grep replace for "pythinker_cli" -> "pythinker_code" and then
grep to confirm no matches) and explicitly instruct to check references like
joint_session.pythinker_code_session and session.pythinker_code_session in web
API and worker files; ensure the document text clearly reflects which path
(execute now vs already done) is correct.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a4f184a6-d6c2-4b24-90af-98a78f34d190

📥 Commits

Reviewing files that changed from the base of the PR and between 1e064d5 and c7be8a2.

📒 Files selected for processing (1)
  • tasks/todo.md

elkaix added 5 commits June 11, 2026 01:01
- Shell error briefs now surface the trailing output of a failed command
  (last non-empty lines rendered as plain text), so the collapsed worklog
  card explains why a command failed instead of only its exit code.
- Subagents no longer receive plan-mode workflow reminders; the reminder
  is root-only, since subagent toolsets usually exclude the plan-mode
  tools and the injection only invited calls to tools they don't have.
- The terminal cursor-position probe no longer risks hanging in raw mode
  on exit: reads are non-blocking during the probe and VMIN/VTIME are
  restored to canonical defaults so a cancelled probe can't wedge the tty.
- Reorder the welcome info block so session/storage details render last,
  and log the auto-save path failure instead of swallowing it silently.
Identical tool calls in one step now share the original task's result
instead of re-executing. Across steps, repeats are detected via
canonical JSON arguments (key order no longer defeats matching) and
nudged with sparse system-reminders at consecutive streaks of 3, 5,
and 8, reducing loop-thrash without hard-blocking legitimate retries.

Per-step state is armed inside the step-retry wrapper so a retried
step never awaits tasks cancelled by the failed attempt, and a D-Mail
revert clears the dedup seed since the reverted history no longer
contains those calls. Adds tool_call_dedup_detected telemetry plus a
dup_type property on tool_call events.
The pythinker-cli -> pythinker-code rename plan is fully realized
(root pyproject is pythinker-code, module is src/pythinker_code/, no
pythinker_cli references remain), so retire it from tasks/todo.md.

Review nits: annotate /reports with NoReturn and assert the injection
counter stays untouched in the subagent plan-mode suppression test.
soul/toolset: append the cross-step dedup reminder inline at the tool task's return instead of behind a second wrapper task. Behaviour is unchanged (toolset tests pass) but the returned task is now the tool task itself, with one fewer task and closure per reminder. Drop the now-unused default on _append_reminder_to_return_value and annotate begin_step/end_step with the existing ToolCallKey alias.

tools/utils: tail() docstring no longer over-specifies the buffer as stderr (it holds mixed command output); rename stripped -> rstripped to reflect that it holds an rstrip() result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/pythinker_code/tools/shell/__init__.py (2)

194-207: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Generic exception handler should include tail for consistency.

For consistency with the timeout and exit-code paths (and with the ACP Terminal tool), include the tail in the exception brief when partial output was captured before the failure. The if tail: guard makes this safe even when no output exists.

Proposed fix
         except Exception as e:
             from pythinker_code.telemetry.errors import report_handled_error
 
             report_handled_error(e, site="tool.shell.exec", tool="Shell")
             logger.error(
                 "Shell command execution failed: {command}: {error}",
                 command=params.command,
                 error=e,
             )
+            brief = "Execution failed"
+            tail = builder.tail()
+            if tail:
+                brief += f"\n{tail}"
             return builder.error(
                 f"Command execution failed: {e}",
-                brief="Execution failed",
+                brief=brief,
                 status=ToolResultStatus.error,
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pythinker_code/tools/shell/__init__.py` around lines 194 - 207, The
generic exception handler in the shell execution path should append the captured
output tail to the brief (like the timeout/exit-code branches) for consistency;
update the except Exception as e block (the handler that calls
report_handled_error and builder.error) to include the tail in the brief string
when tail is non-empty (use an if tail guard) so the returned builder.error
brief includes partial output before the failure while keeping the existing
message and status (ToolResultStatus.error) and leaving the error log call using
params.command and e unchanged.

188-193: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Timeout handler should include tail for consistency.

The ACP Terminal tool includes tail in its timeout brief (line 157 in acp/tools.py), and this file's own exit-code path includes it (lines 179-186). At timeout, the builder holds partial output from the stdout_cb/stderr_cb callbacks, so the tail would surface the last captured lines before the kill — actionable context the user/model won't see otherwise.

Proposed fix
         except TimeoutError:
+            tail = builder.tail()
+            brief = f"Killed by timeout ({params.timeout}s)"
+            if tail:
+                brief += f"\n{tail}"
             return builder.error(
                 f"Command killed by timeout ({params.timeout}s)",
-                brief=f"Killed by timeout ({params.timeout}s)",
+                brief=brief,
                 status=ToolResultStatus.cancelled,
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pythinker_code/tools/shell/__init__.py` around lines 188 - 193, The
TimeoutError handler currently returns builder.error(f"Command killed by timeout
({params.timeout}s)", ...) without the captured output tail; update the
TimeoutError except block to include the last-captured lines (append
builder.tail) in both the main message and the brief, mirroring how the
exit-code path does it and how the ACP Terminal tool does at timeouts; keep
references to builder.error, params.timeout, and include the builder.tail
content gathered via the stdout_cb/stderr_cb callbacks in both message and
brief.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pythinker_code/soul/toolset.py`:
- Around line 230-243: The _make_reminder_text_2 function currently inlines
canonical_args verbatim into the reminder; change it to include a bounded
preview instead (e.g., take the first N characters or first line(s) and append
an ellipsis) so large payloads (like WriteFile bodies) aren’t echoed fully; keep
the full canonical_args identity via the existing hash/telemetry fields already
emitted elsewhere. Update references in _make_reminder_text_2 to use the preview
variable when composing the f"- arguments: ..." line and ensure the reminder
still preserves the hash/telemetry for exact identification.

In `@tests/core/test_toolset.py`:
- Around line 452-468: Replace assertions that inspect private internals
(_previous_step_calls, _current_step_calls, _current_step_tasks) in
test_begin_end_step with observable-behavior checks: keep using begin_step(...)
and then call end_step() to assert the returned calls list is correct, assert
ts.dedup_triggered reflects the deduplication state after end_step(), and add an
explicit scenario that submits a duplicate call (e.g., repeat ("ToolA","{}")
across steps) to verify duplicate handling via end_step() return and
dedup_triggered toggling instead of reading private attributes.

---

Outside diff comments:
In `@src/pythinker_code/tools/shell/__init__.py`:
- Around line 194-207: The generic exception handler in the shell execution path
should append the captured output tail to the brief (like the timeout/exit-code
branches) for consistency; update the except Exception as e block (the handler
that calls report_handled_error and builder.error) to include the tail in the
brief string when tail is non-empty (use an if tail guard) so the returned
builder.error brief includes partial output before the failure while keeping the
existing message and status (ToolResultStatus.error) and leaving the error log
call using params.command and e unchanged.
- Around line 188-193: The TimeoutError handler currently returns
builder.error(f"Command killed by timeout ({params.timeout}s)", ...) without the
captured output tail; update the TimeoutError except block to include the
last-captured lines (append builder.tail) in both the main message and the
brief, mirroring how the exit-code path does it and how the ACP Terminal tool
does at timeouts; keep references to builder.error, params.timeout, and include
the builder.tail content gathered via the stdout_cb/stderr_cb callbacks in both
message and brief.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1ade6d66-d6f5-4e2c-a082-3fa3d67b4fb4

📥 Commits

Reviewing files that changed from the base of the PR and between c7be8a2 and f951b26.

📒 Files selected for processing (40)
  • CHANGELOG.md
  • docs/en/reference/pythinker-vis.md
  • docs/en/reference/slash-commands.md
  • src/pythinker_code/acp/tools.py
  • src/pythinker_code/app.py
  • src/pythinker_code/auth/openai/models.py
  • src/pythinker_code/auth/openai/oauth_client.py
  • src/pythinker_code/auth/platforms.py
  • src/pythinker_code/cli/__init__.py
  • src/pythinker_code/hooks/runner.py
  • src/pythinker_code/llm.py
  • src/pythinker_code/session_cleanup.py
  • src/pythinker_code/soul/dynamic_injections/plan_mode.py
  • src/pythinker_code/soul/pythinkersoul.py
  • src/pythinker_code/soul/toolset.py
  • src/pythinker_code/tools/shell/__init__.py
  • src/pythinker_code/tools/utils.py
  • src/pythinker_code/ui/color_utils.py
  • src/pythinker_code/ui/shell/__init__.py
  • src/pythinker_code/ui/shell/components/markdown.py
  • src/pythinker_code/ui/shell/mcp_status.py
  • src/pythinker_code/ui/shell/motion.py
  • src/pythinker_code/ui/shell/prompt.py
  • src/pythinker_code/ui/shell/render_constants.py
  • src/pythinker_code/ui/shell/slash.py
  • src/pythinker_code/ui/shell/stats_pricing.py
  • src/pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py
  • src/pythinker_code/ui/shell/visualize/_worklog.py
  • src/pythinker_code/ui/terminal_background.py
  • src/pythinker_code/ui/terminal_capabilities.py
  • src/pythinker_code/ui/theme.py
  • src/pythinker_code/utils/path.py
  • src/pythinker_code/utils/term.py
  • tasks/todo.md
  • tests/core/test_plan_mode_injection_provider.py
  • tests/core/test_toolset.py
  • tests/ui_and_conv/test_shell_switch_slash.py
  • tests/ui_and_conv/test_shell_welcome_info.py
  • tests/ui_and_conv/test_worklog_render.py
  • tests/utils/test_result_builder.py

Comment thread src/pythinker_code/soul/toolset.py
Comment thread tests/core/test_toolset.py Outdated
elkaix added 2 commits June 11, 2026 04:39
Bound the canonical arguments echoed in the strong dedup reminder to a 256-char preview so large-payload tools (WriteFile, MultiEdit) don't re-inject their whole body into context on every repeat; exact identity is still carried by the args_hash dedup telemetry.

Rewrite test_begin_end_step to assert observable behaviour (handle()/end_step()/dedup_triggered) instead of poking private _current_step_* internals.
# Conflicts:
#	CHANGELOG.md
#	tasks/todo.md
@elkaix elkaix changed the title chore: clean completed tasks from todo.md feat: tool-call dedup plus shell/terminal hardening and editor fixes Jun 11, 2026
handle() returns Task[ToolResult] | ToolResult; assert isinstance Task before awaiting, matching the other dedup tests, so pyright's check job passes.
@elkaix
elkaix merged commit 2904de0 into main Jun 11, 2026
32 checks passed
@elkaix
elkaix deleted the chore/clean-todo branch July 17, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant