[pull] main from xintaofei:main - #169
Merged
Merged
Conversation
…s them Kimi's composer offered a permission mode but no reasoning strength, while the same account signed in through the subscription had both. Kimi advertises the mode option unconditionally and gates the thinking option on the active model declaring a thinking capability -- something the subscription's own provisioning fills in from the server-side model catalog. The codeg-managed block carried only provider, model and context window, so there was nothing to gate on and the picker was skipped. The Kimi Code panel gains a Reasoning card, beside the model card and only where an API key is in use, that writes the declaration: a switch for whether the model reasons at all, the levels to offer as togglable chips plus a field for any value the presets do not cover, the level to open on, and an always-reasons option that drops the picker's Off row. Levels reach the provider verbatim, since Kimi maps nothing for non-Kimi providers, so the copy says as much, and choosing none is called out as the plain Off/On fallback Kimi degrades to. A default outside the chosen levels is refused rather than left for Kimi to clamp to the middle one. Enabling reasoning also re-declares image input, video input and tool use. Kimi reads an absent capabilities list as "allow everything" but a present one as a whitelist, so naming thinking alone would revoke the modalities that were implicitly available. Switching reasoning back off removes all three keys and restores the block as it was. The chat side is untouched: the composer already renders whatever session config options an agent advertises, and the ACP layer already maps the thought-level category. Subscription setups are unaffected. Saving with the subscription selected sends nothing but the mode and clears the codeg-managed block, and both the write and the read-back address that block alone.
… composer Pasting text that carries serialized references already turned them back into inline badges, but every other way the host fills the composer left them as raw `[label](file:...)` source that only became badges once the message was sent. Filling from a quick message was the visible case -- its content is a bare textarea in settings, so it usually holds a message copied straight out of a conversation -- and the same applied to a restored draft, a queued message reopened for editing, an injected expert or office template, and a saved automation's prompt, which is itself the composer's own serialization. All of those now go through one seeding entry point that hydrates when the text carries references and stays literal when it does not. The queue-edit path needed it too: send serialization emits a single text block with every badge inline, so a queued message's references live inside that text rather than in separate blocks. Hydration cannot change what is sent -- a restored badge re-serializes to exactly the text it came from -- so this is what the composer shows, not what the agent receives. An embedded attachment link stays literal, since send drops those badges and hydrating one would make the text vanish. Parsing a reference link now also decodes the escapes the serializer adds inside an angle-wrapped destination. A uri holding a backslash, `<` or `>` -- a Windows path, or a file named around them -- was wrapped and escaped on the way out but only unwrapped on the way back, so it came back with its backslashes doubled: a badge and an attachment chip pointing at a path that does not exist, doubling again every time the message was reopened. The decoder lives beside the label unescaper as the single codec, and both the transcript parser and the resource-chip adapter use it.
An agent that backgrounds a long-running command through `terminal/create` and then waits on it froze the entire ACP connection. sacp dispatches incoming messages from a single loop that awaits each handler to completion before reading the next one, and `terminal/wait_for_exit` was answered inline, so a command that never exits -- a dev server, a watcher, `tail -f` -- parked that loop forever. Everything behind it stalled: the tool call stayed at "running" because the agent's own completion notification was still queued unread, later turns never arrived, and the terminal's output never reached the UI. The wait is now answered from a task spawned off the dispatch loop, which is what sacp provides for exactly this. Releasing the loop alone was not enough, because `wait_for_exit` also held the terminal's child mutex across the whole wait. `terminal/output`, `terminal/kill`, the output poller and session teardown all contended on that mutex, so they blocked on the same never-exiting command -- including the cancel path, which left the user unable to stop the turn they were watching hang. A per-terminal task now owns the `Child` for the process's whole life and is the only code that touches it. Everything else reads a snapshot or signals that task, so the read paths hold no lock and cannot block. It publishes completion through a watch channel with `send_replace` rather than `send`, since a watch drops values sent while no receiver exists and a command that finished before anyone subscribed would otherwise strand every later waiter. Completion is either "running" or "exited" and nothing else: an unknown-but-maybe-finished state is indistinguishable from "running" to anything polling `terminal/output`, which is how the hang reappears. Kill is routed through that task as a signal rather than by handing a pid to callers, so every signal is delivered while the child is still un-reaped and its pid cannot have been recycled onto an unrelated process. Killing now escalates: `kill_tree` only sends SIGTERM and returns without waiting, which a process that traps it ignores indefinitely, so a SIGKILL pass follows a grace period. The pids each pass signalled are accumulated and swept at the end, because a descendant that outlives its parent is reparented away from the root and no later snapshot can find it again. `kill_terminal` bounds only how long it waits to report success -- the owning task keeps escalating and reaping past that deadline, so returning early never abandons a process -- and session teardown kills its terminals concurrently instead of paying that bound once per terminal. A failing `Child::wait` is classified rather than assumed. `ECHILD` means the child was reaped elsewhere: it is gone, its status is unknowable, and its pid must not be signalled, so completion is published as finished with no code or signal. Any other error means nothing collected the child and it may still be running, so it is killed and the wait retried; if that keeps failing, completion is published anyway once a budget is spent -- no agent turn should hang on it -- while the task keeps holding and reaping the child. Reader draining still happens before completion is published, so an observer that sees an exit status has already seen all of the output.
A codex search -- any `rg`/`grep` the agent runs -- arrived as a card whose
result body was the literal `{"exit_code":0,"formatted_output":"…"}` JSON,
under a title that trailed off into the first line of that output. codex-acp
announces a shell command it classified as a search with nothing but a human
sentence: no `rawInput`, and unlike the sibling read action not even
`locations`, so the query and the scope path exist nowhere but the title.
With no name to dispatch on, the whole title became the tool name -- a
generic wrench icon, an "other" tally in the tool group -- the title
derivation then appended the first string field of the result to it, and the
body fell through to the generic result block, which prints whatever object
it is handed. The list-files action, announced the same way, was identical.
Those titles are now parsed back into the canonical grep/glob identity with
the query and path recovered as tool input, so the card reads "Grep
<pattern>" with a search icon, counts as a search rather than an "other" in
a tool group, and shows the scope path in its body. Output is grouped per
file, each file heading and each line number opening the file at that line,
with literal occurrences of the query highlighted. An empty result reads "no
matches" rather than showing an empty envelope.
Unwrapping is the envelope and nothing else: `formatted_output` is the
process's raw output, so every cleanup step the command and read paths apply
is lossy on a real search hit. A single-file grep prints matches with no
`<path>:` prefix, which means a matched line can begin with any of the words
those paths treat as their own framing -- `grep 'Wall time:' server.log`,
`grep '^Output:' log`, `grep -h '```' *.md` all came back truncated or
empty. Anything that is not that exact envelope, including every other
agent's search output, passes through byte for byte.
Recognizing the output is deliberately conservative. Only `<path>:<line>:
<content>` and bare path lists are grouped, and only when nearly every line
fits; otherwise the plain text is shown as before, which is what `rg
--count`, `--heading` and `--json` output, "Found N files" preambles and
`line:col` diagnostics all fall back to. Stray lines below that threshold,
such as an `rg` warning about a missing path, are kept as notes beside the
results. Rows are capped, with the remainder counted, and highlighting is
capped per line so a one-character query against a minified line cannot
flood the view with elements.
A failed search is still a failed search. codex derives tool status from the
exit code, so a grep that matched nothing arrives as a failure carrying an
empty envelope; only that exact shape is shown as "no matches", and only for
grep, where exit 1 means nothing was selected. For the list-files commands
that classify as glob it is a real failure, as is any grep failure that
carries output.
Claude, Cline, Gemini, OpenCode and Cursor search tools reach the same
renderer and gain the grouped view when their output fits it. A path-only
listing, which is how Cline reports `list_files`, now gets a title naming
the directory instead of falling back to the generic "tool: value" form.
Reloaded codex history is unaffected: the same command is recorded as an
`exec_command` call and keeps rendering as a terminal card.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )