Skip to content

ctx.flybot: extension API in ds-bot for FlyBot plugins #4

Closed

Description

@Birfy

Part of #3 (umbrella). Implemented in #2.

Goal

Give ds-bot a stable, documented extension API so FlyBot features ship as plugins under ds-bot/plugins/ without patching ds-bot internals.

Design

  • Host service flybot implemented in ds-bot/src/host/hooks.js, registered with ctx.provide('flybot') (required by Cordis 4; ctx.set alone fails with cannot set property "flybot" without provide).
  • Plugins declare inject: ['flybot'] and call ctx.flybot.scope('<plugin-name>') to get a handle whose fields and events are namespaced to that plugin. The unscoped service uses the shared namespace.

API surface

Fields

  • fields.get(botId, key), fields.set(botId, key, value) (synchronous; persisted in the background; null deletes), fields.all(botId), fields.query(key, value?).
  • Stored as state.bots[id].fields[plugin][key] in $DSH_HOME/bot/state.json.
  • on('change', fn, { all? }) -> { botId, plugin, key, value, prev }; only your own plugin's fields unless { all: true }.

Bots

  • bot.list() (local bots only), bot.get, bot.find, bot.of(sessionId), bot.isRemote(ref).
  • bot.create({ name, systemPrompt, fields, role?, instructions? }) -> Promise<id>, bot.setSystemPrompt(id, text).
  • Events: bot.created (new record), bot.removed (fires after removal; payload carries the last record including fields, so bot.list() no longer contains it).

Sending

  • bot.send({ from, to, text, role?, hop?, meta?, attachments? }) -> { action: 'allow' | 'hold' | 'deny', ... }.
  • Remote targets: '@handle' or { kind: 'remote', handle, home? }.
  • Middleware: bot.send.use({ name, before?, after?, handle(msg) }). msg.target is { remote: false, id } or { remote: true, handle, home }.
    • undefined / allow continues; deny and hold stop the chain; a throw counts as deny.
    • allow with bypass: ['social'] only takes effect if config.flybot.bypass[name] lists it.
  • Every built-in bot-to-bot send (message_bot, stop_bot, replies) goes through the chain. The gate wraps rt.deliver.
  • Held messages become owner approval cards (flybot.approvals).

Tools, transports, approvals, API

  • tools.register({ name, parameters, visibleTo(botId), execute(args, { botId }) }); JSON Schema parameters are converted to DSH's format (minLength/maxLength must be checked in execute).
  • transports.add(kind, { send }).
  • approvals.list/get/decide/onChange — decisions are owner-only from the UI, never a model tool.
  • api.handle(name, fn), exposed as flybot/<name> on the existing api/bot route.
  • remote.publish/list/get for remote members.

Browser

  • ctx.flybot.bots() (items include fields), ctx.flybot.fields.on('change', fn).
  • UI slots: flybot.sidebar.section, flybot.composer.mention, flybot.bot.details, flybot.approvals.

Types

  • ds-bot/types/flybot.d.ts, exported via package.json (types, ./types). Checks clean with tsc --noEmit.

Files changed in ds-bot

index.js, src/host/{hooks.js (new), delivery.js, tools.js, channel.js, roster.js, store.js, prompt.js}, src/client/{flybot-registry.js, flybot-slots.js (new), sidebar.js, at-menu.js, bot-settings.js, index.js}, rebuilt client.js, test/fake-ctx.js, scripts/lint.mjs (lints plugins/), package.json.

Tests

  • New API tests plus a Cordis 4 ctx.provide test. Full suite 567/567 on Node 22 and 24; lint 0 errors; build-client.mjs --check passes.

Open items

  • Hide tools from a bot's tool list when visibleTo is false (today only enforced at execute time).
  • Push browser field events instead of polling every 3 s.
  • via is set by the calling plugin and could be spoofed by another plugin.
  • Held messages are not re-queued after a restart.
  • fake-ctx should enforce real Cordis semantics so regressions like the provide bug are caught.

Activity

  1. added a commit that references this issue on Oct 14, 2026
    0d31b00
  2. added a commit that references this issue on Oct 14, 2026
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