On terminals that send a bare LF for plain Enter (WSL is the case
prompt_toolkit itself calls out), Alt+Enter arrives as ESC + LF
("\x1b\x0a" = Escape + ControlJ), not the ESC + CR the existing
"escape","enter" binding matches. The Escape was swallowed and the bare
LF hit prompt_toolkit's default submit, so the documented "universally
supported" Alt+Enter newline fallback failed on exactly the terminal path
plain Enter is preserved for.
Bind ESC + ControlJ to insert a newline too, and add a real-PromptSession
regression test covering the WSL Alt+Enter path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"\x1b[27;2;13~" (the older xterm modifyOtherKeys / rxvt encoding of
Shift+Enter) is already registered by prompt_toolkit by default -- as
Keys.ControlM, i.e. plain Enter/submit. The previous `setdefault()` call was
therefore a silent no-op against it: this Shift+Enter variant kept behaving
like a submit instead of inserting a newline, even after the ControlJ/WSL
fix, since setdefault only sets missing keys.
Assign directly to override that default for both known Shift+Enter
sequences, since inserting a newline is the whole point of the binding.
Add a regression test driving a real PromptSession/parser with this exact
sequence, since a mocked-buffer test can't observe prompt_toolkit's default
ANSI_SEQUENCES entries taking priority.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keys.ControlJ is also the literal LF byte ("\x0a") that some terminals send
for a plain Enter keypress -- prompt_toolkit's own default bindings handle
this by re-feeding it as ControlM/submit, and calls out WSL by name as the
case that needs it. Binding our Shift+Enter handler to ControlJ shadowed
that default, so on any terminal sending LF for Enter, pressing Enter only
ever inserted a newline and the prompt could never be submitted.
Register the CSI-u Shift+Enter sequences against Keys.ControlF3 instead: an
enum member prompt_toolkit declares but never wires to a default ANSI
sequence or key binding, so it's only reachable through our own mapping.
Also add a regression test that drives a real PromptSession/Vt100Parser
with a raw LF byte -- the existing key-binding test invoked handlers
directly against a mocked buffer, which exercises the handler logic but not
prompt_toolkit's key-resolution precedence, so it couldn't have caught this.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The interactive prompt used a single-line buffer (multiline=False), so
there was no way to compose a multi-line message before submitting.
Switch to a multiline-capable buffer with custom key bindings: Enter still
submits (unchanged feel), Alt+Enter always inserts a newline, and
Shift+Enter inserts a newline on terminals that emit a distinguishable
CSI-u sequence for it (kitty, iTerm2 with the fixterms protocol) by mapping
those sequences onto the otherwise-unused ControlJ key.
maintainer edit: keep MCP HTTP SSRF checks strict, pin validated DNS for direct web_fetch and HTTP/SSE MCP requests, preserve explicit and environment proxy compatibility, and cover the proxy/redirect/rebinding cases with tests.
fix(gateway): self-heal state file PID on server startup
After /restart on Windows, the gateway process gets a new PID (os.execv
creates a new process on Windows), but the state file at
run/gateway.<suffix>.json still contains the old PID from the initial
background spawn. Nothing rewrites it, leaving state inconsistent.
Add GatewayRuntime.refresh_state_pid() — a classmethod that reads the
existing state file, updates the PID and identity to the current
process, and writes atomically. Call it early in _run_gateway() so the
state file is always correct regardless of how the process was started
(initial spawn, os.execv, or subprocess.Popen).
On POSIX os.execv preserves the PID, so this is a no-op in normal
operation there, but still beneficial after any unusual restart path.
Fixes#4511
@
Maintainer edit: keep the Mattermost adapter running under the gateway, fail closed when team filtering cannot verify the team, isolate new thread sessions immediately, and make buffered stream finalization retry-safe.
- Use Mattermost file metadata/download API paths that work with current servers
- Buffer streamed content and post the final reply once for mobile clients that ignore post edits
- Keep attachment upload behavior on the first split chunk
- Send pairing code response (not empty message) for denied DMs
- Resolve actual channel type in action events before permission check
- Use word-boundary regex in _is_mentioned to avoid partial matches
- Use safe_filename for download path sanitization
- Add tests: denied DM pairing, denied action event, is_mentioned boundary
- Attachments now only attached to first chunk when message is split
- Filename sanitized with Path(name).name to prevent path traversal
- Updated streaming tests to match buffer-and-post-at-end pattern
(iOS compatibility)
* fix(dream): ground commit messages and cursor advance in the real git diff
Dream consolidation could emit a /dream-log audit record that did not match
the actual file changes: build_dream_commit_message appended the LLM's
unverified resp.content, dream_run_completed only checked the stop reason, and
file contents were deliberately omitted from the prompt. The combination let a
single-turn self-report become the durable audit record.
- gitstore: add summarize_working_tree() — a structured, machine-derived
summary (per-file +N/-M, totals, capped unified diff) of working-tree
changes vs HEAD. Pure filesystem/git ground truth, never LLM narrative.
- memory: build_dream_commit_message now takes the diff body instead of resp;
dream_content_diff() exposes the real delta over SOUL/USER/MEMORY.md only
(excludes .dream_cursor so cursor writes aren't mistaken for edits);
build_dream_prompt embeds current file contents so the model edits reality,
not a stale mental model.
- builtin/cli: both Dream paths now compute the diff, gate cursor advance on
a non-empty delta (no-op runs no longer swallow history), and commit with
the diff-grounded message. Non-git workspaces fall back to the completion
check.
- dream.md: document that contents are embedded, and add a chain-of-
verification guardrail so the model's summary cannot claim unmade edits.
A regression test proves a lying resp.content never reaches the audit log
while the real diff does.
* fix(dream): mark non-UTF-8 memory files as binary in diff summary
Address review feedback (Q1 on PR #4673): summarize_working_tree read
working-tree files with errors="replace", which would emit U+FFFD
replacement chars into the audit record if a memory file ever held
invalid UTF-8 — misrepresenting the diff it is meant to make truthful.
Switch to errors="strict" and catch UnicodeDecodeError: a non-UTF-8
(or binary/corrupt) file is now recorded as "{path}: binary or
non-UTF-8 file changed" and omitted from the unified diff, so the
audit record stays honest. An empty diff block is also suppressed when
all changes are binary.
Adds a defensive regression test asserting no replacement char leaks.
57f9ec0 added a CSS containment safety net for .markdown-content, but
several rendering paths still overflowed or truncated on narrow viewports
because they bypass those rules or actively disable wrapping:
- Inline code longer than 120 chars renders as `block whitespace-pre`
with no scroll container, so it bled past the viewport (white-space:pre
defeats overflow-wrap). It now scrolls inside its own box via
max-w-full overflow-x-auto.
- Wide markdown tables never actually scrolled: Tailwind Typography forces
width:100% + table-layout:auto on .prose table, so they shrank to fit
instead of overflowing. Switched to a wrapper-<div> pattern (a custom
`table` component, the approach used by DeepSeek/others) with min-w-max
so wide tables keep natural column widths and scroll inside the
conversation column; the old display:block rule on .markdown-content
table is removed.
- File-reference chips and inline link-preview cards used `truncate`, so
long filenames/labels were ellipsized to one line. Now
[overflow-wrap:anywhere] sm:truncate — wrap on mobile, truncate on
desktop.
- User-authored message bubbles don't pass through .markdown-content, so
a long URL in the user's own message still burst the screen: under flex
min-width:auto, break-words cannot reduce the item's min-content width.
Added max-w-full min-w-0 + [overflow-wrap:anywhere].
No JS/agent/provider behavior touched. Desktop layout is unchanged — the
new rules only engage when content would otherwise overflow.
Verified: bun run lint (clean), bun run test (438 passed), bun run build.
Refs #4693
Co-Authored-By: Claude <noreply@anthropic.com>
On mobile-width browsers the conversation column and bottom composer could
be forced wider than 100vw: long markdown (URLs, branch names, file paths),
fenced code, and tables stretched the prose container, and the thread scroll
area let the overflow bleed horizontally (#4693).
Add a pure-CSS containment safety net in globals.css:
- .markdown-content gets min-width:0, max-width:100%, overflow-wrap:anywhere
so long tokens wrap instead of stretching the column.
- long links and inline code break rather than overflow.
- fenced/pre blocks and markdown tables scroll inside their own box.
- the thread scroll column clips residual horizontal bleed (overflow-x:hidden)
while vertical scroll and internal code-block scroll are unaffected.
No component, JS, or agent-loop behavior touched; desktop layout unchanged.
Fixes#4693
Co-Authored-By: Claude <noreply@anthropic.com>
Add 'serper' as a web search backend, following the existing provider
pattern (keenable/exa): POST to https://google.serper.dev/search with the
X-API-KEY header, map the 'organic' results into the shared result format,
and fall back to DuckDuckGo when no key is configured.
- key resolved from config.api_key or SERPER_API_KEY env var
- 429 handled with a rate-limit message; other HTTP errors surfaced
- tests cover success, env-key, no-key fallback, HTTP error and rate limit
- docs: add Serper config example and list it in tools.web.search providers
Maintainer edit: preserve raw cmd.exe quoting for shell='cmd', propagate native exit codes through the default PowerShell path, and keep quoted Windows executable paths invokable under PowerShell.
Maintainer edit: prefer pwsh when available so single-line commands support modern PowerShell operators, add -NonInteractive, and parse explicit cmd.exe paths with Windows path semantics for cross-platform tests.
fix(exec): default Windows commands to PowerShell and allow shell parameter
Single-line commands on Windows were routed through cmd.exe
(asyncio.create_subprocess_shell) while multi-line commands used
PowerShell. This caused cross-drive cd failures, missing $VAR expansion,
and inconsistent behavior depending on whether a command contained a
newline. The shell parameter was also rejected on Windows.
- Route all Windows commands through PowerShell by default so single-line
and multi-line commands share the same shell semantics.
- Allow the shell parameter on Windows: accepts powershell, pwsh, or cmd.
- cmd.exe remains reachable via shell="cmd" as an explicit escape hatch.
- Update tests to cover the new default and shell-parameter paths.
Fixes#4544
@