Skip to content

Decouple the social and tree plugins from ds-bot so the core can track upstream #34

Closed

Description

@Birfy

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-bot is the DSH plugin from Fei2411/DeepSeek-Bot (Apache-2.0, npm ds-bot, upstream latest still 0.1.5).
  • FlyBot did not fork it with history: commit 28a816c imported the tree of Fei2411/DeepSeek-Bot@2b10c70 as a single squashed commit (a vendored copy). As a result, git cannot 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: the flybot Cordis service, plugin API v1 (bot.*, fields, scope, tools.register, api.handle, remote.*, approvals, transports, bot.send.use, events).
  • src/client/flybot-slots.js and flybot-registry.js: browser slots such as flybot.sidebar.section / .structure, plus botTree.
  • types/flybot.d.ts.
  • The remote-chat and social-related host changes, for example fix-stale-remote-chat in 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) with dsh.bundle.patch and a client.js in the web module-loader format. Each declares peerDependencies: { "ds-bot": "*" } and inject: ['flybot'].
  • Users add them as separate profile packages: github:ThinkFlowLab/FlyBot#main&path:/ds-bot/plugins/social and …/tree. Upgrades use pnpm 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.js and tree-plugin.test.js re-import the plugin tests, so they run under ds-bot's npm test.
  • ds-bot/test/tree-host.test.js and bot-tree.test.js test tree against the real host.
  • plugins/social/test/social.test.js imports ../../../index.js (the core's apply) and ../../../test/fake-ctx.js. Its npm test uses ../../test/harness-resolve.mjs.
  • ds-bot's scripts/lint.mjs lints plugins/.
  • CI (.github/workflows/test.yml) runs only in working-directory: ds-bot, with one lockfile (ds-bot/package-lock.json).
  • Neither plugin has a build step: client.js is hand-written.

User's Mac install (as reported; not inspected from here)

  • Installed from the fork branch Birfy:flybot-plugins, which is main plus about 76 files and +9.3k lines including social and remote-chat fixes.
  • Served by launchd jobs com.flybot.web on ports 3080 and 3081.
  • Profile entries point at path:/ds-bot, path:/ds-bot/plugins/social and path:/ds-bot/plugins/tree.

CI and governance

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.flybot only (api.handle, bot.list/get/find/send/send.use, fields, tools.register, remote.chats.deliver/onSend, transports.add, approvals.onChange, readOnly, ready, on) plus ctx.effect. These are all in API v1, but remote.*, transports and approvals exist only for social, so they are the most likely to drift from upstream.
  • Tests: social.test.js imports ../../../index.js and ../../../test/fake-ctx.js; npm test uses ../../test/harness-resolve.mjs. These need replacing with a package-level import of ds-bot and 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: the flybot Cordis service (API v1).
  • ds-bot/src/client/flybot-slots.js and ds-bot/src/client/flybot-registry.js: the browser flybot service, slots such as flybot.sidebar.section / .structure, and botTree.
  • ds-bot/types/flybot.d.ts: the contract.
  • The small hook-in points in core: registering hooks.js in the MODULES list in index.js, the slot render sites in the client, and the remote.*, transports and approvals seams 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:

  1. Upstream it. Open a PR to Fei2411/DeepSeek-Bot that adds the flybot extension API (hooks service, client slots and registry, flybot.d.ts) as a generic plugin-extension point. FlyBot then consumes an unmodified upstream ds-bot.
  2. 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-api branch (or one patches/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 28a816c has a ds-bot/ tree identical to upstream 2b10c70:ds-bot (tree 5834c00). The current FlyBot changes therefore reduce exactly to git diff 28a816c <FlyBot main> -- ds-bot, which is about 47 files, +4.4k/−2.2k lines in core.
  • Tag v0.1.5 (571de06, released 2026-10-13) is older than the import. Its ds-bot/ tree (4999b29) differs, so pinning the tag would silently drop upstream fixes FlyBot already runs.
  • Newer upstream exists: main is at f346351 (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 npm ds-bot is also 0.1.5.
    • GitHub reports no common ancestor between 2b10c70 and upstream main, 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 f346351 or a future v0.1.6) is the first exercise of the upgrade procedure.
  • 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:

  1. Generic fixes worth upstreaming, for example the update-check tag fallback, and remote-chat and fence fixes that are not FlyBot-specific.
  2. flybot interface layer, host side: src/host/hooks.js and its registration in index.js.
  3. Interface layer, client side: flybot-slots.js, flybot-registry.js, the slot render sites, and the regenerated client.js.
  4. types/flybot.d.ts.
  5. Social seams: remote-chat.js and the remote.* / transports / approvals hooks in channel, delivery, tools, roster and store.
  6. FlyBot release metadata, if wanted: the repository and update source in package.json, ISSUES_URL and 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 if git rev-parse HEAD:ds-bot ≠ the pinned tree.
  • patches/ds-bot/series plus NNNN-*.patch.
  • core/ is the patched core, generated by node scripts/ds-bot-patches.mjs apply and 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 committed core/. Installed package name: keep ds-bot, so existing profiles, storage keys and peerDependencies keep working.
  • plugins/social, plugins/tree depend on the patched core (link:../../core in tests) and only on the interface layer.

CI

  • ds-bot-pristine: ds-bot/ matches ds-bot.pin.json (tree hash).
  • patches-apply: the series applies cleanly to the pin in order (git apply --check, no fuzz), and the regenerated core/ equals the committed core/.
  • core-test: the current matrix runs in core/ (keep the required job name ubuntu-latest · Node 24 · DSH 0.2.0-rc.2, or update branch protection with it).
  • plugins: a matrix over plugins/* against core/.
  • Scheduled upstream-next: applies the series to upstream main HEAD 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)

  1. node scripts/ds-bot-patches.mjs bump <commit|tag>: runs git subtree pull --prefix=ds-bot --squash at the new upstream ref and updates ds-bot.pin.json.
  2. … 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.
  3. Rebuild client.js, regenerate core/, run all tests, and update CHANGELOG.md with "Upstream ds-bot → ".
  4. 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)

  1. Merge PR Brand everything as FlyBot; rewrite README around installing FlyBot and its plugins #27, which keeps ds-bot/ equal to today's main and adds no new core edits.
  2. Generate the series from git diff 28a816c main -- ds-bot, split by topic as listed above.
  3. Reset ds-bot/ to upstream 2b10c70 through git subtree (squash), and add core/ (the generated output) and the CI jobs.
  4. Move the plugins (the earlier sections; tree is recursive-bot's part).
  5. Switch the install specs to path:/core and path:/plugins/*, update the user's Mac (stop the launchd jobs, re-add the plugins, start the jobs), and retire flybot-plugins.
  6. First bump to upstream f346351 or v0.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: add Fei2411/DeepSeek-Bot as a remote and record a merge of upstream 2b10c70 with -s ours on top of the import, so later git merge upstream/main works. Keep FlyBot's API changes as a small, reviewable set of commits. Plugins move to /plugins/* and depend on ds-bot through a workspace link ("ds-bot": "workspace:*" / link:../../ds-bot) for tests.
  • B. Long term. Upstream the flybot hooks API (hooks.js, slots, flybot.d.ts) to Fei2411/DeepSeek-Bot. Then FlyBot stops carrying core code: plugins peer-depend on npm ds-bot@^x with 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 path path:/ds-bot disappears.

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)

  1. Extract the test kit: create packages/test-kit from ds-bot/test/fake-ctx.js, test/harness-resolve.mjs and dev/mock-llm.mjs. ds-bot keeps local copies or imports them, so upstream merges stay small.
  2. Restore upstream history: add the upstream remote and the -s ours merge (option A), and document git merge upstream/main in CONTRIBUTING.
  3. Move social: git mv ds-bot/plugins/social plugins/social. Give it its own package.json scripts, a lockfile and devDependencies: { "ds-bot": "link:../../ds-bot", "@flybot/test-kit": "link:…" }, and set the peer range ds-bot: ">=0.1.5-flybot.1" (or similar).
  4. Fix social's tests: rewrite the imports to ds-bot / @flybot/test-kit, and delete ds-bot/test/social-plugin.test.js.
  5. Tree: recursive-bot does the same for tree (table above), including moving tree-host.test.js.
  6. Lint: remove plugins from ds-bot's scripts/lint.mjs, and give plugins a root oxlint config.
  7. Docs: update the READMEs (install paths), plugins/README.md and the known issues. Keep a redirect period: leave a stub ds-bot/plugins/README.md that points to the new paths.
  8. Releases: tag core-vX, social-vX and tree-vX separately (or one tag), and teach the update check about plugin tags if plugins should self-update. Today it only updates ds-bot.

CI impact

Install and upgrade impact (user's Mac, launchd com.flybot.web :3080/:3081)

  • The profile package.json and cordis.patch.yml entries 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) run dsh plugin --profile <p> remove @flybot/social @flybot/tree, then add with the new paths; keep ds-bot as is; (3) start the jobs again and check both ports. Plugin data lives in ds-bot's store under fields['flybot-social' | 'flybot-tree'] and is keyed by plugin id, not path, so no data migration is expected. Back up DSH_HOME first.
  • Retire the fork branch flybot-plugins once its +9.3k-line delta has landed on main via PRs. Install from ThinkFlowLab/FlyBot tags afterwards.

Risks

  • Plugin API owned only by FlyBot: the flybot API 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

  1. hsliuustc0106 commented on Oct 14, 2026

    @hsliuustc0106
    Contributor

    Closing: peer plugins + pristine pin landed — #37 (pin + patch series → core/), #40 / d9c8a97 / 76bc623 (social+tree under plugins/, per-plugin CI). Remaining follow-ups (upstream bump past pin, API upstreaming) are separate slices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions