Put Claude Code and Codex Inside WeChat, Lark and DingTalk with LangBot Runner Plugins
Scenario A team already lives in Claude Code and Codex — repos, shell, MCP servers on their own machines. Colleagues should reach that same power from WeChat Work, Lark and DingTalk group chats, without a terminal. On 4.10 that was awkward — not inbound: 4.10.11 already ships openclaw_weixin , lark

A team already lives in Claude Code and Codex — repos, shell, MCP servers on their own machines. Colleagues should reach that same power from WeChat Work, Lark and DingTalk group chats, without a terminal.
On 4.10 that was awkward — not inbound: 4.10.11 already ships openclaw_weixin, lark, dingtalk under src/langbot/pkg/platform/sources/. The processing side was the wall: nine built-in runners under src/langbot/pkg/provider/runners/ ( localagent.py, difysvapi.py, … weknoraapi.py ), so adding Claude Code meant patching LangBot or wrapping the CLI in an HTTP service outside the bot.
4.11 flips that: a Runner is a plugin, and the platform is only the event source. Support depends on the adapter and platform — check the support matrix first.
New in 4.11: the built-in runners are gone. src/langbot/pkg/provider/runners/ holds ten files at v4.10.11 and does not exist at v4.11.0; src/langbot/pkg/provider/ keeps only modelmgr/, session/, tools/ ( git ls-tree, both revisions).
A Runner is a plugin component. The runtime reports installed plugins' runners over LIST_RUNNERS; the host discovers and caches them in RunnerRegistry ( src/langbot/pkg/agent/runner/registry.py:20,24,152 ), generating ids as plugin:author/plugin_name/runner_name ( src/langbot/pkg/agent/runner/id.py:24-27 ) and rejecting anything without that prefix ( id.py:52-57; PROTOCOL_V1.md §13).
The manifest is the contract: id, name, label, description, capabilities, permissions, config_schema, metadata (§4.2); capabilities is a typed boolean model for UI and resource projection, not a permission grant, and permissions is the access ceiling, intersected with the binding policy.
Official ids: plugin:langbot-team/ClaudeCodeAgent/default, plugin:langbot-team/CodexAgent/default ( docs/agent-runner-pluginization/OFFICIAL_RUNNER_PLUGINS.md:33-34 ).
New in 4.11: the adapter layer. src/langbot/pkg/platform/adapters/ is absent at v4.10.11; 4.11 splits it into twelve packages with manifest.yaml, adapter.py, event_converter.py, and the Lark manifest declares kind: MessagePlatformAdapter ( src/langbot/pkg/platform/adapters/lark/manifest.yaml:1-4,13 ). The inbound file inventory is unchanged: between v4.10.11 and v4.11.0, all 22 files under src/langbot/pkg/platform/sources/ are modifications — no additions, no deletions — so no new platform arrived here.
Where config lands: ai.runner.id stores the runner id and ai.runner_config[<runner_id>] its settings ( src/langbot/templates/metadata/pipeline/ai.yaml:6-18 ), so switching runners keeps earlier settings.
Runner plugins execute in a separate plugin-runtime container, so this is a two-container setup (local demo values — replace them):
docker network create lang7-411 docker run -d --name lang7-411-core --network lang7-411 -e BOX__ENABLED = false \ -p 15400:5300 -v " $PWD /deploy/411/data:/app/data" rockchin/langbot:v4.11.0 docker run -d --name lang7-411-runtime --network lang7-411 --network-alias langbot_plugin_runtime \ -v " $PWD /deploy/411/data/plugins:/app/data/plugins" \ rockchin/langbot:v4.11.0 uv run --no-sync -m langbot_plugin.cli.__init__ rt Enter fullscreen mode Exit fullscreen mode The admin account starts as admin@langbot.local; the password is a local demo value — replace it.
Step 1 — install from the marketplace. UI: Extensions → Extension Marketplace ( web/src/i18n/locales/zh-Hans.ts:337-341 ); CLI: POST /api/v1/plugins/install/marketplace with {plugin_author, plugin_name}, returning {task_id} for task plugin-install-marketplace; the record carries install_source=marketplace ( src/langbot/pkg/api/http/controller/groups/plugins.py:923-960 ). Here we installed langbot-team/LocalAgent; the Claude Code and Codex plugins use the same door (repo langbot-plugins, Runner/claude-code-agent, Runner/codex-agent ).
Step 2 — confirm where it landed. The artifact is not in the mounted plugins/ directory but inside the runtime container at /app/data/plugin-runtime/artifacts/sha256/<digest>/code/ ( manifest.yaml, main.py, pkg/, components/, CERTIFICATION.md ), while the host-mounted plugins/ stays empty (observed 2026-10-16); <digest> is the recorded artifact_digest. Three checks: the API ( GET /api/v1/plugins returns a {plugins:} array containing it, plugins.py:387-397 ); the database (a plugin_settings row holding artifact_digest, runtime_revision, install_source, enabled, src/langbot/pkg/entity/persistence/plugin.py:12-35,46-51 ); and the logs — Mounting plugin langbot-team/LocalAgent... → Initializing Runner component: default → Component default initialized, instance type: DefaultRunner (observed 2026-10-16).
The point: the runtime instantiates a component instance and the Runner capability hangs off it.
Step 3 — pick the Runner in the pipeline. In the pipeline editor open AI capabilities ( web/src/i18n/locales/zh-Hans.ts:1941 ) and find the stage card labelled "运行方式" ( src/langbot/templates/metadata/pipeline/ai.yaml:7-9 ) with its required "运行器" dropdown (:14-18 ). The value is the full runner id, e.g. plugin:langbot-team/LocalAgent/default; for Claude Code, plugin:langbot-team/ClaudeCodeAgent/default. Here the LocalAgent block at ai.runner_config[<runner_id>] read timeout: 300, max-tool-iterations: 100 (observed 2026-10-16).
Step 4 — connect the chat platform as an event source. Connect a WeChat Work, Lark or DingTalk adapter; events arrive, event bindings route them into a pipeline, and the selected Runner does the work — one bot, several pipelines, each free to use a different Runner.
Claude Code and Codex settings come from their READMEs: command defaults to claude / codex, timeout to 300 / 1800 seconds, plus workspace, ssh-target, mcp-servers-json ( Runner/claude-code-agent, Runner/codex-agent ).
API: GET /api/v1/plugins contains langbot-team/LocalAgent with enabled true. Database: digest and revision persisted — values observed here:
SELECT plugin_author, plugin_name, artifact_digest, runtime_revision, install_source, enabled FROM plugin_settings; -- langbot-team|LocalAgent|2d2ce17055d70dd7736e6902595be3262992fd1598a00bd9d79d19fef1c16329|1|marketplace|1 Enter fullscreen mode Exit fullscreen mode Runtime container: the same ls shows the files listed above while the host-mounted plugins/ stays empty; an empty result means the wrong layer. Pipeline: ai.runner.id must start with plugin: ( web/src/app/home/pipelines/pipeline-config-safety.ts:7-25 ) and ai.runner_config must hold an object keyed by that id. Then send a real message and confirm the reply comes from the installed Runner.
In 4.11 a Runner is not shipped with LangBot but installed from the marketplace, configured in the app, selected per pipeline. The platform delivers the event; the Runner decides how it is processed — a local CLI agent (Claude Code, Codex, ACP) or a service runner such as Dify, Coze, n8n, Langflow or DeerFlow.
Primary reference: the pinned migration doc pipeline-migration-config-map.zh-CN.md, which also lists the minimum Runner plugin versions the migrator requires; the v4.11.0 release page has the notes. Plugins are also listed in Runner/ in langbot-plugins.
Next: if your instance was upgraded from 4.10, that old config landing in the new shape is article 3 — the migration touches ai.runner.runner, ai.runner.id and ai.runner_config together. For a hosted environment, Cloud is worth a separate look ( https://space.langbot.app ).