- Why - Install - Getting Started - Configure Feishu/Lark - Start With The Console - Start From Codex - How It Works - Behavior And Boundaries - Repository Layout - Development
Codex is strongest when it can work inside your local project, but you are not always sitting at the Mac where Codex Desktop is running. Lark Remote bridges that gap: Codex keeps running locally, while Feishu/Lark becomes the lightweight remote control surface.
The default product shape is narrow on purpose:
Codex Desktop session
|
v
Local Lark Remote bridge
|
v
Feishu/Lark bot and control console
|
v
Observe, choose, or take over local Codex sessions
The bridge is local-first. Existing Codex chat history is not sent to Feishu/Lark during startup. Feishu/Lark users must be allowed before project/session takeover is available, and native Codex permission dialogs stay on the Mac.
This repository is a development workspace for the Lark Remote Codex plugin.
The installable marketplace plugin lives in plugins/codex-lark-remote/; the
repository root keeps tests, release notes, and development-only scripts outside
the marketplace scan surface.
First-time users do not need to clone this repository.
Install the approved marketplace artifact:
npx codex-marketplace add GxFn/LarkRemote/plugins/codex-lark-remote --pluginTo pin the reviewed release:
npx codex-marketplace add https://github.com/GxFn/LarkRemote/tree/v0.3.0/plugins/codex-lark-remote --pluginThen restart or refresh Codex if the plugin list does not update immediately.
If Codex asks for a GitHub target or direct artifact path, use the plugin subdirectory:
https://github.com/GxFn/LarkRemote/tree/v0.3.0/plugins/codex-lark-remote
If the Codex dialog separates source, ref, and sparse path, fill it like this:
Source:
https://github.com/GxFn/LarkRemote.git
Git ref:
v0.3.0
Sparse path:
plugins/codex-lark-remote
Enable codex-lark-remote from the plugin list after installation.
This repository includes .agents/plugins/marketplace.json. It declares the
codex-lark-remote marketplace with a single plugin entry that points at
./plugins/codex-lark-remote. Use main only when you intentionally want
unreleased changes.
The short successful path is:
- Install and enable the plugin in Codex.
- Create a Feishu/Lark internal app and copy its App ID/App Secret.
- Tell Codex
copiedor已复制; Codex reads the clipboard and callslark_configure. - Let Codex run
lark_check_authandlark_verify_setup. - Verify long-connection Event Configuration and Callback Configuration in the Feishu/Lark Open Platform while the bridge is running.
- Return to Codex and explicitly approve connecting the current Codex conversation to Lark Remote.
- Send
whoamito the bot from Feishu/Lark, then add the returnedsenderIdtolark.allowedUsers. - In Feishu/Lark, send
console, choose a project/session, and usetakeover 1or the card buttons.
Daily use after setup is simpler: start Lark Remote from a trusted Codex conversation, open the Feishu/Lark console, choose a session, then send ordinary coding requests after takeover is active.
Create the bot app on the platform you want to connect:
- Open Feishu Open Platform or Lark Open Platform.
- Use
lark.domain: "feishu"for Feishu China, which is the default. Uselark.domain: "lark"for international Lark. App ID/App Secret must come from the same Open Platform domain; Feishu and Lark credentials are not interchangeable. - Create an internal/custom app.
- Enable the bot capability.
- In Credentials & Basic Info, copy App ID and App Secret.
- In Event Configuration, choose long connection/WebSocket and subscribe to
im.message.receive_v1. - In Callback Configuration, choose long connection/WebSocket and subscribe
to
card.action.trigger. Keep the Lark Remote bridge running when you click verify/save. - Add the message receive, send/reply, and card interaction permissions requested by the platform, then publish or enable the app for your tenant.
Copy the credentials to the clipboard in this shape:
Feishu/Lark app:
- domain: feishu
- appId: cli_xxx
- appSecret: xxx
Allowed users:
- allowedUsers: []
Optional takeover tuning:
- takeover: { projectLimit: 20, selectionTtlMs: 600000 }
Optional startup intro:
- startup: { receiveId: "oc_xxx", receiveIdType: "chat_id", once: true }
For international Lark, change domain to lark and use credentials created
at https://open.larksuite.com.
Private runtime config is stored outside the repository:
~/.codex-lark-remote/config.json
Do not commit that file. For first private setup, allowedUsers: [] is only a
temporary discovery state. After whoami works, add the returned sender id
before using full project/session takeover.
startup.receiveId is optional. If configured, the bridge proactively sends a
startup intro card to that chat. Without it, the first allowed Feishu/Lark
message supplies the chat_id, receives the intro once, and becomes the
remembered startup target.
The Feishu/Lark side has one main entry point: the natural-language console.
After the bridge is connected, send console or click the console button on the
startup card.
Console display language is bound per Feishu/Lark chat. Enter with English to keep later console cards in English; enter with Chinese to keep them in Chinese. Sending a control phrase in the other language switches that chat's later display language.
Useful console phrases:
console
project list
session list
enter project 1
observe session 2
takeover 1
status
whoami
commands on
commands off
handoff off
close Lark connection
The same controls also work in Chinese: 控制台, 项目列表, 会话列表,
进入项目 1, 观察会话 2, 接管 1, 状态, and 关闭飞书连接.
After takeover, the chat switches to thread-dispatch mode. Ordinary
Feishu/Lark messages are stored as remote commands, routed by the local bridge
runner, and delivered to the selected Codex session through the local
route/dispatch executor. The Codex conversation that started Lark Remote remains
the locked control-window identity, but routine dispatch no longer depends on
starting a fresh codex exec control-window turn or asking that turn to call
MCP tools.
The JavaScript bridge still intercepts deterministic keywords and card actions
such as console, status, observe off, exit handoff,
close Lark connection, and the control: / 控制: prefix. Other text,
including project/session wording and dispatch: / 派发: text, is passed to
the local route/dispatch executor, which uses the stored remote-command state and
the locked control-window target to choose the next Lark Remote action.
From a trusted Codex conversation, ask:
Start codex-lark-remote.
Codex asks for explicit consent before storing local routing state for this thread in the local bridge. Existing chat history is not sent to Feishu/Lark. After you confirm, the plugin starts or reuses the bridge and opens the Feishu/Lark control path.
When a Codex thread is attached, handoff uses the exact thread id or session path supplied by Codex. It does not guess the nearest conversation by workspace path. The Codex conversation that starts the connection is the dedicated control window; Feishu/Lark can later choose another allowed local session as the target from the console.
On macOS, the bridge starts caffeinate -dimsu while handoff is active so the
Mac can keep working with the display off. Set handoff.keepAwake to false
in ~/.codex-lark-remote/config.json if you prefer to manage sleep manually.
The plugin MCP server runs inside Codex and starts a separate local bridge process. The bridge owns Feishu/Lark WebSocket delivery, event parsing, queue state, startup notices, observation streams, and handoff routing.
Feishu/Lark controls target selection with console, windows, project list,
and takeover. The bot first lists local Codex projects from session records,
then lists sessions/windows inside the chosen project. These are Codex session
records, not macOS window handles.
Observation is read-only. Use observe, observe <number>, and observe off
to stream progress from another Codex session without routing Feishu/Lark input
into that session. Observation replies include each newly appended user prompt
as a short User prompt: separator before later Codex progress, so separate
turns do not collapse into one continuous assistant stream.
Takeover is write-capable after confirmation. Full-project takeover requires a
non-empty lark.allowedUsers allowlist. Ordinary Feishu/Lark messages are
queued by the bridge runner, routed locally, and delivered to the selected
target Codex session. If the target session is active, Lark Remote still queues
the dispatch as a higher-priority delivery request instead of failing just
because the target is busy. During takeover, prompts that Lark Remote itself
sent from Feishu/Lark are not echoed back; prompts appended by other sources,
such as automation or local Codex input, are echoed as User prompt:
separators.
The dedicated control Codex window provides the locked local identity and an
explicit manual/diagnostic fallback. Normal runtime dispatch uses the bridge
runner's local route endpoint, then calls local bridge endpoints such as
/bridge/dispatch/execute, /bridge/remote-command/reply, or observation and
target-selection endpoints. The focused Lark Remote MCP tools
lark_route_remote_command, lark_dispatch_remote_command,
lark_record_dispatch, lark_request_clarification, and
lark_reply_remote_command remain available for explicit control-window
diagnostics or recovery, but they are no longer invoked from codex exec for
every Feishu/Lark message.
Remote replies are optimized for coding in chat:
- Final Codex answers are sent back as normal text.
- Long answers are split across multiple Feishu/Lark messages.
- Progress replies hide internal task ids by default.
- Normal shell commands and
Output:are hidden by default. commands onshows command summaries;commands offhides them again.- Risky commands are always shown with a
Warning:line. - Source inspection output from
cat,nl,sed,grep, and normalrgsearches is summarized instead of pasted in full. - Secrets in command text are redacted before they are sent to Feishu/Lark.
Lark Remote takes over the conversation stream, not the native Codex Desktop UI. Feishu/Lark cannot click permission dialogs, MCP approvals, sandbox-escalation prompts, network/install approvals, or other native Codex UI popups. When Codex hits one of those boundaries, it should send a clear Feishu/Lark message explaining what approval is needed and where to approve it.
If the selected target Codex session is already working, Lark Remote still queues the Feishu/Lark message as a target dispatch request. It fails closed only when the bridge cannot address or queue the selected target session.
| Path | Purpose |
|---|---|
./ |
Installable Codex plugin bundle. |
bin/ |
MCP server and local bridge entrypoints. |
src/ |
Bridge, Feishu/Lark, handoff, observer, presenter, and runner modules. |
skills/ |
Codex skill instructions bundled with the plugin. |
config/example.config.json |
Example private runtime config shape. |
test/ |
Node test suite. |
For local development, register this repository as a local marketplace:
[marketplaces.gxfn]
source_type = "local"
source = "/absolute/path/to/LarkRemote"
[plugins."codex-lark-remote@gxfn"]
enabled = trueRun tests before publishing:
npm test
The marketplace submission is the nested plugin directory. Keep development
tests, release scripts, and generated artifacts out of plugins/codex-lark-remote/.
| Symptom | Check |
|---|---|
lark_* tools are missing |
Refresh or re-enable the plugin, then start a new Codex conversation. |
| Dispatch says bridge state is unavailable | Restart Lark Remote so the runner can read the current local bridge state, then resend the retained Feishu/Lark message. |
status says websocket disabled |
Confirm appId, appSecret, and lark.domain in ~/.codex-lark-remote/config.json. |
| Feishu/Lark replies twice | Stop stale bridge processes or duplicate plugin installations. |
| Codex edits the plugin cache | Start handoff from a Codex conversation whose cwd is the project you want to edit. |
| Auth fails for international users | Use lark.domain: "lark" and credentials from https://open.larksuite.com. |