Skip to content

Releases: opensquad-ai/opensquad

v0.9.0

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.

Fixe...

Read more

v0.9.0-beta.2

v0.9.0-beta.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Oct 09:50

Changes since v0.9.0-beta.1

  • chore(release): prepare v0.9.0-beta.2 (3c598ed)
  • fix(tools): native mode falls back to XML when the provider's native FC is not implemented (6fb65a3)
  • fix(ci): the two red tests my own changes caused (fbc62ba)

Full Changelog: v0.9.0-beta.1...v0.9.0-beta.2

v0.9.0-beta.1

v0.9.0-beta.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Oct 09:23

Changes since v0.9.0-alpha.5

  • chore(release): prepare v0.9.0-beta.1 (383ecd0)
  • fix(collab): the ghost-id filter must read the id out of the record, not test the record (d0045f2)
  • chore(gitignore): ignore the task stores and session-cwd signal files (b483093)
  • fix(gateway): refuse a pane message that has no session to be delivered to (c93f22c)
  • test(cwd): pin concurrent-turn isolation with a real interleaving test (7478a73)
  • fix(cwd): a gateway message with no session id applies no working directory (54593f1)
  • fix(cwd): report and describe the executing session's folder, not the process-wide one (fe2f104)
  • feat(cwd): log which session asked for a working directory, and what it got (9d2a848)
  • fix(cwd): every call that sets a working directory names its session (68ccb93)
  • fix(chat): starting a session no longer stops a turn running in another (d3cd986)
  • fix(cwd): the turn's directory comes from the message, not from the last session seen (cc4df9c)
  • fix(cwd): the workspace switch scopes the directory to the session that asked (3637d93)
  • fix(cwd): a session with no folder of its own reports nothing, instead of borrowing another pane's (ea0379b)
  • fix(cwd): a working directory belongs to its session, not to the whole agent (1743c6a)
  • fix(collab): a gate gets one live card, however many sessions ask for it (2116231)
  • feat(chat): a remote member says where it is once, not twice (1d2c55b)
  • fix(chat): a mention highlights the name, not the whole message (8f14dff)
  • fix(chat): a new session is anchored to the workspace on screen, not the chrome's active one (587f17b)
  • feat(chat): the pane's welcome offers a new session, not only the views (de4a40d)
  • fix(chat): the task capsule shows the running count as a number, 0 included (5491db6)
  • fix(chat): the task capsule counts what is running, and says 无 when nothing is (f54dd57)
  • feat(collab): 补充 on the approval cards — ask for a change without deciding (e809843)
  • docs(collab): the four message rules — @ the user sparingly, talk in the task window, deliver in a sentence (fba70e7)
  • fix(collab): the collaboration card keeps reading the board, so it changes without a refresh (15a257e)
  • fix(gateway): a relayed member is matched by the agent the peer named, not by a user id from elsewhere (d3f4cbc)
  • fix(pairing): the LAN address is asked for under /ai-web, which is why detection never worked (bc41f05)
  • feat(pairing): only the coded invite is shown, and the host is detected rather than typed (9a80545)
  • fix(chat): fold only what a person wrote, at a thousand characters, and keep its formatting (2c004aa)
  • fix(chat): a long message folds wherever it appears, tables and code included (a2a7ef2)
  • fix(chat): only the folded edge is blurred, and the rest of the message stays crisp (1ad7535)
  • fix(chat): the folded tail fades on the text itself, using the panel's own mask (08ff883)
  • feat(chat): a long group message folds to its first stretch, with a faded edge (df2b5d9)
  • fix(chat): the wake-mode row narrows the optional handler inside its own closure (0ed9bac)
  • fix(collab): the collaboration card shows the board's state, not the snapshot in its own message (f9c3b3b)
  • test(collab): the whole chain, end to end, against a real board (b9317eb)
  • feat(build): a process now says which code it is running, and shouts if it is the older one (6d65659)
  • fix(collab): a blocked gate now says which gate, how long, and that it never opens by itself (ff228bf)
  • fix(chat): the new-session greeting is back, and the tip that stayed showed why (d1ad416)
  • docs(collab): the documentation now describes the board that exists, and a test keeps it that way (d52c771)
  • fix(collab): a board read-modify-write is atomic across processes, and the lock fails loudly (3e43fda)
  • feat(chat): the wake mode choices move behind a second-level row in the + menu (7b1fb41)
  • feat(chat): wake mode in the composer's + menu, wired to the agent's own setting (cfe7be1)
  • feat(models): the provider filter is a select, not a tag per vendor (183656b)
  • fix(terminal): a character split across two reads survives, and output stops repeating (4e1cf27)
  • fix(chat): the task strip sits after the @ button on the composer toolbar (faf9c31)
  • fix(gateway): a group member on a paired machine no longer reads as offline (c4533d9)
  • feat(chat): the task strip is a control on the composer toolbar, and it looks like one (c6bdae3)
  • feat(chat): a cross-group task strip above the composer, and the merged task list behind it (f7d479e)
  • fix(chat): a re-delivered steer no longer repeats as a new row in the tool stream (e56be9b)
  • fix(collab): a member id that cannot be an agent name no longer blocks dispatch (5306ca8)
  • fix(collab): members given as a string no longer becomes one member per character (064c329)
  • fix(chat): switching away and back no longer loses your place in a conversation (9381bcf)
  • fix(collab): board_view lost three zones, a subscription advertised an unreachable address, and a redundant push is reverted (4415e09)
  • fix(collab): the gate check now reaches the machine that owns the board (937c1cd)
  • fix(collab): a worker that accepted no longer looks invited on the collaboration card (19cedf1)
  • fix(models): assume Function Calling for an unlisted model instead of forcing XML (14399b2)
  • fix(models): a model card can declare Function Calling, and the card wins (d1ff1ad)
  • fix(presence): a member on a paired machine is not offline, it is elsewhere (8a5ab38)
  • feat(collab): an agent's task-window message is pushed now, not only readable (6a03826)

