Repository navigation
Releases: ThinkFlowLab/FlyBot
Releases · ThinkFlowLab/FlyBot
Release list
FlyBot v0.2.11
Added
- Settings has a Workflows page: the factory presets, your own definitions as
workflows/<id>.jsonin the workspace, and which one this project uses. A definition is edited as a node table — id, name, role, inputs, outputs, guards, refusal — with the flowchart drawn above it from the same table and each guard showing the sentence it would print right now, or saying the fact could not be read. Customize copies a preset into a new file and never edits the shipped one, and saving an unedited definition writes identical bytes (#1272, step 3).
Changed
- A finished task card leaves the task board's active list, and the Done column shows the newest 20 archived cards; a group's overall card now completes when its last child finishes, is cancelled by a rejected approval, or fails because its Bot was deleted, and a Bot's board asks for that Bot's own finished cards (#1252).
- Feishu (Lark) is now connected on Settings → Connectors (左下角「连接应用」), not through the Secrets page or a config file. The one Feishu row offers two modes — a custom-bot webhook (outbound only, as before) or a self-built app with its long connection — and renders one field per credential: the App ID is kept in the record because it is not a secret, the App Secret only in
secrets.json. App mode proves the pair with one tenant token, opens the socket from the row, answers a message back into the same Feishu chat as the app, and offersChoose Bots; the row also carries 允许的飞书用户 — nobody by default, the sender who was turned away listed with Allow, and a field to paste anopen_id— so a stranger's message never spends the owner's tokens.Disconnectcloses the socket and clears both credentials. A replayedevent_idstill lands once, and a half-saved pair dials nothing and says so. The app pair is no longer read fromFEISHU_APP_IDon the Secrets page, andfeishuis now a reserved capability name. (#1266, #1292)
Fixed
- Claiming or assigning a card in a group now posts the one public line the room notice asks for —
接了 #1253,第一步是 …— in the claiming member's voice. The line goes into the room log without opening a round or waking anyone, takes the card's own note as the first step, and is not repeated for the same card within 30 minutes; progress stays on the card. (#1253)
FlyBot v0.2.10
Added
- A group's board can now be a pull queue: the lead enqueues an item for a role (
track_taskswithqueueandrole, no assignee), an idle member of that role claims it withclaimand one item is handed to exactly one member, and a member that cannot finish returns it with a one-line reason and aretryAt; the room board shows one line per role with its depth and oldest wait (#1247). - The delivery board now reads the repository this instance actually works in, and the header says which one it read and where the name came from:
delivery.repoin the instance settings first, then the workspace's gitorigin, then the package's own repository as a labelled default (#1250). An instance with none of the three refuses withno repository — set delivery.repoinstead of showing another repository's numbers. - Feishu (Lark) connector, phase 1: paste a group's custom-bot webhook URL on Settings → Connectors and this instance can post a text message into that group. The row is a sender — no Bot tools, no per-Bot switch — it proves the webhook with one test message when you connect, shows the reason on the row when Feishu refuses, and sends on demand with the existing retry policy (a rate limit is retried, a refused signature is not). The URL is kept as a secret and never appears in
state.json, the view or a log line. (#1263) - The Delivery board and the task board now draw their cards through one component, and a card says which side it came from: a repository card is keyed
owner/name#number— one key for an issue and the pull request that closes it — and a task cardtask:<id>(#1265, first step). - The Delivery board can read more than one repository:
delivery.reposnames a list (the olderdelivery.repostill works as a list of one), every name resolves with its own source, and the page draws one block per repository with its own state. A repository that cannot be read leaves the others on a partial board instead of emptying it (#1265, step 4). - Feishu (Lark) connector, phase 2: a message the owner types in Feishu can start a turn here over the long connection. The app pair
FEISHU_APP_ID/FEISHU_APP_SECRETis saved throughsecret-setand stays out ofstate.jsonand the view; the connection is off untilfeishu.enabledis true, dials only from an app pair that is complete, and only the owner's messages start a turn. A replayedevent_idlands once — the id is remembered only after the message reached its chat, so a handover that failed is retried rather than dropped — the message arrives through the same chat seam a friend's Bot uses, and the answer leaves again on the sameFEISHU_WEBHOOKthe connector already posts with, so one chat has one way in and one way out. (#1266)
Changed
- A group turn now shows who is working: one line per member that holds an open card on that room's board, with the card number, column, progress and how long it has been open, and an explicit
idle (no open card)line for a member that holds none — read from the board at the turn, with nothing new written and no one having to post a status (#1213). - A Bot condenses its conversation earlier: the steward's check line is now 45% of the model's usable window instead of 80%. Every call re-sends the fixed block and the conversation is most of each prompt, so condensing sooner cuts the prompt of a long turn by about a quarter; the cost is that a checkpoint arrives sooner and a condensed conversation keeps less detail.
checkRatiostill overrides it in the profile (#1233). - A round-trip now exists to bring back a fact the room did not have: independent reads go in one call, a fact already read is quoted with its anchor instead of fetched again, and the same message carries the tip sha, the CI state and the files read, because every avoidable round-trip re-sends the whole context (#796, #1235).
- A group chat now carries neither the composio tools nor the four
schedule_*tools: eight tool schemas, about 20.4k characters re-sent on every call of every group turn, that a room round does not use. A Bot reaches an outside app and manages its schedules in its own chat, and the refusal says so. The github tools and every built-in tool stay in the group, and a Bot's own chat is unchanged (#1237). - A connector call refused in a group room now says the tool is not available there, instead of pointing the caller at its own chat — a friend's Bot has no composio tools in its chat either (#1271).
Fixed
- The delivery header prints its "as of" time once while today is still running: the partial-day hint now follows the single time instead of repeating it from a second clock rendering, in both English and Chinese. A day whose runs the host could only count by their total now shows the same red share in the header row as the point drawn on the line beside it, instead of a dash next to a dot (#1244).
- A part handoff keeps a repeating task exactly where it was: the same id, the same delivery history and the same instant it was already due, instead of re-anchoring the next run to the handoff and hiding the move in a reset history. (#1245)
- The delivery board reads
delivery.repohowever the instance wrote it — nested (delivery: { repo: … }), as a literal dotted key, or flat (deliveryRepo) — instead of only the flat one, so the setting the page and the hint name really names the repository (#1258). A workspace that is a linkedgit worktreenow resolves itsoriginthrough the worktree'scommondirrather than falling back to the package default, and a repository name with a.or..segment is refused instead of being turned into/repos/../../issues. - A line sent into a group chat while a member is speaking now reaches that member inside its turn: the round watches the same Session queue it drains, logs the line at once and steers it into the speaker's running turn with a header saying it arrived after the turn started, so the member's next step answers it instead of reading it after a long turn as if it had been there all along. The line leaves the relay queue, so it is neither logged nor answered twice. The one exception is an explicit stop (停, 停下, 停止, 别做了, 不要做了, stop, stop it): the speaking slot is marked stopped by the user, its turn is cancelled and its answer is not posted, and the line stays queued to be answered as a fresh line by the room (#1267).
- A Bot whose tool list has to be rebuilt for a group room no longer ends up with every tool listed when one name in the mask cannot be restricted: the host keeps the narrowing it can enforce and warns, instead of dropping the whole mask after lifting it (#1271).
- A turn no longer stays open because making room before its next step never returns: the pre-step compaction now carries a deadline (
COMPACT_DEADLINE_MS,config.compactDeadlineMs, five minutes) beside the turn's own signal, and a summary request still retrying when it passes ends that turn with a warning instead of parking it —turn/endruns, the queued messages stay for the next turn, andstop_botis no longer the only way out (#1285).
FlyBot v0.2.9
Added
- The delivery board draws the last seven days instead of two numbers per metric. New issues, Closed issues, New pull requests, Merged pull requests and the CI red share all get a 168×28 line beside the existing Today / 7 days columns, where a count's week number is a Σ total and the CI share, a ratio, never takes one. Every point carries a title, a day that could not be read breaks the line instead of reading as zero, and a total over an incomplete window wears a star the footnote explains. Below 900px the line moves under its row, and a snapshot without the series says so in one line instead of drawing empty charts. (#1212)
- The delivery board's numbers now come with the seven days behind them:
metrics.dailycarries one entry per Asia/Shanghai day, oldest first and today last, each with the issues opened and closed, the pull requests opened and merged, and the CI share of the runs that actually counted that day. A day the host could not read isnullrather than0, so a gap stays a gap instead of reading as a quiet day, and a week that could not be read at all leaves the whole seriesnullinstead of drawing seven zeros. The seven-day CI share is read over the same week its own row covers — it used to be a week out of step, because the CI windows were walked backwards from the start of the week. (#1222)
Changed
- A claimed card now gets one line in the room —
Claimed #1234 — first step: …— so a member who is working is not read as idle; progress still lives on the task board and nothing else is narrated (#1213).
Fixed
- A delivery board that cannot read part of GitHub now says which part, why, and what to do about it, instead of looking like an instance that was never connected: the first load counts its seconds, a failed refresh keeps the old board with the reason beside it, a round that runs past 90 seconds lands with what it has, and a CI number that could not be read is unknown rather than 0. A 诊断 button and
core/scripts/delivery-probe.mjsprint the same five endpoint statuses, and after a failure the next attempt backs off 10 s, 30 s, then two minutes without dropping the reason. (#1211) - A window that only reads the Bot team — because another dsh holds it — can open the delivery board and its probe again instead of being told
bot/read-only; both are reads, and every write in that window is still refused. (#1225) - The failure screen now counts down instead of freezing:
Retrying in 120 sdrops a second at a time while a retry is really scheduled, and a fresh or loading board starts no timer at all. The rate-limit and timeout hints also say who retries —while this page is open it retries on its own— because the host keeps no timer and a closed page retries nothing. (#1227)
FlyBot v0.2.8
Added
- Each Bot keeps a task list it updates from the conversation, and a board shows Not started, In progress and Done for that Bot, a child Bot, and — for a group admin — the tasks member Bots are tracking in the group (#1058).
- Settings → Bot Social now lists friends and links each of your Bots to People with access, so sharing and revoke are findable without opening each Bot first. Add friend stays on the + menu, and the same panel is also on that page (#1074).
- A group's Tasks board shows one overall task and the per-Bot subtasks under it. Bots record that plan with track_tasks, continue the highest-priority unfinished task, and ask the user or the group when a task is waiting on an answer (#1099).
- A Bot with unfinished conversation tasks keeps working without a new message. The host starts a continue turn for the next task, in that Bot's own chat or in the group session that owns the subtask, and stops when the work is done or waiting on an answer. In a group, members with their own subtasks are woken in the same pass (#1109).
- A task that names the issue or PR it came from now says where your answer goes, and the Bot writes it there itself (#1154): the result card shows a Comment goes to … link, accepting or sending back leaves one comment for the Bot to post with its own GitHub connector, and a comment that could not be posted says so once on the card instead of failing silently.
- The team can set quiet hours — a window (off by default) that a new or edited scheduled task's time must fall outside, checked when it is saved rather than at delivery: a task inside the window is refused with a sentence naming when it ends, and nothing is created or moved. Repeat and cron tasks are still allowed and the page says they may run inside the window, because DSH decides when they fire (#1166).
- The task board dialog gains a Delivery view (#1173): one read-only snapshot of the repository's GitHub issues, pull requests and CI runs, in five columns — original issue, ready to start, in progress, in review, and done today — with a verdict/CI badge per pull request and a header that reports today against the last 7 days. It is repository-wide, so the per-Bot rail does not appear, and it never writes to GitHub: the host serves a cached snapshot built only from
GETrequests, and the token stays host-side. The view replaces the board with one honest line when no GitHub connection is set up. - A CI scan (
.github/scripts/check-mojibake.mjs) now fails a pull request whose tracked text files contain U+FFFD — the three bytesEF BF BDa lossy decode writes in place of every character it could not read — and names each file with its occurrence and line counts, so the fix reads as "regenerate", not "retype the text". Binary assets are skipped by content (a NUL byte in the first 8 KiB), which is the rule that makes the gate usable at all: decoding the tree instead reports 22 binary files and ~2,065,422 replacement characters on a cleanmain.AGENTS.mdnow says why a file rewritten from an outside source, or regenerated, is re-read as bytes and re-encoded by its generator. See #1188. - A Bot can be spoken for by a supervised external process in rooms (#198):
create_bottakes anexternalblock (driver: subprocess, an argv array, optionalcwdandenv), androom-participant.jsroutes that member to a new driver while every other member keeps its DSH Session. The process is started per room and Bot, answers one JSON line per turn, and its start, clean exit, crash and timeout are explicit states — a crash is posted as the Bot's own line in the room, and a turn that reaches a dead process ends with a failure instead of silence. - A finished task can now be accepted or sent back right in the inbox (#401): the result card shows the Bot's evidence links and model, Accept closes the task as done, and Send back asks for one line about what should change and returns the same task to the Bot.
Changed
- Removing one of a friend's Bots now says which link keeps the friend in the list: the question names the Bots of theirs that are still shared ("Bob stays a friend: still shared through Writer."), instead of only saying the owner is not unfriended and leaving the row unexplained (#1001).
- The new-chat menu offers joining and starting as two rows, so one label no longer names both: Join by number or link carries the number and the invite link, and Start a face-to-face room opens the same screen under its own name without the link and scan fields, because those are ways into a group that already exists (#1021).
- A fresh 6-digit code no longer answers with an empty-result line. When nobody is waiting, the dialog's primary line is Start a new room with … and Start a new room is the first button; the note under it still says to ask for an invite link if that room already opened (#1053).
- A face-room line the server sends without an
event_idis still keyed by the line itself, but the key now carries a bounded digest of the text instead of the whole sentence:seenlives in the owner's settings, so on a server that stamps no ids it would otherwise fill up with a copy of the conversation (#1062). The key stays stable across polls, so the duplicate#1045fixed does not come back. - A
Verdict:now counts only when the workstation that claims to have written it signed it: each reviewing workstation holds one Ed25519 private key, its public key sits in.github/agent/signers.json, and the gates verifySigned-by:/Nonce:/Signature:offline over the pull request, the full tip SHA, the verdict and the workstation — so a PASS written in a Reviewer's name on the shared account is no longer a conclusion, it is listed as unverified (#1110). - The social panel's group and dock surfaces are usable at phone width: below 640 px its buttons and inputs (including the invite-preview and waiting-room footers) are 40 px tall, its row controls at least 32 px, a long unbroken run in a held reply wraps inside its card, the dock clears the home indicator, and the phone block now closes the stylesheet so no later rule of equal specificity can override it (#1147, #1149).
- A group picks one task lead to divide the board. That Bot, or the admin when none is picked, assigns each part; the card is claimed and the assignee starts it in the group without waiting for a confirmation. A question stays in the group (#1182).
- The merge gate now refuses a tip whose only review is a comment from the pull request author's own GitHub account. A PASS from that login counts when the comment header carries
Attestation: <full sha> role=<Bot>for this tip, the role matchesWritten-by, and the role is not an Owner of the pull request. A review posted by a different GitHub account is unchanged (#644).
Fixed
- Accepting a friend's invite again now shares your Bot back with them (#1000): the two of you can message each other right away instead of one direction staying silently absent until they share again.
- Coming back to a group nobody has spoken in yet shows the invite link the room already has, instead of minting another one on every visit — the strip now reads the room's links first, as the Members card does, counts a dated link live until its deadline passes, and never keeps the previous room's link when the dock moves to another group (#1019).
- Re-accepting an invite from a Bot Social server whose invite info names no
bot_idnow grants to the peer idlist_friendslists instead of afriend_homethe server never receives (#1024): the pair becomes two-way again, and when no listed friend names that home the accept no longer claims a share that did not happen. - A window that showed the Bot team read-only because another dsh held
state.locknow takes the lock on its next write once that window closes, instead of staying read-only until astateread happened to refresh it (#1027). New work that changes the state outside a request belongs inrt.mutate(fn), which follows what the other host left before applying the change, so neither side's work is lost. - The Add friend result no longer stacks a second "Your" on a Bot whose owner already named it that way — a Bot called
Your Botreads as "Your Bot is shared back, so they can reach you too." instead of "Your Your Bot …" (#1037). - The group strip no longer offers Invite by link before it has read the room's own links, so a fast click cannot mint a second ticket the panel was about to discover, and a chat that draws no strip (a face room, a Bot chat, a chat with turns) no longer issues that read at all (#1038).
- A turn that dies before writing anything is no longer invisible: when a model call is refused (a provider budget cap, a 429), the chat shows one line naming the provider's own error above the composer, and a run of them says how many turns in a row died, so a quota outage reads as an outage instead of a Bot that chose to stay silent. A turn that already wrote text adds no...
FlyBot v0.2.7
Added
- A group made with Create group chat now shows its own strip above the composer while it has not been used yet — the group's name, who is in it, and Invite by link — instead of only a sidebar row; the first message hands the top of the chat back to the session header, so the two never appear together (#998).
Fixed
- A room number is now exactly the six digits stored for a room: a stored value that is not one (
1234567) is no number at all — cut to six it would name a different room — so the approval card, the waiting card and the room title fall back to the room's own name or to the code you typed. Two live rooms with no number and the same name no longer share one label: each card adds a short, fixed tag from the room id (the room Loft · a3f1), the same room keeping its tag on every render (#1005). - People with access now lists one row per Bot instead of one per person, named
Bot (Owner)and carrying that Bot's handle, so a friend who runs two Bots shows two rows and each Revoke takes back only the row it sits on — before this, one Revoke removed every grant that friend had (#995). - A six-digit number whose room has already opened no longer answers "Nobody is using 482913 yet." — the prompt now says Nobody is waiting under 482913., points a late joiner at an invite link when the room already opened, and its button reads Start a new room, so nobody is led into opening a second room while their friend waits in the first (#999).
FlyBot v0.2.6
Three entries merged after the cut (#995, #999, #1005) are listed in the v0.2.7 notes; their code is included here.
Added
- Pasting or scanning a room invite now shows the room before anyone joins it: the join dialog asks the host for the preview and draws the room's name, its lead, how many people are in it, and how long the link lasts, with Join and Clear on that card. The name is cleaned before it is shown; a preview that cannot be fetched (a 404 or a timeout) drops the card and joins the way it did before, and an older Bot Social says invites need a newer server instead of trying a route it does not serve (#798).
- A PASS posted as a review body whose
Written-by:names someone outsidewritersin.github/agent/review-policy.jsonwas silent:#797made thepasslabel job say so for a comment, but a review body fired no job at all, so the reviewer saw no label and no note whilereview-gateandmerge-gateboth refused the conclusion. A separate read-only workflow (permissions: contents: read, the script frommain, no secrets) now prints one warning and the same sentence in the run summary, naming the writer, the tip and the file to edit, and stays green (#801). - The sidebar gets an
Archived (N)section: archived Bots still stay out of the default Bot list and out of the tree, but one collapsed list opens their chats like any other row, and saysarchived · merged into <name>for a Bot a merge replaced (#810). - Two
create_child_botcalls that are in flight at the same time and belong to the same project or domain become one group: a single lead for that group, reused when it exists, with both new Bots under it, instead of each landing in whatever broader group it fits (#810). - Merging on the code fork is now refused by GitHub, not by a script somebody remembered to run: a new required check,
enqueue-guard, is written onto every pull request head from the same decisionnode .github/scripts/enqueue-guard.mjs --pr <n>prints — ahold, a tip with no PASS naming it, a PASS that lists noRan:, a missingagent-pror a body that names no issue all turn it red, and it flips green without a new commit when the label or the conclusion arrives..github/agent/merge-gate-ruleset.jsonis the ruleset that requires it next totests-ok(branchrefs/heads/main,bypass_actors: [], strict checks off so every open pull request does not have to rebase);node .github/scripts/gate-ruleset.mjs --checkreports the live state and--rollbackremoves it (#812). - The upstream fast-forward is written down and checked instead of remembered (#815).
docs/release-runbook.mdrecords the one-window-per-version sequence — the release commit as the tag target, the dry-run's five gates,PUTto disable the ruleset, aforce: falsefast-forward, the restore in afinallyand the read-back that proves it, then upstream's own release with the same four assets — with every read and write as anodesnippet, because the box that runs it has nocurland nojq.node .github/scripts/upstream-sync.mjs --checkanswers the two questions the window needs, read-only: bothmainSHAs withahead_by/behind_by, and the ruleset's enforcement with its bypass list; it exits 0 only when the two mains are the same commit and the ruleset isactivewith no bypass actor, and it exits nonzero naming the condition that failed.--commitsnames the commits the fast-forward would carry. - A friend's Bot that is in a group now answers in that group: a line that names it travels on the room's share envelope carrying the handles it calls, the receiving FlyBot opens one turn for the Bot those handles name, and the answer goes back as a line of the same room instead of landing in the friend's Bot's own chat (#825). Bots can answer each other's lines in the room up to the room's hop limit (3 by default) and a line is answered once, so two Bots cannot call each other forever; an answer the friend's owner has not approved yet, or refuses, still says so in the group in one line. The old "sent — its replies appear in its own chat" receipt is gone, because it is no longer true for a Bot in the room.
- A room's invite link can now be used as an invitation: the link carries a ticket, the inviter's FlyBot answers with the room it belongs to, and only the accepting side is admitted, one home per ticket (#852). Nothing in the invitation can create a room, seat a member, or use up the ticket, and a refused seat gives the ticket back. One field now reads both link shapes — this server's
/r/<ticket>and a room on another FlyBot,/r/@handle/<ticket>— and a link into another FlyBot's room is answered as itself ("That link opens a room on another FlyBot") instead of being sent to Bot Social as a ticket it never issued. - A local group’s Members card has an Invite by link row: make a link, show it as a QR code, copy it, watch its countdown, or withdraw it. Someone who is not a friend can paste or scan that link in the Face-to-face dialog, see the group first, and join it with the Bots they pick.
- A room's invite link now has the endpoints the Members card works through: read the live link, mint one, withdraw it, and knock at a link someone else handed you — first for the preview, then again with
accept: truewhen Join is clicked (#876). The knock leaves from one of your own Bots and names the Bot to knock on from the link, never from the request body; a link into a room on another FlyBot, a bare ticket, and a ticket outside the 16–64 character shape are each refused in one sentence and send nothing. - FlyBot now talks to Bot Social's record of a room link (#883): making a link for a room that lives in your FlyBot registers it with Bot Social, a guest's Join claims it there, and the room's own messages travel between your Bot and the guest's Bots under that room only. Anything else to a Bot that is not your friend — a plain message, another room, another Bot — is still refused.
- A room that lives in your FlyBot can now seat someone who is not your friend: a link Bot Social records admits one home with the Bots it brings, all of them in one step, after this FlyBot reads the claim back from the server (#884, #878). A room can have several links live at once, one per person invited, and each is withdrawn on its own. A guest's Bots get the room's lines and nothing else of yours — no friendship and no sidebar entry — and lose the room when its link is withdrawn, they leave, or the room is deleted.
- The merge gate now reports a silent revert: a pull request built on an old copy of
mainre-uploads whole files, so workmainmerged after the branch was cut disappears even though everything is green —#887deleted#869's 84 test lines the same hour, and#907deleted#909's whole fix 40 minutes after it merged, bothmergeablewithtests-okpassing (#914). It compares the linesmainadded after the merge base with the lines the pull request deletes frommainand writes one warning per affected file plus a total line into themerge-gatejob summary. Report only: it never comments, labels, blocks or merges, and a pull request already rebased ontomainreports nothing.
Changed
Live E2Eno longer fails every night when the repository secretDEEPSEEK_API_KEYis not configured: the steps that need the key are skipped and the run warns instead, and its summary says plainly that nothing ran and nothing was measured. A failure inside the suite still fails the run (#794).AGENTS.mdno longer gives a Main Bot a removal right it has no tool for: it getscreate_botandupdate_bot,stop_botonly ends a running turn, and deleting a Bot stays the person's action in the UI (delete-bot). The team paragraph now says that, and points at #638 for the open decision on a retire tool (#841).- A group's invite link no longer expires and is no longer single-use: anyone you give it to can join with their Bot until you withdraw it, and withdrawing stops new joins without removing people who already joined. The Members card's Invite by link section lines up with the card, the note on who leads the group sits with the members again, the + menu entry reads "Join or create by number or link", and a group joined by link shows its own name in the sidebar.
- Group chats use fewer tokens per turn: a member's group conversation now reads the group's notice, members and rules from its standing instructions instead of receiving them again with every turn, and a turn after a notice edit says only that the notice changed (#894).
Fixed
- The main view no longer jumps to the Main Bot when the roster drops a Bot whose chat is still open (#261):
landingPartasks the session list whether that conversation exists, so a revoked share or a removed Bot leaves the open chat (or its blank chat) in place, while a New...
FlyBot v0.2.5
Added
- A face-to-face room now shows its 6-digit number as a QR code, and FlyBot can scan one instead of typing: the waiting dialog and the room panel draw
FLYBOT-plus the number locally (byte mode, error correction M, version 1 — every module computed in the browser, no request and no image service), the code disappears when joining ends or the room opens, a QR from anywhere else is refused in one sentence, and a scan ends in the samesocial/face-jointhe number field uses. A browser withoutBarcodeDetectorsays so before it asks for the camera, a refused camera gets one sentence, and typing the number keeps working either way (#773). An open room also offers Invite by link (data-flybot=face-invite): Create link asks Bot Social for one and draws the link itself as the QR, so a phone camera opens the room and FlyBot's scan joins with the same ticket; the card shows the link, Copy link, the deadline, and Withdraw link. Pasting or scanning accepts onlyhttps://…/r/<ticket>, and each refusal — expired, withdrawn, full, closed, already in it — is one sentence. The card stays off on a server without theface_room_invitesfeature, which says Invites need a newer Bot Social. The 6-digit number still works. core/dev/token-report.mjsreads the usage ledger into the numbers #796 asks for: per Bot, per Session and per day, with fresh input, cache read, cache write and their sum (prompt) as separate columns, per-call prompt percentiles, the top Sessions by prompt, and each Session's prompt growth between consecutive calls, splitting at a compaction or a new part instead of counting it as a negative delta. It opens the ledger read-only, defaults--dbto$DSH_HOME/usage.sqlite, and exits non-zero naming the path when it is missing. The first report is docs/reports/2026-10-23-token-spend.md (#799).
Fixed
create_teamnow refuses a team before it writes anything when the lead's name or a member's name is already taken anywhere on this home: it names every clashing name and the path of the Bot that holds it (#764). Previously it created the lead first, so a member whose name was taken was dropped and the team was left half-built with a childless<team> Lead.- A PASS whose
Written-by:names someone outsidewritersin.github/agent/review-policy.jsonis refused byreview-gateandmerge-gate, but until now only a log line said so: the reviewer assumed a conclusion that counted, nobody enqueued, and the pull request waited with every check green. Thepasslabel job now leaves one comment per tip naming the author, the tip and the file to edit, and still adds no label for it (#767). - An image that no model can read now arrives as a placeholder that names its file on disk (
<home>/attachments/v1/objects/<first two>/<sha256>), so a Bot can pick the bytes up with its own file tools instead of asking the user to describe the picture. The path is given only when the file is really there, and only when the file sandbox can open it; a chat and a group relay share one placeholder (#779). - Handing the Main role to another Bot now moves the tree's root with it on the next turn: exactly one root remains, the previous Chief is demoted and placed in the tree, and the new Main gets the tree tools instead of waiting for a restart (#748). A tree that already sits at
maxDepthstays inside it on the swap: the demoted root's branch is re-attached under the new root instead of being placed one level too deep.
FlyBot v0.2.4
Added
- A plugin the host allows to make a model call can now also get its own default model:
flybot.modelFor({ '<plugin>': '<provider/model>' }) is used when the call does not name one, so a host can keep a cheap plugin cheap while the rest of the team stays on the team default. A plugin the host did not name is unchanged, a model the call names still wins, and an entry that is not a non-empty string is ignored rather than passed through (#579). - A Copy report button on the timeline turns the window into a short Markdown run report — per Bot its turns and failures, busy share, tokens, top tools and unanswered requests, then the team rollup and the longest open wait — and copies it with one click, in the same hours the panel shows (#582).
- A new read-only
timeline/alertsendpoint turns the timeline window into durable alerts: a Bot that fails most of its turns (errors), a Bot whose request went unanswered (waiting) or a handoff that has waited past 15 minutes (stuck) opens one alert that records when it started, and the alert resolves itself when the condition clears and stays readable for a minute after. Polling again never opens a second copy, a look-back window never resolves a live alert, and nothing new is written to the trace (#584). bot_scheduleslists every scheduled task of the calling Bot across all of its sessions — its own chat, the earlier parts of it, and the group chats it speaks in — with each task's next run, state and owning session, so a task the owner sees in the UI can no longer look missing from the one session a Bot happens to be in (#628).
Changed
- Every one of your own Bots now gets your saved keys (such as
$DSH_SECRET_GITHUB_TOKEN) and your connector tools (GitHub, Composio) on every turn: group rounds, Bot-to-Bot messages, scheduled turns, turns of a Bot you share with a friend, and work a Bot does in a sub-agent. The "also when others use it" connector switch is gone; each Bot keeps one on/off switch per connector. A friend's Bot still never gets your keys or connectors, and a key you set to Chosen Bots still goes only to those Bots. Bots now use the GitHub connector first and the shell token only as a fallback, for example forgit push(#601). - Typing two or three short lines in a row in a Bot's own chat now opens one turn instead of one turn per line: the host holds the lines for 1.5 seconds and hands them over together, in the order they were typed, each on its own line. A line sent while the Bot is already answering still lands on its own, and a group chat is unchanged (#642).
- Making a Bot or a group chat no longer starts a turn: a new Bot is sent no greeting and waits in the sidebar until you write to it or a parent Bot messages it, a new group posts no opening round, and picking a Bot's model applies it without speaking to the Bot. Text you type in the composer before creating a Bot is sent to it as your first message, so it answers you, and it is never saved as the Bot's instructions; making a team costs no model turn (#645).
- Bots stay idle until you address them, they are @-mentioned in a group, or their parent Bot gives them a task: the prompt no longer tells a coordinator to dispatch work or a Bot to report and claim on its own, so a team with nothing to do stops producing status traffic, while a Bot that is addressed answers exactly as before (#699).
list_childrennow describes the routing fields it already answers (#733):loadfor every child, andopenForMs/stalledfor a child with an open turn, so a coordinator can send new work to an idle child instead of learning the shape from the implementation.
Fixed
list_botstags a Bot whose outbound message waits on the owner's decision with a count (Writer (Write, 1 to approve)), so a coordinator sees who is blocked instead of an idle-looking roster. The tag is a count only — never the held message's text — and clears once the owner decides (#587).turn-originnow treats a message source kind it does not recognise asotherinstead of "nobody's request", so an unknown or future source arriving in a Bot's own chat no longer inherits the owner's standing approval: only DSH's own notice kinds and the documented mention/handoff cases carry no verdict (#620).- A Bot-to-Bot message that repeats what the same sender already delivered to that Session within the last minute now makes one turn instead of two, and the drop is recorded in the host log as
delivery: dedupe <sender> -> <session>; a relayed conclusion no longer stacks up in a coordinator's queue twice, while the same line to another Session (a group copy, another Bot) is still delivered (#641). - A Bot's shell now says which saved key it withheld and why, instead of looking like the key was never saved:
DSH_SECRETS_WITHHELDlistsNAME:reason(never a value),list_secretsandrequest_secretname the same reason, and a key the owner scoped to one Bot still reaches that Bot's own scheduled turn while it is shared (#651). - A Bot whose saved key is withheld on a turn can run its shell again:
DSH_SECRETS_WITHHELDis now declared to the shell environment, so the bash call no longer fails with "undeclared key" (#653). - On the newer DeepSeek Harness (
0.2.1-alpha.1) the composer's run-stats footer is no longer a singlestatsentry — the turn and step counts moved into anactivitypill — so FlyBot's "1 turn" / "1 step" wording stopped replacing the shell's "1 turns 1 steps". FlyBot now wraps whichever counts pill the installed shell publishes, on both versions (#659). - A message to a friend's Bot right after that friendship was removed is now refused at once with the usual not a Bot a friend shared with you reason, and the Bot leaves the list, instead of being held for approval and then lost when the approval is clicked: the send policy asks Bot Social about the friendship itself rather than trusting the cached roster, and a removal event drops that cache (#666). The same check runs again when a held card is approved, so a line whose friendship was removed while the card waited fails with a reason instead of being sent into a refusal.
- Creating a Bot from the composer now always opens the new Bot's chat: if sending the line you typed as its first message fails, you land on that chat with the failure shown instead of on a card that answers "a Bot with that name already exists". An older team's setup line (
[Bot team setup]) is no longer drawn as a message from the parent Bot in the current part of its chat, matching how the earlier parts already hid it (#698). - The multi-pane layout no longer overflows a narrow window: the Details panel shrinks before it starves the conversation, every pane can shrink, and long values wrap, so nothing is clipped and no blank strip is left between the panes (#705).
- Deleting a Bot now drops its open-turn records from the tree's load tracker. The tracker used to keep counting the deleted Bot forever, so a coordinator could read a Bot that no longer exists as busy (#732).
- A hold that arrives with the status
pending_approval— the spelling Bot Social's own outbound hold already uses — is now treated as waiting by both places that draw it: the held-reply card keeps its Approve / Reject buttons, and the approvals overlay offers the same decisions instead of drawing a settled "Not sent" note, so such a hold can no longer become unanswerable. One shared rule (pendingandpending_approvalwait;approved,auto,failedand a missing status do not) decides it, and a failed send still reads "Not sent: delivery failed" with Retry (#734). - A Bot whose Auto-send is on for a friend no longer goes silent: a line the Bot sends is delivered, instead of waiting on Bot Social's own outbound approval with no card anywhere to decide it. The send path now clears that hold itself, the way a typed line and a face-room Auto-send line already did, and only when Bot Social named no DLP finding on it — a flagged line still waits on its owner's card, exactly as with Auto-send off (#737).
- A hold whose status is
pending_approvalis now answered by the host the same way the client draws it: Approve and Reject go through instead of being refused as "already decided", the reply's bubble stays a waiting card instead of reading "Not sent", the hold counts against the approval cap, is not cleaned up while it waits, and counts toward the Bot's wait badge. Host and client now read that judgment from one module, so the two spellings of "waiting" cannot be added on one side only (#754). - Durations in the run report no longer overflow their own unit: a wait of 59.6 s reads
1minstead of60s, and 1h59m50s reads2h00minstead of1h60m(#761). - The first Main Bot created during onboarding, and a duplicated Bot, are now announced ...
FlyBot v0.2.3
Added
- A question a Bot asks about work handed out from a group now shows in that group's sidebar row (waiting dot and "Waiting for your answer") and in the Agent inbox as a Needs your answer card whose Open takes you to the group, where the card takes the answer (#552).
- The
flybotservice can now report approval activity:flybot.approvals.stats()returns, read-only, how many of each Bot's outbound sends were held and how many were approved, rejected or went stale, plus a team total. A plugin or a settings view can show it; nothing is changed by reading it. - A Bot can now record a decision that mattered — an option it considered and whether it used or rejected it, and why — with
record_decision. It goes into a local, append-only log (<home>/decisions.jsonl, capped) that a reviewer can read to see near-misses and judgment drift, and it is fired on the activity feed asdecisionso the Timeline or an audit view can show it. Off by default (decisions: trueturns it on); a key, token or password is refused. - When the tree merges two duplicate Bots, the archived Bot's own memory now moves into the Bot that keeps it, so a merge no longer drops what the duplicate had learned. It is capped at 20 entries and tagged
source: merged. This uses a new plugin seam,flybot.memory(read a Bot's entries, append one), which is off unless the host names the plugin inflybot.memoryPlugins; without it the merge is unchanged. - Plugins can now make a short, sanctioned model call.
flybot.model.ask({ prompt, system?, maxTokens? })resolves{ ok: true, text }or{ ok: false, code }for a plugin the host allows — usably for a small classification, a title or a one-line summary that a plugin needs of its own. It is off by default (the host names the allowed plugins inflybot.modelPlugins), capped in input, output and rate, streams through the host's own model, returns text only (never a provider message) and refuses on a read-only host. - The Timeline window now reports how each Bot's turns ended — completed, aborted, error or lost — and lists its recent failed or aborted turns with their error code, so the panel or a tool can see a Bot's reliability at a glance. It is read-only and part of the existing
timeline/windowanswer, so nothing new needs configuring. - The timeline window now also reports the open requests waiting longest (#469) and the tools each Bot used with their failures (#359), and
timeline/healthreduces the same window to per-Bot health signals and flags (#357). - The Timeline window now reports the token spend per Bot — how many model calls, and the input, output and cached tokens they used, when the adapter reports them — so where the team's model quota goes is visible next to the turns it ran. Read-only, counts only (never message text), and part of the existing
timeline/windowanswer. - The tree can now group a Bot by meaning, not only by the words in its name and role. With
modelClassify: truein the tree config (and the host allow-listingflybot-treeinflybot.modelPlugins), a Bot whose role the built-in vocabulary cannot place — a title it does not hold, a brief in another language — is sent to the model once for a single category, which is stored on the Bot and used the next time the tree reorganizes. It is off by default, asks at most once per role change, and the rules still win where they already found a category.
Changed
- A Bot session on
workspace-writewithout a usable sandbox backend now has a one-command preflight (node .github/scripts/sandbox-preflight.mjs) that names the missing backend and prints the unlock in one line, and the agent instructions say to report the block once instead of retrying every shell call (#607). - The integration guide now names the two plugin seams —
flybot.model.askandflybot.memory— with the host config that enables each (flybot.modelPlugins/flybot.memoryPlugins), so a plugin author finds them without reading the host source.
Fixed
- Scheduled tasks no longer stop overnight in a group chat: the limit that let Bots start only three rounds there until you spoke again is turned off, because it blocked owner-set recurring runs without showing anything. Bot-to-Bot chain and per-round limits still apply (#557).
- Sharing a Bot with a friend on a Bot Social server that lacks
grant_bot_to_friendnow tries the REST route and, when that is missing too, says Bot Social needs an update to share with a friend. Invite links still work. instead of showing the rawMCP error -32602: Tool grant_bot_to_friend not found(#563). - Sharing a Bot with a friend works again: Share with a friend names the friend by the friend's Bot id (the id
list_friendsreturns) instead of an owner id the server never lists, the fallback route now matches Bot Social (POST /v1/bots/:id/grants), and a server that refuses the request says so in plain words instead of showing the rawMCP error -32602text (#592). - The approval card in a group's details panel wraps its buttons, so 拒绝 and 允许一次 stay reachable at common desktop widths instead of overflowing off the left edge of the panel (#593).
- A Main Bot the tree adopts only after the server started (onboarding creates it while
dsh webruns) now gets the tree's team-building tools (create_team,create_child_bot, and the other gated tools) on its first turn instead of only after a restart; the plugin-tool mask is recomputed when a turn starts, not only when the Agent is created (#594). - A group member Bot's line that arrives while the group's turn is still running no longer shows up in the message bar above the composer with the internal
[Group post from …]header; it appears at once as that Bot's own bubble, and the round still gets it when the turn ends (#595). - A Bot's own failure, stop or resume notice that lands while its group chat is still in a round no longer flashes FlyBot's internal
[Group post from …]routing header in the queued-message bar above the composer; it waits until the round is done and the round still shows it as that event (#595). - The decision log (
decisions.jsonl) is now bounded on disk: the host trims it to the newest 2000 entries and at most 1 MB as it appends, and a read parses at most that byte cap instead of the whole file, so the log no longer grows without bound (#603). - "Always allow" now keeps working when DSH opens its own retry turn after the owner answered: a turn with no message verdict in a Bot's own chat uses the Bot's standing approval instead of showing the same card again, and a grant that a turn cannot use says why once in the log (#608).
- The sandbox preflight now answers for the file policy it is given (
--mode, orFLYBOT_SANDBOX_MODE) instead of assumingworkspace-write, never reports adanger-full-accesssession as blocked, reports the Windows ACL runner asunknownrather than unavailable, and separates "proven blocked" (exit 3) from "cannot tell" (exit 4), so an agent no longer stops shell work on a probe it cannot complete (#615). - The answers to deleting a Bot, setting a Main Bot, removing the Agent and deleting a group chat now carry only the signed-in member's own scope, like every other team view, instead of the whole team.
- When someone has signed in, the one DSH Agent (and its Sessions: new session, adopt, rename, archive, remove) stays usable to that member while the team is alone, instead of every call being refused before it runs; with more than one member it follows the same shared-resource rule as adding the Agent.
- After switching the Bot Social server, the stopped social layer can no longer restart its poll from a face poll that was still in flight, so the old server is not polled (and does not deliver messages) after the switch.
- A hold on a send now blocks the task that was started last even when two tasks were started in the same millisecond: task order uses a strictly increasing stamp instead of the bare clock, so which task an untagged hold blocks no longer depends on timing.
- The team-health view no longer calls a Bot idle while it is busy in one long turn that began before the window, and its failure rate is never reported as more failures than turns; a Bot busy through the window is flagged busy.
- The timeline panel no longer issues a duplicate request when a window change supersedes a poll in flight and the tab is looked at again: a superseded answer no longer clears the in-flight guard of the request that replaced it.
Security
set-mainno longer lets a member take another owner's Main Bot's place: the endpoint checks the Bot named inreplacetoo, andsetMainrefuses areplaceacross owners, so a member cannot demote another owner's Main Bot or promote their own Bot over it (#613).- Untrusted-content fences are neutralized more strictly: a marker written with whitespace inside the tag (
< / untrusted-message>) is now stripped too, since a parser that ignores whitespace reads the same closing marker. Text from friends' Bots and other untrusted sources stays data, never instructions.
FlyBot v0.2.2
Added
- An Inbox row at the top of the sidebar opens one list of your own Bots' work: Needs you (tool approvals, held messages, tasks waiting for you or failed), In progress (running turns and tasks) and Done (the last 7 days). Each card has one clear status and an Open button to its chat; tool approvals from a group chat can be answered right there with the same buttons as in the chat (#349).
- FlyBot can run in a container that you build yourself: a
Dockerfileandcompose.yamlstartdsh webwith the FlyBot core and plugins as a normal user, with one data volume, no keys in the image, the port on the host's127.0.0.1only and a health check. A guide in English and Chinese, Run FlyBot in Docker, covers start, stop, upgrade, roll back and back up; CI builds the image and runs a start check, and nothing is published (#352). - FlyBot now sends a system notification when one of your own Bots finishes or fails a turn while you are in another chat or app (
Coder finished,Coder failed: <reason>), alongside the existing approval-waiting one; clicking it opens that chat, and it never approves, rejects or sends anything. Settings → General has a Notifications switch, on by default, that turns all of them off (#353). - Scheduled tasks can be edited and paused: in Settings → Scheduled tasks and a Bot's card, Edit opens the task in its form, filled in, and Pause / Resume stop and restart a task without deleting it. The docs now say what happens to a run missed while the app was closed: a recurring task runs once, for its latest missed time, never once per missed time (#354).
- Settings → Connectors: pick Composio apps by searching Composio's app list instead of typing app names, and see for each picked app whether it is ready or still needs a sign-in (#359).
- On a phone browser (about 400px wide) you can skim a chat, press Approve / Reject on a held reply and work the Agent inbox: approval buttons become full-size touch targets, floating approval cards span the screen beside the icon rail, and inbox cards put their buttons under the text so titles stay readable. Wider screens are unchanged (#362).
- You can now see which model each turn used and which model each Bot runs on: a Timeline turn's tooltip ends with the model that turn ran on (
· deepseek-v4-pro), and hovering a Bot in the sidebar tree or Bot list shows its model (Coder · DeepSeek V4 Pro). Nothing new stores keys; models are still chosen per Bot in its settings (#366). - Chief now places new Bots sensibly up front instead of piling them flat under itself. Before creating a Bot it can search the whole tree with
find_botsand reuses a Bot that already has the same role, at any depth, instead of making a duplicate.create_teamsets up a project as one lead with its roles under it (for example Chief → FlyBot Lead → Dev, Test, Maintenance, Repo), which takes one slot under Chief, andcreate_child_botcan put a Bot under one of the caller's own descendants; a later Bot of a group goes under that group's lead. A Bot whose parent is gone is moved back into the tree by role. A full level, orphans and duplicates stay the job of the tree's automatic reorganization (#447). - A guide, Set a DeepSeek API key, explains the ways to give DeepSeek Harness your key (the key dialog, its credentials file, or
DEEPSEEK_API_KEY), and the missing-key error and the no-model setup choices link to it (#451). - Notifications go further: an approval that is still waiting after 15 minutes gets one reminder (
Coder has waited 15 min for your approval), Settings → General → Notifications has a separate switch for waiting approvals, finished turns and failed turns, and each chat keeps a single notification that a newer one replaces and that counts its updates (#469). - The Agent inbox now lists a turn of your own Bot that failed under Needs you, with the reason, until the Bot finishes a turn in that chat again, from the same events as the finished/failed notifications; and each inbox card shows a one-line Latest: row with what happened last in its chat (#471).
- Each scheduled task in Settings → Scheduled tasks now says when it last ran and how it went (still running, finished, stopped, or failed with the reason). A run fails only when DSH could not deliver it or the Bot's turn errored; a failed run gets Retry, which runs the task once now with the same prompt, and the failure also shows up in notifications (#473).
- Face-to-face rooms have one room lead (总负责人), a Bot from any home. The person who created the room picks it with the Room lead button in the room's details; until then it is the first Bot of the creator's home that joined, and everyone else sees it read-only. The lead sees everyone in the room with their roles, writes the one plan and gives out the parts; every other Bot, a Main Bot too, does only its own part or answers when named. With an older Bot Social server the default lead is used and cannot be changed (#476).
- Share a Bot with an existing friend in one click from Sharing (no invite link); the new share appears in People with access and can be Revoked or declined as usual (#500).
- Opt-in state journal for debugging multi-Bot bugs (#511): with
stateJournal: truein thebotconfig, every change to the saved team is recorded on this computer only (<home>/journal/state.jsonl, private to the user, kept 7 days or 20 MB), and the dev scriptcore/scripts/replay.mjsrebuilds the team as it was after any recorded step. Off by default. - Tool approval cards in a group and in the Inbox add Always allow for this Bot and Always allow for this group next to Reject / Allow once, so a Bot stops asking for the same kind of permission (for example a
bashsandbox escalation to full access). Standing approvals are kept across restarts and listed under Always allowed in Bot settings and Group settings, each with Remove (#540). - Memory recall now finds entries by meaning, and shows what memory links.
recallgainssemantic: "true", which ranks entries with a local, dependency-free TF-IDF score over words, word stems and CJK n-grams, so an entry whose wording differs from the query still matches; andrelated, which lists the topics a[[topic]]links to and who wrote a memory. The default exact-word recall is unchanged. - The Timeline plugin can now be asked for an export on demand.
timeline/exportreturns the current trace as one OpenTelemetry OTLP/HTTP JSON payload — the same mapping the timer exporter uses — so a tool or the panel can pull it now instead of waiting for the next flush. It is read-only, sends Bot and room ids only unless the caller setsnames: true, and needs no configuration. - The Timeline plugin can now export its trace as OpenTelemetry spans. Set
exporter: "otlp"with anotlpUrl(https, or http on localhost only) to POST OTLP/HTTP JSON to any collector (Jaeger, Phoenix, Langfuse…), orexporter: "json"to appenddir/export.jsonl. Finished Bot turns becomeinvoke_agentspans and their tool callsexecute_toolspans beneath them. Off by default; it exports only the same metadata the panel shows (never message text, tool arguments or output), sends Bot and room ids only unlessexportNames: true, and an open turn is held until it ends rather than exported as finished. - A tree coordinator can now see how busy its children are. Each Bot's running turns are counted from the activity feed, shown as one line in the coordinator's prompt (
Child load right now — Work: busy (2 running), Travel: idle) and aloadcount on each entry oflist_children, so a new part goes to a free child instead of a busy one. Nothing is stored; the counts start from zero on each start.
Fixed
- Face-to-face rooms: a Bot now looks the same on every home and in every place (waiting tiles, chat bubbles, Members, sidebar). A Bot with no look of its own sends the look its home generates for it to Bot Social, before it joins, so the other home no longer invents a different one; your own Bot in the waiting dialog uses its roster look right after joining; a friend's Bot speaks in the room with the look its owner gave it. Other people show their initial on a color picked from their name instead of a grey silhouette (#182).
- Past rooms list everyone who was in a face-to-face room (a home that stayed no longer drops the person who left), each row shows when the room ended, clicking anywhere on the row opens it, and the Past rooms fold stays open or closed across a reload (#304).
- Face-to-face rooms: another person's line, or another home's approved Bot reply, that arrives while your Bots are answering now shows right away as that person's or Bot's bubble, never as a queued "[Group post from …]" row above the composer that Cmd/Ctrl+Enter could steer; a person's line breaks are kept on the receiving side (#455).
- Face-to-face rooms: a Bo...