Skip to content

acli: sync 0.7.1 and fix cli command routing - #206

Merged
lzsweb merged 20 commits into
mainfrom
release/agentic-cli-7
Oct 14, 2026
Merged

lzsweb merged 20 commits into
mainfrom
release/agentic-cli-7

Conversation

@lzsweb

@lzsweb lzsweb commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Sync the vendored dashscope/acli tree with agenticCLI 0.7.1 (includes 0.7.0): vision chain (oneshot vision-tool registration, injection perception, view_image region reads with sampling votes, vision call timeout, qwen3-vl-plus default), budget discipline (thinking budget, wall-clock hints, run_command back-pressure), reasoning-stall retry fix, oneshot empty-turn backstop, and prompt rules 17/18.
  • fix(cli): the auth group routes to typer; malformed commands exit 2 with a usage error instead of opening a chat.
  • Terminal-Bench on the agenticCLI side: medium best-of 12/12, hard 10/10, overall 36/36.

Test plan

  • Full unit suite green: 1032 passed, 10 skipped
  • Pre-commit gate green on this branch
  • Terminal-Bench verified on agenticCLI (overall best-of 36/36, run 2026-10-14__fix-071w)

zhansheng.lzs added 20 commits September 18, 2026 18:12
…rmed output

Mirrors agenticCLI a7b1a84. Rule 18 only required verifying code changes, so an
agent producing a data document could pass every check it ran and still ship a
broken one: Terminal-Bench hard-066c had organization-json-generator write
employee *names* into project.members where the grader wants employee IDs, and
report the conversion as deliberate -- "member IDs resolved to employee names,
per schema's members: string[]". jsonschema agreed with it, because the schema
only constrains members to an array of strings, so 3 of 4 tests passed and
test_relationships_integrity failed on 'Cristiano Ronaldo' not in
{'D001-E001', 'D001-E002', 'D001-E003'}.

Names the case in the rule: when output transforms or joins data (ETL,
CSV/JSON, migrations), also assert every cross-reference resolves, since
schema-valid output with dangling IDs still fails.

Measured on Terminal-Bench hard (10 tasks, qwen3.8-max, serial; wheel ae86d994
whose only content delta from the before-wheel ab5c911a is this clause): the
task went 3p/1f -> 4p/0f and wrote an explicit dangling-reference check. Batch
score unchanged at 6/10 -- two out-of-scope tasks flipped negative and only 4
of the 6 passes are shared between runs, so single-run task-level variance on
hard is about +/-2. Full attribution in agenticCLI
bench/terminal-bench/RESULTS.md, runs hard-066c / hard-066d.
c4e38e7 registered app.add_typer(auth.app) but never added "auth" to
_TOP_LEVEL_COMMANDS, so main()'s unknown-command branch forwarded the line to
the SDK Expert agent: `dashscope auth whoami` opened a chat session instead of
verifying the key, and login/logout were unreachable. `auth --help` kept
working, which hid the gap -- the help branch dispatches before routing.

Also document auth in the expert's cli.md skill. It claims to be authoritative
for CLI usage but had no auth row, so the agent could not answer either.

The new test asserts every registered typer group is routable. One-directional
on purpose: rl/agentic-rl stay listed when the optional extra is absent.
main() sent any line whose first token was not in _TOP_LEVEL_COMMANDS to the
agent as a question. That catch-all swallowed real errors: `dashscope -w wsid
generation create` opened a chat instead of reporting "No such option: -w",
and `dashscope generaton create` hid a typo that typer would have answered
with "Did you mean 'generation'". It is also why the missing auth entry fixed
in 6008270 silently disabled a working command rather than erroring.

Three paths reach the agent now: a bare `dashscope`, an explicit `dashscope
expert ...`, and a single unknown token -- the last one keeps the documented
`dashscope "question"` one-shot working. Everything else goes to typer.

`expert` is a routing keyword, not a typer group, and stays out of
_TOP_LEVEL_COMMANDS; that set now only bounds global -k/--api-key parsing.
This reverses the routing half of b808fcd -- the typer subcommand and the
~/.acli config sync it deleted stay deleted.

