- Add group-by-type button to extensions category header (mirrors the
agent group-by-kind pattern) — syncs with the extensions page Switch
via shared SidebarDataContext state
- Relocate both group and refresh controls to sit left-aligned
immediately after the title for both Agent and Extensions sections
- Add plugins.groupByTypeShort i18n key to all 8 locales
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a toggle (left of the "+" button) that groups the Agent section by
kind, showing "Agent" and "Pipeline" sub-headers. State persists in
localStorage, mirroring the extensions group-by-type pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the text label; keep the icon with a title tooltip so names have
more room in the narrow sidebar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a trailing badge (icon + label) to each entry in the sidebar Agent
section so users can tell Agent orchestration apart from legacy Pipeline.
Thread the agent `kind` field through SidebarEntityItem.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Only surface `ns.*` wildcard when the namespace has 2+ concrete events,
so single-event adapters (legacy) show just the one event
- Show raw event code as a muted subtitle under each option's label
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Move conditions toggle between event select and arrow; drop "IF" label
- Remove "all events" (*) option from event select
- Add i18n labels for concrete event names (zh/en/ja) via bots.eventNames
- Narrow fallback event set to message.received for adapters without
declared supported_events (legacy adapters only emit message.received)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 12 old adapters that now have an EBA replacement are tagged
`spec.legacy: true` in their source manifests. Principle: don't delete,
de-emphasize.
- sources/*.yaml (aiocqhttp, dingtalk, discord, kook, lark,
officialaccount, qqofficial, slack, telegram, wecom, wecombot,
wecomcs): add spec.legacy: true
- Adapter / IChooseAdapterEntity types: add optional legacy flag
- BotForm adapter Select: split legacy adapters into a collapsed,
grayscale group at the bottom with an explanatory hint; auto-expand
when the bot already uses a legacy adapter
- Wizard platform picker: same collapsed legacy section
- i18n: legacyAdapters / legacyAdaptersHint (zh-Hans, en-US)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unorchestrated events are discarded by default, so an explicit discard
target is redundant. Drop the discard CommandGroup (and the now-unused
CommandSeparator import); the currentLabel() discard branch is kept so
any pre-existing discard bindings still render correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
groupByCategory pushed multi-category adapters (lark, wecom, discord,
slack) into every matching bucket, so the adapter Select rendered
duplicate SelectItem values — triggering React duplicate-key warnings
and corrupting Radix item tracking. Assign each item to its highest
-priority matching category only. Also de-dupes the wizard card grid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace legacy pipeline binding card + RoutingRulesEditor with unified
EventBindingsEditor; remove use_pipeline_uuid/pipeline_routing_rules
from bot form schema and API update handler
- Add _augment_event_data() to botmgr for filter virtual fields
(message_text, message_element_types, chat_type)
- Add alembic migration 0009: migrate use_pipeline_uuid and
pipeline_routing_rules into event_bindings on first run
- Fix command.tsx: data-[disabled] -> data-[disabled=true] so cmdk 1.x
items (data-disabled=false) are not pointer-events:none
- EventBindingsEditor: onSelect on CommandItems, filter conditions panel,
disabled bindings section, dnd reorder
- i18n: add filter/condition keys for zh-Hans and en-US
- Update tests to match new bot service behavior
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Normalize monitoring API responses before rendering so empty or error payloads with data:null cannot crash the dashboard. Also guard chart, token, and box session arrays before reading length/map.
The in-product plugin/add-extension help links went through
link.langbot.app/{lang}/docs/plugins, which now 404s (it resolved to the
removed /usage/plugin/plugin-intro path). Point them directly at the
current docs page docs.langbot.app/{lang}/plugin/plugin-intro (verified
200 for zh/en/ja).
The previous truncate/shrink-0 pass only touched leaf nodes, but the
min-content floor was set by two ancestors: the flex-1 left group lacked
min-w-0, and CardHeader is a CSS grid whose implicit single column
defaults to min-content. Constrain both (min-w-0 on the header grid +
explicit grid-cols-[minmax(0,1fr)], min-w-0 on the inner flex groups) so
the provider name / base_url+key subtitle actually truncate instead of
forcing the card — and the whole settings modal — wider than the viewport.
- PanelToolbar: allow wrapping and tighten padding on small screens so the
primary action (e.g. "创建 API 密钥") no longer runs off the dialog edge.
- ProviderCard header: let the provider name truncate and pin the model-count
badge and right-side action group with shrink-0 so credits / + controls stay
inside the card on narrow viewports.
Radix tooltips open on hover/focus only and stay closed on touch input,
so on mobile every hover tooltip was unreachable. Detect coarse/no-hover
pointers via matchMedia and drive the tooltip's open state ourselves so a
tap on the trigger toggles it. Desktop hover/focus behaviour is unchanged
(we only intercept the tap when the device has no hover capability). Fixes
all tooltips app-wide from the shared primitive.
Two unrelated frontend fixes:
- LanguageSelector mounts each time the sidebar account dropdown opens and
unconditionally called i18n.changeLanguage() on mount, emitting a
languageChanged event even when the language was unchanged. That handed
every useTranslation() consumer a fresh `t` reference, re-running effects
keyed on `t` (e.g. the plugins page system-status fetch) and surfacing as
a page "refresh". Guard the call so it only fires on an actual change.
- Plugin logs auto-refresh control changed from a toggle Button to a
Switch + Label; the on/off button i18n keys are replaced by a single
static logsAutoRefresh label across all 8 locales.
Port the Space marketplace recommendation carousel UX into the in-app
add-extension page: a 10s auto-advance driven by a smooth countdown ring
that doubles as a pause/resume toggle, and manual prev/next now reset the
countdown. Adds market.recommendation.{pause,resume} across 8 locales.
Replace fixed grid-cols breakpoints (which forced up to 4 narrow cards on
wide screens) with auto-fill columns and a 24rem minimum card width on
both the main market grid and the featured recommendation rows. The
featured rows already measure real column count via ResizeObserver, so
pagination adapts automatically.
Replace the three-way transport choice (stdio / sse / httpstream) for
connecting LangBot to external MCP servers with two modes: local (stdio)
and remote. Remote servers only require a URL; the runtime auto-detects
the transport (tries Streamable HTTP, falls back to SSE).
- provider/tools/loaders/mcp.py: add _init_remote_server() with
Streamable-HTTP-then-SSE probing; dispatch 'remote' lifecycle, keep
legacy sse/http branches for back-compat
- plugin/connector.py: normalize legacy http/sse marketplace modes to
'remote' on Space install, preserving connection params
- entity/persistence/mcp.py: document mode as stdio, remote (legacy: sse, http)
- alembic 0006: idempotent data migration mapping existing sse/http rows
to remote (downgrade maps back to http)
- api/http/service/mcp.py: stash runtime_info (status + tool list) into
test task metadata before tearing down the temp session
- web: collapse mode dropdown to local/remote, remote renders URL+timeout
only, edit auto-maps legacy sse/http to remote; show tools after test in
create mode from task metadata; remove dead plugins/mcp-server/ tree
- i18n: local/remote labels + mode/url hints across 8 locales
- monitoring: stack filters full-width, scrollable tab bar, reduce card/content padding on mobile
- models dialog: provider form modal no longer overflows viewport on small screens; shared panel body padding shrinks on mobile
- plugin logs: reduce horizontal padding on mobile
* feat(api): support global API key from config.yaml (api.global_api_key)
Accept a config-defined global API key anywhere a web-UI key is accepted
(X-API-Key / Bearer), with no login session and no DB record. Useful for
automated deployments and AI agents (HTTP API + MCP). Defaults to empty
(disabled); does not require the lbk_ prefix.
- templates/config.yaml: add api.global_api_key with security notes
- service/apikey.py: verify_api_key checks global key first (constant-time)
- docs/API_KEY_AUTH.md: document the global key + security guidance
- tests: cover global-key match, prefix-free, fallback-to-db, disabled
* feat(mcp): expose LangBot management as an MCP server at /mcp
Add an MCP (Model Context Protocol) server so external AI agents can manage a
LangBot instance. Reuses the same API-key auth as the HTTP API (including the
config.yaml global API key).
- pkg/api/mcp/server.py: FastMCP server wrapping the service layer; 21 curated
tools across system/bots/pipelines/models/knowledge/mcp-servers/skills
- pkg/api/mcp/mount.py: ASGI dispatcher fronting Quart; authenticates /mcp
requests with an API key, runs the streamable-HTTP session manager lifespan
- controller/main.py: serve the wrapped ASGI app via hypercorn (was run_task)
- web: new 'MCP' tab in the API integration dialog showing endpoint, auth, and
client config; i18n for 8 locales
- tests/manual/mcp_smoke.py: e2e check (401 unauth, list tools, call tools)
Tool surface is intentionally curated (not all ~25 route groups) to keep the
agent surface small, safe, and maintainable. Extend deliberately.
* feat(skills): add in-repo skills/ as the single source of truth
Migrate the agent skills + QA/e2e test harness from the (now archived)
langbot-app/langbot-skills repo into LangBot/skills/, and add four new skills.
Migrated:
- langbot-plugin-dev, langbot-testing (e2e), langbot-env-setup,
langbot-skills-maintenance, langbot-eba-adapter-dev
- the bin/lbs CLI (src/, test/, scripts/, schemas/, qa-agent-docs/)
New:
- langbot-dev core backend + web development
- langbot-deploy Docker/K8s deployment + config.yaml + global API key
- langbot-mcp-ops operating the LangBot MCP server (/mcp)
- langbot-space-ops operating the Space marketplace MCP server
- src/cli.ts repoRoot(): recognize the skills assets root (skills.index.json +
bin/lbs) so the CLI works when nested inside the LangBot repo
- README.md: unified skill catalog; skills.index.json regenerated
Parity with source verified: bin/lbs validate + node test suite match the
source repo (only the uncommitted .lbpkg build-artifact fixture differs).
* docs(agents): document agent-facing surfaces + API/MCP/skills sync rule
* docs(readme): add 'Built for AI Agents' section across all locales
Highlight MCP server, in-repo skills (single source of truth), AGENTS.md
sync rule, and llms.txt. Cross-link LangBot Space MCP marketplace.
* style(mcp): fix ruff format + prettier lint in MCP server and API panel
* style(web): prettier format MCP i18n locale entries
* docs(skills): note MCP instance control in dev/testing skills
All development-guidance skills now point to the LangBot instance MCP
server (/mcp) and the Space marketplace MCP server, reusing API keys.
Make the quick-filter tag pills more compact: h-8 -> h-7, default text
-> text-xs with px-2.5, gap-2 -> gap-1.5, and the selected-X icon
h-3.5 -> h-3. Keeps the single-row horizontal-scroll layout.
With many category tags the quick-filter row used `sm:flex-wrap` on
desktop, so once tags overflowed the available width they wrapped onto a
second, center-aligned line — leaving an orphan tag floating under the
row (looked broken and only gets worse as more tags are added).
Make the row a single, never-wrapping line that scrolls horizontally at
every breakpoint, left-aligned, with the scrollbar hidden and a subtle
right-edge fade to signal there's more to scroll. Adds a reusable
`.scrollbar-hide` utility to global.css.