Repository navigation
Decouple the social and tree plugins from ds-bot so the core can track upstream #34
Description
Problem
@flybot/social and @flybot/tree live inside ds-bot/plugins/, even though ds-bot is itself just a DeepSeek Harness (DSH) plugin. They are tested through ds-bot's test runner and ship from ds-bot's directory. That makes it impossible to take a newer upstream ds-bot without merging by hand.
Findings
What ds-bot is
ds-botis the DSH plugin from Fei2411/DeepSeek-Bot (Apache-2.0, npmds-bot, upstream latest still 0.1.5).- FlyBot did not fork it with history: commit
28a816cimported the tree ofFei2411/DeepSeek-Bot@2b10c70as a single squashed commit (a vendored copy). As a result,gitcannot compare or merge with upstream: the GitHub compare API answers "no common ancestor".
FlyBot's own changes to the core
Since the import, FlyBot has changed the core significantly: about 47 files, +4.4k/−2.2k lines under ds-bot/src, index.js and types. The key additions do not exist upstream (404 on upstream main):
src/host/hooks.js: theflybotCordis service, plugin API v1 (bot.*,fields,scope,tools.register,api.handle,remote.*,approvals,transports,bot.send.use, events).src/client/flybot-slots.jsandflybot-registry.js: browser slots such asflybot.sidebar.section/.structure, plusbotTree.types/flybot.d.ts.- The remote-chat and social-related host changes, for example
fix-stale-remote-chatin fix(remote-chat): reopen a friend's Bot's chat right after it is deleted #26.
So even with the plugins moved out, the plugin API is a FlyBot patch to ds-bot. "ds-bot pulls its latest upstream" therefore means "rebase that patch onto upstream", unless upstream accepts the API.
How the plugins are loaded today
- Each plugin is its own DSH package (
@flybot/social,@flybot/tree) withdsh.bundle.patchand aclient.jsin the web module-loader format. Each declarespeerDependencies: { "ds-bot": "*" }andinject: ['flybot']. - Users add them as separate profile packages:
github:ThinkFlowLab/FlyBot#main&path:/ds-bot/plugins/socialand…/tree. Upgrades usepnpm update ds-bot @flybot/social @flybot/tree. - At runtime they are already decoupled. The coupling is in the repository, tests and CI.
Build and test coupling
ds-bot/test/social-plugin.test.jsandtree-plugin.test.jsre-import the plugin tests, so they run under ds-bot'snpm test.ds-bot/test/tree-host.test.jsandbot-tree.test.jstest tree against the real host.plugins/social/test/social.test.jsimports../../../index.js(the core'sapply) and../../../test/fake-ctx.js. Itsnpm testuses../../test/harness-resolve.mjs.- ds-bot's
scripts/lint.mjslintsplugins/. - CI (
.github/workflows/test.yml) runs only inworking-directory: ds-bot, with one lockfile (ds-bot/package-lock.json). - Neither plugin has a build step:
client.jsis hand-written.
User's Mac install (as reported; not inspected from here)
- Installed from the fork branch
Birfy:flybot-plugins, which ismainplus about 76 files and +9.3k lines including social and remote-chat fixes. - Served by launchd jobs
com.flybot.webon ports 3080 and 3081. - Profile entries point at
path:/ds-bot,path:/ds-bot/plugins/socialandpath:/ds-bot/plugins/tree.
CI and governance
- Branch protection on
mainrequires one context:ubuntu-latest · Node 24 · DSH 0.2.0-rc.2. - Open PRs that touch CI: ci: CI modeled on yetone/magpie (macOS leg, plugin tests + coverage, package check, tag release) #29 (
ci-magpie: macOS leg, plugin tests + coverage, pack check, tag release) and ci: slim Test matrix to ubuntu Node 24 + DSH 0.2.0-rc.2 #31 (ci/slim-matrix).
Tree → ds-bot internal dependencies (owned by recursive-bot)
Tree's runtime code already reaches ds-bot only through ctx.flybot, wrapped in one file (plugins/tree/src/adapter.js), plus ctx.effect / ctx.logger. The internal couplings to remove are below.
| # | Tree uses today | Kind | Should use instead |
|---|---|---|---|
| 1 | test/tree-host.test.js (in ds-bot): import { apply } from '../index.js' + ./fake-ctx.js, then tree.apply({ flybot: h.fb }) |
test reaches into core internals and core test helpers | Move it to plugins/tree/test/host.test.js. Boot the published or pinned ds-bot package by its public entry (import { apply } from 'ds-bot') in a small fake Cordis context that ships with the plugin. Get the service only through ctx.flybot. |
| 2 | test/tree-plugin.test.js (ds-bot re-imports plugins/tree/test/tree.test.js) |
the core runs the plugin's tests | Delete it. Tree runs its own npm test in its own CI job. |
| 3 | test/bot-tree.test.js → src/client/flybot-registry.js botTree, SLOTS |
core test of a tree-only feature | Keep it in core as a test of the public slot flybot.sidebar.structure (core API). Tree tests only the data it provides to that slot. |
| 4 | tree.apply(ctx, …) called directly in tests |
direct call instead of loading as a plugin | Load as a Cordis plugin (ctx.plugin(tree, config)) and dispose through ctx.effect / scope disposal. Keep the ctx.on('dispose') fallback out of the public contract. |
| 5 | Adapter calls flybot.scope(NS) → bot.list/get/create/setSystemPrompt/isRemote, bot.send.use({ before: ['social'] }), fields.get/set, tools.register, on('bot.created'|'bot.removed'|'change') |
public API v1, but undocumented, unversioned in package terms | Keep these, but treat types/flybot.d.ts as the contract. Peer-depend on a version range of ds-bot that ships API v1 (not "*"). Check flybot.version at startup. before: ['social'] names another plugin, so replace it with a documented order constant or priority. |
| 6 | Browser client.js: window.__ModuleLoader__.load, require('react'), injected browser flybot (bots(), slots.register('flybot.sidebar.section' / '.structure')) |
DSH loader format plus FlyBot's browser service | Same, but document the browser flybot service and its slots in flybot.d.ts. No access to ds-bot CSS internals beyond the documented --bt-* tokens. |
| 7 | plugins/tree/package.json: peerDependencies.ds-bot: "*"; description says "@flybot/core" |
metadata | "ds-bot": ">=<first version with API v1>"; fix the description. |
Social → ds-bot dependencies (our part)
- Runtime:
ctx.flybotonly (api.handle,bot.list/get/find/send/send.use,fields,tools.register,remote.chats.deliver/onSend,transports.add,approvals.onChange,readOnly,ready,on) plusctx.effect. These are all in API v1, butremote.*,transportsandapprovalsexist only for social, so they are the most likely to drift from upstream. - Tests:
social.test.jsimports../../../index.jsand../../../test/fake-ctx.js;npm testuses../../test/harness-resolve.mjs. These need replacing with a package-level import ofds-botand a plugin-local fake context / harness resolver.
The FlyBot plugin interface layer, keeping ds-bot intact
Social and tree depend only on a FlyBot interface layer. Upstream Fei2411/DeepSeek-Bot does not have it:
ds-bot/src/host/hooks.js: theflybotCordis service (API v1).ds-bot/src/client/flybot-slots.jsandds-bot/src/client/flybot-registry.js: the browserflybotservice, slots such asflybot.sidebar.section/.structure, andbotTree.ds-bot/types/flybot.d.ts: the contract.- The small hook-in points in core: registering
hooks.jsin theMODULESlist inindex.js, the slot render sites in the client, and theremote.*,transportsandapprovalsseams that social uses.
ctx.effect, ctx.logger and the window.__ModuleLoader__ client format are not FlyBot additions. They are public Cordis/DSH APIs, so plugins may use them directly.
To keep ds-bot itself intact, the layer is handled in one of two ways:
- Upstream it. Open a PR to Fei2411/DeepSeek-Bot that adds the
flybotextension API (hooks service, client slots and registry,flybot.d.ts) as a generic plugin-extension point. FlyBot then consumes an unmodified upstreamds-bot. - An isolated, minimal patch layer. Until then, keep it as one small, self-contained layer on top of an otherwise unmodified upstream: the files above plus the few hook-in lines, as a short series of commits on a
flybot-apibranch (or onepatches/flybot-api.patch). Rebase or reapply it on every upstream sync. Everything else FlyBot changed in core is either upstreamed, moved into a plugin, or listed as a known extra patch.
Recommendation: pursue option 1, and use option 2 in the meantime. Both plugins (tree via recursive-bot, social via us) must depend only on this layer and on public Cordis/DSH APIs, never on other ds-bot internals. The upstream-compat CI job applies the layer to the latest upstream and runs both plugins' tests.
Related: #33 (remaining ds-bot branding). Group B there, meaning the package name, the ds-bot/ folder and the saved-data names, stays ds-bot under this approach, so upstream syncs stay clean.
Decision: pristine upstream, pinned, with FlyBot changes as an ordered patch series
Decided: ds-bot/ is restored to pristine upstream Fei2411/DeepSeek-Bot, pinned at a fixed upstream commit, and kept byte-identical to that pin. Every FlyBot core change becomes an ordered patch series that a script applies, and CI verifies the series. Nobody edits ds-bot/ by hand. This supersedes the "upstream it / isolated patch layer" options above, as follows:
- The isolated patch layer (option 2) becomes the patch series.
- Upstreaming (option 1) remains the way to shrink the series over time.
The pin
- Pin: upstream
2b10c70(2026-10-14 05:36 UTC, the merge of upstream PR Add friend panel wraps mid-word: “Talk through” becomes “Talk / throug / h” #162, package version 0.1.5).- FlyBot's import
28a816chas ads-bot/tree identical to upstream2b10c70:ds-bot(tree5834c00). The current FlyBot changes therefore reduce exactly togit diff 28a816c <FlyBot main> -- ds-bot, which is about 47 files, +4.4k/−2.2k lines in core.
- FlyBot's import
- Tag
v0.1.5(571de06, released 2026-10-13) is older than the import. Itsds-bot/tree (4999b29) differs, so pinning the tag would silently drop upstream fixes FlyBot already runs. - Newer upstream exists:
mainis atf346351(2026-10-14 12:09 UTC, the merge of upstream PR Face-to-face: received human lines render as grey system notices attributed to the Bot; plain chat bumps the turn counter #180 "group-permissions"). That is about 30 commits after the pin, with no new release: the version is still 0.1.5, and npmds-botis also 0.1.5.- GitHub reports no common ancestor between
2b10c70and upstreammain, which suggests upstream history was rewritten after the pin. The pin must therefore be recorded as a tree hash plus a commit, not only as a branch position. - The first bump (to
f346351or a futurev0.1.6) is the first exercise of the upgrade procedure.
- GitHub reports no common ancestor between
- Record the pin in
ds-bot.pin.json:{ "repo": "Fei2411/DeepSeek-Bot", "commit": "2b10c70…", "tree": "5834c00…", "version": "0.1.5" }.
The patch series
patches/ds-bot/NNNN-<topic>.patch, in git format-patch form with a message and an author. Each patch has one topic, ordered so the earliest ones are the most likely to go upstream:
- Generic fixes worth upstreaming, for example the update-check tag fallback, and remote-chat and fence fixes that are not FlyBot-specific.
flybotinterface layer, host side:src/host/hooks.jsand its registration inindex.js.- Interface layer, client side:
flybot-slots.js,flybot-registry.js, the slot render sites, and the regeneratedclient.js. types/flybot.d.ts.- Social seams:
remote-chat.jsand theremote.*/transports/approvalshooks inchannel,delivery,tools,rosterandstore. - FlyBot release metadata, if wanted: the repository and update source in
package.json,ISSUES_URLand branding of UI strings (see Keep ds-bot/ identical to upstream; FlyBot branding only at the root and in the plugins #33). Keep this last, so dropping it is trivial.
Tests for each patch live inside that patch. client.js is generated: rebuild it after applying the series rather than patching it, or put the rebuild in the final patch.
Mechanism options
| How | Pros | Cons | |
|---|---|---|---|
| A. patch-package style | Install upstream ds-bot from npm or GitHub, then a postinstall applies patches/ds-bot+0.1.5.patch |
Familiar; no vendored copy | One big patch per package version; only for npm-installed deps. DSH profiles install plugins with pnpm from GitHub paths, so users would need the postinstall in their profile. Pins can only be npm versions, and upstream's main is unreleased |
| B. git subtree (squash) + patch series ⭐ | git subtree add/pull --prefix=ds-bot --squash at the pin keeps ds-bot/ pristine with recorded upstream history. patches/ds-bot/*.patch are applied by scripts/ds-bot-patches.mjs (git apply --3way in order) into a generated core/ directory |
Pristine copy and upstream link in git. Bumps are git subtree pull, then re-apply. Ordered, reviewable patches. Plain git, no extra tools |
core/ is a generated copy that must be committed, or built for installs (see below). Subtree commands are a little obscure (wrap them in the script) |
| C. vendored + quilt | Copy upstream into ds-bot/ by tarball or script, manage patches/series with quilt (quilt push -a, quilt refresh) |
Best tooling for maintaining a patch stack (refresh, fold, reorder) | Extra tool, unfamiliar on macOS and Windows dev boxes. No git-level upstream link |
Recommendation: B. It uses git subtree for the pristine pin and a small Node script for the ordered series. If maintainers want quilt's ergonomics, the series stays quilt-compatible: a patches/ds-bot/series file plus plain -p1 patches.
Layout and installs
ds-bot/is pristine upstream at the pin (subtree). CI fails ifgit rev-parse HEAD:ds-bot≠ the pinned tree.patches/ds-bot/seriesplusNNNN-*.patch.core/is the patched core, generated bynode scripts/ds-bot-patches.mjs applyand committed, so that Git installs keep working with no build step:github:ThinkFlowLab/FlyBot#<tag>&path:/core. CI regenerates it and fails on any diff. The alternative is to publish the patched core as an npm package or a release tarball and drop the committedcore/. Installed package name: keepds-bot, so existing profiles, storage keys andpeerDependencieskeep working.plugins/social,plugins/treedepend on the patched core (link:../../corein tests) and only on the interface layer.
CI
ds-bot-pristine:ds-bot/matchesds-bot.pin.json(tree hash).patches-apply: the series applies cleanly to the pin in order (git apply --check, no fuzz), and the regeneratedcore/equals the committedcore/.core-test: the current matrix runs incore/(keep the required job nameubuntu-latest · Node 24 · DSH 0.2.0-rc.2, or update branch protection with it).plugins: a matrix overplugins/*againstcore/.- Scheduled
upstream-next: applies the series to upstreammainHEAD and runs the tests, then opens an issue when patches stop applying or tests fail. This gives early warning before a bump.
Upgrade procedure (bumping the pin)
node scripts/ds-bot-patches.mjs bump <commit|tag>: runsgit subtree pull --prefix=ds-bot --squashat the new upstream ref and updatesds-bot.pin.json.… apply: re-apply the series with--3way. For each patch that conflicts, fix it and regenerate it (… refresh NNNN). Drop patches that upstream has absorbed.- Rebuild
client.js, regeneratecore/, run all tests, and updateCHANGELOG.mdwith "Upstream ds-bot → ". - Open one PR per bump. Reviewers see three things: the upstream diff (subtree squash), the patch diffs and the
core/diff.
Migration (not started; no code changes yet)
- Merge PR Brand everything as FlyBot; rewrite README around installing FlyBot and its plugins #27, which keeps
ds-bot/equal to today'smainand adds no new core edits. - Generate the series from
git diff 28a816c main -- ds-bot, split by topic as listed above. - Reset
ds-bot/to upstream2b10c70throughgit subtree(squash), and addcore/(the generated output) and the CI jobs. - Move the plugins (the earlier sections; tree is recursive-bot's part).
- Switch the install specs to
path:/coreandpath:/plugins/*, update the user's Mac (stop the launchd jobs, re-add the plugins, start the jobs), and retireflybot-plugins. - First bump to upstream
f346351orv0.1.6.
Proposed target layout
FlyBot/
core/ # ds-bot: upstream Fei2411/DeepSeek-Bot + the FlyBot API patch (see options)
plugins/
social/ # @flybot/social, own package.json, lockfile, tests, README
tree/ # @flybot/tree (recursive-bot)
packages/test-kit/ # @flybot/test-kit: fake Cordis ctx, DSH harness resolver, mock LLM (extracted from ds-bot/test + dev)
docs/ README.md
.github/workflows/ # core.yml, plugins.yml (matrix over plugins/*), release.yml
How ds-bot is consumed (choose one)
- A. Recommended short term. Keep
ds-bot/in-tree as a tracked copy, but turn it into a real fork with history: addFei2411/DeepSeek-Botas a remote and record a merge of upstream2b10c70with-s ourson top of the import, so latergit merge upstream/mainworks. Keep FlyBot's API changes as a small, reviewable set of commits. Plugins move to/plugins/*and depend onds-botthrough a workspace link ("ds-bot": "workspace:*"/link:../../ds-bot) for tests. - B. Long term. Upstream the
flybothooks API (hooks.js, slots,flybot.d.ts) to Fei2411/DeepSeek-Bot. Then FlyBot stops carrying core code: plugins peer-depend on npmds-bot@^xwith the API, and CI tests against pinned plus latest upstream. - C. A git submodule pointing at a FlyBot fork of DeepSeek-Bot (branch
flybot). This is cleaner separation than A, but it adds submodule friction and the install pathpath:/ds-botdisappears.
In every option, install specs change to github:ThinkFlowLab/FlyBot#<ref>&path:/plugins/social and …/plugins/tree. The core keeps path:/ds-bot (A) or moves to npm / its own repo (B, C).
Migration steps (our part: social + repo/CI)
- Extract the test kit: create
packages/test-kitfromds-bot/test/fake-ctx.js,test/harness-resolve.mjsanddev/mock-llm.mjs. ds-bot keeps local copies or imports them, so upstream merges stay small. - Restore upstream history: add the upstream remote and the
-s oursmerge (option A), and documentgit merge upstream/mainin CONTRIBUTING. - Move social:
git mv ds-bot/plugins/social plugins/social. Give it its ownpackage.jsonscripts, a lockfile anddevDependencies: { "ds-bot": "link:../../ds-bot", "@flybot/test-kit": "link:…" }, and set the peer rangeds-bot: ">=0.1.5-flybot.1"(or similar). - Fix social's tests: rewrite the imports to
ds-bot/@flybot/test-kit, and deleteds-bot/test/social-plugin.test.js. - Tree: recursive-bot does the same for tree (table above), including moving
tree-host.test.js. - Lint: remove
pluginsfrom ds-bot'sscripts/lint.mjs, and give plugins a root oxlint config. - Docs: update the READMEs (install paths),
plugins/README.mdand the known issues. Keep a redirect period: leave a stubds-bot/plugins/README.mdthat points to the new paths. - Releases: tag
core-vX,social-vXandtree-vXseparately (or one tag), and teach the update check about plugin tags if plugins should self-update. Today it only updatesds-bot.
CI impact
- Split into
core(working dirds-bot, the current matrix) andplugins(a matrix overplugins/*, each runningnpm ci && npm test, with ds-bot linked from the checkout). Add a scheduledupstream-compatjob that installs the latest npmds-bot/ upstreammainwith the FlyBot patch and runs plugin tests to catch API drift. - Branch protection: the required context
ubuntu-latest · Node 24 · DSH 0.2.0-rc.2is the job name oftest.yml. Keep that job name for core, or update protection in the same change, otherwise every PR blocks. Addplugins (social)andplugins (tree)as required checks after they are green. - ci: CI modeled on yetone/magpie (macOS leg, plugin tests + coverage, package check, tag release) #29 (ci-magpie) already adds plugin tests + coverage and a pack check that assume
ds-bot/plugins/*. Rebase it after the move or fold it in. ci: slim Test matrix to ubuntu Node 24 + DSH 0.2.0-rc.2 #31 (slim matrix) changes the sametest.yml. Merge ci: slim Test matrix to ubuntu Node 24 + DSH 0.2.0-rc.2 #31 first, then restructure on top.
Install and upgrade impact (user's Mac, launchd com.flybot.web :3080/:3081)
- The profile
package.jsonandcordis.patch.ymlentries for the plugins point at…#flybot-plugins&path:/ds-bot/plugins/{social,tree}. After the move they must become…&path:/plugins/{social,tree}. - Plan: (1) stop both launchd jobs (
launchctl bootout gui/$UID/com.flybot.web…); (2) rundsh plugin --profile <p> remove @flybot/social @flybot/tree, thenaddwith the new paths; keepds-botas is; (3) start the jobs again and check both ports. Plugin data lives in ds-bot's store underfields['flybot-social' | 'flybot-tree']and is keyed by plugin id, not path, so no data migration is expected. Back upDSH_HOMEfirst. - Retire the fork branch
flybot-pluginsonce its +9.3k-line delta has landed onmainvia PRs. Install fromThinkFlowLab/FlyBottags afterwards.
Risks
- Plugin API owned only by FlyBot: the
flybotAPI is a FlyBot patch. Upstream ds-bot changes, for example in the store, turns or remote chat, can silently break social or tree. Mitigate with option A's history, the compat job and an API version check. - Divergence is already large (+4.4k lines in core, +9.3k on
flybot-plugins). The first real upstream merge will be painful whatever layout is chosen. - Install URL change: this breaks auto-update for existing Git installs of the plugins. The update check only knows
ds-bot. - Branch protection and open CI PRs (ci: CI modeled on yetone/magpie (macOS leg, plugin tests + coverage, package check, tag release) #29, ci: slim Test matrix to ubuntu Node 24 + DSH 0.2.0-rc.2 #31): merge conflicts, or blocked merges if job names change.
- Peer range
*today: a mismatched core and plugin load without warning. - Duplicate tests: while tree's tests are mid-move (recursive-bot), CI may run them twice or not at all. Coordinate the order.
Ownership
- tree: recursive-bot (dependency table above, moving
tree-host.test.js). - social plus repo, CI and docs restructuring: this issue's owner.
- No code changes until this plan is agreed.
Activity
Restore ds-bot/ to main: keep the core identical to upstream (ThinkFl…
Pin pristine upstream ds-bot and apply FlyBot's core changes as an or…
Merge pull request #37 from Birfy/ds-bot-pin
tree: move @flybot/tree to repo-root plugins/tree on the pinned core;…
Move social to plugins/social; per-plugin CI jobs (#34 PR 2)
Merge pull request #40 from Birfy/plugins-decouple
hsliuustc0106 commentedon Oct 14, 2026
Closing: peer plugins + pristine pin landed — #37 (pin + patch series →
core/), #40 /d9c8a97/76bc623(social+tree underplugins/, per-plugin CI). Remaining follow-ups (upstream bump past pin, API upstreaming) are separate slices.Branding: Wingbot logo + wordmark, README hero and architecture (#43)