Full Changelog: v0.9.0-alpha.5...v0.9.0-beta.1

v0.9.0-alpha.5

v0.9.0-alpha.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 04 Oct 04:00

Changes since v0.9.0-alpha.4

  • chore(release): prepare v0.9.0-alpha.5 (f4fbf9c)
  • fix(relay): the group-message branch had the same lost-push bug, and a loopback subscription now says so (0c38d9d)
  • fix(relay): a task push that missed once was lost forever, and our own docs called it a design boundary (697ee63)

Full Changelog: v0.9.0-alpha.4...v0.9.0-alpha.5

v0.9.0-alpha.4

v0.9.0-alpha.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 04 Oct 03:28

Changes since v0.9.0-alpha.3

  • chore(release): prepare v0.9.0-alpha.4 (8ff5351)
  • fix(peers): make a rejected pairing token say which way it failed (f4e6090)

Full Changelog: v0.9.0-alpha.3...v0.9.0-alpha.4

v0.9.0-alpha.3

v0.9.0-alpha.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Oct 19:35

Changes since v0.9.0-alpha.2

  • chore(release): prepare v0.9.0-alpha.3 (1a483ef)
  • fix(collab): a worker joining from another machine can reach the board again (0762c40)
  • fix(approval): read the marker the way it actually arrives - a hand-built card with raw newlines (cc0f795)
  • fix(approval): read the marker the way it actually arrives - a hand-built card with raw newlines (8fd6c27)

Full Changelog: v0.9.0-alpha.2...v0.9.0-alpha.3

v0.9.0-alpha.2

v0.9.0-alpha.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Oct 12:16

Changes since v0.9.0-alpha.1

  • chore(release): prepare v0.9.0-alpha.2 (2abcce2)
  • fix(approval): an approval card is a card, and an answered one is a quiet line - never raw JSON (240f4c4)
  • fix(im): list_groups must include the groups joined on a paired machine (a2eb684)

