Added
-
The Hub can now be started and stopped from a tray icon on Windows.
setupused to leave two shortcuts on your desktop — one to start the Hub and
one to stop it — and that split was the only real complaint left after
packaging this as a desktop app was ruled out.many-ai-cli trayruns a small
resident process whose menu has three entries: open the Hub (starting it first
if it is not running), stop the Hub, and quit the tray. It is a tray icon, not
a window: the UI still opens as a tab in your default browser, and no OS-level
notifications are added.setupnow creates a single "Many AI Hub"
shortcut pointing at it. Existing "Start" and "Stop" icons are left alone and
keep working —setupreports that it found them and lets you delete them
yourself. The tray lives in its own process rather than insideserve, so it
survives stopping the Hub; the Win32 calls go throughx/sys/windowswith no
cgo, keepingCGO_ENABLED=0intact for every release target
(internal/tray/,internal/setupcmd/setup_windows.go). -
Prompt templates can be reordered by dragging. Each row in the template
palette has a grip on the left; drag it to move the template, and the order
is saved asuser_prefs.templatesso it follows you across browsers and
devices. The drag is implemented with pointer events rather than HTML5
drag-and-drop, so it works with a finger or pen as well as a mouse, and the
list auto-scrolls while you hold near its top or bottom edge. With the grip
focused,↑/↓move the template one step for keyboard and assistive
technology use. Reordering is disabled while the search box has text, because
the position of a dragged row relative to filtered-out rows is undefined
(web/src/app/prompt-templates.ts). -
A toggle decides what picking a prompt template does. The template
palette above the input bar now has a Send on select switch: off (the
default, and the previous behavior) drops the template into the input box so
you can edit it first; on sends it immediately through the normal send path.
A one-line note under the switch states which mode is active. The setting is
stored server-side asuser_prefs.template_send.immediate, so it follows you
across browsers and devices (web/src/app/prompt-templates.ts,
internal/config/config.go). -
Worktree isolation is reachable from the spawn panel. Running an ordinary
session in its own git worktree already existed but could only be turned on by
hand-editinguser_prefs.spawn.worktree_autoinconfig.yaml, so in practice
nobody found it. The spawn panel now has a Run this session in a dedicated
git worktree checkbox, with a note that appears while it is ticked warning
that untracked files such asnode_modulesand.envare not carried into the
new checkout. The choice is remembered asspawn.defaults.isolate_worktree.
The checkbox only ever adds isolation: leaving it unticked omits the field so a
worktree_auto: trueinconfig.yamlkeeps working as the default
(web/src/index.html,web/src/app/spawn-panel.ts,web/src/i18n/*.json). -
doctornow reports whether the session log is actually being written.
Withlog.session_enabledon, the new check opens the newest.logthrough a
file handle and reports how many bytes it holds, so "enabled but empty" is
distinguishable from "nothing written yet". The size is read through the handle
rather than the directory entry on purpose: on Windows the size and
last-modified time of a file another process is still writing reach the
directory listing only after a delay, and during that window Explorer,dir
andGet-ChildItemshow 0 bytes with the timestamp frozen at creation. When
the two disagree the check prints both and states that this is not a failure —
the appearance was diagnosed as a logging bug on 2026-08-14 when the logs were
in fact intact (internal/doctor/doctor.go,
internal/sessionlog/sessionlog.go). -
doctornow tells you if a session left something behind in your git
repository. This release already asks you to check your repositories for a
committedopencode.jsonor a stranded approval-rules block inAGENTS.md;
this is the means to do it. Runmany-ai-cli doctorinside the repository and
it classifies each artifact as tracked by git, present only in the working
tree, or absent, and prints nothing at all in the last case — a clean
repository gains no output. Tracked artifacts are the ones that matter, since
.gitignorehas no effect on a file git already follows, so the check names
thegit rm --cachedneeded to undo it. A committed"permission": {"*": "allow"}is reported as a failure rather than a warning, because it disables
the approval prompt for anyone who clones the repository. Tracked status is
read from the git index rather than from disk, so a running opencode session
rewriting its ownopencode.jsonis not mistaken for residue, and a live lock
holder suppresses the working-tree report entirely. The search for the
approval-rules block uses the pre-renameany-ai-cli:approval-rulesneedle,
which is a substring of the current marker and therefore matches blocks left by
either generation — searching for the current name alone makes older residue
look like zero hits. The check is skipped without error when git is missing or
the directory is not a repository (internal/doctor/residue.go).
Changed
- Task-completion notifications are now on by default — but only if you already
set up somewhere to send them. The README says many-ai-cli tells you the moment
a session stops, but the completion notification sat behind two layers of opt-in
and never fired with stock settings.user_prefs.done_summary_notify.enabledis
now a tri-state: if you have touched the toggle, your choice stands, on or off. If
you have not, the Hub turns it on when you already have somewhere to deliver to —
a Web Push subscription, or at least onenotify.backendsentry. If you have
neither, nothing changes: no new notifications, because there is nowhere to send
them. The settings toggle reports the effective state rather than the stored one,
so it cannot show "off" while notifications are going out
(internal/hub/done_summary.go,internal/config/config.go). - The OpenCode slash-command list gained the 12 commands it was missing.
Checked against the opencode 1.18.18 binary:/agents,/debug,/diff,
/mcps,/move,/org,/skills,/slash,/status,/variants,/warp
and/workspacesare built into the TUI but were absent from the picker. Nothing
was removed — the nine entries that are not TUI built-ins (/compact,/export,
/init,/redo,/share,/undo,/unshare,/details,/thinking) exist in
the binary assession.*commands, registered through a different path, so they
stay (resources/slash-commands/opencode.md). - Building from source now needs Go 1.25.13. The
godirective ingo.mod
moved up one patch release to pick up fixes for five standard-library
vulnerabilities that this code reaches:net/url(GO-2026-6218),crypto/tls
(GO-2026-6090),net/http(GO-2026-6089 and GO-2026-5026) andencoding/asn1
(GO-2026-5972). Released binaries are unaffected — they are built by CI, which
reads the same file. With the defaultGOTOOLCHAIN=autothe newer toolchain is
fetched automatically, so no manual install is needed. - The approval-corrupt dump was removed. The instrumentation that saved raw
PTY bytes whenever an approval marker looked corrupt has served its purpose:
three separate causes were found from its output and fixed. Keeping it meant
continuing to write input-derived bytes to disk, which is not worth the
remaining diagnostic value. If a fourth cause turns up, it can be reintroduced
with a fresh expiry (internal/hub/approval_corrupt_dump.go, removed). - An answered approval is now remembered in one place instead of three. The
browser used to decide "have we already answered this?" with three separate
pieces of state that disagreed with each other: a signature that expired on a
5–10 second timer, a hash of the whole marker block that never expired at all,
and a question-text key used only by the manual dismiss button. Each had its
own idea of what counts as the same question, and the gaps between them are
where answered prompts came back — the timer one lapsed while a redrawn block
was still on screen, and the permanent one could bury a question the agent
genuinely asked again. All three are gone. What remains is the candidate key
plus the source generation: the key is built from the provider, the approval
kind, the normalized question and the option numbers and send strings, so a
redraw that shifts labels, padding or box rules resolves to the same
candidate, while a new live prompt boundary produces a new generation and an
intentionally repeated question is shown again. The identity logic moved out
ofstate.tsintoapproval-answered.ts, which touches no DOM and is covered
by 14 regression tests (web/src/app/approval-answered.ts,
web/src/app/approval-answered-fixtures.ts,web/src/app/approval.ts). - Release workflows now pin third-party Actions to immutable commit SHAs.
The npm credential is also limited to the publish step; other release steps
receive only a boolean availability flag (.github/workflows/*.yml). - The template list now keeps the order you set instead of sorting by usage.
Previously the palette sorted by how often each template had been picked, so a
manually chosen order would be undone by the next selection. Templates are now
listed in stored order, newly added ones go to the bottom, and the per-template
frequencycounter is gone from the client, the server struct, and the
user-prefs mirror. Picking a template no longer writes to the server at all.
Existingfrequencyvalues inconfig.yamlare ignored and dropped on the
next save (web/src/app/prompt-templates.ts,web/src/app/user-prefs.ts,
internal/config/config.go).
Fixed
-
Turning the task-completion notification toggle on or off wiped every other
preference.PUT /api/user-prefsreplaces the whole object, and this toggle
sent only its own field, so templates, session order, cwd history and favourites
were all reset to empty on each click. It now reads the current preferences,
merges its one field and sends the whole thing back — the same read-modify-write
the token status bar toggle already used (web/src/app/settings.ts). -
The approval bar's background was see-through, so terminal output showed
through the text. When the bar became an overlay it kept the translucent
rgba(245,158,11,0.04)fill it had while it was part of the normal flow, and
the PTY output underneath bled into the approval text. The fill is now opaque
(color-mix(in srgb, var(--warn) 5%, var(--bg-elev))), which keeps the warning
tint without letting anything through. Making the bar semi-transparent is not
an acceptable fix here — the whole point of the overlay is that the question
stays readable (web/src/styles/approval.css). -
A slash command picked from the picker was sent with a trailing space, and
OpenCode ran it as a prompt instead of a command. Confirming a suggestion
insertscommand + " "so you can keep typing arguments, but OpenCode's TUI
only opens its command list while the line starts with/and contains no
space up to the cursor (checked against the opencode 1.18.18 binary). With the
space still attached the list never opened, Enter fell through to an ordinary
send, and/modelsreached the model as plain text. A trailing space is now
stripped from single-line slash commands before sending — none of the six
wrapped CLIs give one meaning — and/modelswas added to the commands that
send immediately on selection, so it matches how/modelbehaves for Claude
(web/src/app.ts). -
Approval panels were still being withheld over the TUI's own frame lines.
The corruption check treats box-drawing characters inside a marker block as
proof that the composer frame was redrawn over the text — the approval format
forbids rules and tables, so a rule in the block cannot have come from the
model. That reasoning does not hold for a line made of nothing but rules and
spaces: it is the frame itself, landing in the middle of the block in the
Hub's VT mirror rather than overwriting anything. Replaying all 107 recorded
approval-corruptdumps (2026-08-08 to 08-14) at their recorded mirror size
showed 13 of the 36 remainingbox_ruleblocks contained rules only in that
shape, and every one of them was structurally intact once those lines were
set aside — 15 of the 16 offending lines were exactlyvt_colswide, and none
of them sat at the start or end of a block. Lines that are only rules are now
skipped, while a rule that overwrites an option label is still caught. On the
same corpus this turns 12 more blocks into panels; for the dumps recorded
after the v0.6.0 mirror fixes,box_rulehalves from 16 to 8
(internal/hub/approval_marker_verdict.go). -
An approval panel from one session could stay on screen after switching to
another.#action-baris a single shared DOM node and records which session
painted it indataset.approvalSessionId, but nothing removed it on a session
switch:detectApprovalhas many early returns and none of them cleared the
bar first. If the session you switched to was also waiting for approval, the
repaint check saw a bar that was visible and populated, decided no repaint was
needed, and left the previous session's question on screen. Answering what
looked like the current session's question sent the choice to the other
session. Ownership is now the deciding factor in both places that ask the
question — the switch clears a panel owned by someone else before detection
runs, and the repaint check treats a foreign owner as a reason to repaint. The
release drops only the DOM: the previous session's approval state and its Hub
hint survive, because that question is still unanswered and must come back when
you switch there again (web/src/app/approval-owner.ts,
web/src/app/approval.ts,web/src/app/session-list.ts). -
Agent Chat prime no longer advances the cursor before tail-page decoding is
committed. When the selection deadline expires, the Hub keeps the safe
retry cursor so existing tail records cannot be skipped
(internal/hub/agent_chat_parse.go,internal/hub/agent_chat_handler_test.go). -
Managed Whisper releases now embed the verified Windows VC++ runtime. A
Windows release-preparation job fetches Microsoft-signed DLLs and the
GoReleaser job fails closed if the verified payload is absent
(.github/workflows/release.yml). -
Approval panels were withheld for questions the CLI had actually asked.
Two independent defects in the Hub's VT mirror made structurally sound marker
blocks look corrupt, so the panel was dropped and you got the "approval
withheld" banner instead, with the question left sitting unanswered in the
terminal. Replaying all 138 recordedapproval-corruptdumps (2026-08-06 to
08-13) through a mirror of the recorded size reproduced 132 of them, ruling out
a transient desync and pinning both causes:- Wide characters advanced the cursor by one column instead of two.
writeRuneincrementedcolby 1 for every rune, so a line of Japanese left
the mirror's cursor at half the real terminal's column, and the composer's box
rule survived on the right-hand side of the line — landing inside option
labels and tripping thebox_ruleverdict. The mirror now measures East
Asian Wide / Fullwidth andEmoji_Presentationrunes as two columns and
combining marks and variation selectors as zero, and renders the
wide-continuation sentinel out of bothLines()and the scrollback push
(internal/hub/vt_buffer.go). - Extraction fused an older redraw with the current question. TUIs redraw
while scrolling, so a half-drawn generation of the same block stays in the
scrollback; 72 of the 74marker_leakrecords had the stale open marker
there.extractApprovalMarkerBlockmeant to take the newest block, but its
non-greedy regex still started at the first open marker, producing a block
that straddled two generations and therefore carried an open marker inside
itself — the exact shapeclassifyApprovalMarkerBlockrejects. It now takes
the last close marker and the last open marker before it
(internal/hub/approval_marker.go).
Together these turn 93 of the 138 recorded corruptions into panels that render
normally. The remaining 45 — 32 originally judgedbox_rule, 10marker_leak,
3duplicate_option— are not explained by either cause and are still
suppressed. - Wide characters advanced the cursor by one column instead of two.
-
.git-worktrees/was not ignored by git. Normal-session worktree isolation
creates its checkouts in<repo>/.git-worktrees/, but.gitignoreonly
covered.many-ai-cli/, which is the orchestration path. Turning isolation on
therefore left an untracked directory inside the parent working tree
(.gitignore). -
The v0.5.0 entry below documented a worktree path that was never used. It
said normal-session worktrees live under.many-ai-cli/worktrees/normal/<label>/;
the implementation has always placed them in<repo>/.git-worktrees/<label>-<timestamp>
on amany-ai/…branch. The entry has been corrected. -
A failed spawn dumped the raw JSON error body into an alert. The spawn
panel showedalert(t('spawn_failed') + await res.text()), so a rejected
launch surfaced{"ok":false,"error":…,"detail":…}verbatim. Failures now
render thedetailalone, and aworktree_error— the case you hit by
ticking worktree isolation on a directory that is not a git repository —
gets a written explanation instead (web/src/app/spawn-panel.ts,
web/src/i18n/*.json). -
Closing a session did nothing when the wrapper had just reconnected. The ×
on a session card drops the session from the Hub and closes the wrapper's
WebSocket, and the wrapper decided from that bare EOF alone whether you meant
to stop the session or the connection had merely dropped. Within the
ten-secondpostReattachGuardthat guess falls to "dropped", so the wrapper
reconnected two seconds later and the card returned; pressing × again landed
inside a fresh guard window, so it never stopped. Codex sessions ran into this
routinely because a full PTY output queue (pty_output_queue_full)
disconnects and reattaches on its own — one session did so three times in
eight minutes on 2026-08-13. The Hub now sends an explicitsession_dismissed
frame before closing the socket and the wrapper terminates its PTY on receipt,
so intent is transmitted rather than inferred (internal/proto/messages.go,
internal/hub/server.go,internal/wrapper/wrapper.go). Shortening the guard
was rejected: it would reinstate the 2026-07-20 defect where a plain EOF just
after a reattach killed the wrapped CLI. Older wrappers ignore the unknown
frame and keep the previous behaviour. -
A killed opencode session no longer leaves its
opencode.jsonbehind for
good. The wrapper writes{"permission":{"*":…}}into the session's working
directory and restores the file when the session ends, but a killed process
never runs that cleanup. The next session in the same directory then read the
leftover as the "original" and wrote it back on exit, so the override became
permanent — including theallowthat orchestration children write, which
silences approval prompts for anyone who clones the repository. The exclusive
lock now carries the rollback state (what the file was, and what this session
wrote), and a session that takes over a stale lock restores the leftover
before touching the file again; a file that changed after the dead session is
left alone. If you ran opencode sessions through the Hub before this release,
check whetheropencode.jsonwas committed to any of your repositories
(internal/wrapper/opencode_config.go,
internal/wrapper/opencode_config_test.go,.gitignore). -
A killed Hub no longer leaves its approval-rules block in your project's
AGENTS.md. The block the Hub appends forcopilot/cursor-agent/
groksessions is removed on graceful shutdown, but the list of files it had
been injected into lived only in memory — so killing the Hub stranded the
block in the repository, where nothing would ever remove it and an
git add -Acould commit it. The list is now kept in
~/.many-ai-cli/approval-rule-targets.json, and the Hub clears any leftover
block at startup, before the first session connects; a reconnecting session
gets the block injected again through the normal path. Files it cannot clean
stay on the list for the next start, and a list that fails to parse is
reported and ignored rather than acted on. If you ran Hub sessions before this
release, check your repositories'AGENTS.mdfor a stranded block
(internal/hub/approval_rules_state.go,internal/hub/approval_handler.go,
internal/hub/server.go).