Repository navigation
Added
- A plugin can own the startup screen. The window that covers startup is no longer
built into the shell: a plugin contributes it, rotates a playlist until the app is
ready (audio included, stopped on exit and never stalling at a seam), and can keep a
private clip private. - Claude Code mods: a host, and slots in the app. A Node host speaks NDJSON to the
agent process;mod_slot/mod_commandsevents carry the validated render tree and
the commands a mod contributes,ModSlotHost/ModPaneViewrender them, the/mods
routes and the mod slash commands wire them up, and Settings gains a "mods" entry
gated on themods_hostplugin. Mods declare the tools they proxy in their manifests. - A built-in browser the agent and the panel share. Playwright renders in the
launcher and exposes a CDP port; the Playwright MCP is pointed at that port, so a
plugin that believes it owns a browser drives the very page the agent'sbrowser_*
tools and the panel see, instead of a second and invisible one. The panel gains a
read-only "Agent browser" tab that polls frames — a change made over CDP, which calls
none of the session's own methods, still shows up a poll later. Input is never
forwarded: watching is the feature, driving stays with the agent. - A terminal in the file rail, hosted by the launcher. A shell is a workspace tool,
not an agent capability, so it must work with the agent stopped — it runs in the
launcher and the panel polls its output. The shell is picked from what the machine
actually has (cmd / PowerShell / pwsh / Git Bash / WSL / zsh / fish), you type in the
terminal surface itself, and a terminal now keeps its scrollback across a tab switch,
a panel close or a reload because it is addressed by slot and the launcher keeps its
buffer. There is no PTY: the panel says so rather than letting a full-screen program
look broken. - The right panel becomes 浏览器 / 终端 / 文件, and the workspace pane's tab bar
offers all three. - A first-launch tour, with the language switch inside it.
- A messenger-style chat layout for the agent web. The rail becomes one contacts
list for group chat and 1:1; talking to one agent gets its own whole-page layout with
bubbles, attachments in an always-visible footer, quoting, paged history and history
search. Both rails share one footer action set. - Collaboration, end to end. A collaboration-task card protocol with participant
state, a task-scoped channel agents talk in, and a task window that draws the work the
way the board does and renders markdown progress. An invitation is delivered to the
agent and only the coded invite is shown; work is assigned once the whole team has
accepted; one collaboration runs at a time, enforced at every door in; 补充 asks for a
change on an approval card without deciding it; the closure is announced in the window. - Pairing across machines that survives a restart. The invite names this machine's
LAN address instead of127.0.0.1, the host is detected rather than typed, a paired
machine shows whether it has ever connected, and a restart resumes the pairs instead of
forgetting them. - Relay: queue, retry and backfill a push the peer could not receive.
- Guided, step-by-step setup for the plugins that talk to external services.
- A wake mode in the composer's + menu, wired to the agent's own setting, and a
cross-group task strip above the composer with the merged task list behind it. - A process says which code it is running, and shouts if the files beside it have moved
on. A fix that landed after a process started is not running, and that has been
mistaken for "the fix did not work" more than once. - A per-agent token statistics page in the session sidebar, with the input split and
cache share on the cards. - A long group message folds to its first stretch, with a faded edge, and a plugin's
full details open in a dialog. - Mods: the compatibility surface is documented, with the host's trust boundary
written down (declared capabilities, install-time consent) instead of implied. - Desktop: background updates that don't interrupt work. The app already
downloaded installers in the background, but only when the user clicked
"download in background", and installing always forced an immediate restart.
A new Automatic updates section in Settings → About adds three switches:
download new versions in the background (on by default — the main-process
checker starts the download as soon as a release is found, deduped against the
manual button), install on quit (on by default — a downloaded installer is
applied when the app is closed normally, instead of a forced mid-work
restart), and restart & install when idle (off by default — restarts only
after no agent work has run for a couple of minutes). Preferences persist to
update-prefs.jsonin userData so the main process can auto-download and apply
on quit even before the renderer mounts. The install itself is unchanged — the
silent NSIS installer still replaces the files while the app is closing — so
this changes when the restart happens, not whether the app restarts. - Model cards: the OpenAI
/v1/responsesprotocol (api_protocol: openai_responses).
OpenAI now ships two wire formats, so the model-card editor's API Protocol
control is a dropdown offering both:OpenAI Chat Completionsand
OpenAI Responses(the newopenai_responsesvalue), alongside
Anthropic Messagesand the other providers.openaiandopenai_compat
are one wire format (both driveChatAPIover/v1/chat/completions), so
they share a singleOpenAI Chat Completionsentry; a card keeps whichever
of the two values it already had.ResponsesAPI
(opensquad/responses_api.py) reusesChatAPI's whole turn engine — context
compression, retries, stop handling, Native Function Calling, usage accounting
and session persistence — and swaps only the transport: a small client bridge
converts the request intoinstructions+inputitems (tools flattened to
the Responses shape) and translates the typed event stream
(response.output_text.delta,response.function_call_arguments.delta,
response.completed, …) back into the Chat-Completions chunk shape the shared
loop reads. Existing OpenAI models keep working unchanged; only cards set to
openai_responsesuse the new endpoint. - Agent Web: a scheduled task's detail pane now lists its run history.
The pane showedRun count 90with nowhere to see any of those 90 runs — the
history only existed in the 执行 tab, mixed across every task. The detail now
shows that task's runs in a bounded, scrollable box (max 320px) split by local
day (9/24, 9/22, …), each row carrying status, start time, 手动 marker, error
and duration, and each row opens that run's workflow. Because the store keeps a
window (≤200) whilerun_countkeeps counting, the header says
显示最近 49 次,共 90 次rather than passing either number off as the other.
While there, the pane's labels/statuses/schedule summary follow the UI language
(common.enabled,scheduledTasks.info.*,scheduledTasks.status.*) instead
of the hardcoded English the same screenshot showed. - Agent Web: goal mode is reachable from the "+" attach menu. It existed only
as a hand-typed/goal, so it stayed invisible to anyone who never guessed the
command. The new 目标模式 row inserts that exact text and focuses the composer
rather than adding a second implementation: the existing parser still opens the
subcommand picker (pause / resume / clear), the objective hint and the send path. - Agent Web: a branch chip and a Local/Worktree mode switch in the composer
footer. Up to now the only way to see or change git state from the app was
to ask the agent to rungitfor you. Two compact chips sit next to the
folder. The branch chip names the current branch and opens a menu grouped by
recently used, with remote branches and their last-commit age, and supports
create / switch / delete — a dirty worktree is offered a stash first, an
unmerged branch a confirmed force delete. The mode chip switches a draft
session between 本地 and a per-folder Worktree (one stable worktree under
.os-worktrees/per root), and turns read-only once the session's first
message pins its cwd, so a running conversation never changes ground under
itself; the session-level cwd is pinned at that moment so local and worktree
sessions cannot drag each other along. A directory without a repository
renders both chips grey and inert (非 Git 仓库) instead of hiding them. The
launcher gained the matching/git/*routes (status/branches/diff,
the write + network operations, and the worktree prepare), which run each
repository's updates under a cross-process lock and run fetch/pull/push as
pollable background tasks. - Agent Web: committing from the file panel's 改动 view. The view listed what
the conversation had touched; it now also answers "what is uncommitted in git"
and acts on it. A 本对话 / 全部 switch chooses between the two lists —本对话
keeps its existing meaning (the session baseline) and全部adds everything
else git reports, marked with which files the conversation also touched. Rows
carry git's state and can be checked, and the footer stages, unstages, discards
and commits: a discarded selection asks first, and asks again about deleting
files git does not track yet; the commit form names what is missing (a title,
then a non-empty index) rather than letting the refusal come back from the
server; and 撤销上次提交 appears only while the last commit can still be local
(no upstream, or commits ahead of one) and asks before running. Each row's diff
comes from the same viewer as the rest of the panel, read from the worktree or
the index depending on where the row actually is.
Fixed
- CLI:
opensquad webreported "Frontend port 5173 not ready" instead of the
reason. The dev server is spawned without a console, anddetach_popen_kwargs()
sends its stdout/stderr toDEVNULL. On a machine where npm's own shim cannot
find a node to run it — which is whatRoaming\npm\npm.cmddoes when no
node.exesits beside it andnodeis not on PATH — the child exited in the
first millisecond and its error had nowhere to go; the CLI then waited out the
entire 90-second timeout before falling back to the Gateway static UI, leaving no
trace of what to fix. It now (a) checks that npm can actually run before
spawning and names the missing program and where npm resolved to, (b) sends the
child's output to{workspace}/data/logs/frontend.logand prints that path when
the port still does not open, and (c) reuses the daemon stack's fail-fast poll, so
a child that dies stops the wait instead of stretching it to the timeout. - Agent Web: the "+" attach menu ignored the UI language. Every row was a
string literal ("Upload files" / "Upload folder" / "Upload images" /
"Auto speech" / "Skills") and the header read "Add agents, context, tools…",
so the ZH interface showed a half-translated popup — the 语音 row translated,
everything around it English. The labels now come fromaiChat.attach.*
(with the Skills row on the sharednav.skills), andutils/attachMenuI18n.scan.test.ts
fails if a label goes back to being hardcoded, if a key is used without being
added to both locales, or if zh.json gets the English text copied into it. - Agent Web: 深度思考 durations survived nothing but the live stream. The
recordedthought_mswas written byturn_result_handler.parse_and_persist_tags,
a module a refactor had already replaced with_runner/_turn_loop.py: the
clock recorded the value, the UI read it, and no live code path ever persisted
it. Refreshing a session therefore fell back to guessing from ISO-second
timestamps — on a real 97-round session 80 of 95 rows showed no time at all
and the survivors (up to 57s) had swallowed whole tool executions. The round's
persist site now writes the value viathought_clock.thought_event_data(),
guarded so a round that never closed its own phase cannot inherit the previous
round's number; sub-agent thought rows are persisted the same way. The reload
merge also keeps the newest recorded value instead of dropping it. Sessions
recorded before this change still show no number — it was never written down. - Agent Web: a project folder that is gone can no longer be registered or
activated. The workspace registry only ever unions, so a path the launcher
cannot see stayed registered for good and was copied to every origin on the next
sync. Nothing surfaced it beyond a bare "Root not found" in the files panel,
while the launcher rejected that session's cwd write and the agent quietly kept
answering about its previous project. A workspace now carries amissingflag
set from a launcher probe on every pull: the entry is kept (the drive may come
back) but it is dropped from the open tabs and never becomes the active
workspace, the "+" menu lists it disabled with a hint, and registering a folder
probes it first — refusing an explicit "gone" answer, and only that, so a
launcher that is briefly unreachable never blocks the user. A rejected cwd write
now raises a banner naming the path and the reason instead of being swallowed. - Agent Web: killing the backend no longer leaves an activity fold counting up
forever. An unfinished workflow fold is live by definition in the activity row
and its elapsed isDate.now() - started_ms, so a turn the backend never settled
kept ticking — a service killed mid-turn left a day-long "执行中 · 23h 48m 48s"
on screen. The disconnect path sealed only the sessions the client believed were
busy, a snapshot broadcast on a ~5s loop, so a stale or empty one left the fold
open for good. Sealing now waits out a 10s grace window — a blip reconnects in a
second and must not end a fold the agent is still working on — and then freezes
every live fold in every session, dropping the busy/streaming markers and the
turn clock with them. - Agent Web: a terminal keeps its scrollback across a tab switch and a reload.
The rail rendered the terminal with a ternary, so switching to 文件 unmounted it,
and its unmount closed the shell; coming back minted a fresh id, started a fresh
shell and showed a blank screen. A terminal is now addressed by slot
(rail:<workspace>,pane:<paneId>) instead of by mount, and the launcher keys
shells on that id and keeps each one's buffer — so a remount reattaches to the
same shell and the poll replays its output. The rail additionally keeps the
terminal mounted once it has been opened, so 文件 ↔ 终端 costs neither a restart
nor a blank frame. - Plugins: the SenseVoice service now declares the ffmpeg it resolves. The
manifest listed every other dependency the service imports but not the one that
provides ffmpeg, and the launcher installs only declared deps into the Agent
Python — the interpreter the service runs in — so the bundled resolver had no
provider to find. This was the single failure keeping the test suite red.
What's Changed
- chore: prepare v0.9.0 by @opensquad-ai in #10
Full Changelog: v0.8.49...v0.9.0