Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
582c8fd
fix(tree): Copy invite link always copies the full instruction messag…
Oct 14, 2026
08760fc
Friends' bots: full chat sessions, per-friend auto-accept, one sideba…
Oct 14, 2026
417c9f0
Friends' bots: keep remote chats on the relay route; @-mentions and m…
Oct 14, 2026
1e9a9a5
Tree: stop/report control messages, prompt refresh after removal, ser…
Oct 14, 2026
59637e2
Untrusted fences cannot be escaped; remote names sanitized (Fixes #9)
Oct 14, 2026
bdfb5b3
Social inbox: per-event cursor, persisted, deduped, single-flight; de…
Oct 14, 2026
f5e2594
Social files: refuse private/credential paths and unknown types, alwa…
Oct 14, 2026
87f4b37
Social identity: credentials bound to server, explicit token wins, Un…
Oct 14, 2026
d71ae1b
Approvals: approve resumes the chain after the holder, failed sends v…
Oct 14, 2026
c9b32cf
Mentions: friends' Bots matched by the shared @ rule (code, links, qu…
Oct 14, 2026
e05a658
Read-only host: FlyBot and plugin query endpoints served, social poll…
Oct 14, 2026
d272186
ctx.flybot: change epoch survives host restarts, listeners get copies…
Oct 14, 2026
38d465d
FlyBot client: error boundary per plugin slot, approval copy through …
Oct 14, 2026
378e3a6
Friends' Bots sidebar: per-home groups drawn with ds-bot's Bot row (g…
Oct 14, 2026
9bd3723
Review nits: stale flybot.js refs, dead flybotSendUserMentions, appro…
Oct 14, 2026
0d31b00
ctx.flybot tools: visibleTo also removes the tool from a Bot's tool l…
Oct 14, 2026
aedce82
Docs: FlyBot sections in README.zh.md mirror README.md, incl. install…
Oct 14, 2026
fb0831b
tree: #6 and #20 tree items from recursive-bot (flybot-tree 5631cbc)
Oct 14, 2026
5fe15ab
client: friends' Bots never get the lost-model picker
Oct 14, 2026
ee92675
client: inbound friend chats read like a two-party chat
Oct 14, 2026
f422006
client: sidebar preview drops the one-line trace; a lone arrow line s…
Oct 14, 2026
487f235
tree: trace line only when relaying down the tree, not in replies to …
Oct 14, 2026
36f269c
fix(remote-chat): don't drop typed text when a time-context snapshot …
Oct 14, 2026
3f73992
invites: "Copy invite link" copies only the URL; new "Copy invite wit…
Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 79 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,49 +108,88 @@ Bots use your own model quota, and they read and write files and run commands wh
## FlyBot plugin architecture

FlyBot is DS Bot (`ds-bot/`) plus feature plugins. ds-bot itself provides the extension
API, so plugins never patch it:

- **Host service `flybot`** (`ds-bot/src/host/hooks.js`, provided via `ctx.set('flybot')`;
plugins declare `inject: ['flybot']` and call `ctx.flybot.scope('<plugin>')`):
- `fields.get/set/all/query` – synchronous, per-plugin custom fields on Bots, saved in
`state.json` as `bots[id].fields[plugin][key]`; `on('change', fn, { all })`.
- `bot.list/get/find/of` (local Bots only), `bot.isRemote(ref)`,
`bot.create({ name, systemPrompt, fields }) → id`, `bot.setSystemPrompt(id, text)`;
`on('bot.created' | 'bot.removed' | 'change', fn)` (disposable).
- `bot.send(msg)` and `bot.send.use({ name, before, after, handle })` – every
Bot-to-Bot message (including built-in `message_bot` and replies) runs one middleware
chain: `undefined`/`allow` continue, `deny` refuses, `hold` queues it for the owner's
approval card. `allow` with `bypass: ['social']` skips a middleware only if
`config.flybot.bypass` grants it.
- `transports.add(kind, { send })` for remote Bots, `approvals.list/decide`,
`tools.register({ name, parameters, visibleTo(botId), execute(args, { botId }) })`,
`api.handle(name, fn)` for UI endpoints (`flybot/<name>` on ds-bot's `api/bot` route).
- **Browser**: `ctx.flybot` (`bots()`, `fields.on('change')`) and slots
`flybot.sidebar.section`, `flybot.sidebar.structure` (the Bot list drawn as one tree under the Main Bot; `menuItems({bot})` adds right-click items such as "Copy invite link"), `flybot.composer.mention`, `flybot.bot.details`,
`flybot.approvals` (`ds-bot/src/client/flybot-slots.js`).
- **Types**: `ds-bot/types/flybot.d.ts`.
- **Plugins** (`ds-bot/plugins/`): `social` (friends' Bots via Bot Social, approval-gated
sends) and `tree` (recursive Bot tree, maintained by recursive-bot).
API, so plugins never patch it.

### Install FlyBot (clean profile)

Needs Node.js >= 22.19 and pnpm. Add ds-bot and the two plugins to a DSH profile, then
enable them in that profile's `cordis.patch.yml` (with the Bot Social server URL for social):

```sh
npx @deepseek-ai/dsh plugin --profile web add "github:Birfy/FlyBot#flybot-plugins&path:/ds-bot"
npx @deepseek-ai/dsh plugin --profile web add "github:Birfy/FlyBot#flybot-plugins&path:/ds-bot/plugins/social"
npx @deepseek-ai/dsh plugin --profile web add "github:Birfy/FlyBot#flybot-plugins&path:/ds-bot/plugins/tree"
```

Update later with `pnpm update ds-bot @flybot/social @flybot/tree` inside the profile
folder, then restart DSH. A plugin that fails to start logs one warning; its UI slots
draw nothing and the rest of ds-bot keeps working.

### Host API (`ctx.flybot`)

`ds-bot/src/host/hooks.js`, provided with `ctx.provide('flybot')`. Plugins declare
`inject: ['flybot']` and call `ctx.flybot.scope('<plugin>')` (`'shared'` is reserved).

- `fields.get/set/all/query`: synchronous per-plugin fields on Bots, saved in `state.json`
as `bots[id].fields[plugin][key]`; `on('change', fn, { all })` gets a copy of each change.
- `bot.list/get/find/of` (local Bots only), `bot.isRemote(ref)`,
`bot.create({ name, systemPrompt, fields }) → id`, `bot.setSystemPrompt(id, text)`;
`on('bot.created' | 'bot.removed' | 'change', fn)`; `ready()` resolves once the saved
team is loaded; `readOnly()` is true while another dsh holds the team.
- `bot.send(msg)` and `bot.send.use({ name, before, after, handle })`: every Bot-to-Bot
message (built-in `message_bot` and replies included) runs one middleware chain.
`undefined`/`allow` continue, `deny` refuses, `hold` queues an approval card. Approving
resumes the chain after the middleware that held it, so a later `deny` still wins; a
send whose delivery failed stays on the card with Retry. At most 100 holds wait at a
time and a message is at most 20,000 characters.
- `transports.add(kind, { send })`, `approvals.list/decide/onChange`,
`tools.register({ name, parameters, visibleTo(botId), execute(args, { botId }) })`
(a tool `visibleTo` refuses is also taken out of that Bot's tool list),
`api.handle(name, fn, { read })` for UI endpoints (`flybot/<name>`; `read: true` keeps
it working on a read-only host; built-in names are reserved).
- `remote.publish/list/get` for friends' Bots, and `remote.chats.{open, deliver, onSend,
list, of}`: each friend's Bot is a chat of its own (see below).
- Browser: `ctx.flybot` (`bots()`, `fields.on('change')`, which survives host restarts)
and slots `flybot.sidebar.section`, `flybot.sidebar.structure`, `flybot.composer.mention`,
`flybot.bot.details`, `flybot.approvals` (`ds-bot/src/client/flybot-slots.js`). Each
plugin component renders inside an error boundary.
- Types: `ds-bot/types/flybot.d.ts`.
- Plugins (`ds-bot/plugins/`): `social` (friends' Bots via Bot Social) and `tree`
(recursive Bot tree, maintained by recursive-bot).

CI (`.github/workflows/test.yml`) runs ds-bot's tests, which include every plugin's tests,
plus the client build check and lint.

### Social design: homes and members
### Social: homes, members and friends

- **Home**: one Bot Social identity per FlyBot install (e.g. `@danyang.home`), made by a
single `POST /v1/onboard/flybot` on first start (owner + home + connector token +
optional webhook). The token lives in `$DSH_HOME/bot/flybot-social.json` (mode 600),
never in `state.json` or the repo.
- **Members**: every local Bot is registered as `@<bot>.<home>` (key custodied by the
server); new Bots (including tree children) join on `bot.created`, deleted ones are
revoked on `bot.removed`.
- **Friendship is home-to-home**: once accepted, every local Bot can message every Bot of
the friend home. The server signs each message as the sending member and checks that
its home is friends with the recipient's home. Per-bot identities keep working.
- **Remote Bots** are never in `bot.list()`; they are addresses (`'@coder.bob.home'` or
`{ kind: 'remote', handle }`), and middleware sees `msg.target = { remote: true, handle, home }`.
The sidebar lists them grouped by home with a "remote" badge, and the `@` menu offers them.
- **Approval**: per friend home (`config.approval.homes`, default hold), overridden per
local Bot (`fields.social.autoApprove[home]`); held messages show in the
`flybot.approvals` card. The tree middleware passes remote sends on to `social`.
- Inbound messages arrive in the addressed member Bot's chat as fenced untrusted text.
single `POST /v1/onboard/flybot` on first start. The token lives in
`$DSH_HOME/bot/flybot-social.json` (mode 600) together with the server it belongs to; it
is never sent to another server, and an explicit `config.token` or
`FLYBOT_SOCIAL_TOKEN` wins.
- **Members**: every local Bot is registered as `@<name>.<home>`; a name with no ASCII
letters (研究员) gets `@bot-<hash>.<home>`. New Bots join on `bot.created`, deleted ones
are revoked.
- **Add friend**: right-click one of your Bots and choose **Copy invite link** (copies only
the bare URL) or **Copy invite with instructions** (the URL plus how to join), or use
**+ Add** in Friends' Bots to paste a friend's link or code and to accept requests. Your
click is the owner's confirmation; no model tool can accept a friend.
- **Friends' Bots in the sidebar**: one collapsible group per friend home, each Bot drawn
like your own (avatar, last message, unread badge) with a small globe. Clicking one opens
a normal chat: what you type goes to that Bot, its replies come back as its messages.
- **Auto-accept** (per friend home, on by default, a checkbox in Friends' Bots): incoming
messages are delivered and the server's own approval is switched off for that
friendship. What you type is always sent at once. What your Bots send on their own
(`message_bot` / `message_friend_bot` to a friend's Bot, replies) goes at once only with
auto-accept on; otherwise it waits on an approval card.
- **@-mentions**: `@handle`, `@Name` or the `@` menu row of a friend's Bot in any chat sends
that message to it (code, links and quoted lines are ignored); the reply comes back into
the same chat, and your own Bot does not answer the mention.
- **Files**: a Bot attaches files with `message_friend_bot` `files` (absolute paths of
documents, data or images, at most 5 × 20 MB). Hidden folders (`~/.ssh`, `~/.dsh`),
credential-like files and FlyBot's own files are refused, and a Bot's files are always
held for your approval, with the full path on the card, whatever auto-accept says. Bytes
are uploaded only after approval, hash-checked. Incoming files show as chips you can save;
the receiving Bot reads them inside an `<untrusted-file>` fence.
- Everything a friend's Bot writes reaches your Bots fenced as untrusted text, with its
name cleaned to one line.
74 changes: 74 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,77 @@ ds bot 拥有群聊功能。群聊是非常好的多 bot 协作方式,但它
ds bot 是独立的社区项目,**不是 DeepSeek 官方产品**,与 DeepSeek 没有隶属、认可或赞助关系。“DeepSeek”“DeepSeek Harness”等名称是各自所有者的商标,这里只用来说明本插件和什么一起用。

bot 会消耗你自己的模型额度,也会按你的要求读写文件、运行命令。重要的操作请自己检查。

## FlyBot 插件架构

FlyBot = DS Bot(`ds-bot/`)+ 功能插件。扩展接口由 ds-bot 自己提供,插件无需改动 ds-bot。

### 安装 FlyBot(全新 profile)

需要 Node.js >= 22.19 和 pnpm。把 ds-bot 和两个插件加入 DSH profile,再在该 profile 的
`cordis.patch.yml` 里启用(social 需要填 Bot Social 服务器地址):

```sh
npx @deepseek-ai/dsh plugin --profile web add "github:Birfy/FlyBot#flybot-plugins&path:/ds-bot"
npx @deepseek-ai/dsh plugin --profile web add "github:Birfy/FlyBot#flybot-plugins&path:/ds-bot/plugins/social"
npx @deepseek-ai/dsh plugin --profile web add "github:Birfy/FlyBot#flybot-plugins&path:/ds-bot/plugins/tree"
```

之后在 profile 目录里运行 `pnpm update ds-bot @flybot/social @flybot/tree` 更新,再重启 DSH。
某个插件启动失败只会记一条警告,它的界面位置不显示,ds-bot 其余部分照常工作。

### 宿主接口(`ctx.flybot`)

实现在 `ds-bot/src/host/hooks.js`,用 `ctx.provide('flybot')` 提供。插件声明
`inject: ['flybot']`,并调用 `ctx.flybot.scope('<插件名>')`(`'shared'` 为保留名)。

- `fields.get/set/all/query`:Bot 上按插件划分的同步字段,存于 `state.json` 的
`bots[id].fields[plugin][key]`;`on('change', fn, { all })` 收到每次变更的副本。
- `bot.list/get/find/of`(仅本地 Bot)、`bot.isRemote(ref)`、
`bot.create({ name, systemPrompt, fields }) → id`、`bot.setSystemPrompt(id, text)`;
`on('bot.created' | 'bot.removed' | 'change', fn)`;`ready()` 在团队数据加载后完成;
`readOnly()` 在另一个 dsh 持有团队时为真。
- `bot.send(msg)` 与 `bot.send.use({ name, before, after, handle })`:所有 Bot 之间的消息
(包括内置 `message_bot` 和回复)都经过同一条中间件链。`undefined`/`allow` 继续,
`deny` 拒绝,`hold` 生成审批卡。批准后从暂扣它的中间件之后继续,后面的 `deny` 仍然生效;
投递失败的消息留在卡片上,可点"重试"。同时最多 100 条待批,单条消息最多 20,000 字符。
- `transports.add(kind, { send })`、`approvals.list/decide/onChange`、
`tools.register({ name, parameters, visibleTo(botId), execute(args, { botId }) })`
(`visibleTo` 拒绝的工具也会从该 Bot 的工具列表中移除)、
`api.handle(name, fn, { read })` 注册界面接口(`flybot/<name>`;`read: true` 的接口在只读宿主上也可用;内置名称保留)。
- `remote.publish/list/get` 管理好友的 Bot,`remote.chats.{open, deliver, onSend, list, of}`:
每个好友 Bot 都有自己的对话(见下文)。
- 浏览器:`ctx.flybot`(`bots()`、`fields.on('change')`,宿主重启后仍继续推送)以及
`flybot.sidebar.section`、`flybot.sidebar.structure`、`flybot.composer.mention`、
`flybot.bot.details`、`flybot.approvals` 等界面位置(`ds-bot/src/client/flybot-slots.js`),
每个插件组件都包在错误边界里。
- 类型:`ds-bot/types/flybot.d.ts`。
- 插件(`ds-bot/plugins/`):`social`(通过 Bot Social 连接好友的 Bot)和 `tree`
(递归 Bot 树,由 recursive-bot 维护)。

CI(`.github/workflows/test.yml`)运行 ds-bot 的测试(包含每个插件的测试)、客户端构建检查和 lint。

### Social:home、成员与好友

- **Home**:每个 FlyBot 安装在 Bot Social 上有一个身份(如 `@danyang.home`),首次启动时由
一次 `POST /v1/onboard/flybot` 创建。令牌连同所属服务器保存在
`$DSH_HOME/bot/flybot-social.json`(权限 600),绝不会发给其他服务器;显式的
`config.token` 或 `FLYBOT_SOCIAL_TOKEN` 优先。
- **成员**:每个本地 Bot 注册为 `@<名字>.<home>`;没有 ASCII 字母的名字(如 研究员)
使用 `@bot-<哈希>.<home>`。新 Bot 通过 `bot.created` 加入,删除的 Bot 会被吊销。
- **加好友**:右键自己的某个 Bot,选择 **Copy invite link**(只复制链接本身)或
**Copy invite with instructions**(链接加加入说明);或在 Friends' Bots 里点
**+ Add** 粘贴好友的链接或邀请码、接受请求。你的点击就是主人的确认,模型工具无法替你接受好友。
- **侧边栏里的好友 Bot**:每个好友 home 一个可折叠分组,每个 Bot 的样子和你自己的 Bot 一样
(头像、最新消息、未读标记),名字后有一个小地球图标。点击即打开普通对话:你输入的内容发给它,
它的回复作为它的消息显示。
- **自动接受**(按好友 home 设置,默认开启,在 Friends' Bots 里勾选):收到的消息直接送达,
并关闭服务器对该好友关系的审批。你亲自输入的内容总是立即发送。你的 Bot 自行发给好友 Bot 的消息
(`message_bot` / `message_friend_bot`、回复)只有在自动接受开启时才立即发送,否则等待审批卡。
- **@ 提及**:在任何对话里用 `@handle`、`@名字` 或 `@` 菜单选中好友的 Bot,这条消息就会发给它
(代码、链接和引用行不算);回复回到同一个对话,你自己的 Bot 不会抢答。
- **文件**:Bot 用 `message_friend_bot` 的 `files` 附加文件(文档、数据或图片的绝对路径,最多
5 个、每个 20 MB)。隐藏目录(`~/.ssh`、`~/.dsh`)、疑似凭据的文件和 FlyBot 自己的文件会被拒绝;
无论是否自动接受,Bot 附带的文件都要你批准,审批卡上显示完整路径。批准后才上传并校验哈希。
收到的文件显示为可保存的附件,接收的 Bot 在 `<untrusted-file>` 围栏中读取内容。
- 好友 Bot 写的所有内容都以不可信文本的形式交给你的 Bot,名字会清理成单行。
Loading