Full Changelog: v0.9.0-alpha.1...v0.9.0-alpha.2

v0.9.0-alpha.1

v0.9.0-alpha.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Oct 11:05

Changes since v0.8.49

  • chore(release): prepare v0.9.0-alpha.1 (bac0f76)
  • fix(ci): the last Linux failure - my product fix exposed one more Windows-shaped fixture (4ff2250)
  • fix(ci): the four remaining Linux failures, from Windows-shaped fixtures (96a2d83)
  • ci(browser): give CI a real Chromium, and skip the session tests where there is none (0ad3f30)
  • feat(desktop): background updates that apply on quit (452e6dc)
  • feat(model-cards): add the OpenAI Responses protocol (openai_responses) (765b243)
  • fix(browser): say why a page is blank instead of leaving it a mystery (12b0e34)
  • revert(browser): the panel is the user own browser again - real DOM, no agent session (b7df0c0)
  • feat(browser): a real browser window (option A), shared with the agent (dbf3770)
  • fix(browser): show the page at real size, and make the view usable (46c1b50)
  • feat(browser): the panel is a view of the browser the agent drives (4b5fbfa)
  • feat(browser): browser_use — the agent gets hands on the built-in browser (9193fb5)
  • fix(terminal): talk to a Windows console shell in its own code page (49be650)
  • feat(browser): a Playwright session the agent's tools and the panel both drive (8475af3)
  • test(terminal): assert the picker's calls as they are written (29b3e1f)
  • feat(terminal): pick the shell this machine has, and type in the terminal itself (0e5e75c)
  • fix(terminal): a user's terminal is not fenced to the agent's workspace (d780667)
  • feat(agent-web): the terminal is hosted by the launcher, not the agent (ffd7ef7)
  • fix(agent-web): the rail tabs were gated behind treeOnly, and the terminal never dialled (00705b9)
  • feat(agent-web): the file rail's title row becomes 浏览器 / 终端 / 文件 tabs (bf77f1b)
  • feat(agent-web): terminal and browser get visible buttons in the tab bar (4460460)
  • fix(agent-web): put the terminal/browser entry where it is actually seen (70b79a1)
  • feat(agent-web): terminal and browser are tabs of the workspace pane (dc95aa4)
  • feat(agent-web): the right panel becomes browser / terminal / files tabs (5d23359)
  • feat(plugins): guided, step-by-step setup for the external-connection plugins (3f72492)
  • feat(relay): queue, retry and backfill a push the peer could not receive (df5c8ae)
  • fix(collab): commit the shared task-step parser the window and board import (bafb589)
  • feat(collab): the task window draws work the way the board does, and the project has a directory (3f39a50)
  • feat(cross-machine): the invite names this machine's LAN address instead of 127.0.0.1 (beed9c0)
  • docs(collab): the PM is told to split by boundary, not by volume (6efd738)
  • feat(collab): work is assigned only once the whole team has accepted (0bb7112)
  • feat(cross-machine): a restart resumes the paired machines instead of forgetting them (02575f0)
  • feat(collab): the closure is announced in the task window, and the rule leads the prompt (30b585a)
  • feat(collab): an agent's task message wakes the other members, and the prompt says who the window is for (21a6265)
  • feat(collab): assignments stop posting cards, an answered approval leaves the chat, and invitations are delivered to the agent (52939b4)
  • fix(collab): the creator is a member at once, and a gate with no card reads the board (10f16ea)
  • feat(collab): one collaboration at a time, enforced at every door in (ee16ffe)
  • feat(collab): the gates and the invitation are enforced, and the board reads as a board (caa70db)
  • test(collab): the board's peer token is read from either store (39771a3)
  • feat(collab): the task thread is the group chat's bubble, pictures included (0a11c68)
  • fix(collab): a task-window message wakes a strict agent, and the board finds its peer token (128b117)
  • feat(collab): the task window renders markdown and shows progress; the prompt draws the task-vs-group line (b029bd9)
  • fix(relay): a message from the other machine keeps its files fetchable (8a111b0)
  • feat(relay): a task-window message crosses to the agent on the other machine (b7e2908)
  • fix(cross-machine): the agent-push frame is the canonical one, and leaving a peer unsubscribes (62eb68f)
  • fix(relay): the agent no longer receives a relayed copy of its own message (1d24418)
  • fix(cross-machine): joining by invite subscribes, and a stale peer entry cannot shadow a fresh one (c559b37)
  • fix(relay): the agent-push route relays, so a server-side push reaches a paired agent (f36296a)
  • fix(relay): one file per writer, a bounded fan-out, and a secret bound to its user (058df50)
  • feat(group): mark members that joined from a paired machine (af97bb0)
  • chore(gitignore): ignore Command Code state and the _promo scratch dir (5ada945)
  • feat(cross-machine): a group joined on a peer now relays its messages home (e35aaf2)
  • fix(cross-machine): collaboration notices go to the group's owning machine (024476a)
  • docs(cross-machine): the relay the interim arrangement is standing in for (08e943c)
  • fix(cross-machine): a board joined on a peer routes back to that peer (33d7b33)
  • docs(skill): the board routing a paired peer does not get for free (70cb671)
  • fix(cross-machine): pairing a second machine no longer takes the agent off its own (f386eec)
  • fix(agent-web): a message that arrives mid-turn renders in the tool flow, live (2aa6f4a)
  • fix(boot): the cross-machine join tools are mandatory, not config-gated (66b6f7c)
  • feat(cross-machine): one string to send, and a skill so the agent drives it (5cddf45)
  • fix(ui): an invite names the gateway, not the page the browser is on (0db9ffe)
  • fix(agent-web): a mid-turn DM is labelled by its origin, not as 插话 (c04d495)
  • fix(ui): bucket token statistics in the viewer's local time (6bc1a4b)
  • Revert "fix(runner): a private message that lands mid-turn is not a 插话" (b817408)
  • fix(runner): a private message that lands mid-turn is not a 插话 (fa0c911)
  • fix(ui): a quoted private message no longer prints its marker (c1ef4fd)
  • test(invite): drop a monkeypatch line that patched nothing (25979ac)
  • test(nodes): cover the peer side of pairing end to end (d4359d1)
  • feat(ui): the pairing panel — code, waiting machines, paired machines (fb59a67)
  • feat(ui): the group page shows the invite string and the join queue (cfc3040)
  • feat(ui): invite strings and the join-request API, ready for the group panel (a02952d)
  • feat(nodes): spend the peer token where it is needed (5a3fa45)
  • feat(nodes): the peer side of pairing — paste the code, get a token (bcf0ca9)
  • feat(nodes): a paired machine gets its own scoped token (62684c1)
  • feat(invite): join a group on another machine by pasting one string (7b7eedc)
  • test(groups): case-insensitive membership-insert assertion (1f36441)
  • feat(groups): a member can see the asks, the owner decides them (446d1de)
  • feat(groups): a private group can be asked to join (b4472f0)
  • feat(nodes): bind an @ai account to the node that claimed it (d22a648)
  • fix(im): say why an @ai account could not be taken over (ea79209)
  • fix(gateway): refuse to steal an account that is in use (1fccbdd)
  • fix(launcher): no token means localhost-only, not wide open (35d8037)
  • fix(gateway): a token may only reset its own password (721332e)
  • feat(collab): the user talks in the task window too (27d1f74)
  • test(collab): repair the attachments suite after the board-only change (81ef1b2)
  • feat(im): one send tool, a task id decides group vs task (aaffc11)
  • feat(collab): keep task talk out of the group chat (1c2f619)
  • feat(chat): one markdown pipeline for group chat, plus working @mentions (9c3326e)
  • feat(collab): attach files and images to a task, from any machine (a1b0ff8)
  • fix(remote): talk to the right gateway and fetch its uploads (e417ffd)
  • docs: how to run collaborating agents on several machines (ca9eebb)
  • feat(collab): one board per group, owned by the gateway (630ac8f)
  • feat(collab): approval gates inside the task window (772fd6f)
  • docs(prompts): tell agents when to send a window card (2910afb)
  • feat(cards): cards you can answer, not just read (0499e8d)
  • feat(cards): generic, agent-sent window cards for group chat and DMs (280ce7b)
  • feat(ui): quote a message in the DM window (9e904de)
  • feat(collab): accept an invite from the card, close the loop on finish (3a7066c)
  • feat(ui): collaboration cards open a task window (e9e2888)
  • feat(collab): agents talk in a task-scoped channel (5526ccc)
  • feat(collab): collaboration-task card protocol and participant state (822ee6f)
  • feat(ui): attachments and an always-visible footer in the DM window (00c56ec)
  • feat(ui): show paths and line deltas for Code's changed files (e7f81a5)
  • feat(ui): one footer action set for both rails (59873c8)
  • fix(agent): keep one event-notification block in the request (7c67be5)
  • fix(plugins): declare sensevoice's bundled ffmpeg again (f498294)
  • fix(agent): deliver each inbound event exactly once (fae1341)
  • fix(agent): drop a duplicate im.send_message inside a short window (76871b2)
  • feat(ui): make the rail footer fit the layout it sits in (57c3c35)
  • fix(ui): give the DM window's drawer a real project and status (8dff7bb)
  • refactor(ui): make chatting a single surface, not two (b5b8ffc)
  • fix(ui): give the DM window the detail drawer back (dfbca80)
  • refactor(ui): point the 聊天 switch at the contacts shell (47c756b)
  • feat(ui): page a long DM thread instead of loading it whole (b9647e5)
  • perf(ui): make the DM window live off the websocket, not the poll (51a4910)
  • feat(ui): search the DM history in the chat layout's detail drawer (050268a)
  • feat(ui): talk to an agent over the DM channel in the chat layout (e3f50ea)
  • feat(ui): bring the join/create group panel into the rail (0945630)
  • style(ui): give the group chat the chat layout's stage padding (7e1724d)
  • feat(ui): one contacts rail for both group chat and 1:1 chat (9a8066c)
  • feat(gateway): let /direct-messages take a contact and a keyword (a715b96)
  • feat(ui): drop the tab strip from the chat layout (01b2d1a)
  • feat(ui): make chatting its own whole-page layout, bubbles and all (e11f3f5)
  • feat(ui): put the input split and cache share on t...
