English | ไธญๆ
๐ก Credits: this project is heavily modified and extended from the original 4t145/lark-acp โ adding native support for the Kiro and Amazon Q agents, Feishu/Lark region (domain) switching, Windows build support, and other fixes. Many thanks to @4t145 for the excellent foundation.
Turn your Feishu / Lark bot into an AI coding agent. lark-acp bridges a Feishu/Lark bot to any AI agent that speaks the Agent Client Protocol (ACP) โ Claude Code, Kiro CLI, OpenAI Codex, Google Gemini CLI, GitHub Copilot CLI, OpenCode, Amazon Q Developer CLI, Microsoft Copilot Studio agents, Microsoft 365 Copilot, or your own ACP server.
You send a message in Feishu/Lark; the agent runs on your machine; its thinking, tool calls, and answer stream into a single interactive Feishu card. Tool-call authorization, task interruption, and cross-restart session resume are all handled inside the card.
๐ Found this project useful โ or just mildly interesting? A โญ Star in the top-right corner is the most direct encouragement you can give the author.
โ ๏ธ WIP: still iterating โ CLI options and config fields may change before 1.0.
For real-world use we strongly recommend pairing this with the Lark CLI and its skills โ the bridge injects chat context (chat id, sender name, group name) into the prompt, so the agent can chain into all kinds of Lark operations through the Lark CLI.
Contents
- How it works
- Quick start
- CLI reference โ presets ยท options ยท in-chat commands ยท config file ยท env vars
- Connecting specific agents โ Kiro ยท Gemini ยท Amazon Q ยท Copilot Studio ยท M365 Copilot
- Feishu/Lark developer console setup
- Deployment
- Using as a library
- Troubleshooting
How it works
Feishu / Lark cloud Your machine
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ user message โ WebSocket โ lark-acp bridge โ
โ card button click โ โโโโโโโโโโโโโโบ โ โโ interpreter (msg โ ACP blocks) โ
โ โ (long conn, โ โโ presenter (ACP โ Lark cards) โ
โ interactive card โ no public โ โโ session store (resume across โ
โ (streamed updates) โ IP needed) โ โ restarts) โ
โโโโโโโโโโโโโโโโโโโโโโโ โ โโ ACP client โโโบ agent subprocess โ
โ JSON-RPC 2.0 (claude / kiro โ
โ over stdio / codex / ...) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- No public endpoint required โ events arrive over Lark's persistent WebSocket connection, so the bridge runs fine behind NAT, on a laptop, or in a container.
- One card per task โ thoughts, tool calls, and the final answer are merged into a single continuously-updated card instead of flooding the chat (especially group chats) with messages.
- Per-tool permission cards โ when the agent wants to run a tool, the bridge pops an authorization card and pauses the agent until you answer (unanswered requests auto-cancel after 5 minutes; policy configurable via
--permission-mode). - Session persistence โ chat โ session mappings are stored on disk; agents that support ACP
session/load/ resume pick up right where they left off after a bridge restart. - Concurrent chats โ each Feishu chat gets its own agent session, with idle eviction (
--idle-timeout,--max-chats).
Quick start
Prerequisites: Node.js โฅ 20, a Feishu/Lark custom app (setup below), and at least one agent CLI installed and authenticated.
1. Install (no git clone needed):
npm i -g lark-acp-bridge
# or straight from GitHub (builds on install):
npm i -g github:wthislifehuh/lark-acp-bridge
Either one puts the lark-acp command on your $PATH โ every example in this README uses it.
2. Write your app credentials (one-time):
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/lark-acp"
cat > "${XDG_CONFIG_HOME:-$HOME/.config}/lark-acp/config.json" <<'EOF'
{
"credentials": {
"appId": "cli_a1b2c3d4e5f60001",
"appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"domain": "feishu"
}
}
EOF
chmod 600 "${XDG_CONFIG_HOME:-$HOME/.config}/lark-acp/config.json"
On Windows (PowerShell) the config lives at ~\.config\lark-acp\config.json โ create the same JSON there.
3. Start the bridge:
lark-acp proxy --agent claude # Feishu app (open.feishu.cn)
lark-acp proxy --domain lark --agent gemini # Lark International app (open.larksuite.com)
Then find the bot in Feishu/Lark โ DM it, or add it to a group and @mention it.
โ ๏ธ Region matters: if your app was created on Lark International (
open.larksuite.com), set"domain": "lark"in the config (or pass--domain lark) โ otherwise the handshake is rejected with error code1000040351("Incorrect domain name"). The defaultfeishuis for apps onopen.feishu.cn.
๐ฑ๏ธ On Windows and don't want to use a terminal?
windows/has a double-click.batlauncher per preset (run-claude.bat,run-kiro.bat,run-q.bat, โฆ) โ seewindows/README.md.
Developing on the bridge itself? Run from a checkout instead.
git clone https://github.com/wthislifehuh/lark-acp-bridge
cd lark-acp-bridge
npm install && npm run build
node dist/bin/lark-acp.js proxy --agent claude
# optional: npm link โ makes the bare `lark-acp` command point at this checkout
CLI reference
Command format
lark-acp [global-options] proxy --agent <preset> [-- <extra-args>...]
lark-acp [global-options] proxy -- <agent-cmd> [agent-args...]
lark-acp prepare [--agent <preset>]
lark-acp service <install|uninstall|status> [--agent <preset>]
lark-acp agents
lark-acp help
lark-acp version
Two ways to pick an agent:
--agent <preset>โ use a built-in preset (most common). Runlark-acp agentsfor the full list.-- <agent-cmd>โ a raw command; everything after--is forwarded to the agent verbatim.
They compose: proxy --agent claude -- --debug appends --debug to the preset's args before launching.
Global options must come before the proxy subcommand (--domain is also accepted after proxy for convenience).
Built-in agent presets
| Preset | Description |
|---|---|
claude |
Claude Code via Zed's ACP adapter. Run claude in a terminal once first to log in. |
claude-agent |
Claude Agent SDK adapter โ direct Anthropic API. Needs ANTHROPIC_API_KEY. |
codex |
OpenAI Codex via Zed's ACP adapter. |
copilot |
GitHub Copilot CLI (native --acp). |
gemini |
Google Gemini CLI (experimental --acp). Personal "Sign in with Google" was discontinued โ use an API key instead, see Connecting Gemini. |
opencode |
OpenCode. Assumes opencode is on $PATH. |
kiro |
Kiro CLI (native ACP via kiro-cli acp). Assumes kiro-cli is on $PATH and logged in. See Connecting Kiro. |
q |
Amazon Q Developer CLI via the bundled adapter (q has no native ACP). Needs q on $PATH and q login done. See Connecting Amazon Q. |
copilot-studio |
Microsoft Copilot Studio agent via the bundled Direct-to-Engine adapter. Needs COPILOT_STUDIO_* config + a one-time login. See Connecting Copilot Studio. |
m365-copilot |
Microsoft 365 Copilot (BizChat) via the bundled Graph Chat API adapter (preview API; needs a Copilot license). See Connecting M365 Copilot. |
mock |
Built-in scripted agent (thoughts / tool calls / permission cards / Markdown) for local end-to-end debugging. |
Agents not covered by a preset can be launched as a raw command:
lark-acp proxy -- node ./my-acp-server.js --port 9000
You can also persist your own presets in the config file's agents field (see Configuration file).
Global options
| Option | Description |
|---|---|
--cwd <dir> |
Agent working directory (default: current directory) |
--config <path> |
Override the config file path |
--data-dir <dir> |
Override the session-storage directory |
--domain <region> |
Deployment region: feishu (default, open.feishu.cn) / lark (International, open.larksuite.com), or a full base URL for a self-hosted deployment |
--idle-timeout <min> |
Release a chat's agent session after N idle minutes (0 = never, default 1440) |
--max-chats <n> |
Maximum concurrent chat sessions (default 10) |
--hide-thoughts |
Don't render the agent's thinking process in the card |
--hide-tools |
Don't render tool calls in the card |
--hide-cancel-button |
Don't render the "cancel current task" button at the bottom of the card |
--permission-mode <m> |
Tool authorization policy: alwaysAsk (default, pops a card for the user) / alwaysAllow (auto-approve) / alwaysDeny (auto-reject) |
--owner <open_id> |
Set the bot owner. When omitted, the first user to DM the bot claims ownership (access control is on by default) |
--no-access-control |
Disable access control entirely โ open the bot to everyone in the app's scope (not recommended) |
--identity <policy> |
lark-cli identity policy: bot-only (default) / user-default |
--inject-lark-credentials |
Inject the bot app credentials into the agent subprocess env (for lark-cli; off by default โ see the security note) |
-h, --help |
Show help |
-v, --version |
Show version |
In-chat commands
Send these directly to the bot (in a group, @mention the bot first):
| Command | Effect |
|---|---|
/cancel / /stop / ๅๆถ / ๅๆญข |
Interrupt the current task (the agent process stays alive; the session continues) |
/new / /restart |
Reset the session โ the next message starts a brand-new agent session |
/help / ๅธฎๅฉ |
Show the available in-chat commands |
/status / ็ถๆ |
Show the current status (identity, access, session, agent, WebSocket connection) |
/config / ้
็ฝฎ |
Show the effective configuration (presentation, permission mode, access, identity) โ owner/admin only |
Owner/admin-only access-control commands: /access, /invite user\|admin @โฆ, /invite group, /remove โฆ, /mention on\|off.
Configuration file
The CLI reads one config file (default ~/.config/lark-acp/config.json) holding credentials and runtime defaults. Precedence: CLI flag > environment variable > config file > built-in default.
All fields are optional:
{
"credentials": {
"appId": "cli_xxxxxxxxxxxxxxxx",
"appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
// Deployment region: feishu (default) / lark (International),
// or a full base URL for a self-hosted deployment
"domain": "feishu",
},
"dataDir": "./var/lark-acp",
"runtime": {
"cwd": "/work/project",
"idleTimeoutMinutes": 1440,
"maxChats": 10,
"hideThoughts": false,
"hideTools": false,
"hideCancelButton": false,
"permissionMode": "alwaysAsk",
// WebSocket liveness watchdog window in seconds (default 60; 0 disables).
"pingTimeoutSeconds": 60,
// Abort a WebSocket handshake that stalls past this many ms (default 15000; 0 disables).
"handshakeTimeoutMs": 15000,
},
"access": {
// Access control is on by default; set false to open the bot to everyone.
"enabled": true,
// Pin the owner; when omitted the first user to DM the bot claims it.
"ownerOpenId": "ou_xxxxxxxxxxxxxxxx",
},
"identity": {
// lark-cli identity policy: "bot-only" (default) or "user-default".
"policy": "bot-only",
// Inject bot app credentials into the agent env (for lark-cli). Off by default.
"injectCredentials": false,
// Prepend a chat/sender context block to prompts. On by default.
"promptContext": true,
},
"identity": {
// lark-cli acting identity: "bot-only" (default) or "user-default".
"policy": "bot-only",
// Inject the bot app credentials into the agent env for lark-cli (default false).
"injectCredentials": false,
// Prepend a chat/sender context block to each prompt (default true).
"promptContext": true,
},
"agents": {
// Patch an existing built-in preset โ only write the fields you change
"claude": {
"env": { "ANTHROPIC_BASE_URL": "https://my-proxy.example.com" },
},
// Add your own preset โ must define both `label` and `command`
"my-agent": {
"label": "My ACP Agent",
"command": "node",
"args": ["./my-agent.js", "--acp"],
"description": "Locally developed agent",
"env": { "FOO": "bar" },
},
},
}
lark-acp agents lists every preset available under the current config, tagged with its source ([built-in] / [user] / [overridden]).
Faster cold start: lark-acp prepare
Several presets (claude, codex, copilot, gemini, claude-agent) launch a translation shim via npx -y <pkg>. npx re-resolves the package on every spawn โ adding cold-start latency and, unpinned, silently pulling whatever is "latest".
lark-acp prepare installs those shims once into <data-dir>/shims. After that, proxy launches them directly with node (no npx resolution, no network on spawn, and a version pinned by the prepared install). Nothing else changes โ the same shim runs, so permission cards and every other behavior are identical.
lark-acp prepare # prepare every npx-based preset
lark-acp prepare --agent claude # prepare just one
When a shim isn't prepared, proxy falls back to the original npx invocation, so this is purely opt-in. Native-ACP presets (kiro, opencode) and the bundled adapters (q, copilot-studio, m365-copilot, mock) don't use npx and need no preparation.
Why not bundle the shims as dependencies? They're heavyweight agent packages (the Copilot/Gemini CLIs, the Zed adapters); making them hard dependencies would bloat every install โ including for users who only run a native-ACP agent like Kiro.
preparekeeps installs lean while still giving a fully offline, pinned launch path when you want it.
Environment variables
| Variable | Effect |
|---|---|
LARK_ACP_APP_ID |
Overrides credentials.appId |
LARK_ACP_APP_SECRET |
Overrides credentials.appSecret |
LARK_ACP_DOMAIN |
Overrides credentials.domain |
LARK_ACP_CONFIG |
Overrides the config file path |
LARK_ACP_DATA_DIR |
Overrides the session-storage dir |
LARK_ACP_PERMISSION_MODE |
Overrides runtime.permissionMode |
LARK_ACP_OWNER |
Overrides access.ownerOpenId |
LARK_ACP_IDENTITY |
Overrides identity.policy |
LARK_ACP_TENANT_ID |
Overrides tenantId |
Connecting specific agents
Connecting Kiro
Kiro CLI (AWS's official successor to Amazon Q Developer CLI) supports ACP natively via kiro-cli acp โ no adapter needed. Install Kiro CLI, log in once, then:
lark-acp proxy --age
