Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1d12f3b
feat(config): disable automatic turn recaps by default
elkaix Jun 9, 2026
5cef06d
feat(tui): adaptive theme foundation — bg probe, color depth, blending
elkaix Jun 9, 2026
6b47694
feat(tui): renderer guards + md-fence table unwrapping
elkaix Jun 9, 2026
edb06de
docs(tasks): Codex TUI adoption gap analysis + Phase 1 plan
elkaix Jun 9, 2026
25d3427
feat(tui): reference-CLI design polish + probe hardening
elkaix Jun 9, 2026
6126db1
feat(tui): reference-CLI layout, palette, and chrome refinements
elkaix Jun 9, 2026
bbe34ce
fix(tui): unify the two todo-list renderers into one design
elkaix Jun 9, 2026
c8c0f05
fix(tui): white running-task titles, bg-status metadata, diff palette…
elkaix Jun 9, 2026
9a629a9
feat(tui): elapsed/tokens/t-s metadata on the background status line
elkaix Jun 9, 2026
4a92cbd
fix(tui): transcript-row bullets use the record marker, not the list dot
elkaix Jun 9, 2026
13105df
feat(agents): structured prompt overhaul + subagent/background hardening
elkaix Jun 10, 2026
edf9080
docs(changelog): add Unreleased entry for TUI enhancements
elkaix Jun 10, 2026
33a2f8a
fix: remediate 65 security and correctness audit findings
elkaix Jun 10, 2026
8ddebac
fix(tui): address CodeRabbit review findings
elkaix Jun 10, 2026
2aec0e4
feat(llm): controllable reasoning effort for Qwen on OpenCode Go
elkaix Jun 10, 2026
2495020
feat(tui): static-grey input border with a top-right effort label
elkaix Jun 10, 2026
a39d29b
feat(tui): single top-right effort label; Qwen treated as native-thin…
elkaix Jun 10, 2026
32108f0
fix: address CodeRabbit review findings on the security-remediation diff
elkaix Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ GitHub Releases page; `0.8.0` is the new starting line.

## Unreleased

- **Thinking effort moved to a single top-right label on the input border.** The input box border is now one static frame grey at every effort level instead of recoloring the whole bar cold→hot, and the effort is no longer duplicated in the footer line. It's shown once, as a small label flushed to the right of the input's top border — a level-colored dot (slate→blue→teal→amber→orange→red as `off→max`) plus the muted level word — so the dial stays glanceable without tinting the typing area or cluttering the footer. The label is hidden entirely for native-thinking models (`always_thinking`, no user dial) and non-thinking models, and the rule auto-shortens by the label width so the line never wraps.
- **Qwen models treated as native-thinking across both plans.** Qwen3.x/3.7 (e.g. `qwen3.7-max`, `qwen3.6-plus`, the Qwen3 Coder models) now carry the `always_thinking` capability on both the Alibaba Model Studio and OpenCode Go plans, matching GLM/MiniMax: reasoning is built in and always on, with no user effort dial and no top-border effort label. Reasoning still flows over the Anthropic `thinking` block that both Anthropic-compatible routes accept.
- **TUI enhancements: adaptive theme, layout, and agent prompt overhaul.** Adaptive terminal-background probe + color-depth blending; reference-CLI layout and palette refinements; unified todo-list renderer; white running-task titles with consistent diff palette; elapsed/tokens/t-s metadata on the background status line; transcript-row bullet fix; renderer guards and markdown fence table unwrapping. All default agent prompts restructured with explicit Mission / Hard Constraints / Workflow / Output Contract sections. Background manager and subagent runner hardened with stale-record reconciliation and resume contract enforcement. Automatic turn recaps disabled by default.

## 0.39.0 (2026-06-09)

- **Refreshed TUI theme and Catppuccin syntax highlighting.** The interface adopts a brand periwinkle/indigo accent (`#B3B9F4` dark / `#0B114E` light) with a reharmonized selection tint, and code blocks now highlight with Catppuccin Mocha (dark) / Latte (light), adaptive to the active theme — implemented as foreground-only Pygments styles with no new dependency. Markdown inline code and links render terminal-native cyan, blockquotes green, and ordered-list markers bright blue (so they adapt per terminal), and user messages sit on a neutral grey block instead of the prior blue tint.
Expand Down
12 changes: 12 additions & 0 deletions packages/pythinker-core/tests/test_anthropic_thinking.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ def test_supports_adaptive_thinking(model: str, expected: bool) -> None:
("claude-opus-4-8", "max", "max"),
("claude-opus-5-0", "max", "max"),
("claude-opus-5-0", "xhigh", "high"),
# Qwen via the Anthropic-compatible endpoint (Alibaba Model Studio /
# OpenCode Go @ai-sdk/anthropic): a non-Claude model, so it takes the
# pre-4.6 budget path. Effort must land in {low, medium, high} so the
# budgets[...] lookup in with_thinking can never KeyError, and xhigh/max
# clamp to high while minimal floors to low.
("qwen3.7-max", "off", "off"),
("qwen3.7-max", "minimal", "low"),
("qwen3.7-max", "low", "low"),
("qwen3.7-max", "medium", "medium"),
("qwen3.7-max", "high", "high"),
("qwen3.7-max", "xhigh", "high"),
("qwen3.7-max", "max", "high"),
],
)
def test_clamp_effort(model: str, effort: str, expected: str) -> None:
Expand Down
8 changes: 8 additions & 0 deletions packages/pythinker-host/src/pythinker_host/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ async def chdir(self, path: StrOrHostPath) -> None:
"""Change the current working directory."""
...