README, README_zh and the expert's cli.md skill all advertised the removed
catch-all, so their wording is updated to match.
The single-unknown-token short form made a quoted command line
indistinguishable from a question: `dashscope "auth whoami"` opened a chat
whose first user message was the command itself, which in a session log
looks exactly like someone asking about it. Drop the short form so every
invocation that is not a bare `dashscope` or `dashscope expert ...` goes to
typer, which parses real commands and reports unknown ones with a "Did you
mean" suggestion. `dashscope expert "question"` remains the one-shot form.

The vendored acli entrypoint had the same flaw in a worse shape: an
unrecognized token fell through to the interactive agent and discarded the
arguments entirely, so `acli auth whoami` produced no error at all. It now
exits 2 with a usage error, matching typer.
Synced from agenticCLI. The gateway binds each model to exactly one aigc
service path and answers the other with a 400 "url error", so the probe
order decides who pays for an unlearned model. text-generation went first,
which made the default model (qwen3.8-max, multimodal-bound) fail the first
request of every session — in the prod gateway log that is an
InvalidParameter with a fully tokenized body thrown away, then a retry that
re-uploads and re-tokenizes it.

The learned path was also kept only on the instance, and a provider is
built per session start, per model switch and per vision call, so nothing
survived between them. Persist the binding next to the global config and
share it across instances; drop unreadable or unknown-path entries instead
of repairing them, since a lost binding only costs one probe.
Eleven tests over the two commands a user runs first, against a throwaway
~/.dashscope and a stubbed Models.list, so no real key file is touched and
no request is sent.

whoami: the login guidance and exit 1 when nothing is configured; the masked
key with its source on success; an environment key reported as its own source
rather than the file; a short key masked as *** instead of half-revealed;
exit 2 on a rejected key without leaking it; exit 2 with the reason when the
validation call itself fails.

login: prompting and saving, --key skipping the prompt, surrounding whitespace
stripped, a blank key refused without writing anything, and a round trip
proving whoami accepts what login wrote.

The key_store fixture has to move both bindings of the api-key path constant:
save_api_key/get_default_api_key read it from dashscope.common.api_key while
auth reads its own imported copy, so patching one leaves login and whoami
silently disagreeing about where the key lives.

Messages are compared through a newline-stripping helper because rich hard-wraps
at 80 columns under CliRunner and force-breaks long tokens without inserting a
space. Wrapping Console.width instead would pin _width on the module-level
consoles past teardown.

Checked against three deliberate breaks of auth.py: 7 of the 11 failed, then
all 11 passed again once it was restored.
Vendors agenticCLI 4876f43 plus the 0.6.7 bump. All seven files are
byte-identical to the upstream sources modulo the dashscope. import prefix.

render_skill no longer runs str.format() over a skill body. A body is Markdown
full of literal braces from the JSON and Python it documents, so
text-generation.md — which shows messages=[{"role": "user", ...}] and declares
no arguments at all — raised KeyError('"role"'), and the old `except KeyError`
reported that as "missing args" for a skill that takes none. 6 of the 13
bundled skills were unusable through use_skill while /skill and the catalog
kept advertising them. Substitution is now a plain replace of the declared
arguments only; use_skill and the scheduler branch on `is None` rather than
falsiness, so the missing-args hint can no longer render empty.

