Skip to content

v0.9.0

Latest

Choose a tag to compare

@github-actions github-actions released this 09 Oct 09:25
9f779b2

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_commands events carry the validated render tree and
    the commands a mod contributes, ModSlotHost / ModPaneView render them, the /mods
    routes and the mod slash commands wire them up, and Settings gains a "mods" entry
    gated on the mods_host plugin. 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's browser_*
    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 of 127.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.json in 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/responses protocol (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 Completions and
    OpenAI Responses (the new openai_responses value), alongside
    Anthropic Messages and the other providers. openai and openai_compat
    are one wire format (both drive ChatAPI over /v1/chat/completions), so
    they share a single OpenAI Chat Completions entry; a card keeps whichever
    of the two values it already had. ResponsesAPI
    (opensquad/responses_api.py) reuses ChatAPI'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 into instructions + input items (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_responses use the new endpoint.
  • Agent Web: a scheduled task's detail pane now lists its run history.
    The pane showed Run count 90 with 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) while run_count keeps 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 run git for 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 web reported "Frontend port 5173 not ready" instead of the
    reason.
    The dev server is spawned without a console, and detach_popen_kwargs()
    sends its stdout/stderr to DEVNULL. On a machine where npm's own shim cannot
    find a node to run it — which is what Roaming\npm\npm.cmd does when no
    node.exe sits beside it and node is 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.log and 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 from aiChat.attach.*
    (with the Skills row on the shared nav.skills), and utils/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
    recorded thought_ms was written by turn_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 via thought_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 a missing flag
    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 is Date.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

Full Changelog: v0.8.49...v0.9.0