async def realpath(self, path: StrOrHostPath) -> HostPath:
"""Resolve symlinks and return the real absolute path."""
...

async def stat(self, path: StrOrHostPath, *, follow_symlinks: bool = True) -> StatResult:
"""Get the stat result for a path."""
...
Expand Down Expand Up @@ -282,6 +286,10 @@ async def chdir(path: StrOrHostPath) -> None:
await get_current_host().chdir(path)


async def realpath(path: StrOrHostPath) -> HostPath:
return await get_current_host().realpath(path)


async def stat(path: StrOrHostPath, *, follow_symlinks: bool = True) -> StatResult:
return await get_current_host().stat(path, follow_symlinks=follow_symlinks)

Expand Down
10 changes: 8 additions & 2 deletions packages/pythinker-host/src/pythinker_host/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ async def chdir(self, path: StrOrHostPath) -> None:
local_path = path.unsafe_to_local_path() if isinstance(path, HostPath) else Path(path)
os.chdir(local_path)

async def realpath(self, path: StrOrHostPath) -> HostPath:
"""Resolve symlinks and return the real path (follows symlinks)."""
local = path.unsafe_to_local_path() if isinstance(path, HostPath) else Path(path)
resolved = await asyncio.to_thread(os.path.realpath, str(local))
return HostPath.unsafe_from_local_path(Path(resolved))

async def stat(self, path: StrOrHostPath, *, follow_symlinks: bool = True) -> StatResult:
local_path = path.unsafe_to_local_path() if isinstance(path, HostPath) else Path(path)
st = await aiofiles.os.stat(local_path, follow_symlinks=follow_symlinks)
Expand Down Expand Up @@ -143,7 +149,7 @@ async def readtext(
errors: Literal["strict", "ignore", "replace"] = "strict",
) -> str:
local_path = path.unsafe_to_local_path() if isinstance(path, HostPath) else Path(path)
async with aiofiles.open(local_path, encoding=encoding, errors=errors) as f:
async with aiofiles.open(local_path, encoding=encoding, errors=errors, newline="") as f:
return await f.read()