search_files matches the pattern against paths relative to the search root as
well as the basename, and skips dot-directories only unless the pattern names
one, so .acli/skills/*.md is reachable without walking every .git in the tree.
Its description now says that * crosses separators, which it does under fnmatch
and did not under the old basename-only match, and the no-match message states
what was actually searched instead of implying the file is absent.

EnvironmentSection states the platform and how run_command executes, through a
new shell_label() that lives next to _spawn so the prompt cannot drift from
what the tool really runs. Nothing used to say which shell this is: on Windows
the model wrote `2>/dev/null || find /` and spent a turn on a PowerShell
ParserError.

Child-process output decoding tries UTF-8 strictly first, then the Windows OEM
code page from GetOEMCP() and the stdio/locale encodings. A piped child writes
in its console's code page, which need not equal sys.stdout.encoding, and cp936
accepts nearly any byte sequence so it would silently mangle UTF-8 rather than
fail. A UnicodeDecodeError in the last four bytes is the byte-budget truncation
splitting a character, not evidence of another encoding, so it is repaired
instead of re-decoding the whole buffer under a DBCS page.

Adds tests/unit/test_acli_skill_render.py: the 13 bundled skill files ship only
in this wheel, so they are the real corpus that broke and the one piece of
coverage with dashscope-only value. Verified by restoring the pre-fix
render_skill — exactly those 6 skills fail, 19 tests — then restoring the fix.
The upstream coverage for the other three fixes stays in agenticCLI, which is
where those test modules live.

dashscope unit: 1011 passed, 7 skipped. pre-commit --all-files green, mypy
included. dashscope/acli/examples/ is untouched by the sync.
pytest fixtures are injected by name, so every test taking key_store or
bundled_skills redefines it from module scope, and the ones that need only
the fixture's side effects never read the parameter. Both are the repo's
existing convention for test modules; the assertions are unchanged apart
from `calls == []` becoming `not calls`.

pre-commit run --all-files only started reporting these once the files were
tracked — it lints tracked files, so the untracked originals were skipped.
…ples

sync_acli.sh deliberately preserves dashscope/acli/examples/, so the bundled
example config drifts from its upstream repo in agenticCLI-examples and has
to be reconciled by hand. Both files had moved independently:

- agent.md: upstream gained the 2026-09 error-code pass (request_too_large,
  permission_error, billing_error, timeout_error, and the note to match on the
  exception class rather than .code). Take the upstream version.
- system-prompt.md: Bailian docs moved to
  docs.modelstudio.console.alibabacloud.com. Point the product overview at the
  new domain and add the API-key, app-id/workspace-id and knowledge-base
  entries, whose titles were verified against the live pages. The remaining
  help.aliyun.com links are left alone rather than guessed at.

The two trees are now byte-identical.
When the agent is invoked without a terminal and nothing is piped in, the
error told the user to rerun as `dashscope "your question"`. That stopped
working in de16112, which routes everything except a bare `dashscope` and an
explicit `expert` keyword to typer. So the one message that could have
recovered the command line sent the user straight into
`No such command 'your question'`.

Cover the whole non-TTY branch, which had no tests: piped text becomes a
one-shot prompt, an empty pipe exits 2 with the corrected hint, and an
unreadable stdin is refused the same way rather than crashing. The hint
assertions name both the corrected form and the stale one, so a revert shows
up as a failure instead of passing quietly.
write_file refused any path outside the cwd, which made it stricter than every
layer around it without protecting anything: run_command can already write to
temp because the shell is not path-guarded, delete_file reaches anywhere behind
its own prompt, and the opt-in OS sandbox confines writes to "the workspace and
temp areas". The refusal also cost more than a failed call — models reach for
/tmp for scratch files by instinct, so the retry put the scratch file in the
user's repo instead, which is the worse outcome.

Writes now land in the cwd or the realpath'd system temp dir. Both sides are
resolved with realpath, so a symlink inside either root still cannot escape,
and validate_path runs first so SENSITIVE_NAMES keeps blocking e.g.
/tmp/.ssh/config. The refusal message names both roots generically rather than
echoing absolute paths.

Per-file sync only: the two files are byte-identical to agenticCLI modulo the
acli. -> dashscope.acli. import rewrite. examples/ was preserved by the sync
script, and no version is bumped -- agenticCLI stays at 0.6.7.
The Response Strategy offered three question types — issue analysis, API
question, example generation — and the Workflow said to classify into one of
them. A question about the assistant matches none of the three, so it fell
through to the only behaviour left: probe the environment until an answer
turns up.

Observed on a real session: "你现在是什么模型" and "你能干嘛" together burned four
run_command calls, two of which failed on bash syntax under PowerShell, before
producing an answer that was already sitting in the prompt on the Current
model line. Nothing about the assistant is discoverable by running a command.

Add the fourth branch and make the Workflow route to it first, rather than
relying on the anti-pattern list to be re-read: the model was never
classifying these as "explore unrelated files to get oriented", so that line
never applied.

Vendored from agenticCLI-examples 77c5284; the two trees are byte-identical.
Two changes, both from the same reported transcript:

3f40870 — tool output is now redacted on the way into context. save_session
already ran sanitize over the persisted history, so the session file was the
only thing protected: a key read out of the environment was scrubbed on disk
and handed to the model in full, where it stayed in the prompt prefix for the
rest of the turn. It runs before truncate_head_tail rather than after, since
a character-budget cut can split a token in half and leave a fragment too
short for the sk- pattern to match. tool_result_for_display is untouched —
that path renders to the user's own terminal.

39d0781 — one reminder per turn after ACLI_SILENT_TOOL_NUDGE (default 6, 0
disables) consecutive tool calls stream no prose. The turn shape the transcript
had has no other backstop: StagnationTracker's read-only classifier counts
run_command as mutating and so resets its streak, its repeat counters need
byte-identical calls, ReflectionTracker needs failures, and the convergence
section is oneshot-only. Fires in interactive mode too, which is where the
user is watching a spinner and getting nothing.

The agenticCLI unit tests covering both are not vendored by sync_acli.sh, so
this repo ships them without coverage; verified against agenticCLI's 2728
passing tests. dashscope suite: 1079 passed, 42 skipped.
Oneshot tool registrations and memory wiring, prompt-composition and
hint/token trace stats, create_plan prompt cleanup with the invisible-
tool CI guard, loader-compatible skill generator behind the
ACLI_EVOLVE_SKILL gate, runtime skill registration with deletion
propagation, and concurrent-write hardening.
…ticCLI

7a1105c — failed/silent turns now leave a "[Turn failed before any
response: ...]" assistant trace, an identical resubmit reuses the
orphaned trailing user message instead of stacking, markdown-only
interrupt partials are dropped, the tongyi native wire strips
reasoning_content (it was re-billing the thinking trace every turn),
the gateway 403 overdue-payment block is explicitly non-retryable with
an account/billing hint at both display layers, unknown slash commands
print a dim notice before falling through to the model, the Current
user env line is labeled as the configured display name, and an ENC:
config value this machine cannot decrypt loads as unset with a stderr
note instead of killing startup with a bare UnicodeDecodeError (the
"Agent failed to start ('utf-8' codec ...)" report).

a3d2a2c — /model <name> switches the model under the current provider
without the /provider wizard; bare /model shows the current model.
Non-interactive, so both REPL and TUI route it through the synchronous
dispatch path.

Mirrored tests added to tests/unit (tongyi wire sanitizing, account
classification/hint, gateway 403 never retried); the remaining
coverage lives in agenticCLI, whose suite passes 2756. dashscope acli
suite: 323 passed.
…y from agenticCLI 0.6.12

Source changes arrive via scripts/sync_acli.sh; the reflection and
tongyi-native tests are ported by hand with dashscope.acli imports.
Single-command timeout now tracks the remaining wall-clock budget
(min 30s, 180s finalize reserve), and the oneshot policy gains the
long-command clause; version 0.7.0 marks the Terminal-Bench hard set
reaching best-of 10/10 upstream.
# Conflicts:
#	dashscope/acli/__init__.py
#	dashscope/acli/agent.py
#	dashscope/acli/providers/tongyi.py
_clamp_run_command_timeout subtracts the monotonic anchor after the
ceiling-None guard, but the repo's mypy (pinned --python-version=3.10)
cannot infer _run_started is non-None from that guard across methods —
CI on PR #206 failed with "Unsupported operand types for - (float and
None)". Fold the check into the guard; upstream agenticCLI gets the
same line so the next sync stays byte-identical. black also explodes
two long calls in the merge-touched tongyi tests.
Vision chain (oneshot registration, injection perception, region
reads with voting, vision timeout, qwen3-vl-plus default), reasoning
stall retry fix, oneshot empty-turn backstop, rules 17/18 and the
hard-band best-partial write. vendored suite 1032 passed, 10 skipped.
@lzsweb lzsweb changed the title acli: sync 0.7.0 budget discipline and fix cli command routing acli: sync 0.7.1 and fix cli command routing Oct 14, 2026
@lzsweb
lzsweb merged commit 2aa724a into main Oct 14, 2026
6 checks passed
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