Skip to content

v3.5.88

Choose a tag to compare

@chauncygu chauncygu released this 20 Sep 17:34
· 9 commits to main since this release
28e0ac9
  • September 20, 2026 (v3.5.88): Autonomous by default, /resume by name, and transcripts you can read. A request becomes a result — /auto carries a task end to end, and /resume lists sessions by name. (1) Autonomous mode, and it is now the default. The permission prompt was doing its job on the wrong axis. A turn that needs six commands stopped six times, and the answers didn't accumulate: approving Bash:rm with s lasted exactly one session, so the same keystroke came back tomorrow, and the only way out was accept-all — which is deliberately session-only and has to be re-chosen every launch. Ask a simple question and you'd get an approval menu instead of an answer (Allow: Run: date; date -u; TZ=Asia/Shanghai date, six options, for reading the clock). So auto_approve now ships as True: a request is carried out end to end and you get the result. /auto off turns the prompts back on and is saved, /auto / /status report the state, --ask (alias --no-auto) enables prompting for one run and --auto (alias --yolo) forces autonomy for one run without touching your saved preference. The startup banner stays quiet on the default and calls out the other state — auto (asks before acting) — since that's the one worth a line. Two answers were also added to the prompt itself, for people who run with /auto off, and both stick: ! saves the grant for one signature (Bash:rm, Edit:/repo/app.py) to the always_allow list in config.json, so a command you have already vouched for is never asked about again in any future session; auto switches to autonomous mode from the prompt. What autonomy does not bypass: the Bash hard denylist (host-destroying commands are refused below the permission gate, so "approved" still doesn't mean "runs"), the filesystem sandbox and credential-path denylist, and the manual / plan permission modes — those are explicit "ask me" / "touch nothing" requests, and a saved convenience must not silently undo them. The launch flag writes to a _-prefixed session key rather than auto_approve, so an unrelated save_config later in the run (from /model, say) can't leak one-off autonomy into every future session. /permissions lists saved grants next to session ones, /permissions allow <sig> adds them, /permissions forget <sig|all> drops them. (2) /resume shows session names. It used to load session_latest.json and nothing else; /load offered a menu, but its rows read 14:32 id:4f2a1c turns:12 session_143207_4f2a1c.json — an id tells you nothing about which conversation it was. Sessions now carry a title derived from their opening request (first real user message; slash commands, tool results and framework-injected turns are skipped, newlines collapsed, ellipsized to one line), saved into the session JSON and the SQLite row. /resume with no arguments lists recent sessions newest-first — "Add a /resume picker with session names" · 2026-09-19 14:32 · 12 turns · id:4f2a1c — with the live autosave pinned at the top and marked ← last, deduped across the autosave, the daily backups and the database, and a corrupted file is skipped rather than fatal. Answer with a row number or an id; /resume last, /resume <id> and /resume <path> skip the picker; a non-interactive run (-p, piped stdin, cron) has nobody to answer a menu, so it keeps the old behavior and resumes the latest directly. 49 new tests. Fix: a changed default could never reach an existing user. save_config wrote every key, defaults included — so the first time anything saved (picking a model, answering ! at a prompt) all ~76 default values were frozen into config.json. Changing a default in a later release then reached new installs only: v3.5.88 shipped autonomous-by-default and existing users still got Permissions: auto (asks before acting) and the six-option approval menu, because their file said auto_approve: false — a value nobody had chosen. save_config now persists only what differs from DEFAULTS, which loads identically (the saved file has always been layered over DEFAULTS) while letting defaults actually behave like defaults, and a one-time config migration cleans up the frozen values already on disk (a real 76-key file drops to 22 — API keys, model, bridge credentials, budgets; nothing a user set is touched). The migration is recorded with a config_version marker that is deliberately not a DEFAULTS key, since the new pruning would otherwise erase the marker and re-run every migration on the next launch — which would have silently undone a deliberate /auto off. Also fixed: permission signatures named a shell fragment, not a program. shlex.split keeps operators glued to the token they touch, so date; date -u; TZ=Asia/Shanghai date produced the signature Bash:date; — meaning the grant you saved with ! matched nothing you would ever run again, and the same command prompted forever. Operators are trimmed and a leading VAR=value assignment is skipped, so that command signs as Bash:date and curl -s a ; echo ---; curl -s b as Bash:curl; the migration also drops the dead operator-bearing grants already saved. 13 new tests. The stray horizontal line mid-turn was the model's ---, and it is now off by default. Models separate sections with a Markdown thematic break, and Rich paints that as a full-width rule. Because the turn summary prints at every tool round-trip, the rule landed between two summary lines (Fetched 1 URL, ran 1 shell command … rule … Ran 2 shell commands), where it reads like the app drew a divider rather than the answer containing one — the blank line around it already did the separating. Breaks are now stripped before rendering; /config markdown_rules=true brings them back. The stripping is position-aware rather than a blanket delete of --- lines, because the same characters mean different things: a bare - run directly under a paragraph is a setext H2 underline (deleting it silently demotes a heading to body text), a | --- | --- | row is a table separator, - item is a list, and a --- inside a fenced block (``` or ~~~, including an unterminated one from a truncated stream) is content, such as YAML front matter in a code sample. All four survive; only a real thematic break — ---, `***`, `___`, `- - -` — is dropped. 22 new tests. `/resume` now brings the conversation back, and transcripts are stored as text. (1) A resumed session looks resumed. `/resume` restored `state.messages` and printed one line about where the file was — so the model had the whole history and the user had an empty screen, talking to something that remembered a conversation they could not see. The transcript is now repainted: user turns in the prompt's own styling, assistant replies through the normal Markdown renderer, and each batch of tool calls collapsed to the same one-line summary a live turn prints (`Fetched 1 URL, ran 1 shell command`). Order follows what actually happened — an assistant message that says "let me check" and calls a tool prints the sentence above the summary it announces, not below it — and a `tool_result` message is plumbing, so it is not replayed as a second user turn. `resume_replay_limit` (default 40) caps how much is painted and says how many messages were elided; `resume_replay=false` restores silently. Replay is skipped on a Telegram / Slack / WeChat / QQ turn, where a whole transcript would arrive as one enormous chat message and the user is scrolling that history in the chat app anyway, and a rendering failure warns instead of costing the restored session. (2) Sessions are saved as UTF-8, not `\uXXXX`. Every `json.dumps` on the session path ran with the default `ensure_ascii=True`, so a Chinese message was stored as `"\u5e2e\u6211\u770b\u770b..."` — valid JSON, unreadable to a human opening the file, and hostile to `grep`. `ensure_ascii=False` now applies to the session files, the daily backups, `history.json`, the SQLite message blob, the `/cloudsave` Gist copy and `config.json`; reads and writes on those paths also pin `encoding="utf-8"` explicitly, since a C/POSIX locale would otherwise raise `UnicodeEncodeError` on the first non-ASCII character now that one can reach the file. Older escaped files keep loading unchanged — the escaping was never a content difference. 12 new tests. 143 new tests across the five areas. Details