Read more

v0.8.49

Choose a tag to compare

@github-actions github-actions released this 29 Sep 12:18

Mostly a reliability cycle driven by three field reports from a pip-installed
deployment: plugin services that crashed or stalled at boot, a reranker whose
1.2GB of weights could not be downloaded or found, model-download status files
that collided across processes, and a built-in ASR card that was never shipped
in the wheel. Two long-standing invisible-config gaps went with them — a
downloaded model and an edited voice setting both now reach the running
service/agent without a restart.

Added

  • Test builds (alpha / beta / rc) are now installable the way a real user
    installs OpenSquad.
    Both release workflows used to exclude those tags, so
    tagging v0.8.49-beta.1 only built desktop installers — nothing reached PyPI
    or npm, and the Release that was created was not flagged pre-release (i.e.
    the update check would have offered it to stable users). Every v* tag now
    runs the full pipeline: the PyPI upload carries the PEP 440 spelling
    (0.8.49b1, still invisible to pip install opensquad), npm publishes under
    the next dist-tag (npm install -g opensquad-ai@next), Docker is skipped so
    :latest cannot move, and the GitHub Release is flagged prerelease. Testers
    install with pip install --pre opensquad==0.8.49b1; stable users keep
    getting the last stable release from every channel. The tag↔version pair is
    validated up front by scripts/sync_version.py --check-tag.
  • The model-provider list can be refreshed, and a configured provider's API key
    can be replaced.
    The "connect provider" dialog offered about a dozen vendors
    while the catalog holds 225 providers / 8292 models: a v1 localStorage copy
    won over the backend, the in-memory copy had no TTL, and the refresh button had
    been deleted (its i18n keys were still in the bundle, unused) — so a stale
    short list could never heal. There is a refresh action again, and the dialog
    now states the provider count, where the list came from and whether it is
    stale. Each configured vendor gets a "change API key" action that rewrites
    api_key on the vendor's existing model cards in place (merge semantics — the
    parameters a user edited by hand survive) instead of asking them to edit JSON.
    On the backend, a failed upstream refresh no longer quietly keeps an old disk
    cache: after 7 days without a successful fetch the bundled catalog is preferred
    and the response says so (source, cache_age_seconds, cache_stale in
    meta).