async def readlines(
Expand All @@ -154,7 +160,7 @@ async def readlines(
errors: Literal["strict", "ignore", "replace"] = "strict",
) -> AsyncGenerator[str]:
local_path = path.unsafe_to_local_path() if isinstance(path, HostPath) else Path(path)
async with aiofiles.open(local_path, encoding=encoding, errors=errors) as f:
async with aiofiles.open(local_path, encoding=encoding, errors=errors, newline="") as f:
async for line in f:
yield line

Expand Down
4 changes: 4 additions & 0 deletions packages/pythinker-host/src/pythinker_host/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ def expanduser(self) -> HostPath:
return home
return home.joinpath(*parts[1:])

async def realpath(self) -> HostPath:
"""Resolve symlinks and return the real absolute path."""
return await pythinker_host.realpath(self)

async def stat(self, follow_symlinks: bool = True) -> pythinker_host.StatResult:
"""Return an os.stat_result for the path."""
return await pythinker_host.stat(self, follow_symlinks=follow_symlinks)
Expand Down
5 changes: 5 additions & 0 deletions packages/pythinker-host/src/pythinker_host/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ async def chdir(self, path: StrOrHostPath) -> None:
await self._sftp.chdir(str(path))
self._cwd = await self._sftp.realpath(".")

async def realpath(self, path: StrOrHostPath) -> HostPath:
"""Resolve symlinks and return the real path via SFTP realpath."""
real = await self._sftp.realpath(str(path))
return HostPath(real)

async def stat(
self,
path: StrOrHostPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def test_intel_client_disables_implicit_redirects() -> None:
from pythinker_review.security_intel.client import IntelHttpClient

client = IntelHttpClient()
assert any(isinstance(h, _NoRedirectHandler) for h in client._opener.handlers)
handlers = client._opener.handlers # pyright: ignore[reportAttributeAccessIssue]
assert any(isinstance(h, _NoRedirectHandler) for h in handlers)


def test_intel_cache_roundtrip(tmp_path: Path) -> None:
Expand Down
58 changes: 0 additions & 58 deletions src/pythinker_code/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,6 @@
if TYPE_CHECKING:
from typing import TextIO

ROOT_HELP = """Usage: pythinker [OPTIONS] COMMAND [ARGS]...

Pythinker, your next CLI agent.

Options:
-h, --help Show this message and exit.
-V, --version Show version and exit.
--verbose Print verbose information.
--debug Log debug information.
-w, --work-dir DIRECTORY Working directory for the agent.
--add-dir DIRECTORY Add an additional workspace directory.
-S, -r, --session, --resume TEXT Resume a session.
-C, --continue Continue the previous session.
--config TEXT Config TOML/JSON string to load.
--config-file FILE Config TOML/JSON file to load.
-m, --model TEXT LLM model to use.
--thinking / --no-thinking Enable or disable thinking mode.
-y, --yolo, --yes, --auto-approve
Dangerously skip permission approvals.
--plan Start in plan mode.
--auto Run in auto mode (no user present).
-p, -c, --prompt, --command TEXT User prompt to the agent.
--print Run in print mode.
--acp Deprecated; use `pythinker acp`.
--wire Run as Wire server.
--quiet Print only the final assistant message.
--agent [default|okabe] Builtin agent specification to use.
--agent-file FILE Custom agent specification file.
--mcp-config-file FILE MCP config file to load; repeatable.
--mcp-config TEXT MCP config JSON to load; repeatable.
--skills-dir DIRECTORY Custom skills directory; repeatable.
--no-telemetry Disable anonymous telemetry & error reporting.

Commands:
acp Run Pythinker CLI ACP server.
term Run Toad TUI backed by Pythinker CLI ACP server.
login Login with a model provider.
logout Logout from a model provider.
info Show version and protocol information.
export Export session data.
mcp Manage MCP server configurations.
plugin Manage plugins.
review Diff-focused code review (delegates to pythinker-review).
secscan Diff-focused security review (delegates to pythinker-review).
security-scan Repo-wide Pythinker Security Scan pipeline (Python-native).
debug Failure/log root-cause analysis (delegates to pythinker-review).
update Check for and install Pythinker CLI updates.
vis Run Pythinker Agent Tracing Visualizer.
web Run Pythinker CLI web interface.

Documentation: https://pythoughts-labs.github.io/pythinker-code/
LLM friendly version: https://pythoughts-labs.github.io/pythinker-code/llms.txt
"""


def _prog_name() -> str:
return Path(sys.argv[0]).name or "pythinker"
Expand Down Expand Up @@ -126,10 +72,6 @@ def main(argv: Sequence[str] | None = None) -> int | str | None:
print(f"pythinker, version {get_version()} — by {ORGANIZATION}")
return 0

if len(args) == 1 and args[0] in {"--help", "-h"}:
print(ROOT_HELP, end="")
return 0

from pythinker_code.telemetry.crash import install_crash_handlers, set_phase
from pythinker_code.utils.proxy import normalize_proxy_env

Expand Down
3 changes: 3 additions & 0 deletions src/pythinker_code/acp/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ def getcwd(self) -> HostPath:
async def chdir(self, path: StrOrHostPath) -> None:
await self._fallback.chdir(path)

async def realpath(self, path: StrOrHostPath) -> HostPath:
return await self._fallback.realpath(path)

async def stat(self, path: StrOrHostPath, *, follow_symlinks: bool = True) -> StatResult:
return await self._fallback.stat(path, follow_symlinks=follow_symlinks)

Expand Down
20 changes: 14 additions & 6 deletions src/pythinker_code/agents/default/ask.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ agent:
mode: primary
system_prompt_args:
ROLE_ADDITIONAL: |
You are in Ask mode: a read-only assistant for answering questions, explaining code,
and recommending next steps without modifying files.
You are in Ask mode: a read-only assistant for answering questions, explaining code, and recommending next steps without modifying files.

Ask-mode rules:
## Mission
Answer the user's questions about the codebase, architecture, debugging, and configuration with repository evidence — never by modifying the workspace.

## Hard Constraints
- Do not edit files, write plans to disk, launch mutating tools, commit, stage, push, or run commands that modify the system.
- Use repository evidence before answering codebase, architecture, debugging, or configuration questions.
- Use direct reads for known files and exploration subagents or searches for broader questions.
- Use repository evidence before answering codebase, architecture, debugging, or configuration questions; never present an unverified guess as an answer.
- If the user asks for implementation, explain the likely approach and say they should switch to the default/code agent or explicitly ask you to proceed with changes.
- The global todo-list protocol does not apply in Ask mode (SetTodoList is unavailable); when you launch subagents, track progress in your reply instead.

## Workflow
- Use direct reads for known files and exploration subagents or searches for broader questions.
- Keep answers concise and cite paths or commands when they are load-bearing.

Final response contract:
## Output Contract
- Start with the direct answer.
- Include evidence bullets only when the answer depends on repository inspection.
- End with blockers only if missing context prevents a reliable answer.

## Escalation
- If the question cannot be answered reliably from available evidence, say exactly what is missing instead of guessing.
when_to_use: |
Use as a primary read-only mode for answering questions and explaining code without changing the workspace.
allowed_tools:
Expand Down
Loading
Loading