Changed

  • The whisper plugin is no longer shipped. Its declared dependency
    openai-whisper was never what provided the whisper import: that name came
    from whisper 1.1.10, an unrelated round-robin-database package, whose import
    raises TypeError: argument of type 'NoneType' is not iterable. Installing
    openai-whisper on top would not have fixed it either — both distributions
    claim the same top-level module. So the launcher's dependency self-check
    reported the dependency missing on every start and refused to start the
    service: a plugin nobody could use, surfacing as a service that would not run.
    The plugin, its admin panel (plugin-views/whisper/) and its tests are gone.
    The ASR surface is unchanged — sensevoice is still the built-in ASR card,
    services.whisper_url and the builtin_service: whisper handling are
    untouched, and a Whisper-compatible service you run yourself on that port
    still works.

Fixed

  • Plugin services crash-looped on a pip install with ModuleNotFoundError: No module named 'pydantic_core._pydantic_core'. external_api, feishu and
    telegram (adapter + config), the feishu/telegram send_tools,
    websearch/websearch.py and plugins/plugin_manager.py put a computed project
    root at the front of sys.path. In a pip layout that root is a
    site-packages (…/site-packages/plugins/external_api/adapter.py → three
    levels up), and plugin services are executed by the bundled Agent Python (3.11)
    even when the tree was installed by 3.12 — so insert(0) shadowed the runtime's
    own compiled packages with cp312 binaries, and pydantic_core/__init__.py then
    could not find its own _pydantic_core extension. They all append now, which
    keeps plugins.* / opensquad importable without giving them priority.
    Reproduced against the real bundled 3.11 runtime with a 3.12-tagged
    pydantic_core tree: exactly the reported error with insert(0), clean import
    with append.
  • Every plugin service waited ~66s at boot because of a dependency no running
    service used.
    The startup batch answered "is this declared pip dependency
    installed?" by importing it in the plugin interpreter. A cold
    import lark_oapi measures 10-13s and blew the 60s probe budget under startup
    load, and the batch is a global gate (_plugin_deps_ready), so one disabled
    feishu's dependency kept websearch, sensevoice and the rest in "Starting…" while
    the agent's calls to 127.0.0.1:9001 were refused. The batch now answers from
    importlib.metadata — one cached subprocess for the whole run, an installed
    distribution is "present" with no import — and no longer pre-installs
    dependencies for services that are disabled or not set to auto-start; those are
    installed on demand when the user starts the service. Measured here: 29 light
    dependencies went from 26.3s to 3.6s with identical verdicts. The import probe
    stays as the fallback for a dependency that is not installed (it fails fast),
    and the per-service check keeps using it.
  • The reranker weights could not finish downloading through the mirror, and a
    stale "Download failed" outlived the download.
    The legacy huggingface_hub
    fallback set HF_ENDPOINT and HF_HUB_DISABLE_XET inside the download thread
    — i.e. after import huggingface_hub, which freezes both into
    huggingface_hub.constants at its own import time (verified on
    huggingface_hub 1.26.0: they keep their old values whatever the environment
    says later). So the download went to huggingface.co rather than hf-mirror.com,
    and with Xet enabled the 1.19GB model.safetensors died at ~79% on a 401 from
    cas-server.xethub.hf.co, which the mirror does not proxy. Both variables are
    now set before the import. Separately, once the weights had been completed by
    another path nothing ever cleared the persisted state: error, so the card kept
    showing "Download failed: HTTP 502" for a model that was already loaded —
    reranker_model_store.get_status() now reconciles a complete model to ready
    instead of reporting a failure that no longer applies.
  • Plugin services were told their dependencies were not installed when they
    were, so websearch / sensevoice / feishu / telegram refused to start.
    The
    launcher decides by importing each declared pip dependency in the plugin
    interpreter. Two things made that decision wrong. (1) The probe guessed the
    import name from the distribution name, so python-telegram-bot was probed as
    python_telegram_bot — a module that has never existed under any version. The
    package was reinstalled on every startup and reported missing every time, the
    circuit breaker opened, and the service never ran; the live launcher log shows
    six such failures. The import name now comes from the interpreter's own
    metadata (importlib.metadata.packages_distributions(), re-read after every
    install), with the static map as an explicit override and the dash-to-underscore
    guess only as a last resort. (2) The probe budget was 15 s and a timeout was
    read as "missing". Cold imports are not fast — measured on the bundled Agent
    Python, lark_oapi takes 10-13 s and torch ~4 s — and the box is busiest
    exactly when the launcher starts a dozen services, so a healthy dependency
    timed out and blocked its service. Probes now have 60 s, verified modules are
    cached (a positive cannot become a negative without an uninstall), and a probe
    that still runs out of time is reported as inconclusive: it is installed to
    be safe and the service is started, with a warning, instead of being refused.
    A real ModuleNotFoundError still blocks the start, as before. Separately,
    plugin children were handed the launcher's own PYTHONPATH (its 3.12 package
    tree) although they run on a different interpreter; _build_child_process_env
    now takes the child's interpreter and clears PYTHONPATH whenever it differs
    from the launcher's, mirroring what the frozen branch already did. Agent
    children are unaffected — they resolve to the launcher's interpreter.
  • An agent boot could freeze the whole process stack, with nothing in any log
    saying why.
    opensquad start pipes each child's stderr and only read it
    after the child exited. A pipe holds ~4 KiB on Windows; once it filled, the
    child's next write blocked forever — and here that write was a log call made
    while holding logging's handler lock. Every thread that logs then queued
    behind the lock, so the launcher never reached the line that starts the
    log-forwarding thread for the agent it had just spawned, so the agent's own
    stdout pipe was never drained, so the agent's event loop froze inside its own
    log write: agents never registered and the UI showed "reconnecting" until the
    tree was killed. Three independent guards now break the chain, any one of
    which is sufficient: (1) opensquad start drains every child's stderr from
    the moment it is spawned, via StreamTail — a daemon reader with a bounded
    ring buffer, whose tail is what gets printed if the child dies; (2) the
    launcher starts a child's log-forwarding thread before logging anything
    about that child (agent and plugin-service paths both); (3) the console copy
    of every logger is a bounded queue drained by a daemon thread
    (log_setup.nonblocking_console_handler) whose console writes go through a
    private duplicate of the file descriptor, so a stalled console drops records
    instead of blocking the ...
Read more

v0.8.49-alpha.5

v0.8.49-alpha.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Sep 12:08

Changes since v0.8.49-alpha.4

  • chore(release): prepare v0.8.49-alpha.5 (8ccefb9)
  • feat(plugins): pick up a downloaded model and edited voice settings live (0b3d838)
  • fix(websearch): load the reranker from the workspace, and migrate old weights (87559a9)

Full Changelog: v0.8.49-alpha.4...v0.8.49-alpha.5